orionis 0.320.0__py3-none-any.whl → 0.322.0__py3-none-any.whl

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.
@@ -1,10 +1,11 @@
1
1
  import threading
2
2
  from typing import Any, Callable
3
+ from orionis.container.context.manager import ScopeManager
3
4
  from orionis.container.contracts.container import IContainer
4
5
  from orionis.container.entities.binding import Binding
5
6
  from orionis.container.enums.lifetimes import Lifetime
6
7
  from orionis.container.exceptions.container_exception import OrionisContainerException
7
- from orionis.container.exceptions.type_error_exception import OrionisContainerTypeError
8
+ from orionis.container.resolver import Resolver
8
9
  from orionis.container.validators.implements import ImplementsAbstractMethods
9
10
  from orionis.container.validators.is_abstract_class import IsAbstractClass
10
11
  from orionis.container.validators.is_callable import IsCallable
@@ -13,12 +14,9 @@ from orionis.container.validators.is_instance import IsInstance
13
14
  from orionis.container.validators.is_not_subclass import IsNotSubclass
14
15
  from orionis.container.validators.is_subclass import IsSubclass
15
16
  from orionis.container.validators.is_valid_alias import IsValidAlias
17
+ from orionis.container.validators.lifetime import LifetimeValidator
16
18
  from orionis.services.introspection.abstract.reflection_abstract import ReflectionAbstract
17
19
  from orionis.services.introspection.callables.reflection_callable import ReflectionCallable
18
- from orionis.services.introspection.concretes.reflection_concrete import ReflectionConcrete
19
- from orionis.services.introspection.dependencies.entities.resolved_dependencies import ResolvedDependency
20
- from orionis.services.introspection.instances.reflection_instance import ReflectionInstance
21
- from orionis.services.introspection.reflection import Reflection
22
20
 
23
21
  class Container(IContainer):
24
22
 
@@ -37,9 +35,7 @@ class Container(IContainer):
37
35
  _initialized = False
38
36
 
39
37
  def __new__(
40
- cls,
41
- *args,
42
- **kwargs
38
+ cls
43
39
  ) -> 'Container':
44
40
  """
45
41
  Creates and returns a singleton instance of the class.
@@ -48,12 +44,7 @@ class Container(IContainer):
48
44
  of the class exists. If an instance does not exist, it acquires a lock to
49
45
  ensure thread safety and creates the instance. Subsequent calls return the
50
46
  existing instance.
51
- Parameters
52
- ----------
53
- *args : tuple
54
- Variable length argument list.
55
- **kwargs : dict
56
- Arbitrary keyword arguments.
47
+
57
48
  Returns
58
49
  -------
59
50
  Container
@@ -440,7 +431,7 @@ class Container(IContainer):
440
431
  # Return True to indicate successful registration
441
432
  return True
442
433
 
443
- def function(
434
+ def callable(
444
435
  self,
445
436
  alias: str,
446
437
  fn: Callable[..., Any],
@@ -471,21 +462,9 @@ class Container(IContainer):
471
462
  OrionisContainerException
472
463
  If the lifetime is not allowed for the function signature.
473
464
  """
465
+
474
466
  # Normalize and validate the lifetime parameter
475
- if not isinstance(lifetime, Lifetime):
476
- if isinstance(lifetime, str):
477
- lifetime_key = lifetime.strip().upper()
478
- if lifetime_key in Lifetime.__members__:
479
- lifetime = Lifetime[lifetime_key]
480
- else:
481
- valid = ', '.join(Lifetime.__members__.keys())
482
- raise OrionisContainerTypeError(
483
- f"Invalid lifetime '{lifetime}'. Valid options are: {valid}."
484
- )
485
- else:
486
- raise OrionisContainerTypeError(
487
- f"Lifetime must be of type str or Lifetime enum, got {type(lifetime).__name__}."
488
- )
467
+ lifetime = LifetimeValidator(lifetime)
489
468
 
490
469
  # Ensure that the alias is a valid string
491
470
  IsValidAlias(alias)
@@ -544,7 +523,7 @@ class Container(IContainer):
544
523
  or abstract_or_alias in self.__aliasses
545
524
  )
546
525
 
