Saltar a contenido

dorm.budget

Per-block query budget — wall-clock timeout + max-rows ceiling. Both context-manager forms, sync and async; BudgetExceeded subclasses DatabaseError.

See framework-agnostic helpers for recipes.

API

dorm.budget.budget(*, timeout_ms: int | None = None, max_rows: int | None = None, using: str = 'default')

Enter a query budget block.

timeout_ms — wall-clock ceiling per statement.

  • On PostgreSQL the budget opens an implicit atomic() block so it can SET LOCAL statement_timeout; the database side aborts the query when the budget is exceeded. Side-effect: every write inside the block participates in one transaction — on rollback all the writes revert together. Code that is already inside atomic() reuses the outer transaction (savepoint).
  • Other backends ignore timeout_ms — there is no portable statement-timeout primitive.

max_rows — fail with :class:BudgetExceeded when a query materialises more than this many rows (post-fetch check, so the abort happens after the row count is known). Backend-agnostic.

using — alias whose connection receives the timeout. Only the named alias is touched.

Nested blocks combine via minimum — inner blocks can tighten but never relax the outer budget.

dorm.budget.abudget(*, timeout_ms: int | None = None, max_rows: int | None = None, using: str = 'default') async

Async counterpart of :func:budget. On PG opens an implicit aatomic() block; see :func:budget for the side-effects.

dorm.budget.BudgetExceeded

Bases: DatabaseError

Raised when a query inside an active :func:budget block exceeds either the wall-clock timeout or the row ceiling.

Subclasses :class:DatabaseError so generic except DatabaseError handlers swallow it gracefully — a budget violation is, after all, a database-level signal that the call should be aborted.

dorm.budget.current() -> _BudgetState | None

Return the currently-effective budget for inspection.

Backends call this before issuing a query to know whether to pre-set statement_timeout and how many rows to allow.

dorm.budget.check_rowcount(n: int) -> None

Raise :class:BudgetExceeded when n exceeds the active max_rows ceiling. Backends call this once per fetch so the abort happens before the rows escape into the caller's buffer.