Skip to content

0.8.0

Latest

Choose a tag to compare

@h0rn3t h0rn3t released this 05 Sep 21:49
· 2 commits to main since this release
f4b7e9e

First stable release of the 0.8 line, and the first stable since 0.7.1.post3.

⚠️ Breaking

  • Python 3.12+ is now required (was 3.9+).
  • SQLAlchemy 2.0+ is now required (was 1.4.19+), and the dependency is SQLAlchemy[asyncio]. Plain SQLAlchemy>=2.0 only pulls greenlet for a fixed list of platform_machine values that excludes arm64, so on Apple Silicon the first query failed with ValueError: the greenlet library is required. The asyncio extra requires it unconditionally.

Pool resilience

A saturated connection pool doesn't fail — it queues, for the whole engine-wide pool_timeout. When a readiness probe takes its connection from the same pool as business traffic, the probe queues too, the pod leaves Service endpoints, and callers get 503s while the application logs almost nothing. This release adds the API to fail fast on a saturated pool, to see saturation coming, and to bound load before it gets there.

  • db(pool_timeout=...) / db.connection(timeout=...) — a per-context checkout deadline layered on the engine-wide pool_timeout, which SQLAlchemy cannot override per call. A probe fails in 1s instead of parking for 60.
  • PoolTimeoutError — subclasses the builtin TimeoutError, so existing handlers keep working, and carries retry_after plus a pool snapshot. Exhaustion can be answered with a controlled 503 + Retry-After instead of an opaque 500.
  • db.pool_status() — live size / capacity / checked_out / available / saturation for metrics. NullPool / StaticPool report None rather than crashing.
  • pool_warn_threshold — a throttled WARNING once the pool crosses a share of its capacity, well before anything has timed out.
  • exclude_paths — listed paths get no request session at all, so a probe cannot quietly borrow from the business pool.
  • max_concurrent_requests / request_queue_timeout — bound in-flight requests per middleware instance, returning 503 + Retry-After before the route runs.

Request session lifetime

  • The request session is finalized once the response body is ready, so a slow background task no longer pins its connection. A failing commit still prevents a successful response from being reported.
  • yield dependencies that own their transaction (db.session.begin() / begin_nested()) are exempt from that early finalization. FastAPI runs their teardown after the response, so finalizing there rolled their work back right before their own commit — a 200 with no row written.
  • Streaming bodies: the request session is finalized when the body starts flowing, and touching it afterwards raises an error pointing at async with db(). This behaves the same behind a @app.middleware("http"), which re-emits every response as a chunked one.
  • http.response.pathsend is now handled as the end of the response body, so FileResponse's background task no longer deletes the served file before the server reads it.

Fixes

  • A plain db() nested inside db(multi_sessions=True) kept resolving db.session to the enclosing session: the nested block committed an empty session while its writes rode on — and were rolled back with — the outer one.
  • db.session silently ignored a context pool_timeout in multi-session mode, parking on the engine-wide deadline instead. It now refuses, pointing at db.connection() / db.gather().
  • session_args={"expire_on_commit": ...} or {"class_": ...} raised TypeError: got multiple values for keyword argument.
  • db.gather() leaked a never-awaited coroutine when entering the managed context failed, and ignored a context pool_timeout when max_concurrent was unset.
  • Multi-session cleanup keys off Task objects rather than id(task), and creates its cleanup task directly instead of via call_soon.
  • Exceptions are exported from the package root, as the docs already claimed.
  • DBSessionMeta is exposed as a typing Protocol, so db.session / db.connection() / db.gather() type-check and autocomplete (#18).

Docs

A full documentation site: https://h0rn3t.github.io/fastapi-async-sqlalchemy/ — including new guides on health checks and pool saturation, HTTP load and background tasks, concurrency, and streaming responses.

Full Changelog: 0.7.1.post3...0.8.0