A single API read, wrapped in a safety net. When Gmail is slow, down, or hitting a rate limit, the function still returns a list — just an empty one. How do you make the return shape stable across both the happy and sad paths?
The try path extracts snippets; the except path returns []. Same type both ways, so the caller never needs to check if it got a list or something else?
Type-stable returns are the key. Whether the API succeeds or fails, the caller always iterates over a list. No None checks, no isinstance, no branching based on failure mode:
try:
result = toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {"max_results": max_results})
return [m.get("snippet", "") for m in result.get("messages", [])]
except Exception:
return []So the happy path does the full extract, and the except path short-circuits to an empty list — identical shape either way?
Identical shape, predictable behaviour. Callers write for s in safe_email_snippets(5): and the loop is happy regardless of whether Gmail answered:
def safe_email_snippets(max_results: int) -> list:
try:
result = toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {"max_results": max_results})
snippets = [m.get("snippet", "") for m in result.get("messages", [])]
print(f"Fetched {len(snippets)} snippets safely")
return snippets
except Exception as e:
print(f"Gmail read failed: {e} — returning empty list")
return []The except logs the exception — should the function re-raise it anywhere, or is swallowing it fine for a defensive read?
Defensive reads swallow, they don't re-raise. The caller moves on; you log so the failure isn't invisible. If the caller needs to know whether the read succeeded, return a dict with a "status" key instead of a bare list.
So this wrapper is a drop-in replacement for any raw read — same inputs, same return shape, plus a safety net I get for free?
Drop-in, type-stable, log-friendly. Every read in your pipeline can be upgraded by wrapping with the same seven lines — the shape carries itself.
TL;DR: Keep the return type stable across try and except — callers never branch on success.
| Pattern | When |
|---|---|
| Swallow + log | Defensive reads, dashboards |
| Re-raise | Orchestrators where failure must abort |
| Return status dict | Pipelines that need per-step health |
Swallowing is default for reads. Re-raising is for writes where a silent failure would break downstream state.
A single API read, wrapped in a safety net. When Gmail is slow, down, or hitting a rate limit, the function still returns a list — just an empty one. How do you make the return shape stable across both the happy and sad paths?
The try path extracts snippets; the except path returns []. Same type both ways, so the caller never needs to check if it got a list or something else?
Type-stable returns are the key. Whether the API succeeds or fails, the caller always iterates over a list. No None checks, no isinstance, no branching based on failure mode:
try:
result = toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {"max_results": max_results})
return [m.get("snippet", "") for m in result.get("messages", [])]
except Exception:
return []So the happy path does the full extract, and the except path short-circuits to an empty list — identical shape either way?
Identical shape, predictable behaviour. Callers write for s in safe_email_snippets(5): and the loop is happy regardless of whether Gmail answered:
def safe_email_snippets(max_results: int) -> list:
try:
result = toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {"max_results": max_results})
snippets = [m.get("snippet", "") for m in result.get("messages", [])]
print(f"Fetched {len(snippets)} snippets safely")
return snippets
except Exception as e:
print(f"Gmail read failed: {e} — returning empty list")
return []The except logs the exception — should the function re-raise it anywhere, or is swallowing it fine for a defensive read?
Defensive reads swallow, they don't re-raise. The caller moves on; you log so the failure isn't invisible. If the caller needs to know whether the read succeeded, return a dict with a "status" key instead of a bare list.
So this wrapper is a drop-in replacement for any raw read — same inputs, same return shape, plus a safety net I get for free?
Drop-in, type-stable, log-friendly. Every read in your pipeline can be upgraded by wrapping with the same seven lines — the shape carries itself.
TL;DR: Keep the return type stable across try and except — callers never branch on success.
| Pattern | When |
|---|---|
| Swallow + log | Defensive reads, dashboards |
| Re-raise | Orchestrators where failure must abort |
| Return status dict | Pipelines that need per-step health |
Swallowing is default for reads. Re-raising is for writes where a silent failure would break downstream state.
Create a free account to get started. Paid plans unlock all tracks.