mixinv2 package¶
Module contents¶
MIXINv2: A dependency injection framework with pytest-fixture-like semantics.
Public API¶
- Decorators:
- Runtime:
- Exceptions:
- Reference types (parameters to
extend()): ResourceReference
- final class mixinv2.AbsoluteReference(*, path: Final[tuple[Hashable, ...]])¶
Bases:
objectAn absolute reference to a resource starting from the root scope.
- path: Final[tuple[Hashable, ...]]¶
- exception mixinv2.FixpointRecursionError(message: str, *, incomplete_result: object)¶
Bases:
RecursionErrorRaised when fixpoint iteration is exhausted or reentry is detected with no iterations remaining.
Carries the best approximation computed so far in
incomplete_result. As aRecursionErrorsubclass, existing code that catchesRecursionErrorwill also catchFixpointRecursionError.- incomplete_result: object¶
- final class mixinv2.LexicalReference(*, path: Final[tuple[Hashable, ...]])¶
Bases:
objectA lexical reference following the MIXINv2 spec resolution algorithm.
This reference type implements lexical scoping with late-binding and same-name skip semantics (pytest fixture style).
Symbol Level (Static Analysis)¶
Resolution starts from
self.outer(not self). The algorithm traverses up the static hierarchy (outer,outer.outer, …) to calculate thede_bruijn_index(number of levels to go up).Same-name skip semantics: When
path[0]matches the symbol’s key, the first match is skipped. This allows a symbol to reference an outer symbol with the same name, similar to pytest fixtures shadowing outer fixtures.Runtime Level (Late Binding)¶
The calculated index is resolved at runtime through the
outerchain. This provides late-binding semantics: a reference defined in a base scope will resolve to the definition in the actual derived scope if it has been overridden.Algorithm Steps (at Symbol Layer)¶
At each static level (
outer, …):Is path[0] a property of that level?
If path[0] == symbol.key and this is the first match → skip, continue to outer
Otherwise → return full path
Recurse to static outer
The returned
RelativeReferencehas:de_bruijn_index: Static levels to go up.path: Always the full path from the original reference.
- path: Final[tuple[Hashable, ...]]¶
- final class mixinv2.QualifiedThisReference(*, self_name: str, path: Final[tuple[str, ...]])¶
Bases:
objectA qualified this reference: [SelfName, ~, property, path].
The second element is null (~ in YAML), distinguishing from regular inheritance. This provides late binding by resolving through the dynamic self of the enclosing scope named SelfName.
Semantics: Walk up the symbol table chain to find a scope whose key matches self_name, retrieve that scope’s dynamic self (fully composed evaluation), then navigate the path segments through allProperties.
This is analogous to Java’s Outer.this.property.path.
- path: Final[tuple[str, ...]]¶
- self_name: str¶
- final class mixinv2.RelativeReference(*, de_bruijn_index: Final[int], path: Final[tuple[Hashable, ...]])¶
Bases:
objectA reference to a resource relative to the current lexical scope.
This is used to refer to resources in outer scopes.
- de_bruijn_index: Final[int]¶
Number of levels to go up in the lexical scope.
- path: Final[tuple[Hashable, ...]]¶
- mixinv2.eager(definition: TMergerDefinition) TMergerDefinition¶
Decorator to mark a MergerDefinition as eager.
Eager resources are evaluated immediately when the scope is accessed, rather than being lazily evaluated on first use.
Example:
@eager @resource def config() -> dict: return load_config() # Loaded immediately
- Parameters:
definition – A MergerDefinition to mark as eager.
- Returns:
A new MergerDefinition with is_eager=True.
- mixinv2.evaluate(*namespaces: ModuleType | ScopeDefinition, modules_public: bool = False) Scope¶
Resolves a Scope from the given namespaces.
This is the V2 entrypoint that provides: - Single lazy evaluation level (at Mixin.evaluated only) - Proper is_public semantics (private resources hidden from attributes) - Proper is_eager semantics (eager resources evaluated immediately) - Circular dependency support via two-phase construction
When multiple namespaces are provided, they are union-mounted at the root level. Resources from all namespaces are merged according to the merger election algorithm.
To control fixpoint iteration, set the class-level ContextVar before calling:
from mixinv2._core import fixpoint_cached_property fixpoint_cached_property.max_fixpoint_iterations.set(0) # single-pass fixpoint_cached_property.max_fixpoint_iterations.set(100) # bounded multi-pass fixpoint_cached_property.max_fixpoint_iterations.set(FixpointIterationSentinel.UNLIMITED) # unbounded (default)
- Parameters:
namespaces – Modules or namespace definitions (decorated with @scope) to resolve.
modules_public – If True, modules are marked as public, making their submodules accessible via attribute access. Defaults to False (private by default).
- Returns:
The root Scope.
Example:
root = evaluate(MyNamespace) root = evaluate(Base, Override) # Union mount root = evaluate(my_package, modules_public=True) # Make modules accessible
- mixinv2.extend(*inherits: AbsoluteReference | RelativeReference | LexicalReference | QualifiedThisReference) Callable[[TDefinition], TDefinition]¶
Decorator that adds inheritance references to a Definition.
Use this decorator to specify that a scope extends other scopes, inheriting their mixins.
- Parameters:
inherits – ResourceReferences to other scopes whose mixins should be included. This allows composing scopes without explicit merge operations.
Example - Extending a sibling scope:
from mixinv2 import LexicalReference @scope class Root: @scope class Base: @resource def value() -> int: return 10 @extend(LexicalReference(path=("Base",))) @scope class Extended: @patch def value() -> Callable[[int], int]: return lambda x: x + 1 root = evaluate(Root) root.Extended.value # 11
Example - Extending sibling modules in a package:
When a package contains multiple modules, use ``@extend`` in ``__init__.py`` to combine them:: # my_package/branch1.py @resource def foo() -> str: return "foo" # my_package/branch2.py @extern def foo(): ... @resource def bar(foo: str) -> str: return f"{foo}_bar" # my_package/__init__.py from mixinv2 import LexicalReference, extend, scope @extend( LexicalReference(path=("branch1",)), LexicalReference(path=("branch2",)), ) @scope class combined: pass # Usage: # root = evaluate(my_package) # root.combined.foo # "foo" # root.combined.bar # "foo_bar"
- mixinv2.extern(callable: Callable[[...], Any]) PatcherDefinition[Any]¶
A decorator that marks a callable as an external resource.
This is syntactic sugar equivalent to
patch_many()returning an empty collection. It registers the resource name in the lexical scope without providing any patches, making it clear that the value should come from injection from an outer lexical scope viaInstanceScopeorStaticScope.__call__().The decorated callable may have parameters for dependency injection, which will be resolved from the lexical scope when the resource is accessed. However, the callable’s return value is ignored.
Example:
@extern def database_url(): ... # Equivalent to: @patch_many def database_url(): return ()
This pattern is useful for:
Configuration parameters: Declare dependencies without providing values
Dependency injection: Mark injection points for external values
Module decoupling: Declare required resources without hardcoding
- Parameters:
callable – A callable that may have parameters for dependency injection. The return value is ignored.
- Returns:
A PatcherDefinition that provides no patches.
- class mixinv2.fixpoint_cached_property(func: Callable = None, *, bottom: Callable[[], object], accumulate: Callable[[object, object], bool] | None = None)¶
Bases:
objectA cached_property that supports mutual-recursion via least fixpoint iteration.
API-compatible with functools.cached_property. When reentry is detected (mutual recursion), returns the previous iteration’s approximation (or
bottom()on the first iteration). The outermost caller drives a digest loop until values stabilize (no reentry occurs in a round).Usage:
@fixpoint_cached_property(bottom=lambda: defaultdict(set)) def qualified_this(self): ...
The class-level
max_fixpoint_iterationsContextVar controls the maximum number of digest rounds.0disables fixpoint iteration and raisesFixpointRecursionErroron reentry. DefaultFixpointIterationSentinel.UNLIMITEDiterates until convergence or until Python’s stack is exhausted:fixpoint_cached_property.max_fixpoint_iterations.set(0) # single-pass fixpoint_cached_property.max_fixpoint_iterations.set(100) # bounded multi-pass fixpoint_cached_property.max_fixpoint_iterations.set(FixpointIterationSentinel.UNLIMITED) # unbounded (default)
- attrname: str¶
- func: Callable¶
- max_fixpoint_iterations: ClassVar[ContextVar] = <ContextVar name='fixpoint_cached_property.max_fixpoint_iterations' default=<FixpointIterationSentinel.UNLIMITED: inf>>¶
- mixinv2.merge(callable: Callable[[...], Callable[[Iterator[TPatch_contra]], TResult_co]]) MergerDefinition[TPatch_contra, TResult_co]¶
A decorator that converts a callable into a merger definition with a custom aggregation strategy for patches.
Example:
The following example defines a merge that deduplicates strings from multiple patches into a frozenset:
from mixinv2 import merge, patch, resource, extend, scope, evaluate, extern from mixinv2 import LexicalReference @scope class Root: @scope class Branch0: @merge def deduplicated_tags(): return frozenset[str] @scope class Branch1: @patch def deduplicated_tags(): return "tag1" @resource def another_dependency() -> str: return "dependency_value" @scope class Branch2: @extern def another_dependency(): ... @patch def deduplicated_tags(another_dependency): return f"tag2_{another_dependency}" @extend( LexicalReference(path=("Branch0",)), LexicalReference(path=("Branch1",)), LexicalReference(path=("Branch2",)), ) @scope class Combined: pass root = evaluate(Root) root.Combined.deduplicated_tags # frozenset(("tag1", "tag2_dependency_value"))
Note: For combining multiple scopes, use
@extendwithRelativeReference. Seescope()for examples.
- mixinv2.patch(callable: Callable[[...], TPatch_co]) PatcherDefinition[TPatch_co]¶
A decorator that converts a callable into a patch definition.
- mixinv2.patch_many(callable: Callable[[...], Iterable[TPatch_co]]) PatcherDefinition[TPatch_co]¶
A decorator that converts a callable into a patch definition.
- mixinv2.public(definition: TPublicDefinition) TPublicDefinition¶
Decorator to mark a definition as public.
Public definitions are accessible from child scopes via dependency injection and from getattr/getitem access on the scope object.
Definitions are private by default, meaning they are only accessible as dependencies within the same scope. Use @public to expose them externally.
Example:
@public @resource def api_endpoint() -> str: return "/api/v1" @public @scope class NestedScope: pass
- Parameters:
definition – A Definition to mark as public.
- Returns:
A new Definition with is_public=True.
- mixinv2.resource(callable: Callable[[...], TResult]) MergerDefinition[Callable[[TResult], TResult], TResult]¶
A decorator that converts a callable into a merger definition that treats patches as endofunctions.
It’s a syntactic sugar for using
mergewith a standard endofunction application strategy.- Example:
The following example defines a resource that can be modified by patches:
from mixinv2 import resource, patch @resource def greeting() -> str: return "Hello" @patch def enthusiastic_greeting() -> Endofunction[str]: return lambda original: original + "!!!"
Alternatively,
greetingcan be defined with an explicit merge:from mixinv2 import merge @merge def greeting() -> Callable[[Iterator[Endofunction[str]]], str]: return lambda endos: reduce( (lambda original, endo: endo(original)), endos, "Hello" )
- mixinv2.scope(c: object) ObjectScopeDefinition¶
Decorator that converts a class into a ScopeDefinition. Nested classes MUST be decorated with @scope to be included as sub-scopes.
Example - Using @extend to inherit from another scope:
@extend(RelativeReference(de_bruijn_index=1, path=("Base",))) @scope class MyScope: @patch def foo() -> Callable[[int], int]: return lambda x: x + 1
Example - Union mounting multiple scopes using @extend:
Use ``@extend`` with ``LexicalReference`` to combine multiple scopes. This is the recommended way to create union mount points:: from mixinv2 import LexicalReference @scope class Root: @scope class Branch1: @resource def foo() -> str: return "foo" @scope class Branch2: @extern def foo(): ... @resource def bar(foo: str) -> str: return f"{foo}_bar" @extend( LexicalReference(path=("Branch1",)), LexicalReference(path=("Branch2",)), ) @scope class Combined: pass root = evaluate(Root) root.Combined.foo # "foo" root.Combined.bar # "foo_bar"