Stage 2 · L15
Give each database session a clear owner and lifetime
Core · original Session 2; extended normalization/cardinality visuals
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.
The engine owns connections and pooling. A Session is a unit of work: it tracks objects and coordinates one transaction's database interaction. It is not a global request cache and should not be shared between concurrent requests. configure_database creates one engine/session factory; session_dependency opens a session for one request and closes it on exit.
session.add marks an object pending. flush sends its INSERT within the current transaction and obtains generated identifiers; it does not commit. A second connection cannot see the uncommitted row. commit completes the transaction so fresh sessions can see it. rollback cancels pending database changes; closing an unfinished session rolls them back. The baseline proves this with an observer session before and after rollback.
refresh reloads a row from PostgreSQL. The transfer uses it after flush to read the server-generated created_at. Returning a freshly generated UUID is not the same as proving its row was committed. Keep the return inside session_factory.begin's context: successful exit commits before the function returns to its caller; an exception from the rental constraint rolls back both inserted rows.
FastAPI's yield dependency owns cleanup. Depends(..., scope='function') closes the session before the response is sent in this pinned runtime. Cleanup alone does not choose the successful business commit. Stage 03 explicitly commits the write before claiming 201; stage 04 moves that decision to the service layer. expire_on_commit=False prevents surprise reloads of simple fields after commit, but does not make lazy relationships safe.
After an IntegrityError, SQLAlchemy requires rollback before that same session can do more work. Prefer a clear transaction context that rolls back on exception. The stage-end capstone's invalid child quantity demonstrates atomic parent/child rollback through a fresh session. It does not yet enforce total stock, authorization or idempotency; later stages add those business rules.
Follow the running code
Focused lesson example; see the end-of-stage capstone for the cumulative app · stage 02
with SessionLocal() as session:
session.add(customer)
session.flush() # INSERT inside this transaction; obtains its generated key.
session.refresh(customer)
session.rollback() # Nothing became durable.Predict and observe this focused example using the concepts explained above. Its boundary is stated in the focused answer.
Guided lab
- Read the explanation and predict the focused example’s outcome.
- Predict what a second connection sees before commit, after rollback and after a successful transaction-context exit.
- Compare the observed outcome with the focused answer and state its boundary.
Expected: A second connection cannot see the uncommitted row. Rollback leaves no inserted row. Successful session_factory.begin exit commits before the function returns, so a fresh observer can read both related rows. refresh reloads generated values; flush is not durable completion. Apply these taught operations in the separate stage capstone.
- flush is not durable commit; an aborted transaction must be rolled back before reuse.
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: Predict what a second connection sees before commit, after rollback and after a successful transaction-context exit.
- Predict what a second connection sees before commit, after rollback and after a successful transaction-context exit.
Inspect the matching answer
This answer addresses the focused exercise above; the cumulative implementation is shown only after the stage prerequisites.
A second connection cannot see the uncommitted row. Rollback leaves no inserted row. Successful session_factory.begin exit commits before the function returns, so a fresh observer can read both related rows. refresh reloads generated values; flush is not durable completion. Apply these taught operations in the separate stage capstone.Stage 02 capstone — after these prerequisites
Implement the atomic customer/rental insert; flush/refresh generated facts and prove parent/child rollback.
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 02 --role starter --prepare
python run_checks.py --stage 02 --role starter
python run_checks.py --stage 02 --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 02 --role solution --transfer
Inspect the cumulative capstone implementation
def record_customer_rental(session_factory, email, equipment_id, quantity):
# Capacity/permissions are deliberately not promised here: stage 05/07 adds them.
with session_factory.begin() as session:
equipment = session.get(Equipment, equipment_id)
if equipment is None:
raise ValueError("Equipment not found")
customer = User(email=email)
session.add(customer)
session.flush() # Obtain its UUID before assigning the foreign key.
rental = Rental(equipment_id=equipment.id, owner_id=customer.id, quantity=quantity)
session.add(rental)
session.flush()
session.refresh(rental) # Read the database-generated created_at.
return customer.id, rental.id, rental.created_at
Check your reasoning
Can another connection read the row after flush but before commit?
Show the explanation
No. Flush sends SQL inside the still-open transaction. Commit makes successful changes visible; rollback discards them.
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.
Your earlier place on this device suggests these lessons. No new lesson is marked read.