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.
- orionis/container/container.py +31 -377
- orionis/container/context/manager.py +94 -0
- orionis/container/context/scope.py +40 -0
- orionis/container/contracts/container.py +62 -16
- orionis/container/resolver.py +432 -7
- orionis/container/validators/lifetime.py +53 -0
- orionis/metadata/framework.py +1 -1
- {orionis-0.320.0.dist-info → orionis-0.322.0.dist-info}/METADATA +1 -1
- {orionis-0.320.0.dist-info → orionis-0.322.0.dist-info}/RECORD +14 -16
- orionis/_container/container.py +0 -543
- orionis/_container/container_integrity.py +0 -292
- orionis/_container/exception.py +0 -54
- orionis/_container/lifetimes.py +0 -13
- orionis/_container/resolve.py +0 -64
- /orionis/{_container → container/context}/__init__.py +0 -0
- {orionis-0.320.0.dist-info → orionis-0.322.0.dist-info}/WHEEL +0 -0
- {orionis-0.320.0.dist-info → orionis-0.322.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.320.0.dist-info → orionis-0.322.0.dist-info}/top_level.txt +0 -0
- {orionis-0.320.0.dist-info → orionis-0.322.0.dist-info}/zip-safe +0 -0
orionis/container/container.py
CHANGED
@@ -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.
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
-
#
|
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
|
629
|
+
# Get the binding for the requested abstract type or alias
|
630
|
+
binding = self.getBinding(abstract_or_alias)
|
850
631
|
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
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
|
639
|
+
def createContext(self) -> ScopeManager:
|
872
640
|
"""
|
873
|
-
|
641
|
+
Creates a new context for managing scoped services.
|
874
642
|
|
875
|
-
|
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
|
-
|
883
|
-
|
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
|
-
|
651
|
+
Usage
|
909
652
|
-------
|
910
|
-
|
911
|
-
|
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
|
-
|
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)
|