Skip to content
Aabha AI Academy

Stage 3 · L16

Trace validated input into a row and back into a response

Core · original Session 3

Native checkpoint 03

Download starter · Download solution

Use the starter for this stage's focused examples. The cumulative transfer and solution belong at the stage-end capstone. Baseline checks pass; transfer checks initially fail. Downloads contain the matching native starter and solution for this stage.

Follow one complete vertical slice before introducing layers. A client POSTs JSON; FastAPI constructs EquipmentIn; create_equipment constructs an Equipment row from those validated fields; session.add/flush writes within a transaction; EquipmentOut validates the scalar row representation; session.commit makes it durable; the handler returns 201 and Location.

This sequence makes the boundaries observable. At the API boundary, invalid input never enters the write handler. At the database boundary, a duplicate name can fail even when input validation succeeds. At the output boundary, the public schema selects and validates the result. At the transaction boundary, commit happens before success. A fresh-session GET confirms the claimed stored effect.

Keep model_dump limited to the declared input schema. The extra-field rule means a caller cannot set an id or later private role by adding JSON keys. That is useful defense at the data boundary, but it is not authentication or authorization. The current training create routes remain intentionally unauthenticated until the identity stages.

The customer transfer repeats this sequence with a different identity type and resource. Use User(email=payload.email), flush its generated UUID, create CustomerOut, commit, set Location and read it by UUID. Preserve 409/422/404 failure behavior and never add login semantics. The solution is exactly these two handlers; it does not solve a later registration/verification task for you.

Stage 04 will move the write decision and commit into a service and queries into a repository without changing this public behavior. First prove this simple slice works. Refactoring an untested request→row→response flow would make it difficult to tell whether an error came from architecture changes or from the original contract.

JSON → EquipmentIn; EquipmentIn → Equipment row; add + flush → pending stored row; EquipmentOut → validated response; commit → durable row; 201 + Location → fresh GET; Transfer: the same path for; Customer + UUID.
JSON → EquipmentIn; EquipmentIn → Equipment row; add + flush → pending stored row; EquipmentOut → validated response; commit → durable row; 201 + Location → fresh GET; Transfer: the same path for; Customer + UUID.

Follow the running code

Focused lesson example; see the end-of-stage capstone for the cumulative app · stage 03

Request CustomerIn → add User profile → flush identity
→ validate CustomerOut → successful commit → 201/Location
Next GET → a new owned session reads the committed profile.

Predict and observe this focused example using the concepts explained above. Its boundary is stated in the focused answer.

Guided lab

  1. Read the explanation and predict the focused example’s outcome.
  2. Trace a customer POST and later GET. Identify which boundary rejects invalid email, obtains the UUID and excludes private fields.
  3. Compare the observed outcome with the focused answer and state its boundary.

Expected: Pydantic accepts only the public input contract; User stores the profile; flush obtains the generated UUID within the transaction; CustomerOut validates public output before successful commit. 201/Location follows completion, and a later GET must read through another session. This is a profile, not a login account. Implement the full customer slice only in the stage capstone.

  • A model constructed in memory is not proof of persistence; a TestClient response alone is not proof that an independent session can read the row.

Focused exercise and answer

Complete this focused exercise before reading its answer. The full native transfer is introduced only at the end of the stage.

Your transfer task: Trace a customer POST and later GET. Identify which boundary rejects invalid email, obtains the UUID and excludes private fields.

  1. Trace a customer POST and later GET. Identify which boundary rejects invalid email, obtains the UUID and excludes private fields.
Inspect the matching answer

This answer addresses the focused exercise above; the cumulative implementation is shown only after the stage prerequisites.

Pydantic accepts only the public input contract; User stores the profile; flush obtains the generated UUID within the transaction; CustomerOut validates public output before successful commit. 201/Location follows completion, and a later GET must read through another session. This is a profile, not a login account. Implement the full customer slice only in the stage capstone.

Stage 03 capstone — after these prerequisites

Implement Customer POST/GET, public output,201/Location, invalid/duplicate no-effect and fresh-session persistence.

Use the downloaded starter after completing this stage's focused exercises. The cumulative implementation below is a stage transfer answer, not an answer to an earlier lesson.

python run_checks.py --stage 03 --role starter --prepare
python run_checks.py --stage 03 --role starter
python run_checks.py --stage 03 --role starter --transfer

From this extracted starter: preparation and baseline pass; transfer initially fails only at the named unfinished target. After implementing it, rerun the same starter --transfer command and expect success.

Optional comparison in a separate solution directory

Optional comparison: download and extract this stage’s solution ZIP into a separate directory. Change your terminal into that extracted solution root (beside checkpoint.json and run_checks.py) before running the following commands. Your starter remains a starter even after you implement its task.

python run_checks.py --stage 03 --role solution --transfer
Inspect the cumulative capstone implementation
@app.post("/customers", status_code=201, response_model=CustomerOut)
def create_customer(payload: CustomerIn, response: Response, session=Depends(session_dependency, scope="function")):
    row = User(email=payload.email)
    session.add(row)
    session.flush()
    output = CustomerOut.model_validate(row)
    session.commit()
    response.headers["Location"] = f"/customers/{output.id}"
    return output


@app.get("/customers/{customer_id}", response_model=CustomerOut)
def get_customer(customer_id: UUID, session=Depends(session_dependency, scope="function")):
    row = session.get(User, customer_id)
    if row is None:
        raise HTTPException(404, detail="Customer not found")
    return row

Check your reasoning

What evidence establishes the complete slice?

Show the explanation

The actual HTTP contract plus a fresh PostgreSQL session reading the committed row, and failure cases proving no unintended writes.

Reading progress

54 lessons remain open to guests. Marking a lesson read records reading only; it does not award assessment credit or a certificate.

Device reading marks require browser storage. Reading is always available.

Sign in or create an account to save separate account progress. Your current page is kept.