loom.core.repository.sqlalchemy.rls¶
Row-level security support for the SQLAlchemy backend.
The product declares its database users, names and schema here; loom installs a static guard and configures it from that declaration as bound data, and never names anything itself.
- class loom.core.repository.sqlalchemy.rls.BootstrapConfig(schema, roles, database_users, names, revoke_public=False, scram_iterations=4096)[source]¶
Bases:
objectEverything the bootstrap applies for one application schema; no name has a default.
revoke_publicrevokes everyPUBLICprivilege on thepublicschema; it is opt-in because that schema belongs to the whole database.scram_iterationsis the PBKDF2 iteration count of every password verifier; it defaults to Postgres’ ownscram_iterations(4096) and may only be raised.- Parameters:
schema (str)
roles (DatabaseRoles)
database_users (Mapping[str, DatabaseUser])
names (SchemaNames)
revoke_public (bool)
scram_iterations (int)
- property bypass_users: tuple[str, ...]¶
The declared users with
access="bypass", in declaration order.
- validated()[source]¶
Return
selfafter checking every name Postgres will see.- Raises:
ValueError – Naming the first identifier Postgres would fold, truncate or resolve to something else, or two roles sharing a name.
- Return type:
- class loom.core.repository.sqlalchemy.rls.Finding(table, check, expected, actual)[source]¶
Bases:
objectOne difference between the declaration and the database.
- class loom.core.repository.sqlalchemy.rls.Report(ok, findings)[source]¶
Bases:
objectThe outcome of
verify;okwhen nothing differs.
- class loom.core.repository.sqlalchemy.rls.DatabaseRoles(owner, migrator)[source]¶
Bases:
objectThe two database roles every scoped schema needs; unrelated to RBAC roles.
- class loom.core.repository.sqlalchemy.rls.DatabaseUser(login, access)[source]¶
Bases:
objectA database user the product declares, with how far it may reach.
- class loom.core.repository.sqlalchemy.rls.SQLAlchemyElevationSink[source]¶
Bases:
objectSets and clears elevation flags on the transaction of the active session.
- class loom.core.repository.sqlalchemy.rls.SchemaNames(guard, readers, writers, version_table, data_version_table)[source]¶
Bases:
objectThe names around one application schema; declared by the product, never defaulted.
loom schema initproposes them withderived()and writes them into the product’s configuration, where the product may change any of them.
- async loom.core.repository.sqlalchemy.rls.apply_bootstrap(superuser_url, config, passwords, *, salt_factory=lambda : ..., lock_timeout='5s')[source]¶
Install or upgrade the guard of
config.schemaand configure it, in one transaction.- Raises:
ConfigError – When a name or the lock timeout is invalid, the server refuses to create the event triggers (superuser or
rds_superuserneeded), the guard holds revisions this release does not know, or the guard differs from the released one before commit.- Parameters:
- Return type:
None
- async loom.core.repository.sqlalchemy.rls.create_schema(migrator_url, application, *, lock_timeout='5s')[source]¶
Run
create_allon the application’s metadata through the migrator.The schema’s advisory lock is taken first, the guard’s revision is checked before any guard call, and the listeners registered at compile time open the hatch and call the guard, guard first, for every scoped table, so each table is protected in the transaction that creates it; the guard’s assertion closes the transaction.
- Raises:
ConfigError – When scoped tables exist on a non-Postgres dialect without the opt-out, when the guard of the application schema is missing, unknown or below the minimum compatible revision, or when the migrator does not land in the application schema.
- Parameters:
migrator_url (str)
application (Application)
lock_timeout (str)
- Return type:
None
- async loom.core.repository.sqlalchemy.rls.detach_range_partitions(target, application, model, start, end, *, interval='month', drop=False, lock_timeout='5s')[source]¶
Detach, and with
dropdrop, the partitions ofmodelcovering[start, end).Each partition is unprotected and detached by the guard; a partition that no longer exists is skipped. A detached table keeps its rows without row-level security and without group grants; only the owner and the bypass users reach it.
- Returns:
The names of the partitions detached, in order.
- Raises:
- Parameters:
- Return type:
- async loom.core.repository.sqlalchemy.rls.elevate(scope, decision, *, at, catalog=None)[source]¶
Elevate
scopefor the current execution after checkingdecision.- Raises:
RuntimeError – Outside an execution frame.
ConfigError – Without a product, or when the product did not map
scope.PermissionError – When the decision does not justify the elevation.
- Parameters:
scope (str)
decision (Decision)
at (Scope)
catalog (RoleCatalog | None)
- Return type:
None
- async loom.core.repository.sqlalchemy.rls.ensure_range_partitions(target, application, model, start, end, *, interval='month', lock_timeout='5s')[source]¶
Create the partitions of
modelcovering[start, end)that do not exist yet.targetis the migrator’s URL, run in its own transaction, or an open connection of the migrator, run inside the caller’s transaction. Each partition is created, registered and protected by the guard in that transaction.- Returns:
The names of the partitions created, in order.
- Raises:
ConfigError – When
modelis not a range-partitioned scoped table of the application, the connection does not act as the schema’s owner inside it, or the guard predates partition support.ValueError – When
intervalis unknown or a partition name is too long.
- Parameters:
target (str | AsyncConnection)
application (Application)
start (dt.date)
end (dt.date)
interval (Interval)
lock_timeout (str)
- Return type:
- loom.core.repository.sqlalchemy.rls.install_pool_reset(engine)[source]¶
Run
RESET ALLwhenever a connection returns to the pool.The open transaction is rolled back first and the reset is committed, because Postgres reverts session-level settings changed inside a transaction that is later rolled back, which is exactly what the pool does on return.
- Parameters:
engine (sqlalchemy.ext.asyncio.AsyncEngine)
- Return type:
None
- loom.core.repository.sqlalchemy.rls.register_scope_source(name, source)[source]¶
Publish
request.<name>; registering a name twice is an error.
- loom.core.repository.sqlalchemy.rls.rls_session_settings(application, product=None)[source]¶
Build the provider for
application; the product’s settings are merged in.- Raises:
ConfigError – When a scope binding is missing, malformed or unregistered.
ValueError – At call time, when the product emits a key under
loom.scope..
- Parameters:
application (Application)
product (SessionSettings | None)
- Return type:
- loom.core.repository.sqlalchemy.rls.validate_elevations(product, scoped)[source]¶
Fail at startup when
elevationsnames a scope that is unknown or not elevable.- Parameters:
product (AuthzProduct)
scoped (Mapping[tuple[str | None, str], ScopedTable])
- Return type:
None
- async loom.core.repository.sqlalchemy.rls.verify(url, application)[source]¶
Compare the database with the application’s declaration.
- Parameters:
url (str)
application (Application)
- Return type: