mixinv2 0.3.0.post58.dev0__tar.gz → 0.3.0.post65.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/PKG-INFO +1 -1
  2. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/src/mixinv2/__init__.py +1 -0
  3. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/src/mixinv2/_core.py +20 -6
  4. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/src/mixinv2/_runtime.py +34 -43
  5. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/.gitignore +0 -0
  6. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/README.md +0 -0
  7. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/docs/Makefile +0 -0
  8. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/docs/_static/favicon.svg +0 -0
  9. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/docs/_static/logo.svg +0 -0
  10. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/docs/conf.py +0 -0
  11. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/docs/index.rst +0 -0
  12. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/docs/installation.rst +0 -0
  13. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/docs/mixinv2-tutorial.rst +0 -0
  14. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/docs/specification.md +0 -0
  15. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/docs/tutorial.rst +0 -0
  16. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/pyproject.toml +0 -0
  17. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/src/mixinv2/_config.py +0 -0
  18. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/src/mixinv2/_interned_linked_list.py +0 -0
  19. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/src/mixinv2/_mixin_directory.py +0 -0
  20. {mixinv2-0.3.0.post58.dev0 → mixinv2-0.3.0.post65.dev0}/src/mixinv2/_mixin_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mixinv2
3
- Version: 0.3.0.post58.dev0
3
+ Version: 0.3.0.post65.dev0
4
4
  Summary: A dependency injection framework with pytest-fixture syntax, plus a configuration language for declarative programming
5
5
  Project-URL: Repository, https://github.com/Atry/MIXINv2
6
6
  Author-email: "Yang, Bo" <yang-bo@yang-bo.com>
@@ -44,6 +44,7 @@ from mixinv2._core import public as public
44
44
  from mixinv2._core import resource as resource
45
45
  from mixinv2._core import scope as scope
46
46
  from mixinv2._core import Bottom as Bottom
47
+ from mixinv2._core import fixpoint_cached_property as fixpoint_cached_property
47
48
  from mixinv2._runtime import evaluate as evaluate
48
49
 
49
50
  if TYPE_CHECKING:
