dorm.contrib.asyncguard¶
Surfaces sync ORM calls executed inside a running event loop — the bug pattern that turns a fast async route into a slow one because every other request on the worker stalls while the sync query blocks.
The guard hooks pre_query and walks the call stack: a sync ORM
call inside a coroutine triggers the configured action
("warn" / "raise" / "raise_first"); legitimate a*
async calls stay silent.
When to enable¶
- Tests / dev:
"warn"(or"raise"if you want hard failures) — catches the bug at design time. - Production: leave disabled. Stack walking on every query has a small cost; you'd rather pay a bit at dev to never ship the pattern in the first place.
# conftest.py — opt in for the whole test suite
from dorm.contrib.asyncguard import enable_async_guard
def pytest_configure(config):
enable_async_guard(mode="warn")
Modes¶
| Mode | Behaviour |
|---|---|
"warn" |
Single WARNING per call site (template-deduped). |
"raise" |
Raises :class:SyncCallInAsyncContext on every offender. |
"raise_first" |
Raises once, then degrades to "warn" so logs aren't drowned. |
SyncCallInAsyncContext inherits from :class:BaseException so it
bypasses Signal.send's except Exception and surfaces as a 500
instead of a logged-and-swallowed warning.
API¶
dorm.contrib.asyncguard.enable_async_guard(mode: Mode = 'warn') -> None
¶
Activate the guard. Idempotent — calling twice does not attach
a second receiver. Call :func:disable_async_guard to turn off.
mode: "warn" (log once per offender), "raise" (always
raise), or "raise_first" (raise once, then degrade to warn).
dorm.contrib.asyncguard.disable_async_guard() -> None
¶
Disconnect the guard. After this call, sync ORM operations inside a running event loop are no longer flagged.
dorm.contrib.asyncguard.SyncCallInAsyncContext
¶
Bases: BaseException
Raised when enable_async_guard(mode="raise") (or
"raise_first") detects a sync ORM call inside a running event
loop.
Inherits from :class:BaseException (not :class:Exception) so
that the dispatcher inside Signal.send — which catches
Exception to keep one bad receiver from sinking the whole
save — propagates this one to the caller anyway. That's exactly
what we want for a programming-error guard: surface as a 500,
don't get logged-and-swallowed.