Module 5 of 9 · Lesson 13 of 26
Convert database I/O to AsyncSession
Work through convert database I/O to AsyncSession using a runnable reference, a focused regression check and a local extension.
Read in any order. All lessons stay open, including after an unanswered or incorrect check.
Create an asynchronous engine with an async-capable driver and an async_sessionmaker. Each request opens and closes its own AsyncSession. expire_on_commit=False helps the reference construct and return an already-loaded response without unexpected post-commit attribute I/O. Explicit eager loading is still necessary for relationships; a serializer must not accidentally try implicit async database work. Transaction completion precedes the success response just as it did synchronously. Convert one boundary at a time and compare behavior through the same contracts. An async rewrite is not permission to relax capacity checks, hide failures or share mutable session state across concurrently running tasks.
Compare reserve and reserve_async side by side. Run lesson13: the async service reserves one unit, rejects a subsequent request for two, and a separate synchronous session confirms only one active unit was committed. This cross-check verifies database visibility as well as the response model.
async def reserve_async(session, equipment_id, owner_id, quantity):
if quantity < 1:
raise DomainError("invalid_quantity", "Request at least one unit.", 422)
async with session.begin():
item = await session.scalar(
select(Equipment).where(Equipment.id == equipment_id).with_for_update()
)
if item is None:
raise missing()
used = await session.scalar(
select(func.coalesce(func.sum(Rental.quantity), 0)).where(
Rental.equipment_id == equipment_id, Rental.status == "active"
)
)
if used + quantity > item.quantity:
raise DomainError(
"capacity_unavailable", "Not enough equipment units are available."
)
rental = Rental(equipment_id=equipment_id, owner_id=owner_id, quantity=quantity)
session.add(rental)
await session.flush()
result = RentalOut.model_validate(rental)
return result
python run_checks.py -k lesson13
Expected result The selected lesson test passes against a new temporary PostgreSQL database; the container is removed afterward.
Keep for reference
Equipment Rental lab and lesson checks
ZIP containing Python source, real Alembic migrations, 26 lesson checks, a dependency lock and text instructions. Extract it before following the local exercise.
Practise locally
Port a read-only equipment lookup to AsyncSession in your own copy. Add success and missing-record tests, then compare their outputs with a synchronous implementation. Keep session ownership in the caller and await every database operation. Document which values are loaded before returning the response and why serialization cannot issue an unawaited query.
The lesson check verifies the reference behavior. Add your own assertions for your change. Local practice is not uploaded or scored by this learning release.
Pause and reflect
What failure does this lesson prevent, and which assertion in lesson13 would expose it?
Use a concrete input, expected result and limitation from your local work. Saving a reflection does not certify the project.
Optional knowledge check
What must stay unchanged when converting this reservation to async?
The capacity transaction, public response and deliberate failure contract.
Correct. Async changes I/O scheduling; it should preserve the business invariant and client behavior.
Only the function name; locking and rollback can be removed.
Try another answer. Removing the transaction protection changes correctness even if requests appear faster.
Return the ORM object and let lazy relationships load themselves later.
Try another answer. Implicit async I/O during serialization can fail; load the required data explicitly.
This practice does not assess your project or award a certificate.
Your reading progress
Progress is saved in this browser when storage is available.