Lab: handle an error that never throws
Wrap an SDK call in try/except, watch it fail anyway, and find where the error actually was.
Every SDK in this course returns { data, error, meta } rather than raising. The lesson said to check error before reading data. This is what happens when you do the thing that feels more careful instead.
- 1
Call an endpoint you are not permitted to read, wrapped in the handler most people write.
bashcat > /tmp/env.py <<'EOF' def get_secret(path): if path.startswith("prod/"): return {"data": None, "error": {"type": "forbidden", "message": "no policy grants this path"}} return {"data": {"value": "sk-live-9f2a"}, "error": None} print("the handler that looks careful:") try: resp = get_secret("prod/db/root") value = resp["data"]["value"] print(" got:", value) except Exception as e: print(f" caught: {type(e).__name__}: {e}") print("\nwhat the call actually returned:") resp = get_secret("prod/db/root") print(" error:", resp["error"]) print(" data: ", resp["data"]) print("\nthe check that works:") resp = get_secret("prod/db/root") if resp["error"]: print(" handled:", resp["error"]["message"]) EOF python3 /tmp/env.py - 2
The exception you caught is not the error that occurred.
textthe handler that looks careful: caught: TypeError: 'NoneType' object is not subscriptable what the call actually returned: error: {'type': 'forbidden', 'message': 'no policy grants this path'} data: None the check that works: handled: no policy grants this path - 3
Clean up.
bashrm /tmp/env.py
A permissions problem arrived as a TypeError about NoneType, several lines away from the call that failed, with the real message sitting untouched in a field nobody read.
- The try/except did catch something, which is what makes this so durable. Your error handling appears to work and your logs fill with type errors that describe the symptom rather than the cause.
- The same shape hides retryable conditions. A 402 payment-required or an approval-required response is a value in error, so code that only handles exceptions treats both as an unexpected crash.
- It is worse in TypeScript, where resp.data is typed as possibly null and the compiler will tell you, right up until someone adds a non-null assertion to make the build pass.
Check error first on every call. It is more verbose than optimistic destructuring and it is the difference between a log line naming your actual problem and one describing where your program happened to fall over.
If you want the exception-style ergonomics, write one wrapper that raises on error and use it everywhere. What does not work is a codebase where some calls check and others do not.
Check your understanding
3 questionsWhy did the try block catch a TypeError rather than a permissions error?
What else does this pattern hide?
What is the recommended fix?