aeat.adapters.persistence.storage.master_key._active_session module

Active-bucket session resolution for the column-level encrypt path.

The column-level TypeDecorator set in adapters/persistence/storage/crypto/_encrypted_columns.py cannot thread an explicit session reference through SQLAlchemy’s process_bind_param() signature (the method is invoked by SQLAlchemy’s column machinery with a fixed (self, value, dialect) shape). The substrate also forbids module-global mutable state that could survive a bucket switch — the BucketSession instance is the only legitimate owner of unlocked KEK and DEK bytes.

This module composes both constraints with a ContextVar (PEP 567) holding the active BucketSession. The CLI entry point opens a session and enters activate_session() as a contextmanager; every column-level decrypt or encrypt call inside the block resolves the active DEK through get_active_master_key(). On exit the ContextVar token is reset to the previous value (None at the top of the stack), so no key material outlives the with-block.

The pattern is per-thread and per-async-task by PEP 567 semantics. asyncio.Task instances inherit a copy of the parent context at creation time, so the active session crosses into spawned tasks correctly. concurrent.futures.ThreadPoolExecutor workers do NOT inherit ContextVar state by default; future code introducing a thread-pool worker on the encrypt path must propagate the active session explicitly via contextvars.copy_context().

exception NoActiveBucketSessionError(detail=None)[source]

Bases: SecretStoreError

Raised when the encrypt path runs outside an active session block.

Carries no payload — the diagnostic message names the canonical remediation verb so operators see how to recover without re-parsing the message.

Parameters:

detail (str | None)

Return type:

None

code: ClassVar[ErrorCode]
activate_session(session)[source]

Bind session as the active BucketSession for the block.

The previous value of the ContextVar is restored on exit via the contextvars.Token returned by set(), so nested activations stack and unwind cleanly. The session itself is not closed on exit — ownership of the BucketSession lifecycle stays with the caller that opened it.

Parameters:

session (BucketSession) – The unlocked BucketSession whose DEK becomes the column-level encryption key for the duration of the block.

Return type:

Iterator[None]

get_active_master_key()[source]

Return the DEK bytes of the currently-active BucketSession.

Used by every column-level encrypt and decrypt operation in _encrypted_columns.py. The DEK (not the KEK) is the AES-256-GCM key for the row-ciphertext layer — the KEK only ever unwraps the DEK during BucketSession.open().

Return type:

bytes

Returns:

The 32-byte DEK used for AES-256-GCM column-level encryption.

Raises:
has_active_bucket_session()[source]

Return whether an active BucketSession is bound.

Return type:

bool

current_active_bucket_session()[source]

Return the currently-bound BucketSession, or None.

Read-only observation of the active-session ContextVar for callers (storage runtime readiness, per-request secure-object session gating) that need the live session’s attributes (bucket_id, sealed, idle deadline) rather than only its DEK (get_active_master_key()) or its presence (has_active_bucket_session()). Never mutates the context; only activate_session() and suspend_active_session() may bind or clear it.

Return type:

BucketSession | None

suspend_active_session()[source]

Temporarily clear the active BucketSession for the current context.

Return type:

Iterator[None]