Source code for fsh_lib.utils
"""General-purpose runtime utilities used by generated apps.
Three unrelated concerns share this module by convention --
:func:`get_object_from_query_or_404` used by every read-or-mutate
CRUD handler, the :func:`run_once` decorator used by the
generated telemetry init, and :func:`compile_query` for tests
that assert against rendered SQL. Bundling them here keeps the
public ``fsh_lib`` surface flat enough that consumers learn one
import path (``from fsh_lib.utils import ...``) for everything that
doesn't fit under a more specific submodule.
"""
import functools
from typing import TYPE_CHECKING, Any, Literal
from fastapi import HTTPException, status
from sqlalchemy.dialects import postgresql, sqlite
if TYPE_CHECKING:
from collections.abc import Callable
from sqlalchemy.engine import Dialect
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.sql import ClauseElement
# -------------------------------------------------------------------
# HTTP-status row-lookup guards
# -------------------------------------------------------------------
[docs]
async def get_object_from_query_or_404(
db: AsyncSession,
stmt: Any,
*,
detail: str = "Not found",
) -> Any:
"""Execute *stmt* and return the first row, or raise HTTP 404.
Args:
db: The async database session.
stmt: A SQLAlchemy selectable statement.
detail: The error message for the 404 response.
Returns:
The first row from the result set.
Raises:
HTTPException: With status 404 when no row is found.
"""
result = await db.execute(stmt)
row = result.scalars().one_or_none()
if row is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=detail,
)
return row
# -------------------------------------------------------------------
# Once-only execution
# -------------------------------------------------------------------
_UNSET: Any = object()
[docs]
def run_once[T](fn: Callable[..., T]) -> Callable[..., T]:
"""Idempotency decorator: run ``fn`` once, return its result thereafter.
Unlike :func:`functools.cache`, the gate is *argument-blind* --
a second call with a different argument set is still a no-op,
not a fresh execution keyed on the new args. This is the
correct shape for one-shot setup functions (``init_telemetry``,
where a second ``init_telemetry(app2)`` must not install a
second tracer provider) *and* for factory singletons (an OPA
client built once at first use and reused thereafter).
The first call's return value is cached and returned on every
later call. Setup-only callers that ignore the return value
are unaffected; factory callers ``@run_once`` their
constructor and read the cached instance.
"""
cached: T = _UNSET
@functools.wraps(fn)
def wrapper(*args: Any, **kwargs: Any) -> T:
nonlocal cached
if cached is _UNSET:
cached = fn(*args, **kwargs)
return cached
return wrapper
# -------------------------------------------------------------------
# SQL inspection helper (test-only, but ships in fsh_lib so consumers
# can reuse it)
# -------------------------------------------------------------------
_DIALECTS: dict[str, Callable[[], Dialect]] = {
"postgres": postgresql.dialect,
"postgresql": postgresql.dialect,
"sqlite": sqlite.dialect,
}
"""Named dialect factories accepted by :func:`compile_query`.
``"postgres"`` and ``"postgresql"`` are aliases."""
[docs]
def compile_query(
stmt: ClauseElement,
*,
dialect: Literal["postgres", "postgresql", "sqlite"] | None = None,
literal_binds: bool = True,
) -> str:
"""Render a SQLAlchemy statement to a single SQL string.
Test-oriented helper: tests that assert against generated SQL
(locking modifiers, where-clause shape, computed expressions)
repeatedly spell ``str(stmt.compile(compile_kwargs={"literal_binds":
True}))`` and frequently need a Postgres dialect to surface
pg-specific syntax (``SKIP LOCKED``, ``ON CONFLICT``, ...).
Centralising the boilerplate keeps assertions readable and
avoids per-test imports of the dialect submodule.
Args:
stmt: Any SQLAlchemy clause -- ``select()``, ``insert()``,
``update()``, raw ``text()``, etc.
dialect: Optional dialect name. ``None`` (the default) uses
SQLAlchemy's generic compiler, which strips
dialect-specific clauses (``FOR UPDATE`` survives;
``SKIP LOCKED`` does not). Pass ``"postgres"`` to
render Postgres SQL or ``"sqlite"`` for sqlite.
literal_binds: When ``True`` (the default), bound parameters
render inline -- ``WHERE id = 'abc'`` rather than
``WHERE id = :id_1``. Set ``False`` to inspect the
parameter map separately (via ``stmt.compile().params``).
Returns:
Compiled SQL as a string.
"""
compile_kwargs: dict[str, Any] = (
{"literal_binds": True} if literal_binds else {}
)
bound_dialect: Dialect | None = (
_DIALECTS[dialect]() if dialect is not None else None
)
return str(
stmt.compile(dialect=bound_dialect, compile_kwargs=compile_kwargs)
)