mixinv2 package

Module contents

MIXINv2: A dependency injection framework with pytest-fixture-like semantics.

Public API

Decorators:
Runtime:
Exceptions:
Reference types (parameters to extend()):
final class mixinv2.AbsoluteReference(*, path: Final[tuple[Hashable, ...]])

Bases: object

An absolute reference to a resource starting from the root scope.

path: Final[tuple[Hashable, ...]]
exception mixinv2.FixpointRecursionError(message: str, *, incomplete_result: object)

Bases: RecursionError

Raised when fixpoint iteration is exhausted or reentry is detected with no iterations remaining.

Carries the best approximation computed so far in incomplete_result. As a RecursionError subclass, existing code that catches RecursionError will also catch FixpointRecursionError.

incomplete_result: object
final class mixinv2.LexicalReference(*, path: Final[tuple[Hashable, ...]])

Bases: object

A 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 the de_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 outer chain. 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, …):

  1. 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

  2. Recurse to static outer

The returned RelativeReference has:

  • 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: object

A 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: object

A 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 via InstanceScope or StaticScope.__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: object

A 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_iterations ContextVar controls the maximum number of digest rounds. 0 disables fixpoint iteration and raises FixpointRecursionError on reentry. Default FixpointIterationSentinel.UNLIMITED iterates 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 @extend with RelativeReference. See scope() 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 merge with 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, greeting can 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"