Two-step chain. Step 1 succeeds (you got the data). Step 2 fails (network blip). Without protection, the script crashes mid-chain — you lose the wasted read AND don't get a clear signal which step broke.
try:
result = toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {"max_results": 1})
messages = result.get("messages", [])
except Exception as e:
print(f"step 1 (read) failed: {type(e).__name__}")
raise
try:
toolset.execute_action(Action.GOOGLECALENDAR_UPDATE_EVENT, {"calendar_id": "primary", "event_id": "missing", "summary": str(messages), "start_datetime": "2026-12-31T10:00:00+00:00", "event_duration_minutes": 30})
except Exception as e:
print(f"step 2 (write) failed: {type(e).__name__}")
raiseEach step in its own try/except?
That's the granular pattern — pinpoints the exact failure. The trade-off is verbosity. For 2-step chains it's worth it. For 10-step chains you'd refactor into a helper function or a list of (step_name, action) tuples.
And raise inside the except — re-raises the same error?
Yes. After printing the diagnostic, raise lets the original exception propagate so the script still crashes. Without raise, you'd silently continue to the next step on a partially-broken state — usually worse than failing loudly.
try/exceptThe pattern:
try:
# step 1
except Exception as e:
print(f"step 1 failed: {type(e).__name__}")
raise
try:
# step 2
except Exception as e:
print(f"step 2 failed: {type(e).__name__}")
raiseThe diagnostic print + raise combo gives you:
raise mattersWithout raise, the except swallows the error and the script keeps running. If step 1 actually failed, step 2 would proceed with incomplete data — worse than crashing immediately.
try:
messages = step_1()
except Exception:
print("step 1 failed")
# no raise — `messages` is undefined, but script continues
step_2(messages) # NameError on `messages` — confusing two-stage failureSome steps are optional — the rest of the workflow should continue even if this step failed. Logging instead of crashing makes sense:
try:
send_optional_metric()
except Exception as e:
print(f"metric send failed (continuing): {e}")
# script continues normallyThis is judgment per step. "Critical to the workflow" → re-raise. "Nice to have" → log and continue.
At 4+ steps the per-step pattern gets verbose. A helper:
def run_step(name, fn):
try:
return fn()
except Exception as e:
print(f"{name} failed: {type(e).__name__}: {e}")
raise
messages = run_step("fetch", lambda: toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {...}))
run_step("create-event", lambda: toolset.execute_action(Action.GOOGLECALENDAR_CREATE_EVENT, {...}))Functions you'll write fluently after Auto Intermediate.
Two-step chain. Step 1 succeeds (you got the data). Step 2 fails (network blip). Without protection, the script crashes mid-chain — you lose the wasted read AND don't get a clear signal which step broke.
try:
result = toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {"max_results": 1})
messages = result.get("messages", [])
except Exception as e:
print(f"step 1 (read) failed: {type(e).__name__}")
raise
try:
toolset.execute_action(Action.GOOGLECALENDAR_UPDATE_EVENT, {"calendar_id": "primary", "event_id": "missing", "summary": str(messages), "start_datetime": "2026-12-31T10:00:00+00:00", "event_duration_minutes": 30})
except Exception as e:
print(f"step 2 (write) failed: {type(e).__name__}")
raiseEach step in its own try/except?
That's the granular pattern — pinpoints the exact failure. The trade-off is verbosity. For 2-step chains it's worth it. For 10-step chains you'd refactor into a helper function or a list of (step_name, action) tuples.
And raise inside the except — re-raises the same error?
Yes. After printing the diagnostic, raise lets the original exception propagate so the script still crashes. Without raise, you'd silently continue to the next step on a partially-broken state — usually worse than failing loudly.
try/exceptThe pattern:
try:
# step 1
except Exception as e:
print(f"step 1 failed: {type(e).__name__}")
raise
try:
# step 2
except Exception as e:
print(f"step 2 failed: {type(e).__name__}")
raiseThe diagnostic print + raise combo gives you:
raise mattersWithout raise, the except swallows the error and the script keeps running. If step 1 actually failed, step 2 would proceed with incomplete data — worse than crashing immediately.
try:
messages = step_1()
except Exception:
print("step 1 failed")
# no raise — `messages` is undefined, but script continues
step_2(messages) # NameError on `messages` — confusing two-stage failureSome steps are optional — the rest of the workflow should continue even if this step failed. Logging instead of crashing makes sense:
try:
send_optional_metric()
except Exception as e:
print(f"metric send failed (continuing): {e}")
# script continues normallyThis is judgment per step. "Critical to the workflow" → re-raise. "Nice to have" → log and continue.
At 4+ steps the per-step pattern gets verbose. A helper:
def run_step(name, fn):
try:
return fn()
except Exception as e:
print(f"{name} failed: {type(e).__name__}: {e}")
raise
messages = run_step("fetch", lambda: toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {...}))
run_step("create-event", lambda: toolset.execute_action(Action.GOOGLECALENDAR_CREATE_EVENT, {...}))Functions you'll write fluently after Auto Intermediate.
Create a free account to get started. Paid plans unlock all tracks.