Importer assessor key · open after the independent attempt
Use the candidate brief. Record what the candidate ran, what they predicted and which hypothesis a test disproved. Timeboxes are training choices. Do not reveal every failure at minute zero.
| Stage / expected repair | Regression and exact observation | Why another explanation loses |
|---|---|---|
0: restore store.advance declaration colon |
PACKAGE=starter python -m unittest test_importer first fails with SyntaxError: expected ':' |
No HTTP or SQLite request has executed yet; retry tuning cannot fix an import failure |
| 1: integer money | test_integer_money_dedup_and_completion: 0.29 → 29, -1.10 → -110, 0.00 → 0 |
int(float('0.29')*100) truncates binary representation; changing sort order does not repair value loss |
| 2: persist then checkpoint; completed flag | test_restart_after_persist_before_checkpoint reopens SQLite, initially (None,0), finally 3 rows |
Server delivery was successful; progress ahead of durable effects is an integration failure, not eventual consistency |
| 3: reject seen cursors | test_cycle_and_page_budget: p2 points at itself, exactly 2 requests before failure; held-back pack adds longer cycle |
A larger page budget postpones termination and can conceal the bad provider contract |
| 4: respect numeric retry timing | test_retry_timing: second request begins at 0.75; total-deadline case rejects 0.8 delay after 0.3 elapsed |
Sleeping a fixed second ignores service advice; retries are not an unlimited separate budget |
| 5: retain validation and transactional conflict check | malformed page leaves only a/b; conflicting page's new ID is rolled back | INSERT OR IGNORE alone hides equal-ID/different-money corruption |
| 6: preserve attempts and finite budgets | 503 makes exactly 3 calls; 10-second transport request times out against 1 second | Catch-all retry turns permanent malformed/domain failures into load amplification |
The starter contains four deliberate repair areas after the syntax issue: amount conversion, early checkpointing, cycle checks and ignored Retry-After. Validation, transactional conflict checks and timeout mechanisms are existing code to understand and preserve, not gratuitously rewrite. Each expected repair has a named regression.
Reference: coordinator (download file, source below), transport and fake clock (download file, source below),
domain model (download file, source below), SQLite store (download file, source below). Test injection
points are intentional seams. after_persist models process failure at the durable
boundary, while FakeServer.elapsed models time spent inside the transport. Fake
timeouts are a contract test; they do not establish a real HTTP library cancels sockets.
Read the supplied code · importer.py
from .model import page
from .transport import fetch
def run(server, clock, store, budget=5.0, max_pages=100, after_persist=lambda: None):
if budget <= 0 or max_pages <= 0:
raise ValueError("positive budgets required")
deadline = clock.now + budget
cursor, done = store.checkpoint()
seen = set()
if done:
return store.rows()
for _ in range(max_pages):
if cursor in seen:
raise ValueError("cursor cycle")
seen.add(cursor)
rows, next_cursor = page(fetch(server, clock, cursor, deadline))
if clock.now >= deadline:
raise TimeoutError("total deadline before persistence")
if next_cursor is not None and next_cursor in seen:
raise ValueError("cursor cycle")
store.persist(rows)
after_persist() # deliberate crash window: restart must safely replay this page
if clock.now >= deadline:
raise TimeoutError("total deadline after persistence; replay on restart")
store.advance(next_cursor)
if next_cursor is None:
return store.rows()
cursor = next_cursor
raise ValueError("page budget exhausted")
Read the supplied code · transport.py
"""Injected transport owns timeout enforcement; importer owns the total budget."""
class FakeClock:
def __init__(self):
self.now = 0.0
self.waits = []
def sleep(self, seconds):
self.waits.append(seconds)
self.now += seconds
class FakeServer:
def __init__(self, responses, clock):
self.responses, self.clock, self.calls = responses, clock, []
self.counts = {}
def get(self, cursor, timeout):
key = cursor if cursor is not None else "START"
self.calls.append((key, self.clock.now, timeout))
count = self.counts.get(key, 0)
options = self.responses[key]
response = options[min(count, len(options) - 1)]
self.counts[key] = count + 1
elapsed = response.get("elapsed", 0)
self.clock.now += min(elapsed, timeout)
if elapsed >= timeout:
raise TimeoutError("request deadline")
return response
def fetch(server, clock, cursor, deadline, attempts=3):
for attempt in range(attempts):
remaining = deadline - clock.now
if remaining <= 0:
raise TimeoutError("total deadline")
response = server.get(cursor, timeout=remaining)
if clock.now >= deadline:
raise TimeoutError("total deadline")
status = response["status"]
if status == 200:
return response["body"]
if status not in (429, 500, 502, 503, 504):
raise ValueError(f"non-retryable HTTP {status}")
if attempt == attempts - 1:
raise TimeoutError("attempt budget exhausted")
# This exercise's API specifies numeric seconds, not HTTP-date syntax.
delay = response.get("retryAfter", 0.1 * 2**attempt)
if isinstance(delay, bool) or not isinstance(delay, (int, float)) or not 0 <= delay < float("inf"):
raise ValueError("invalid Retry-After")
if clock.now + delay >= deadline:
raise TimeoutError("retry exceeds total deadline")
clock.sleep(delay)
raise AssertionError("unreachable")
Read the supplied code · model.py
"""Domain validation: cents are integers; malformed pages have no partial effect."""
from decimal import Decimal, InvalidOperation
def normalize(item):
if not isinstance(item, dict) or not isinstance(item.get("id"), str) or not item["id"]:
raise ValueError("id required")
if item.get("currency") != "USD" or not isinstance(item.get("amount"), str) or len(item["amount"]) > 128:
raise ValueError("USD amount must be a decimal string")
try:
amount = Decimal(item["amount"])
if not amount.is_finite():
raise ValueError("whole cents required")
if amount.copy_abs() > Decimal("10000000000"):
raise ValueError("amount out of range")
# Never multiply under Decimal's ambient context: it can round away a tiny
# fractional cent. The parsed coefficient and exponent are exact.
sign, digits, exponent = amount.as_tuple()
coefficient = 0
for digit in digits:
coefficient = coefficient * 10 + digit
if not coefficient:
cents = 0
elif exponent >= -2:
cents = coefficient * 10 ** (exponent + 2)
else:
places = -exponent - 2
if places > len(digits):
raise ValueError("whole cents required")
cents, fraction = divmod(coefficient, 10 ** places)
if fraction:
raise ValueError("whole cents required")
return (item["id"], -cents if sign else cents, "USD")
except InvalidOperation as exc:
raise ValueError("invalid amount") from exc
def page(body):
if not isinstance(body, dict) or not isinstance(body.get("items"), list):
raise ValueError("items list required")
if "nextCursor" not in body or (body["nextCursor"] is not None and
(not isinstance(body["nextCursor"], str) or not body["nextCursor"])):
raise ValueError("opaque cursor or null required")
return [normalize(item) for item in body["items"]], body["nextCursor"]
Read the supplied code · store.py
import sqlite3
class Store:
def __init__(self, path):
self.db = sqlite3.connect(path)
self.db.executescript("""
CREATE TABLE IF NOT EXISTS transactions(id TEXT PRIMARY KEY, cents INTEGER NOT NULL, currency TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS progress(singleton INTEGER PRIMARY KEY CHECK(singleton=1), cursor TEXT, done INTEGER NOT NULL);
INSERT OR IGNORE INTO progress VALUES(1,NULL,0);
""")
def checkpoint(self):
return self.db.execute("SELECT cursor,done FROM progress WHERE singleton=1").fetchone()
def persist(self, rows):
with self.db:
for row in rows:
old = self.db.execute("SELECT id,cents,currency FROM transactions WHERE id=?", (row[0],)).fetchone()
if old is not None and old != row:
raise ValueError("duplicate id with conflicting payload")
self.db.execute("INSERT OR IGNORE INTO transactions VALUES(?,?,?)", row)
def advance(self, cursor):
with self.db:
self.db.execute("UPDATE progress SET cursor=?,done=? WHERE singleton=1", (cursor, cursor is None))
def rows(self):
return self.db.execute("SELECT id,cents,currency FROM transactions ORDER BY id").fetchall()
def close(self):
self.db.close()
Withhold additional fixtures (download file, source below) until the candidate has a baseline. Run them against the candidate's selected package. Grade using observable anchors. Debrief through the method, then change currency representation or checkpoint schema on a later session.
Read the supplied code · heldback_importer.py
"""Assessor-only variants. Select candidate package after its baseline is repaired."""
import argparse
import importlib
from pathlib import Path
import sys
import tempfile
import unittest
parser = argparse.ArgumentParser()
parser.add_argument("--package", choices=["reference", "starter"], default="reference")
args, remaining = parser.parse_known_args()
root = Path(__file__).resolve().parents[2] / "curriculum/02-applications/04-testing/labs/importer"
sys.path.insert(0, str(root))
run = importlib.import_module(args.package + ".importer").run
Store = importlib.import_module(args.package + ".store").Store
transport = importlib.import_module(args.package + ".transport")
def item(ident="held", amount="0.29"):
return {"id":ident, "amount":amount, "currency":"USD"}
def page(items, cursor=None):
return {"status":200, "body":{"items":items, "nextCursor":cursor}}
class HeldBack(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.path = str(Path(self.temp.name) / "held.db")
self.store = Store(self.path)
self.clock = transport.FakeClock()
def tearDown(self):
self.store.close()
self.temp.cleanup()
def test_multi_hop_cycle(self):
server = transport.FakeServer({"START":[page([],"p2")],"p2":[page([],"p3")],"p3":[page([],"p2")]},self.clock)
with self.assertRaisesRegex(ValueError,"cycle"):
run(server,self.clock,self.store)
self.assertEqual(len(server.calls),3)
def test_retry_consumes_transport_budget(self):
server = transport.FakeServer({"START":[{"status":429,"retryAfter":0.8,"elapsed":0.3}]},self.clock)
with self.assertRaises(TimeoutError):
run(server,self.clock,self.store,budget=1)
self.assertEqual(len(server.calls),1)
self.assertEqual(self.clock.waits,[])
def test_crash_and_equal_decimal_spelling(self):
server = transport.FakeServer({"START":[page([item(amount="0.290")])]},self.clock)
def crash():
raise RuntimeError("lost process")
with self.assertRaises(RuntimeError):
run(server,self.clock,self.store,after_persist=crash)
self.store.close()
self.store = Store(self.path)
server = transport.FakeServer({"START":[page([item(amount="0.29")])]},self.clock)
self.assertEqual(run(server,self.clock,self.store),[("held",29,"USD")])
def test_late_malformed_page_preserves_progress(self):
server = transport.FakeServer({"START":[page([item("first")],"next")],
"next":[page([item("valid"),item("bad","0.00001")])]},self.clock)
with self.assertRaises(ValueError):
run(server,self.clock,self.store)
self.assertEqual(self.store.rows(),[("first",29,"USD")])
self.assertEqual(self.store.checkpoint(),("next",0))
if __name__ == "__main__":
unittest.main(argv=[sys.argv[0]] + remaining)