Source code for loom.core.repository.sqlalchemy.rls.sources
"""Where a scope's value comes from on each request.
A binding is ``identity.<attribute>`` (``subject`` or a verified attribute of
the caller) or ``request.<name>`` (a callable the product registered before
startup). loom never names a scope or a source itself.
"""
from __future__ import annotations
import re
from collections.abc import Callable, Mapping
from loom.core.config import ConfigError
from loom.core.identity import current_identity
ScopeSource = Callable[[], object | None]
_BINDING = re.compile(r"^(identity|request)\.([A-Za-z_]\w*)$")
_sources: dict[str, ScopeSource] = {}
[docs]
def register_scope_source(name: str, source: ScopeSource) -> None:
"""Publish ``request.<name>``; registering a name twice is an error."""
if name in _sources:
raise ValueError(f"scope source {name!r} is already registered")
_sources[name] = source
def clear_scope_sources() -> None:
"""Forget every registered source; for tests."""
_sources.clear()
def validate_bindings(scope_sources: Mapping[str, str]) -> None:
"""Fail at startup when a binding is malformed or names an unregistered source.
Raises:
ConfigError: Naming the scope and the binding.
"""
for scope, binding in scope_sources.items():
kind, name = _parse(scope, binding)
if kind == "request" and name not in _sources:
raise ConfigError(
f"database.schema.scopes.{scope} binds {binding!r} but no scope source "
f"named {name!r} was registered"
)
def resolve_binding(binding: str) -> object | None:
"""Return the current value of ``binding`` or ``None`` when there is none."""
kind, name = _parse("", binding)
if kind == "identity":
identity = current_identity()
if name == "subject":
return identity.subject or None
return identity.attribute(name)
source = _sources.get(name)
return source() if source is not None else None
def _parse(scope: str, binding: str) -> tuple[str, str]:
match = _BINDING.fullmatch(binding)
if match is None:
raise ConfigError(
f"database.schema.scopes.{scope} {binding!r} must be identity.<attribute> "
"or request.<name>"
)
return match.group(1), match.group(2)