Files
prompts/docs/skills/async-fastapi-sqlmodel/references/implicit_io.md
T

3.2 KiB

Preventing Implicit ORM I/O (Asyncio)

!!! info "Primary sources" - Preventing implicit I/O with AsyncSession - SQLAlchemy relationship loading

??? abstract "Decision metadata" - Status: adopted - Decision level: advisory - Applies to: api-runtime, workers, tests - Last reviewed: 2026-06-17


Purpose

Minimize unexpected database round-trips caused by attribute access in async ORM code.

In asyncio applications, hidden lazy loads are easy to miss and can produce runtime surprises. This guide defines explicit-loading defaults and progressive enforcement practices.


Scope and Non-Goals

  • In scope: relationship loading strategy, post-commit attribute access, explicit refresh/awaitable access patterns.
  • Out of scope: full ORM performance tuning and domain-specific query architecture.

Rules

  • Prefer explicit eager loading for data required by endpoint/service outputs.
  • Avoid relying on implicit lazy-load behavior in request critical paths.
  • Keep expire_on_commit=False unless strict expiration behavior is intentionally required.
  • Use explicit refresh or awaitable-attribute access when loading deferred state is necessary.

Pattern A: Eager-load what you need

from sqlalchemy import select
from sqlalchemy.orm import selectinload

stmt = select(User).options(selectinload(User.roles))
users = (await session.scalars(stmt)).all()

Pattern B: Explicit refresh of named attributes

user = await session.get(User, user_id)
await session.refresh(user, ["roles"])

Pattern C: Awaitable attribute access where needed

# Requires AsyncAttrs mixin on mapped base or class.
roles = await user.awaitable_attrs.roles

Practical Enforcement Model

Require explicit I/O behavior on every async ORM path:

  1. Define loader options for relationships and deferred columns needed by the operation.
  2. Use refresh() or awaitable attributes only when the additional query is deliberate and visible.
  3. Add review checks that reject unplanned lazy-load paths.

This keeps event-loop behavior predictable and makes query boundaries reviewable from the code.


Anti-Patterns

  • Returning ORM objects from handlers and triggering lazy loads during serialization.
  • Assuming post-commit attribute access will always be loaded without explicit strategy.
  • Relying on broad expiration + implicit reload behavior in async request flows.
  • Enabling relationship patterns that hide SQL behavior in critical code paths.

Operational Checks

  • Endpoint query blocks define loader options for returned related data.
  • Critical handlers do not depend on incidental lazy loads.
  • Known exceptions are documented with rationale and follow-up items.

Testing Checks

  • Integration tests cover endpoints that return related objects.
  • Tests verify expected data is present without hidden secondary query surprises.
  • Regression tests exist for routes previously affected by implicit-load failures.