orionis 0.320.0__py3-none-any.whl → 0.321.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.
- orionis/container/container.py +16 -385
- orionis/container/contracts/container.py +61 -15
- orionis/container/resolver.py +383 -7
- orionis/container/validators/lifetime.py +53 -0
- orionis/metadata/framework.py +1 -1
- {orionis-0.320.0.dist-info → orionis-0.321.0.dist-info}/METADATA +1 -1
- {orionis-0.320.0.dist-info → orionis-0.321.0.dist-info}/RECORD +11 -10
- {orionis-0.320.0.dist-info → orionis-0.321.0.dist-info}/WHEEL +0 -0
- {orionis-0.320.0.dist-info → orionis-0.321.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.320.0.dist-info → orionis-0.321.0.dist-info}/top_level.txt +0 -0
- {orionis-0.320.0.dist-info → orionis-0.321.0.dist-info}/zip-safe +0 -0
orionis/container/container.py
CHANGED
@@ -4,7 +4,7 @@ from orionis.container.contracts.container import IContainer
|
|
4
4
|
from orionis.container.entities.binding import Binding
|
5
5
|
from orionis.container.enums.lifetimes import Lifetime
|
6
6
|
from orionis.container.exceptions.container_exception import OrionisContainerException
|
7
|
-
from orionis.container.
|
7
|
+
from orionis.container.resolver import Resolver
|
8
8
|
from orionis.container.validators.implements import ImplementsAbstractMethods
|
9
9
|
from orionis.container.validators.is_abstract_class import IsAbstractClass
|
10
10
|
from orionis.container.validators.is_callable import IsCallable
|
@@ -13,12 +13,9 @@ from orionis.container.validators.is_instance import IsInstance
|
|
13
13
|
from orionis.container.validators.is_not_subclass import IsNotSubclass
|
14
14
|
from orionis.container.validators.is_subclass import IsSubclass
|
15
15
|
from orionis.container.validators.is_valid_alias import IsValidAlias
|
16
|
+
from orionis.container.validators.lifetime import LifetimeValidator
|
16
17
|
from orionis.services.introspection.abstract.reflection_abstract import ReflectionAbstract
|
17
18
|
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
19
|
|
23
20
|
class Container(IContainer):
|
24
21
|
|
@@ -37,9 +34,7 @@ class Container(IContainer):
|
|
37
34
|
_initialized = False
|
38
35
|
|
39
36
|
def __new__(
|
40
|
-
cls
|
41
|
-
*args,
|
42
|
-
**kwargs
|
37
|
+
cls
|
43
38
|
) -> 'Container':
|
44
39
|
"""
|
45
40
|
Creates and returns a singleton instance of the class.
|
@@ -48,12 +43,7 @@ class Container(IContainer):
|
|
48
43
|
of the class exists. If an instance does not exist, it acquires a lock to
|
49
44
|
ensure thread safety and creates the instance. Subsequent calls return the
|
50
45
|
existing instance.
|
51
|
-
|
52
|
-
----------
|
53
|
-
*args : tuple
|
54
|
-
Variable length argument list.
|
55
|
-
**kwargs : dict
|
56
|
-
Arbitrary keyword arguments.
|
46
|
+
|
57
47
|
Returns
|
58
48
|
-------
|
59
49
|
Container
|
@@ -471,21 +461,9 @@ class Container(IContainer):
|
|
471
461
|
OrionisContainerException
|
472
462
|
If the lifetime is not allowed for the function signature.
|
473
463
|
"""
|
464
|
+
|
474
465
|
# Normalize and validate the lifetime parameter
|
475
|
-
|
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
|
-
)
|
466
|
+
lifetime = LifetimeValidator(lifetime)
|
489
467
|
|
490
468
|
# Ensure that the alias is a valid string
|
491
469
|
IsValidAlias(alias)
|
@@ -544,7 +522,7 @@ class Container(IContainer):
|
|
544
522
|
or abstract_or_alias in self.__aliasses
|
545
523
|
)
|
546
524
|
|
547
|
-
def
|
525
|
+
def getBinding(
|
548
526
|
self,
|
549
527
|
abstract_or_alias: Any
|
550
528
|
) -> Binding:
|
@@ -563,29 +541,6 @@ class Container(IContainer):
|
|
563
541
|
"""
|
564
542
|
return self.__bindings.get(abstract_or_alias) or self.__aliasses.get(abstract_or_alias)
|
565
543
|
|
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
544
|
def drop(
|
590
545
|
self,
|
591
546
|
abstract: Callable[..., Any] = None,
|
@@ -665,341 +620,17 @@ class Container(IContainer):
|
|
665
620
|
OrionisContainerException
|
666
621
|
If the requested service is not registered in the container.
|
667
622
|
"""
|
668
|
-
|
669
|
-
binding = self.__getService(abstract_or_alias)
|
670
|
-
|
671
|
-
# Check if the requested service is registered in the container
|
672
|
-
if not binding:
|
623
|
+
if not self.bound(abstract_or_alias):
|
673
624
|
raise OrionisContainerException(
|
674
625
|
f"The requested service '{abstract_or_alias}' is not registered in the container."
|
675
626
|
)
|
676
627
|
|
677
|
-
#
|
678
|
-
|
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
|
850
|
-
|
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.
|
628
|
+
# Get the binding for the requested abstract type or alias
|
629
|
+
binding = self.getBinding(abstract_or_alias)
|
859
630
|
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
params = self.__resolveDependencies(concrete, is_class=True)
|
867
|
-
|
868
|
-
# Instantiate the concrete class with resolved dependencies
|
869
|
-
return concrete(**params)
|
870
|
-
|
871
|
-
def __instantiateCallableReflective(self, fn: Callable[..., Any]) -> Any:
|
872
|
-
"""
|
873
|
-
Invokes a callable reflectively, resolving its dependencies from the container.
|
874
|
-
|
875
|
-
Parameters
|
876
|
-
----------
|
877
|
-
fn : Callable[..., Any]
|
878
|
-
The callable to invoke.
|
879
|
-
|
880
|
-
Returns
|
881
|
-
-------
|
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).
|
907
|
-
|
908
|
-
Returns
|
909
|
-
-------
|
910
|
-
dict
|
911
|
-
A dictionary of resolved dependencies.
|
912
|
-
"""
|
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
|
631
|
+
# Resolve the binding using the Resolver class
|
632
|
+
return Resolver(self).resolve(
|
633
|
+
binding,
|
634
|
+
*args,
|
635
|
+
**kwargs
|
636
|
+
)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
2
|
from typing import Any, Callable
|
3
3
|
from orionis.container.enums.lifetimes import Lifetime
|
4
|
+
from orionis.container.entities.binding import Binding
|
4
5
|
|
5
6
|
class IContainer(ABC):
|
6
7
|
"""
|
@@ -18,7 +19,7 @@ class IContainer(ABC):
|
|
18
19
|
enforce_decoupling: bool = False
|
19
20
|
) -> bool:
|
20
21
|
"""
|
21
|
-
|
22
|
+
Registers a service with a singleton lifetime.
|
22
23
|
|
23
24
|
Parameters
|
24
25
|
----------
|
@@ -27,7 +28,7 @@ class IContainer(ABC):
|
|
27
28
|
concrete : Callable[..., Any]
|
28
29
|
The concrete implementation to associate with the abstract type.
|
29
30
|
alias : str, optional
|
30
|
-
An alternative name to register the service under.
|
31
|
+
An alternative name to register the service under. If not provided, the abstract's class name is used.
|
31
32
|
enforce_decoupling : bool, optional
|
32
33
|
Whether to enforce that concrete is not a subclass of abstract.
|
33
34
|
|
@@ -48,7 +49,7 @@ class IContainer(ABC):
|
|
48
49
|
enforce_decoupling: bool = False
|
49
50
|
) -> bool:
|
50
51
|
"""
|
51
|
-
|
52
|
+
Registers a service with a transient lifetime.
|
52
53
|
|
53
54
|
Parameters
|
54
55
|
----------
|
@@ -57,7 +58,7 @@ class IContainer(ABC):
|
|
57
58
|
concrete : Callable[..., Any]
|
58
59
|
The concrete implementation to associate with the abstract type.
|
59
60
|
alias : str, optional
|
60
|
-
An alternative name to register the service under.
|
61
|
+
An alternative name to register the service under. If not provided, the abstract's class name is used.
|
61
62
|
enforce_decoupling : bool, optional
|
62
63
|
Whether to enforce that concrete is not a subclass of abstract.
|
63
64
|
|
@@ -78,7 +79,7 @@ class IContainer(ABC):
|
|
78
79
|
enforce_decoupling: bool = False
|
79
80
|
) -> bool:
|
80
81
|
"""
|
81
|
-
|
82
|
+
Registers a service with a scoped lifetime.
|
82
83
|
|
83
84
|
Parameters
|
84
85
|
----------
|
@@ -87,7 +88,7 @@ class IContainer(ABC):
|
|
87
88
|
concrete : Callable[..., Any]
|
88
89
|
The concrete implementation to associate with the abstract type.
|
89
90
|
alias : str, optional
|
90
|
-
An alternative name to register the service under.
|
91
|
+
An alternative name to register the service under. If not provided, the abstract's class name is used.
|
91
92
|
enforce_decoupling : bool, optional
|
92
93
|
Whether to enforce that concrete is not a subclass of abstract.
|
93
94
|
|
@@ -108,7 +109,7 @@ class IContainer(ABC):
|
|
108
109
|
enforce_decoupling: bool = False
|
109
110
|
) -> bool:
|
110
111
|
"""
|
111
|
-
|
112
|
+
Registers an instance of a class or interface in the container.
|
112
113
|
|
113
114
|
Parameters
|
114
115
|
----------
|
@@ -117,7 +118,8 @@ class IContainer(ABC):
|
|
117
118
|
instance : Any
|
118
119
|
The concrete instance to register.
|
119
120
|
alias : str, optional
|
120
|
-
An optional alias to register the instance under.
|
121
|
+
An optional alias to register the instance under. If not provided,
|
122
|
+
the abstract's `__name__` attribute will be used as the alias if available.
|
121
123
|
enforce_decoupling : bool, optional
|
122
124
|
Whether to enforce that instance's class is not a subclass of abstract.
|
123
125
|
|
@@ -137,7 +139,7 @@ class IContainer(ABC):
|
|
137
139
|
lifetime: Lifetime = Lifetime.TRANSIENT
|
138
140
|
) -> bool:
|
139
141
|
"""
|
140
|
-
|
142
|
+
Registers a function or factory under a given alias.
|
141
143
|
|
142
144
|
Parameters
|
143
145
|
----------
|
@@ -163,16 +165,16 @@ class IContainer(ABC):
|
|
163
165
|
**kwargs: dict
|
164
166
|
) -> Any:
|
165
167
|
"""
|
166
|
-
|
168
|
+
Resolves and returns an instance of the requested service.
|
167
169
|
|
168
170
|
Parameters
|
169
171
|
----------
|
170
172
|
abstract_or_alias : Any
|
171
173
|
The abstract class, interface, or alias (str) to resolve.
|
172
174
|
*args : tuple
|
173
|
-
Positional arguments to pass to the constructor.
|
175
|
+
Positional arguments to pass to the constructor of the resolved service.
|
174
176
|
**kwargs : dict
|
175
|
-
Keyword arguments to pass to the constructor.
|
177
|
+
Keyword arguments to pass to the constructor of the resolved service.
|
176
178
|
|
177
179
|
Returns
|
178
180
|
-------
|
@@ -187,16 +189,60 @@ class IContainer(ABC):
|
|
187
189
|
abstract_or_alias: Any
|
188
190
|
) -> bool:
|
189
191
|
"""
|
190
|
-
|
192
|
+
Checks if a service (by abstract type or alias) is registered in the container.
|
191
193
|
|
192
194
|
Parameters
|
193
195
|
----------
|
194
196
|
abstract_or_alias : Any
|
195
|
-
The abstract class, interface, or alias (str) to check.
|
197
|
+
The abstract class, interface, or alias (str) to check for registration.
|
196
198
|
|
197
199
|
Returns
|
198
200
|
-------
|
199
201
|
bool
|
200
|
-
True if the service is registered, False otherwise.
|
202
|
+
True if the service is registered (either as an abstract type or alias), False otherwise.
|
203
|
+
"""
|
204
|
+
pass
|
205
|
+
|
206
|
+
@abstractmethod
|
207
|
+
def getBinding(
|
208
|
+
self,
|
209
|
+
abstract_or_alias: Any
|
210
|
+
) -> Binding:
|
211
|
+
"""
|
212
|
+
Retrieves the binding for the requested abstract type or alias.
|
213
|
+
|
214
|
+
Parameters
|
215
|
+
----------
|
216
|
+
abstract_or_alias : Any
|
217
|
+
The abstract class, interface, or alias (str) to retrieve.
|
218
|
+
|
219
|
+
Returns
|
220
|
+
-------
|
221
|
+
Binding
|
222
|
+
The binding associated with the requested abstract type or alias.
|
223
|
+
"""
|
224
|
+
pass
|
225
|
+
|
226
|
+
@abstractmethod
|
227
|
+
def drop(
|
228
|
+
self,
|
229
|
+
abstract: Callable[..., Any] = None,
|
230
|
+
alias: str = None
|
231
|
+
) -> None:
|
232
|
+
"""
|
233
|
+
Drops a service from the container by removing its bindings and aliases.
|
234
|
+
|
235
|
+
Warning
|
236
|
+
-------
|
237
|
+
Using this method irresponsibly can severely damage the system's logic.
|
238
|
+
Only use it when you are certain about the consequences, as removing
|
239
|
+
critical services may lead to system failures and unexpected behavior.
|
240
|
+
|
241
|
+
Parameters
|
242
|
+
----------
|
243
|
+
abstract : Callable[..., Any], optional
|
244
|
+
The abstract type or interface to be removed from the container.
|
245
|
+
alias : str, optional
|
246
|
+
The alias of the service to be removed.
|
201
247
|
"""
|
202
248
|
pass
|
orionis/container/resolver.py
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
from typing import Any, Callable
|
1
2
|
from orionis.container.contracts.container import IContainer
|
2
3
|
from orionis.container.entities.binding import Binding
|
3
4
|
from orionis.container.enums.lifetimes import Lifetime
|
4
|
-
|
5
|
+
from orionis.container.exceptions.container_exception import OrionisContainerException
|
6
|
+
from orionis.services.introspection.callables.reflection_callable import ReflectionCallable
|
7
|
+
from orionis.services.introspection.concretes.reflection_concrete import ReflectionConcrete
|
8
|
+
from orionis.services.introspection.dependencies.entities.resolved_dependencies import ResolvedDependency
|
5
9
|
|
6
10
|
class Resolver:
|
7
11
|
"""
|
@@ -10,19 +14,391 @@ class Resolver:
|
|
10
14
|
|
11
15
|
def __init__(
|
12
16
|
self,
|
13
|
-
container:IContainer
|
14
|
-
lifetime:Lifetime
|
17
|
+
container:IContainer
|
15
18
|
):
|
19
|
+
"""
|
20
|
+
Initialize the resolver.
|
21
|
+
|
22
|
+
This method initializes the resolver with a reference to the container.
|
23
|
+
|
24
|
+
Parameters
|
25
|
+
----------
|
26
|
+
container : IContainer
|
27
|
+
The container instance that this resolver will use to resolve dependencies.
|
28
|
+
"""
|
16
29
|
self.container = container
|
17
|
-
self.lifetime = lifetime
|
18
30
|
|
19
|
-
def
|
31
|
+
def resolve(
|
20
32
|
self,
|
21
33
|
binding:Binding,
|
22
34
|
*args,
|
23
35
|
**kwargs
|
24
36
|
):
|
25
37
|
"""
|
26
|
-
|
38
|
+
Resolves an instance from a binding.
|
39
|
+
This method resolves an instance based on the binding's lifetime and type.
|
40
|
+
It delegates to specific resolution methods based on the lifetime (transient, singleton, or scoped).
|
41
|
+
Args:
|
42
|
+
binding (Binding): The binding to resolve.
|
43
|
+
*args: Additional positional arguments to pass to the constructor.
|
44
|
+
**kwargs: Additional keyword arguments to pass to the constructor.
|
45
|
+
Returns:
|
46
|
+
Any: The resolved instance.
|
47
|
+
Raises:
|
48
|
+
OrionisContainerException: If the binding is not an instance of Binding
|
49
|
+
or if scoped lifetime resolution is attempted (not yet implemented).
|
50
|
+
"""
|
51
|
+
|
52
|
+
# Ensure the binding is an instance of Binding
|
53
|
+
if not isinstance(binding, Binding):
|
54
|
+
raise OrionisContainerException(
|
55
|
+
"The binding must be an instance of Binding."
|
56
|
+
)
|
57
|
+
|
58
|
+
# Handle based on binding type and lifetime
|
59
|
+
if binding.lifetime == Lifetime.TRANSIENT:
|
60
|
+
return self.__resolveTransient(binding, *args, **kwargs)
|
61
|
+
elif binding.lifetime == Lifetime.SINGLETON:
|
62
|
+
return self.__resolveSingleton(binding, *args, **kwargs)
|
63
|
+
elif binding.lifetime == Lifetime.SCOPED:
|
64
|
+
# TODO: Implement scoped lifetime resolution
|
65
|
+
raise OrionisContainerException(
|
66
|
+
"Scoped lifetime resolution is not yet implemented."
|
67
|
+
)
|
68
|
+
|
69
|
+
def __resolveTransient(self, binding: Binding, *args, **kwargs) -> Any:
|
70
|
+
"""
|
71
|
+
Resolves a service with transient lifetime.
|
72
|
+
|
73
|
+
Parameters
|
74
|
+
----------
|
75
|
+
binding : Binding
|
76
|
+
The binding to resolve.
|
77
|
+
*args : tuple
|
78
|
+
Positional arguments to pass to the constructor.
|
79
|
+
**kwargs : dict
|
80
|
+
Keyword arguments to pass to the constructor.
|
81
|
+
|
82
|
+
Returns
|
83
|
+
-------
|
84
|
+
Any
|
85
|
+
A new instance of the requested service.
|
86
|
+
"""
|
87
|
+
|
88
|
+
# Check if the binding has a concrete class or function defined
|
89
|
+
if binding.concrete:
|
90
|
+
if args or kwargs:
|
91
|
+
return self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
|
92
|
+
else:
|
93
|
+
return self.__instantiateConcreteReflective(binding.concrete)
|
94
|
+
|
95
|
+
# If the binding has a function defined
|
96
|
+
elif binding.function:
|
97
|
+
if args or kwargs:
|
98
|
+
return self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
|
99
|
+
else:
|
100
|
+
return self.__instantiateCallableReflective(binding.function)
|
101
|
+
|
102
|
+
# If neither concrete class nor function is defined
|
103
|
+
else:
|
104
|
+
raise OrionisContainerException(
|
105
|
+
"Cannot resolve transient binding: neither a concrete class nor a function is defined."
|
106
|
+
)
|
107
|
+
|
108
|
+
def __resolveSingleton(self, binding: Binding, *args, **kwargs) -> Any:
|
109
|
+
"""
|
110
|
+
Resolves a service with singleton lifetime.
|
111
|
+
|
112
|
+
Parameters
|
113
|
+
----------
|
114
|
+
binding : Binding
|
115
|
+
The binding to resolve.
|
116
|
+
*args : tuple
|
117
|
+
Positional arguments to pass to the constructor (only used if instance doesn't exist yet).
|
118
|
+
**kwargs : dict
|
119
|
+
Keyword arguments to pass to the constructor (only used if instance doesn't exist yet).
|
120
|
+
|
121
|
+
Returns
|
122
|
+
-------
|
123
|
+
Any
|
124
|
+
The singleton instance of the requested service.
|
125
|
+
"""
|
126
|
+
# Return existing instance if available
|
127
|
+
if binding.instance:
|
128
|
+
return binding.instance
|
129
|
+
|
130
|
+
# Create instance if needed
|
131
|
+
if binding.concrete:
|
132
|
+
if args or kwargs:
|
133
|
+
binding.instance = self.__instantiateConcreteWithArgs(binding.concrete, *args, **kwargs)
|
134
|
+
else:
|
135
|
+
binding.instance = self.__instantiateConcreteReflective(binding.concrete)
|
136
|
+
return binding.instance
|
137
|
+
|
138
|
+
# If the binding has a function defined
|
139
|
+
elif binding.function:
|
140
|
+
if args or kwargs:
|
141
|
+
result = self.__instantiateCallableWithArgs(binding.function, *args, **kwargs)
|
142
|
+
else:
|
143
|
+
result = self.__instantiateCallableReflective(binding.function)
|
144
|
+
|
145
|
+
# Store the result directly as the singleton instance
|
146
|
+
# We don't automatically invoke factory function results anymore
|
147
|
+
binding.instance = result
|
148
|
+
return binding.instance
|
149
|
+
|
150
|
+
# If neither concrete class nor function is defined
|
151
|
+
else:
|
152
|
+
raise OrionisContainerException(
|
153
|
+
"Cannot resolve singleton binding: neither a concrete class, instance, nor function is defined."
|
154
|
+
)
|
155
|
+
|
156
|
+
def __instantiateConcreteWithArgs(self, concrete: Callable[..., Any], *args, **kwargs) -> Any:
|
157
|
+
"""
|
158
|
+
Instantiates a concrete class with the provided arguments.
|
159
|
+
|
160
|
+
Parameters
|
161
|
+
----------
|
162
|
+
concrete : Callable[..., Any]
|
163
|
+
Class to instantiate.
|
164
|
+
*args : tuple
|
165
|
+
Positional arguments to pass to the constructor.
|
166
|
+
**kwargs : dict
|
167
|
+
Keyword arguments to pass to the constructor.
|
168
|
+
|
169
|
+
Returns
|
170
|
+
-------
|
171
|
+
object
|
172
|
+
A new instance of the specified concrete class.
|
173
|
+
"""
|
174
|
+
|
175
|
+
# try to instantiate the concrete class with the provided arguments
|
176
|
+
try:
|
177
|
+
|
178
|
+
# If the concrete is a class, instantiate it directly
|
179
|
+
return concrete(*args, **kwargs)
|
180
|
+
|
181
|
+
except TypeError as e:
|
182
|
+
|
183
|
+
# If instantiation fails, use ReflectionConcrete to get class name and constructor signature
|
184
|
+
rf_concrete = ReflectionConcrete(concrete)
|
185
|
+
class_name = rf_concrete.getClassName()
|
186
|
+
signature = rf_concrete.getConstructorSignature()
|
187
|
+
|
188
|
+
# Raise an exception with detailed information about the failure
|
189
|
+
raise OrionisContainerException(
|
190
|
+
f"Failed to instantiate [{class_name}] with the provided arguments: {e}\n"
|
191
|
+
f"Expected constructor signature: [{signature}]"
|
192
|
+
) from e
|
193
|
+
|
194
|
+
def __instantiateCallableWithArgs(self, fn: Callable[..., Any], *args, **kwargs) -> Any:
|
195
|
+
"""
|
196
|
+
Invokes a callable with the provided arguments.
|
197
|
+
|
198
|
+
Parameters
|
199
|
+
----------
|
200
|
+
fn : Callable[..., Any]
|
201
|
+
The callable to invoke.
|
202
|
+
*args : tuple
|
203
|
+
Positional arguments to pass to the callable.
|
204
|
+
**kwargs : dict
|
205
|
+
Keyword arguments to pass to the callable.
|
206
|
+
|
207
|
+
Returns
|
208
|
+
-------
|
209
|
+
Any
|
210
|
+
The result of the callable.
|
211
|
+
"""
|
212
|
+
|
213
|
+
# Try to invoke the callable with the provided arguments
|
214
|
+
try:
|
215
|
+
|
216
|
+
# If the callable is a function, invoke it directly
|
217
|
+
return fn(*args, **kwargs)
|
218
|
+
|
219
|
+
except TypeError as e:
|
220
|
+
|
221
|
+
# If invocation fails, use ReflectionCallable to get function name and signature
|
222
|
+
rf_callable = ReflectionCallable(fn)
|
223
|
+
function_name = rf_callable.getName()
|
224
|
+
signature = rf_callable.getSignature()
|
225
|
+
|
226
|
+
# Raise an exception with detailed information about the failure
|
227
|
+
raise OrionisContainerException(
|
228
|
+
f"Failed to invoke function [{function_name}] with the provided arguments: {e}\n"
|
229
|
+
f"Expected function signature: [{signature}]"
|
230
|
+
) from e
|
231
|
+
|
232
|
+
def __instantiateConcreteReflective(self, concrete: Callable[..., Any]) -> Any:
|
233
|
+
"""
|
234
|
+
Instantiates a concrete class reflectively, resolving its dependencies from the container.
|
235
|
+
|
236
|
+
Parameters
|
237
|
+
----------
|
238
|
+
concrete : Callable[..., Any]
|
239
|
+
The concrete class to instantiate.
|
240
|
+
|
241
|
+
Returns
|
242
|
+
-------
|
243
|
+
Any
|
244
|
+
A new instance of the concrete class.
|
245
|
+
"""
|
246
|
+
# Resolve dependencies for the concrete class
|
247
|
+
params = self.__resolveDependencies(concrete, is_class=True)
|
248
|
+
|
249
|
+
# Instantiate the concrete class with resolved dependencies
|
250
|
+
return concrete(**params)
|
251
|
+
|
252
|
+
def __instantiateCallableReflective(self, fn: Callable[..., Any]) -> Any:
|
253
|
+
"""
|
254
|
+
Invokes a callable reflectively, resolving its dependencies from the container.
|
255
|
+
|
256
|
+
Parameters
|
257
|
+
----------
|
258
|
+
fn : Callable[..., Any]
|
259
|
+
The callable to invoke.
|
260
|
+
|
261
|
+
Returns
|
262
|
+
-------
|
263
|
+
Any
|
264
|
+
The result of the callable.
|
265
|
+
"""
|
266
|
+
|
267
|
+
# Resolve dependencies for the callable
|
268
|
+
params = self.__resolveDependencies(fn, is_class=False)
|
269
|
+
|
270
|
+
# Invoke the callable with resolved dependencies
|
271
|
+
return fn(**params)
|
272
|
+
|
273
|
+
def __resolveDependencies(
|
274
|
+
self,
|
275
|
+
target: Callable[..., Any],
|
276
|
+
*,
|
277
|
+
is_class: bool = False
|
278
|
+
) -> dict:
|
279
|
+
"""
|
280
|
+
Resolves dependencies for a target callable or class.
|
281
|
+
|
282
|
+
Parameters
|
283
|
+
----------
|
284
|
+
target : Callable[..., Any]
|
285
|
+
The target callable or class whose dependencies to resolve.
|
286
|
+
is_class : bool, optional
|
287
|
+
Whether the target is a class (True) or a callable (False).
|
288
|
+
|
289
|
+
Returns
|
290
|
+
-------
|
291
|
+
dict
|
292
|
+
A dictionary of resolved dependencies.
|
27
293
|
"""
|
28
|
-
|
294
|
+
try:
|
295
|
+
|
296
|
+
# Use ReflectionConcrete for classes and ReflectionCallable for callables
|
297
|
+
if is_class:
|
298
|
+
reflection = ReflectionConcrete(target)
|
299
|
+
dependencies = reflection.getConstructorDependencies()
|
300
|
+
name = reflection.getClassName()
|
301
|
+
|
302
|
+
# If the target is a callable, use ReflectionCallable
|
303
|
+
else:
|
304
|
+
reflection = ReflectionCallable(target)
|
305
|
+
dependencies = reflection.getDependencies()
|
306
|
+
name = reflection.getName()
|
307
|
+
|
308
|
+
# Check for unresolved dependencies
|
309
|
+
if dependencies.unresolved:
|
310
|
+
unresolved_args = ', '.join(dependencies.unresolved)
|
311
|
+
raise OrionisContainerException(
|
312
|
+
f"Cannot resolve '{name}' because the following required arguments are missing: [{unresolved_args}]."
|
313
|
+
)
|
314
|
+
|
315
|
+
# Resolve dependencies
|
316
|
+
params = {}
|
317
|
+
for param_name, dep in dependencies.resolved.items():
|
318
|
+
|
319
|
+
# If the dependency is a ResolvedDependency, resolve it
|
320
|
+
if isinstance(dep, ResolvedDependency):
|
321
|
+
|
322
|
+
# If the dependency is a built-in type, raise an exception
|
323
|
+
if dep.module_name == 'builtins':
|
324
|
+
raise OrionisContainerException(
|
325
|
+
f"Cannot resolve '{name}' because parameter '{param_name}' depends on built-in type '{dep.type.__name__}'."
|
326
|
+
)
|
327
|
+
|
328
|
+
# Try to resolve from container using type (Abstract or Interface)
|
329
|
+
if self.container.bound(dep.type):
|
330
|
+
params[param_name] = self.resolve(
|
331
|
+
self.container.getBinding(dep.type)
|
332
|
+
)
|
333
|
+
|
334
|
+
# Try to resolve from container using full class path
|
335
|
+
elif self.container.bound(dep.full_class_path):
|
336
|
+
params[param_name] = self.resolve(
|
337
|
+
self.container.getBinding(dep.full_class_path)
|
338
|
+
)
|
339
|
+
|
340
|
+
# Try to instantiate directly if it's a concrete class
|
341
|
+
elif ReflectionConcrete.isConcreteClass(dep.type):
|
342
|
+
params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=True))
|
343
|
+
|
344
|
+
# Try to call directly if it's a callable
|
345
|
+
elif callable(dep.type) and not isinstance(dep.type, type):
|
346
|
+
params[param_name] = dep.type(**self.__resolveDependencies(dep.type, is_class=False))
|
347
|
+
|
348
|
+
# If the dependency cannot be resolved, raise an exception
|
349
|
+
else:
|
350
|
+
raise OrionisContainerException(
|
351
|
+
f"Cannot resolve dependency '{param_name}' of type '{dep.type.__name__}' for '{name}'."
|
352
|
+
)
|
353
|
+
else:
|
354
|
+
# Use default value
|
355
|
+
params[param_name] = dep
|
356
|
+
|
357
|
+
# Return the resolved parameters
|
358
|
+
return params
|
359
|
+
|
360
|
+
except ImportError as e:
|
361
|
+
|
362
|
+
# Get target name safely
|
363
|
+
target_name = getattr(target, '__name__', str(target))
|
364
|
+
module_name = getattr(target, '__module__', "unknown module")
|
365
|
+
|
366
|
+
# Improved circular import detection with more helpful guidance
|
367
|
+
if "circular import" in str(e).lower() or "cannot import name" in str(e).lower():
|
368
|
+
raise OrionisContainerException(
|
369
|
+
f"Circular import detected while resolving dependencies for '{target_name}' in module '{module_name}'.\n"
|
370
|
+
f"This typically happens when two modules import each other. Consider:\n"
|
371
|
+
f"1. Restructuring your code to avoid circular dependencies\n"
|
372
|
+
f"2. Using delayed imports inside methods rather than at module level\n"
|
373
|
+
f"3. Using dependency injection to break the cycle\n"
|
374
|
+
f"Original error: {str(e)}"
|
375
|
+
) from e
|
376
|
+
else:
|
377
|
+
raise OrionisContainerException(
|
378
|
+
f"Import error while resolving dependencies for '{target_name}' in module '{module_name}':\n"
|
379
|
+
f"{str(e)}"
|
380
|
+
) from e
|
381
|
+
|
382
|
+
except Exception as e:
|
383
|
+
|
384
|
+
# More robust attribute extraction with fallbacks
|
385
|
+
target_type = "class" if isinstance(target, type) else "function"
|
386
|
+
target_name = getattr(target, '__name__', str(target))
|
387
|
+
module_name = getattr(target, '__module__', "unknown module")
|
388
|
+
|
389
|
+
# More detailed error message with context about the failure
|
390
|
+
error_msg = (
|
391
|
+
f"Error resolving dependencies for {target_type} '{target_name}' in '{module_name}':\n"
|
392
|
+
f"{str(e)}\n"
|
393
|
+
)
|
394
|
+
|
395
|
+
# Add specific guidance based on error type
|
396
|
+
if isinstance(e, TypeError):
|
397
|
+
error_msg += "This may be caused by incompatible argument types or missing required parameters."
|
398
|
+
elif isinstance(e, AttributeError):
|
399
|
+
error_msg += "This may be caused by accessing undefined attributes in the dependency chain."
|
400
|
+
error_msg += "\nCheck that all dependencies are properly registered in the container."
|
401
|
+
|
402
|
+
# Raise a more informative exception
|
403
|
+
raise OrionisContainerException(error_msg) from e
|
404
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
from typing import Any, Union
|
2
|
+
from orionis.container.enums.lifetimes import Lifetime
|
3
|
+
from orionis.container.exceptions.type_error_exception import OrionisContainerTypeError
|
4
|
+
|
5
|
+
class __LifetimeValidator:
|
6
|
+
"""
|
7
|
+
Validator that checks if a value is a valid lifetime and converts string representations
|
8
|
+
to Lifetime enum values.
|
9
|
+
"""
|
10
|
+
|
11
|
+
def __call__(self, lifetime: Union[str, Lifetime, Any]) -> Lifetime:
|
12
|
+
"""
|
13
|
+
Validates and normalizes the provided lifetime value.
|
14
|
+
|
15
|
+
Parameters
|
16
|
+
----------
|
17
|
+
lifetime : Union[str, Lifetime, Any]
|
18
|
+
The lifetime value to validate. Can be a Lifetime enum or a string
|
19
|
+
representing a valid lifetime.
|
20
|
+
|
21
|
+
Returns
|
22
|
+
-------
|
23
|
+
Lifetime
|
24
|
+
The validated Lifetime enum value.
|
25
|
+
|
26
|
+
Raises
|
27
|
+
------
|
28
|
+
OrionisContainerTypeError
|
29
|
+
If the value is not a valid Lifetime enum or string representation,
|
30
|
+
or if the string doesn't match any valid Lifetime value.
|
31
|
+
"""
|
32
|
+
# Already a Lifetime enum
|
33
|
+
if isinstance(lifetime, Lifetime):
|
34
|
+
return lifetime
|
35
|
+
|
36
|
+
# String that might represent a Lifetime
|
37
|
+
if isinstance(lifetime, str):
|
38
|
+
lifetime_key = lifetime.strip().upper()
|
39
|
+
if lifetime_key in Lifetime.__members__:
|
40
|
+
return Lifetime[lifetime_key]
|
41
|
+
|
42
|
+
valid_options = ', '.join(Lifetime.__members__.keys())
|
43
|
+
raise OrionisContainerTypeError(
|
44
|
+
f"Invalid lifetime '{lifetime}'. Valid options are: {valid_options}."
|
45
|
+
)
|
46
|
+
|
47
|
+
# Invalid type
|
48
|
+
raise OrionisContainerTypeError(
|
49
|
+
f"Lifetime must be of type str or Lifetime enum, got {type(lifetime).__name__}."
|
50
|
+
)
|
51
|
+
|
52
|
+
# Exported singleton instance
|
53
|
+
LifetimeValidator = __LifetimeValidator()
|
orionis/metadata/framework.py
CHANGED
@@ -135,9 +135,9 @@ orionis/console/output/console.py,sha256=TE_Hl720ADd82dbERFSWhkoQRukDQZmETSw4nkw
|
|
135
135
|
orionis/console/output/executor.py,sha256=bdvkzW2-buy0BPpy2r5qUGrRFW2Ay6k-5rSeHb0gQ3o,3352
|
136
136
|
orionis/console/output/progress_bar.py,sha256=vFy582z6VJS46LV6tuyrmr9qvdVeTEtw3hyNcEHezeg,3088
|
137
137
|
orionis/console/output/styles.py,sha256=6a4oQCOBOKMh2ARdeq5GlIskJ3wjiylYmh66tUKKmpQ,4053
|
138
|
-
orionis/container/container.py,sha256=
|
139
|
-
orionis/container/resolver.py,sha256=
|
140
|
-
orionis/container/contracts/container.py,sha256=
|
138
|
+
orionis/container/container.py,sha256=eBVQMzfSvXcxrdm-0hq0gmXZzxf3uabIcxbGVvGZmIo,22342
|
139
|
+
orionis/container/resolver.py,sha256=iuyTWCnCqWXQCtWvFXPdS9cR_g3Q58_wjHVn0Yg0YNM,16209
|
140
|
+
orionis/container/contracts/container.py,sha256=ksMn6ZLnFkIxd9_hbUGz3OpHzaYTA5izcDtH959kOow,7603
|
141
141
|
orionis/container/entities/binding.py,sha256=Qp6Lf4XUDp2NjqXDAC2lzvhOFQWiBDKiGFcKfwb4axw,4342
|
142
142
|
orionis/container/enums/lifetimes.py,sha256=RqQmugMIB1Ev_j_vFLcWorndm-to7xg4stQ7yKFDdDw,190
|
143
143
|
orionis/container/exceptions/container_exception.py,sha256=goTDEwC70xTMD2qppN8KV-xyR0Nps218OD4D1LZ2-3s,470
|
@@ -152,6 +152,7 @@ orionis/container/validators/is_instance.py,sha256=vUwWIMeG1zLMsGX9GyoOU-LZtCgqB
|
|
152
152
|
orionis/container/validators/is_not_subclass.py,sha256=-iZw5ZCYnQtlU-xPO-o7lUj5DxPkN0G67-bLbLVhwtw,1167
|
153
153
|
orionis/container/validators/is_subclass.py,sha256=O2aF0KLg5ZnDutKQ1xDvet34kentftlqgEkRNvumCoM,1135
|
154
154
|
orionis/container/validators/is_valid_alias.py,sha256=avm6uz-huVi_kRov4BksSToslIHQfz-MLynjkzhdSRM,1246
|
155
|
+
orionis/container/validators/lifetime.py,sha256=78o7BHBCRReG8vnF0gW2RUf6m6wwMhr7w4VA6F2B46A,1865
|
155
156
|
orionis/foundation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
156
157
|
orionis/foundation/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
157
158
|
orionis/foundation/config/startup.py,sha256=JKAH2ZRhlAZgkD2w11LR1-TVktfjSH9cHo3PsZXOLrg,8275
|
@@ -243,7 +244,7 @@ orionis/foundation/contracts/config.py,sha256=Rpz6U6t8OXHO9JJKSTnCimytXE-tfCB-1i
|
|
243
244
|
orionis/foundation/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
244
245
|
orionis/foundation/exceptions/integrity.py,sha256=mc4pL1UMoYRHEmphnpW2oGk5URhu7DJRREyzHaV-cs8,472
|
245
246
|
orionis/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
246
|
-
orionis/metadata/framework.py,sha256=
|
247
|
+
orionis/metadata/framework.py,sha256=gv-aVbvyQwRvOTPPCJTIwJffZOEd9Ak6L9jdwT46oOE,4960
|
247
248
|
orionis/metadata/package.py,sha256=tqLfBRo-w1j_GN4xvzUNFyweWYFS-qhSgAEc-AmCH1M,5452
|
248
249
|
orionis/patterns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
249
250
|
orionis/patterns/singleton/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -355,7 +356,7 @@ orionis/test/suite/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
355
356
|
orionis/test/suite/test_unit.py,sha256=MWgW8dRCRyT1XZ5LsbXQ7-KVPReasoXwzEEL1EWWfE4,52190
|
356
357
|
orionis/test/view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
357
358
|
orionis/test/view/render.py,sha256=jXZkbITBknbUwm_mD8bcTiwLDvsFkrO9qrf0ZgPwqxc,4903
|
358
|
-
orionis-0.
|
359
|
+
orionis-0.321.0.dist-info/licenses/LICENCE,sha256=-_4cF2EBKuYVS_SQpy1uapq0oJPUU1vl_RUWSy2jJTo,1111
|
359
360
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
360
361
|
tests/example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
361
362
|
tests/example/test_example.py,sha256=kvWgiW3ADEZf718dGsMPtDh_rmOSx1ypEInKm7_6ZPQ,601
|
@@ -456,8 +457,8 @@ tests/support/wrapper/test_services_wrapper_docdict.py,sha256=yeVwl-VcwkWSQYyxZu
|
|
456
457
|
tests/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
457
458
|
tests/testing/test_testing_result.py,sha256=MrGK3ZimedL0b5Ydu69Dg8Iul017AzLTm7VPxpXlpfU,4315
|
458
459
|
tests/testing/test_testing_unit.py,sha256=DjLBtvVn8B1KlVJNNkstBT8_csA1yeaMqnGrbanN_J4,7438
|
459
|
-
orionis-0.
|
460
|
-
orionis-0.
|
461
|
-
orionis-0.
|
462
|
-
orionis-0.
|
463
|
-
orionis-0.
|
460
|
+
orionis-0.321.0.dist-info/METADATA,sha256=kQ_DamwqrnAqkqPJQZd6mR4u-U_ERbRLwEw8FqTgTwE,4772
|
461
|
+
orionis-0.321.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
462
|
+
orionis-0.321.0.dist-info/top_level.txt,sha256=2bdoHgyGZhOtLAXS6Om8OCTmL24dUMC_L1quMe_ETbk,14
|
463
|
+
orionis-0.321.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
464
|
+
orionis-0.321.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|