cadrumo.core.process_binding module¶
One process-wide binding with a context-scoped override.
A ContextVar is the right shape for a value that must
stay private to one span of work, and the wrong shape for a value that names
what is true of the whole process. The live profile session is the second
kind: exactly one profile is logged in per process, and every entry point –
a parsed CLI invocation, a Textual thread worker, a second
asyncio.run() frame in the same program – must observe the same
answer.
PEP 567 semantics defeat that on their own. asyncio.run runs its loop in
a fresh context, a thread does not inherit its spawner’s context, and
contextvars.Context.run() discards every write made inside it. A
binding installed in any of those frames is therefore invisible to the frame
that reads it, which presents to the operator as a correct login followed by
a surface that cannot find the profile it just authenticated.
This binding composes both needs. The process-wide value is guarded by a
lock and is what an unscoped bind publishes; the context-scoped override
is what a with block installs for the duration of one span and unwinds on
exit. Reads consult the override first, so a nested span still shadows the
process value exactly as a plain ContextVar did, and fall back to the
process value when no override is in force.
The binding holds a reference, never a copy: an owner that zeroises key material on close keeps that responsibility, and this type only decides who can see the object.
- final class ProcessScopedBinding(name)[source]¶
Bases:
GenericPublish one value process-wide while allowing a scoped override.
The override is stored as a one-tuple rather than the bare value so that “no override in this context” and “an override deliberately set to
None” stay distinguishable. The second is a real state: a caller that suspends the binding for a block is asserting an absence, and collapsing it into the first would let the process value show through the very block that hid it.- Parameters:
name (str)
- get()[source]¶
Return the override in force for this context, else the process value.
- Return type:
T | None
- bind(value)[source]¶
Publish
valueprocess-wide, and into any override in force here.Refreshing an override in force is what keeps an unscoped bind truthful to its own caller: a bind performed inside a span that shadows the process value would otherwise be published to every context except the one that performed it.
- Return type:
None
- Parameters:
value (T | None)
- clear_bound(expected)[source]¶
Remove
expectedwherever this context can still observe it.A retiring owner must unbind the exact object it observed, from the override in force here as well as from the process value, because a session closed inside an overriding block is bound in both places and clearing only one leaves a zeroised object advertised as live.
Identity, not equality: a replacement installed reentrantly during the retirement is a different object and must survive it, which is exactly what an equality comparison would fail to preserve.
- Return type:
None
- Parameters:
expected (T)
- override(value)[source]¶
Shadow the process value with
valuefor the duration of the block.Overrides stack and unwind through the
Tokenthe override variable returns, so a nested block restores the exact binding its caller observed. The process value is never touched here; onlybind()andclear_bound()change what the rest of the program sees.- Return type:
Generator[None]
- Parameters:
value (T | None)