Skip to content

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.