One useful idea
A failure is part of an API contract. A missing record is not a valid empty evaluation; a storage bug is not a malformed student request. Give callers a stable status/code and a safe action, while preserving unexpected faults for local diagnosis. The provided framework handlers connect your domain mapping to actual HTTP responses.
Your problem_for maps only the exact declared expected exception types: InvalidInput→400/invalid_input, Unauthorized→401/unauthorized, Forbidden→403/forbidden, Missing→404/not_found and Conflict→409/conflict. Its body has error.code, a fixed safe message and the validated internal request_id. Never interpolate str(error), SQL, evidence, token, path or body into that message. Re-raise unknown exceptions; do not wrap every exception as a client fault or print success after a failure. See the status and recovery contract.
The framework supplies additional boundaries: strict schema mismatch is 422/invalid_request; malformed UTF-8, duplicate keys, nonfinite numbers or excessive nesting is 400/invalid_json; an actual body over 32,768 bytes is 413; unsupported content type/encoding is 415. Unknown routes and wrong methods get safe 404/405. The adapter refuses ambiguous boundary headers, noncanonical local Host authorities and cross-origin browser requests. Its request ID is generated by the server and matches X-Request-ID, never copied from an input header. Responses use Cache-Control:no-store; 401 also advertises WWW-Authenticate:Bearer.
Expected faults return their declared safe envelope. Unexpected programming/storage failures return controlled 500/internal_error and a request ID; the actual error remains visible locally. Application error logging retains a controlled code/request ID, not raw fields. Framework/terminal tracebacks may still include raw exception text: inspect them locally, do not upload them automatically. The course tutor sends nothing until you choose Send.
TestClient normally rethrows unexpected server errors so tests reveal bugs. Only set raise_server_exceptions=False deliberately when inspecting the controlled 500 response. Do not use it to make an unexpected error appear acceptable. In-process API observations are not real socket/timing evidence. Read current state/revision before addressing 409; blindly retrying a stale write cannot repair it. 401 means a configured public fixture token is needed, not your DVP website password.
Refresh first: Request/response schemas and configured actors, Specific expected exceptions, Preserve a real failure cause.
Trace a finished example
from pathlib import Path
from tempfile import TemporaryDirectory
import re
from fastapi.testclient import TestClient
from evaluation_service.app import create_app, FIXTURE_CREDENTIALS
from evaluation_service.storage import Repository
headers = {"Authorization": "Bearer fixture-author-a"}
payload = {"asset_id": "Fixture", "width": 1920, "height": 1080,
"frames": 240, "fps": 24}
def fail(request, actor, evaluation_id):
raise RuntimeError("invented-debugging-detail")
with TemporaryDirectory(prefix="dvp-owned-errors-") as folder:
store = Repository(Path(folder) / "example.db")
store.initialize(new=True)
with TestClient(create_app(store, credentials=FIXTURE_CREDENTIALS)) as client:
responses = [
client.post("/evaluations", json={**payload, "width": True}, headers=headers),
client.get("/evaluations/missing", headers=headers),
client.get("/evaluations/missing"),
client.post("/evaluations", content=b'{"fps":24,"fps":25}',
headers={**headers, "Content-Type": "application/json"})]
for response in responses:
print(response.status_code, response.json()["error"]["code"])
app = create_app(store, credentials=FIXTURE_CREDENTIALS, build=fail)
with TestClient(app, raise_server_exceptions=False) as client:
response = client.post("/evaluations", json=payload, headers=headers)
error = response.json()["error"]
print(response.status_code, error["code"])
print(bool(re.fullmatch("[a-f0-9]{32}", error["request_id"])),
"invented-debugging-detail" in response.text)Expected output
422 invalid_request
404 not_found
401 unauthorized
400 invalid_json
500 internal_error
True FalseFour real in-process requests observe distinct schema/missing/auth/JSON refusals. A deliberately injected programming fault remains a server failure; the explicit response-inspection client sees a safe 500 and generated request ID, with no raw debugging detail in its body. A controlled diagnostic line may appear on stderr; it is not the response or a success report.
The finished implementation is in evaluation_service/core.py. Reading it is guided practice, not independent evidence.
Separate the failure
A syntactically valid request uses width=True. Is this 400 invalid_json, 422 invalid_request or 201?
Compare your answer · self-reviewed
422 invalid_request: JSON syntax is valid, but the strict integer schema refuses a boolean. Duplicate keys instead fail the transport with 400 invalid_json before a mutation.
Find the unsafe fallback
A database exception is caught and returned as 200 with an empty evaluation. What is wrong?
Compare your answer · self-reviewed
It invents success and discards the real fault. Expected domain failures have declared mappings; unexpected storage/programming failures remain controlled 500 and locally diagnosable.
Recall identity and sharing
Can a supplied X-Request-ID or your DVP password be copied into this classroom response?
Compare your answer · self-reviewed
No. The server creates the request ID and the kit uses public invented fixture credentials. Response/log design omits raw fields; full tracebacks still need a separate sharing review.
Change it, then build your own
One controlled change
Send malformed JSON, duplicate keys, width="1920", a missing resource and an unknown fixture token. Predict status/code before each run. Retain the actual error envelope without private data; observe a 500 explicitly instead of suppressing it.
Your independent task
Implement problem_for in practice.py using the exact declared domain types/statuses/codes/safe messages in CONTRACT.md. Validate request_id with the reviewed Identifier policy. Unknown exceptions must propagate rather than become false client errors. Use the selected Build 2 marker/demo; reviewed API/transport/reference repository remain assistance, not your completed boundaries.
What success looks like
Five domain mappings return stable safe envelopes; changed raw exception messages never appear in them. Unknown faults visibly propagate in ordinary tests and have controlled 500 responses when deliberately inspected. Malformed body, strict schema, credential and state problems retain their separate meanings.
Hint 1 · a question
For each expected exception, write status/code/message/recovery. Which failures are transport or schema boundaries instead?
Hint 2 · a concept cue
Use exact exception-type selection, validate the internal request ID and build a fresh three-field error object. No raw input belongs in its message.
Hint 3 · a localized example
If type(error) has no declared mapping, raise error. For Conflict use 409/conflict and the fixed instruction to refresh the record before retrying.
Need the complete worked solution?
Open evaluation_service/core.py from the kit. Trace it, close it, then try fresh inputs in your own files. Treat the attempt as guided; seeing the solution does not award a practical pass.
Course help is guidance, not independent evidence. With JavaScript, opening help records guidance locally; otherwise note it in your README. Reset does not erase that history.
Repair a failed check
If error messages contain raw text, replace interpolation with the declared safe message; do not just mask one example token. If every error becomes 422, narrow the domain handler. If a 500 test rethrows, that is TestClient’s useful default—inspect its response only in a separate deliberate test. If a request ID is missing, inspect the shared request scope rather than fabricating a constant.
NotImplementedError means a practice stub is still unfinished. Read the failing test name and the last error line. Change one behavior, rerun that build, then rerun all implemented builds.
Show it works on new inputs
Add changed malformed/schema/auth/missing/conflict cases and a deliberately unexpected local fault. Retain safe status/code observations and show that no failed operation is reported complete. Explain default test rethrow versus deliberate 500 response inspection and why full tracebacks are not automatically share-safe.
Self-review: name the input, result, refused case and reason. Your local test output and explanation are separate from a quiz score; this page does not certify a pass.
Keep the idea
Honest failure handling is not silence. Give the caller a safe recovery contract and keep the real unexpected fault available for local diagnosis.