@@ -553,6 +553,7 @@ from typing import (
553
553
  Awaitable,
554
554
  Callable,
555
555
  ChainMap,
556
+ ClassVar,
556
557
  Collection,
557
558
  ContextManager,
558
559
  Final,
@@ -665,9 +666,6 @@ class Bottom(RecursionError):
665
666
  self.incomplete_result = incomplete_result
666
667
 
667
668
 
668
- _max_fixpoint_iterations_var: ContextVar[int] = ContextVar(
669
- "_max_fixpoint_iterations_var", default=100
670
- )
671
669
  _FIXPOINT_SENTINEL = object()
672
670
 
673
671
  # Registry of attribute names that need clearing during fixpoint digest cycles.
@@ -706,8 +704,20 @@ class fixpoint_cached_property:
706
704
  @fixpoint_cached_property(bottom=lambda: defaultdict(set))
707
705
  def qualified_this(self):
708
706
  ...
707
+
708
+ The class-level ``max_fixpoint_iterations`` ContextVar controls the
709
+ maximum number of digest rounds. ``0`` disables fixpoint iteration
710
+ and raises ``Bottom`` on reentry. Default ``100`` iterates until
711
+ convergence or raises ``Bottom`` if not converged::
712
+
713
+ fixpoint_cached_property.max_fixpoint_iterations.set(0) # single-pass
714
+ fixpoint_cached_property.max_fixpoint_iterations.set(100) # multi-pass (default)
709
715
  """
710
716
 
717
+ max_fixpoint_iterations: ClassVar[ContextVar[int]] = ContextVar(
718
+ "fixpoint_cached_property.max_fixpoint_iterations", default=100
719
+ )
720
+
711
721
  def __init__(
712
722
  self,
713
723
  func: Callable = None,
@@ -737,6 +747,10 @@ class fixpoint_cached_property:
737
747
  self.attrname = name
738
748
  _fixpoint_clearable_attrs.add(self.attrname)
739
749
 
750
+ @classmethod
751
+ def _get_max_iterations(cls) -> int:
752
+ return cls.max_fixpoint_iterations.get()
753
+
740
754
  def __get__(self, instance: object, owner: type = None) -> object:
741
755
  if instance is None:
742
756
  return self
@@ -745,7 +759,7 @@ class fixpoint_cached_property:
745
759
  cache = instance.__dict__
746
760
  value = cache.get(self.attrname)
747
761
  if value is not None:
748
- max_iterations = _max_fixpoint_iterations_var.get()
762
+ max_iterations = self._get_max_iterations()
749
763
  if max_iterations == 0:
750
764
  return value
751
765
  # Detect reentry: if this key is currently being computed
@@ -759,7 +773,7 @@ class fixpoint_cached_property:
759
773
  context.add_participant(instance)
760
774
  return value
761
775
 
762
- max_iterations = _max_fixpoint_iterations_var.get()
776
+ max_iterations = self._get_max_iterations()
763
777
  context = _fixpoint_context_var.get()
764
778
  instance_id = id(instance)
765
779
  key = (instance_id, self.attrname)
@@ -895,7 +909,7 @@ class _fixpoint_dependent_property:
895
909
  if value is not None:
896
910
  return value
897
911
 
898
- if _max_fixpoint_iterations_var.get() > 0:
912
+ if fixpoint_cached_property.max_fixpoint_iterations.get() > 0:
899
913
  # Register as participant so clear_participant_caches can
900
914
  # invalidate this cached value between fixpoint iterations.
901
915
  context = _fixpoint_context_var.get()
@@ -34,7 +34,6 @@ from mixinv2._core import (
34
34
  HasDict,
35
35
  OuterSentinel,
36
36
  SymbolKind,
37
- _max_fixpoint_iterations_var,
38
37
  )
39
38
 
40
39
 
@@ -639,7 +638,6 @@ class MultiplePatcher(Patcher[TPatch_co]):
639
638
  def evaluate(
640
639
  *namespaces: "ModuleType | ScopeDefinition",
641
640
  modules_public: bool = False,
642
- max_fixpoint_iterations: int = 100,
643
641
  ) -> Scope:
644
642
  """
645
643
  Resolves a Scope from the given namespaces.
@@ -653,13 +651,15 @@ def evaluate(
653
651
  When multiple namespaces are provided, they are union-mounted at the root level.
654
652
  Resources from all namespaces are merged according to the merger election algorithm.
655
653
 
654
+ To control fixpoint iteration, set the class-level ContextVar before calling::
655
+
656
+ from mixinv2._core import fixpoint_cached_property
657
+ fixpoint_cached_property.max_fixpoint_iterations.set(0) # single-pass
658
+ fixpoint_cached_property.max_fixpoint_iterations.set(100) # multi-pass (default)
659
+
656
660
  :param namespaces: Modules or namespace definitions (decorated with @scope) to resolve.
657
661
  :param modules_public: If True, modules are marked as public, making their submodules
658
662
  accessible via attribute access. Defaults to False (private by default).
659
- :param max_fixpoint_iterations: Maximum number of fixpoint iterations for
660
- resolving cyclic dependencies. ``0`` disables fixpoint iteration and
661
- raises ``Bottom`` on reentry. Default ``100`` iterates until convergence
662
- or raises ``Bottom`` if not converged.
663
663
  :return: The root Scope.
664
664
 
665
665
  Example::
@@ -667,7 +667,6 @@ def evaluate(
667
667
  root = evaluate(MyNamespace)
668
668
  root = evaluate(Base, Override) # Union mount
669
669
  root = evaluate(my_package, modules_public=True) # Make modules accessible
670
- root = evaluate(MyNamespace, max_fixpoint_iterations=0) # No fixpoint iteration
671
670
 
672
671
  """
673
672
  from dataclasses import replace
@@ -682,44 +681,36 @@ def evaluate(
682
681
  )
683
682
 
684
683
  assert namespaces, "evaluate() requires at least one namespace"
685
- if max_fixpoint_iterations < 0:
686
- raise ValueError(
687
- f"max_fixpoint_iterations must be non-negative, got {max_fixpoint_iterations}"
688
- )
689
684
 
690
- token = _max_fixpoint_iterations_var.set(max_fixpoint_iterations)
691
- try:
692
- def to_scope_definition(
693
- namespace: ModuleType | ScopeDefinition,
694
- ) -> ScopeDefinition:
695
- if isinstance(namespace, ScopeDefinition):
696
- return namespace
697
- if isinstance(namespace, ModuleType):
698
- definition = _parse_package(namespace)
699
- if modules_public:
700
- return replace(definition, is_public=True)
701
- return definition
702
- assert_never(namespace)
703
-
704
- definitions = tuple(to_scope_definition(namespace) for namespace in namespaces)
705
-
706
- root_symbol = MixinSymbol(origin=definitions)
707
-
708
- # Create a synthetic root Mixin to enable lexical scope navigation
709
- # This is needed so that children of the root scope can navigate up
710
- # to find parent scope dependencies (via get_mixin)
711
- root_mixin = Mixin(
712
- symbol=root_symbol,
713
- outer=OuterSentinel.ROOT,
714
- kwargs=KwargsSentinel.STATIC, # Root is always static
715
- )
685
+ def to_scope_definition(
686
+ namespace: ModuleType | ScopeDefinition,
687
+ ) -> ScopeDefinition:
688
+ if isinstance(namespace, ScopeDefinition):
689
+ return namespace
690
+ if isinstance(namespace, ModuleType):
691
+ definition = _parse_package(namespace)
692
+ if modules_public:
693
+ return replace(definition, is_public=True)
694
+ return definition
695
+ assert_never(namespace)
696
+
697
+ definitions = tuple(to_scope_definition(namespace) for namespace in namespaces)
698
+
699
+ root_symbol = MixinSymbol(origin=definitions)
700
+
701
+ # Create a synthetic root Mixin to enable lexical scope navigation
702
+ # This is needed so that children of the root scope can navigate up
703
+ # to find parent scope dependencies (via get_mixin)
704
+ root_mixin = Mixin(
705
+ symbol=root_symbol,
706
+ outer=OuterSentinel.ROOT,
707
+ kwargs=KwargsSentinel.STATIC, # Root is always static
708
+ )
716
709
 
717
- # Evaluate the root mixin to get the Scope
718
- result = root_mixin.evaluated
719
- assert isinstance(result, Scope)
720
- return result
721
- finally:
722
- _max_fixpoint_iterations_var.reset(token)
710
+ # Evaluate the root mixin to get the Scope
711
+ result = root_mixin.evaluated
712
+ assert isinstance(result, Scope)
713
+ return result
723
714
 
724
715
 
725
716
  # Re-export types needed by TYPE_CHECKING imports