547
- def __getService(
526
+ def getBinding(
548
527
  self,
549
528
  abstract_or_alias: Any
550
529
  ) -> Binding:
@@ -563,29 +542,6 @@ class Container(IContainer):
563
542
  """
564
543
  return self.__bindings.get(abstract_or_alias) or self.__aliasses.get(abstract_or_alias)
565
544
 
566
- def __getFirstService(
567
- self,
568
- abstract_or_aliasses: list
569
- ) -> Binding:
570
- """
571
- Retrieves the first binding from a list of abstract types or aliases.
572
-
573
- Parameters
574
- ----------
575
- abstract_or_aliasses : list
576
- A list of abstract classes, interfaces, or aliases (str) to retrieve.
577
-
578
- Returns
579
- -------
580
- Binding
581
- The first binding found in the container for the provided abstract types or aliases.
582
- """
583
- for item in abstract_or_aliasses:
584
- binding = self.__getService(item)
585
- if binding:
586
- return binding
587
- return None
588
-
589
545
  def drop(
590
546
  self,
591
547
  abstract: Callable[..., Any] = None,
@@ -665,341 +621,39 @@ class Container(IContainer):
665
621
  OrionisContainerException
666
622
  If the requested service is not registered in the container.
667
623
  """
668
- # Retrieve the binding for the requested abstract or alias
669
- binding = self.__getService(abstract_or_alias)
670
-
671
- # Check if the requested service is registered in the container
672
- if not binding:
624
+ if not self.bound(abstract_or_alias):
673
625
  raise OrionisContainerException(
674
626
  f"The requested service '{abstract_or_alias}' is not registered in the container."
675
627
  )
676
628
 
677
- # Handle based on binding type and lifetime
678
- if binding.lifetime == Lifetime.TRANSIENT:
679
- return self.__resolveTransient(binding, *args, **kwargs)
680
- elif binding.lifetime == Lifetime.SINGLETON:
681
- return self.__resolveSingleton(binding, *args, **kwargs)
682
- elif binding.lifetime == Lifetime.SCOPED:
683
- # TODO: Implement scoped lifetime resolution
684
- raise OrionisContainerException(
685
- "Scoped lifetime resolution is not yet implemented."
686
- )
687
-
688
- def __resolveTransient(self, binding: Binding, *args, **kwargs) -> Any:
689
- """
690
- Resolves a service with transient lifetime.
691
-
692
- Parameters
693
- ----------
694
- binding : Binding
695
- The binding to resolve.
696
- *args : tuple
697
- Positional arguments to pass to the constructor.
698
- **kwargs : dict
699
- Keyword arguments to pass to the constructor.
700
-
701
- Returns
702
- -------
703
- Any
704
- A new instance of the requested service.
705
- """
706
-
707
- # Check if the binding has a concrete class or function defined
708
- if binding.concrete:
709
- if args or kwargs:
710
- return self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
711
- else:
712
- return self.__instantiateConcreteReflective(binding.concrete)
713
-
714
- # If the binding has a function defined
715
- elif binding.function:
716
- if args or kwargs:
717
- return self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
718
- else:
719
- return self.__instantiateCallableReflective(binding.function)
720
-
721
- # If neither concrete class nor function is defined
722
- else:
723
- raise OrionisContainerException(
724
- "Cannot resolve transient binding: neither a concrete class nor a function is defined."
725
- )
726
-
727
- def __resolveSingleton(self, binding: Binding, *args, **kwargs) -> Any:
728
- """
729
- Resolves a service with singleton lifetime.
730
-
731
- Parameters
732
- ----------
733
- binding : Binding
734
- The binding to resolve.
735
- *args : tuple
736
- Positional arguments to pass to the constructor (only used if instance doesn't exist yet).
737
- **kwargs : dict
738
- Keyword arguments to pass to the constructor (only used if instance doesn't exist yet).
739
-
740
- Returns
741
- -------
742
- Any
743
- The singleton instance of the requested service.
744
- """
745
- # Return existing instance if available
746
- if binding.instance:
747
- return binding.instance
748
-
749
- # Create instance if needed
750
- if binding.concrete:
751
- if args or kwargs:
752
- binding.instance = self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
753
- else:
754
- binding.instance = self.__instantiateConcreteReflective(binding.concrete)
755
- return binding.instance
756
-
757
- # If the binding has a function defined
758
- elif binding.function:
759
- if args or kwargs:
760
- result = self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
761
- else:
762
- result = self.__instantiateCallableReflective(binding.function)
763
-
764
- # Store the result directly as the singleton instance
765
- # We don't automatically invoke factory function results anymore
766
- binding.instance = result
767
- return binding.instance
768
-
769
- # If neither concrete class nor function is defined
770
- else:
771
- raise OrionisContainerException(
772
- "Cannot resolve singleton binding: neither a concrete class, instance, nor function is defined."
773
- )
774
-
775
- def __instantiateConcreteWithArgs(self, concrete: Callable[..., Any], *args, **kwargs) -> Any:
776
- """
777
- Instantiates a concrete class with the provided arguments.
778
-
779
- Parameters
780
- ----------
781
- concrete : Callable[..., Any]
782
- Class to instantiate.
783
- *args : tuple
784
- Positional arguments to pass to the constructor.
785
- **kwargs : dict
786
- Keyword arguments to pass to the constructor.
787
-
788
- Returns
789
- -------
790
- object
791
- A new instance of the specified concrete class.
792
- """
793
-
794
- # try to instantiate the concrete class with the provided arguments
795
- try:
796
-
797
- # If the concrete is a class, instantiate it directly
798
- return concrete(*args, **kwargs)
799
-
800
- except TypeError as e:
801
-
802
- # If instantiation fails, use ReflectionConcrete to get class name and constructor signature
803
- rf_concrete = ReflectionConcrete(concrete)
804
- class_name = rf_concrete.getClassName()
805
- signature = rf_concrete.getConstructorSignature()
806
-
807
- # Raise an exception with detailed information about the failure
808
- raise OrionisContainerException(
809
- f"Failed to instantiate [{class_name}] with the provided arguments: {e}\n"
810
- f"Expected constructor signature: [{signature}]"
811
- ) from e
812
-
813
- def __instantiateCallableWithArgs(self, fn: Callable[..., Any], *args, **kwargs) -> Any:
814
- """
815
- Invokes a callable with the provided arguments.
816
-
817
- Parameters
818
- ----------
819
- fn : Callable[..., Any]
820
- The callable to invoke.
821
- *args : tuple
822
- Positional arguments to pass to the callable.
823
- **kwargs : dict
824
- Keyword arguments to pass to the callable.
825
-
826
- Returns
827
- -------
828
- Any
829
- The result of the callable.
830
- """
831
-
832
- # Try to invoke the callable with the provided arguments
833
- try:
834
-
835
- # If the callable is a function, invoke it directly
836
- return fn(*args, **kwargs)
837
-
838
- except TypeError as e:
839
-
840
- # If invocation fails, use ReflectionCallable to get function name and signature
841
- rf_callable = ReflectionCallable(fn)
842
- function_name = rf_callable.getName()
843
- signature = rf_callable.getSignature()
844
-
845
- # Raise an exception with detailed information about the failure
846
- raise OrionisContainerException(
847
- f"Failed to invoke function [{function_name}] with the provided arguments: {e}\n"
848
- f"Expected function signature: [{signature}]"
849
- ) from e
629
+ # Get the binding for the requested abstract type or alias
630
+ binding = self.getBinding(abstract_or_alias)
850
631
 
851
- def __instantiateConcreteReflective(self, concrete: Callable[..., Any]) -> Any:
852
- """
853
- Instantiates a concrete class reflectively, resolving its dependencies from the container.
854
-
855
- Parameters
856
- ----------
857
- concrete : Callable[..., Any]
858
- The concrete class to instantiate.
859
-
860
- Returns
861
- -------
862
- Any
863
- A new instance of the concrete class.
864
- """
865
- # Resolve dependencies for the concrete class
866
- params = self.__resolveDependencies(concrete, is_class=True)
867
-
868
- # Instantiate the concrete class with resolved dependencies
869
- return concrete(**params)
632
+ # Resolve the binding using the Resolver class
633
+ return Resolver(self).resolve(
634
+ binding,
635
+ *args,
636
+ **kwargs
637
+ )
870
638
 
871
- def __instantiateCallableReflective(self, fn: Callable[..., Any]) -> Any:
639
+ def createContext(self) -> ScopeManager:
872
640
  """
873
- Invokes a callable reflectively, resolving its dependencies from the container.
641
+ Creates a new context for managing scoped services.
874
642
 
875
- Parameters
876
- ----------
877
- fn : Callable[..., Any]
878
- The callable to invoke.
643
+ This method returns a context manager that can be used with a 'with' statement
644
+ to control the lifecycle of scoped services.
879
645
 
880
646
  Returns
881
647
  -------
882
- Any
883
- The result of the callable.
884
- """
885
-
886
- # Resolve dependencies for the callable
887
- params = self.__resolveDependencies(fn, is_class=False)
888
-
889
- # Invoke the callable with resolved dependencies
890
- return fn(**params)
891
-
892
- def __resolveDependencies(
893
- self,
894
- target: Callable[..., Any],
895
- *,
896
- is_class: bool = False
897
- ) -> dict:
898
- """
899
- Resolves dependencies for a target callable or class.
900
-
901
- Parameters
902
- ----------
903
- target : Callable[..., Any]
904
- The target callable or class whose dependencies to resolve.
905
- is_class : bool, optional
906
- Whether the target is a class (True) or a callable (False).
648
+ ScopeManager
649
+ A context manager for scoped services.
907
650
 
908
- Returns
651
+ Usage
909
652
  -------
910
- dict
911
- A dictionary of resolved dependencies.
653
+ with container.createContext():
654
+ # Scoped services created here will be disposed when exiting this block
655
+ service = container.make(IScopedService)
656
+ ...
657
+ # Scoped services are automatically disposed here
912
658
  """
913
- try:
914
-
915
- # Use ReflectionConcrete for classes and ReflectionCallable for callables
916
- if is_class:
917
- reflection = ReflectionConcrete(target)
918
- dependencies = reflection.getConstructorDependencies()
919
- name = reflection.getClassName()
920
-
921
- # If the target is a callable, use ReflectionCallable
922
- else:
923
- reflection = ReflectionCallable(target)
924
- dependencies = reflection.getDependencies()
925
- name = reflection.getName()
926
-
927
- # Check for unresolved dependencies
928
- if dependencies.unresolved:
929
- unresolved_args = ', '.join(dependencies.unresolved)
930
- raise OrionisContainerException(
931
- f"Cannot resolve '{name}' because the following required arguments are missing: [{unresolved_args}]."
932
- )
933
-
934
- # Resolve dependencies
935
- params = {}
936
- for param_name, dep in dependencies.resolved.items():
937
-
938
- # If the dependency is a ResolvedDependency, resolve it
939
- if isinstance(dep, ResolvedDependency):
940
-
941
- # If the dependency is a built-in type, raise an exception
942
- if dep.module_name == 'builtins':
943
- raise OrionisContainerException(
944
- f"Cannot resolve '{name}' because parameter '{param_name}' depends on built-in type '{dep.type.__name__}'."
945
- )
946
-
947
- # Try to resolve from container
948
- service = self.__getFirstService([dep.type, dep.full_class_path])
949
- if service:
950
- params[param_name] = self.make(service.alias)
951
-
952
- # Try to instantiate directly if it's a concrete class
953
- elif ReflectionConcrete.isConcreteClass(dep.type):
954
- params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=True))
955
-
956
- # Try to call directly if it's a callable
957
- elif callable(dep.type) and not isinstance(dep.type, type):
958
- params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=False))
959
-
960
- # If the dependency cannot be resolved, raise an exception
961
- else:
962
- raise OrionisContainerException(
963
- f"Cannot resolve dependency '{param_name}' of type '{dep.type.__name__}' for '{name}'."
964
- )
965
- else:
966
- # Use default value
967
- params[param_name] = dep
968
-
969
- # Return the resolved parameters
970
- return params
971
-
972
- except ImportError as e:
973
-
974
- # Extract module name from the error message if possible
975
- import_msg = str(e)
976
- module_name = target.__module__ if hasattr(target, '__module__') else "unknown module"
977
-
978
- # Check for potential circular import patterns
979
- if "circular import" in import_msg.lower() or "cannot import name" in import_msg.lower():
980
- raise OrionisContainerException(
981
- f"Circular import detected while resolving dependencies for '{target.__name__}' in module '{module_name}'.\n"
982
- f"This typically happens when two modules import each other. Consider:\n"
983
- f"1. Restructuring your code to avoid circular dependencies\n"
984
- f"2. Using delayed imports inside methods rather than at module level\n"
985
- f"3. Using dependency injection to break the cycle\n"
986
- f"Original error: {import_msg}"
987
- ) from e
988
- else:
989
- raise OrionisContainerException(
990
- f"Import error while resolving dependencies for '{target.__name__}' in module '{module_name}':\n"
991
- f"{import_msg}"
992
- ) from e
993
-
994
- except Exception as e:
995
-
996
- # Get more context about where the error occurred
997
- target_type = "class" if isinstance(target, type) else "function"
998
- target_name = target.__name__ if hasattr(target, '__name__') else str(target)
999
- module_name = target.__module__ if hasattr(target, '__module__') else "unknown module"
1000
-
1001
- raise OrionisContainerException(
1002
- f"Error resolving dependencies for {target_type} '{target_name}' in '{module_name}':\n"
1003
- f"{str(e)}\n"
1004
- f"Check that all dependencies are properly registered in the container."
1005
- ) from e
659
+ return ScopeManager()
@@ -0,0 +1,94 @@
1
+ from orionis.container.context.scope import ScopedContext
2
+
3
+ class ScopeManager:
4
+ """
5
+ A context manager to manage scoped lifetimes in the container.
6
+ """
7
+ def __init__(self):
8
+ """
9
+ Initialize a new ScopeManager with an empty instances dictionary.
10
+ """
11
+ self._instances = {}
12
+
13
+ def __getitem__(self, key):
14
+ """
15
+ Get an instance by key.
16
+
17
+ Parameters
18
+ ----------
19
+ key : hashable
20
+ The key of the instance to retrieve.
21
+
22
+ Returns
23
+ -------
24
+ object or None
25
+ The instance associated with the key or None if not found.
26
+ """
27
+ return self._instances.get(key)
28
+
29
+ def __setitem__(self, key, value):
30
+ """
31
+ Store an instance by key.
32
+
33
+ Parameters
34
+ ----------
35
+ key : hashable
36
+ The key to associate with the instance.
37
+ value : object
38
+ The instance to store.
39
+ """
40
+ self._instances[key] = value
41
+
42
+ def __contains__(self, key):
43
+ """
44
+ Check if a key exists in this scope.
45
+
46
+ Parameters
47
+ ----------
48
+ key : hashable
49
+ The key to check.
50
+
51
+ Returns
52
+ -------
53
+ bool
54
+ True if the key exists in the scope, False otherwise.
55
+ """
56
+ return key in self._instances
57
+
58
+ def clear(self):
59
+ """
60
+ Clear all instances from this scope.
61
+ """
62
+ self._instances.clear()
63
+
64
+ def __enter__(self):
65
+ """
66
+ Enter the scope context.
67
+
68
+ Sets this scope as the current active scope.
69
+
70
+ Returns
71
+ -------
72
+ ScopeManager
73
+ This scope manager instance.
74
+ """
75
+ ScopedContext.setCurrentScope(self)
76
+ return self
77
+
78
+ def __exit__(self, exc_type, exc_val, exc_tb):
79
+ """
80
+ Exit the scope context.
81
+
82
+ Clears this scope and the active scope reference.
83
+
84
+ Parameters
85
+ ----------
86
+ exc_type : type or None
87
+ The exception type if an exception was raised, None otherwise.
88
+ exc_val : Exception or None
89
+ The exception instance if an exception was raised, None otherwise.
90
+ exc_tb : traceback or None
91
+ The exception traceback if an exception was raised, None otherwise.
92
+ """
93
+ self.clear()
94
+ ScopedContext.clear()
@@ -0,0 +1,40 @@
1
+ import contextvars
2
+
3
+ class ScopedContext:
4
+ """
5
+ Holds scoped instances for the current context.
6
+ """
7
+ _active_scope = contextvars.ContextVar("orionis_scope", default=None)
8
+
9
+ @classmethod
10
+ def getCurrentScope(cls):
11
+ """
12
+ Get the currently active scope.
13
+
14
+ Returns
15
+ -------
16
+ object or None
17
+ The current active scope or None if no scope is active.
18
+ """
19
+ return cls._active_scope.get()
20
+
21
+ @classmethod
22
+ def setCurrentScope(cls, scope):
23
+ """
24
+ Set the current active scope.
25
+
26
+ Parameters
27
+ ----------
28
+ scope : object
29
+ The scope object to set as active.
30
+ """
31
+ cls._active_scope.set(scope)
32
+
33
+ @classmethod
34
+ def clear(cls):
35
+ """
36
+ Clear the current active scope.
37
+
38
+ Sets the active scope to None.
39
+ """
40
+ cls._active_scope.set(None)