Zero records written, exit code 0
Sam inherits a nightly importer that pulls 5,000 records from a partner API, validates them, and writes them to a warehouse. The retry wrapper is for attempt in range(3): try: return call(); except Exception: time.sleep(1). Parsing of each record is wrapped in try: ... except: pass. Last night's run logged 0 records written, produced no error, and exited 0. The partner's status page later showed a 400 for a malformed field on one record and a 503 blip lasting about 20 seconds. One record had a missing timestamp and simply did not appear downstream; nobody noticed until a report was short by one day's row. The suite has 14 tests, all green. The importer test is assert import_records(fixture); the retry tests sleep for real, which makes the suite take 40 seconds, so developers have started skipping it locally.
1. Explain how a run can write zero records, log nothing, and exit 0. Identify each failure mode in the code and what it hides.
Read the worked answer
Three separate mechanisms combine.
except: passaround parsing swallows every error, includingKeyErrorfor the missing timestamp. The record is dropped without a log line, and the caller never learns it failed. A bareexceptalso catchesKeyboardInterruptandSystemExit, so the parse loop cannot even be interrupted cleanly. That accounts for the missing row.except Exceptionaround the call retries the 400. A 400 means the request is wrong and will be wrong on attempt 2 and 3; retrying it three times with no backoff burns time and produces the same error, then raises. If the caller of the wrapper catches broadly or if the whole import is inside a similar block, the run continues with an empty result.- The 503 is retried three times with 1-second sleeps, i.e. the wrapper waits about 2 seconds in total against a 20-second outage. All three attempts fail, the record set is empty, and the loop finishes normally. Nothing in the code distinguishes 'finished with 0' from 'succeeded with 0'.
The exit code is 0 because nothing raised to the top level; the process has no concept of partial failure. Logging the exception at the point it is caught, and returning a result object rather than an empty list, would have made the run visible without changing the retry behaviour at all.
2. Design the retry policy. State which failures should be retried, which must not, and how to avoid duplicate writes.
Read the worked answer
Retry only when the failure is plausibly transient on the next attempt.
Retry: connection errors, timeouts, 429, 500, 502, 503, 504. Use exponential backoff with jitter (e.g. 1s, 2s, 4s, capped, ±20% jitter so a fleet of workers does not synchronise), a maximum of about 5 attempts, and honour Retry-After when present. Against a 20-second outage, a policy that waits about 2 seconds is functionally a no-retry policy.
Do not retry: 400, 401, 403, 404, 409, 422. These are deterministic; retrying just triples the load and delays the alert. Classify on the exception type or status code, never on Exception.
Duplicate writes: retrying a POST is not automatically safe. If the request may have reached the server before the timeout, a retry can create a second record. Use an idempotency key per record generated before the first attempt, or make the write an upsert keyed on a natural ID, so attempt 2 is a no-op. Without that, a 503 retry during the blip can double-write the records that did succeed.
Also make the outcome loud: a run that writes fewer records than it fetched must exit non-zero and emit a count plus the failing IDs.
3. Rewrite the weak test and add the tests that would have caught this. Address the 'developers skip the slow suite' problem as part of the answer.
Read the worked answer
assert import_records(fixture) only checks that the return value is truthy. An empty list is falsy, so it catches the total-empty case but not the partial case, and it says nothing about which records were written or how errors were handled.
Replace it with assertions on content and failure behaviour:
- Exact output:
assert [r['id'] for r in result.records] == [1, 2, 3]andassert result.dropped == []. Catches the silent drop of the missing-timestamp record only if the fixture includes one, so build a fixture with a missing timestamp and assert the record is reported as rejected with a reason, not silently gone. - Error propagation: make the fake client raise a
KeyErrorduring parsing and assert the run reports a failure rather than returning 0 records with no message. This is the test that fails today. - Retry classification: fake client returns 400 → assert exactly one call. Returns 503 twice then 200 → assert three calls and one record. The second is the test that would have exposed the 2-second-versus-20-second gap.
- Idempotency: simulate a timeout after the server accepted the write, retry, and assert the warehouse has one row.
Speed: do not call time.sleep. Inject the sleep/clock, or use a fake clock, so retry tests run in milliseconds; otherwise the tests that protect the money path are the first ones people skip.