aspyx 1.3.0__py3-none-any.whl → 1.4.1__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.
Potentially problematic release.
This version of aspyx might be problematic. Click here for more details.
- aspyx/__init__.py +0 -0
- aspyx/di/__init__.py +2 -2
- aspyx/di/aop/__init__.py +16 -2
- aspyx/di/aop/aop.py +169 -102
- aspyx/di/configuration/__init__.py +3 -3
- aspyx/di/configuration/configuration.py +7 -5
- aspyx/di/di.py +224 -46
- aspyx/di/threading/synchronized.py +8 -5
- aspyx/exception/__init__.py +10 -0
- aspyx/exception/exception_manager.py +185 -0
- aspyx/reflection/proxy.py +2 -4
- aspyx/reflection/reflection.py +81 -5
- aspyx/threading/__init__.py +10 -0
- aspyx/threading/thread_local.py +51 -0
- aspyx/{di/util → util}/stringbuilder.py +15 -0
- {aspyx-1.3.0.dist-info → aspyx-1.4.1.dist-info}/METADATA +235 -93
- aspyx-1.4.1.dist-info/RECORD +25 -0
- aspyx-1.3.0.dist-info/RECORD +0 -20
- /aspyx/{di/util → util}/__init__.py +0 -0
- {aspyx-1.3.0.dist-info → aspyx-1.4.1.dist-info}/WHEEL +0 -0
- {aspyx-1.3.0.dist-info → aspyx-1.4.1.dist-info}/licenses/LICENSE +0 -0
- {aspyx-1.3.0.dist-info → aspyx-1.4.1.dist-info}/top_level.txt +0 -0
|
@@ -66,10 +66,12 @@ class ConfigurationManager:
|
|
|
66
66
|
def get(self, path: str, type: Type[T], default : Optional[T]=None) -> T:
|
|
67
67
|
"""
|
|
68
68
|
Retrieve a configuration value by path and type, with optional coercion.
|
|
69
|
-
|
|
69
|
+
|
|
70
|
+
Args:
|
|
70
71
|
path (str): The path to the configuration value, e.g. "database.host".
|
|
71
72
|
type (Type[T]): The expected type.
|
|
72
73
|
default (Optional[T]): The default value to return if the path is not found.
|
|
74
|
+
|
|
73
75
|
Returns:
|
|
74
76
|
T: The configuration value coerced to the specified type, or the default value if not found.
|
|
75
77
|
"""
|
|
@@ -119,17 +121,17 @@ class ConfigurationSource(ABC):
|
|
|
119
121
|
|
|
120
122
|
# decorator
|
|
121
123
|
|
|
122
|
-
def
|
|
124
|
+
def inject_value(key: str, default=None):
|
|
123
125
|
"""
|
|
124
126
|
Decorator to inject a configuration value into a method.
|
|
125
127
|
|
|
126
|
-
|
|
128
|
+
Args:
|
|
127
129
|
key (str): The configuration key to inject.
|
|
128
130
|
default: The default value to use if the key is not found.
|
|
129
131
|
|
|
130
132
|
"""
|
|
131
133
|
def decorator(func):
|
|
132
|
-
Decorators.add(func,
|
|
134
|
+
Decorators.add(func, inject_value, key, default)
|
|
133
135
|
|
|
134
136
|
return func
|
|
135
137
|
|
|
@@ -139,7 +141,7 @@ def value(key: str, default=None):
|
|
|
139
141
|
@order(9)
|
|
140
142
|
class ConfigurationLifecycleCallable(LifecycleCallable):
|
|
141
143
|
def __init__(self, manager: ConfigurationManager):
|
|
142
|
-
super().__init__(
|
|
144
|
+
super().__init__(inject_value, Lifecycle.ON_INJECT)
|
|
143
145
|
|
|
144
146
|
self.manager = manager
|
|
145
147
|
|
aspyx/di/di.py
CHANGED
|
@@ -5,14 +5,16 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
import inspect
|
|
7
7
|
import logging
|
|
8
|
+
import importlib
|
|
9
|
+
import pkgutil
|
|
10
|
+
import sys
|
|
8
11
|
|
|
9
12
|
from abc import abstractmethod, ABC
|
|
10
13
|
from enum import Enum
|
|
11
14
|
import threading
|
|
12
|
-
from operator import truediv
|
|
13
15
|
from typing import Type, Dict, TypeVar, Generic, Optional, cast, Callable, TypedDict
|
|
14
16
|
|
|
15
|
-
from aspyx.
|
|
17
|
+
from aspyx.util import StringBuilder
|
|
16
18
|
from aspyx.reflection import Decorators, TypeDescriptor, DecoratorDescriptor
|
|
17
19
|
|
|
18
20
|
T = TypeVar("T")
|
|
@@ -44,9 +46,9 @@ class DIRegistrationException(DIException):
|
|
|
44
46
|
|
|
45
47
|
class ProviderCollisionException(DIRegistrationException):
|
|
46
48
|
def __init__(self, message: str, *providers: AbstractInstanceProvider):
|
|
47
|
-
|
|
49
|
+
super().__init__(message)
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
self.providers = providers
|
|
50
52
|
|
|
51
53
|
def __str__(self):
|
|
52
54
|
return f"[{self.args[0]} {self.providers[1].location()} collides with {self.providers[0].location()}"
|
|
@@ -60,33 +62,76 @@ class DIRuntimeException(DIException):
|
|
|
60
62
|
|
|
61
63
|
class AbstractInstanceProvider(ABC, Generic[T]):
|
|
62
64
|
"""
|
|
63
|
-
|
|
65
|
+
An AbstractInstanceProvider is responsible to create instances.
|
|
64
66
|
"""
|
|
65
67
|
@abstractmethod
|
|
66
68
|
def get_module(self) -> str:
|
|
67
|
-
|
|
69
|
+
"""
|
|
70
|
+
return the module name of the provider
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
str: the module name of the provider
|
|
74
|
+
"""
|
|
68
75
|
|
|
69
76
|
def get_host(self) -> Type[T]:
|
|
77
|
+
"""
|
|
78
|
+
return the class which is responsible for creation ( e.g. the injectable class )
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Type[T]: the class which is responsible for creation
|
|
82
|
+
"""
|
|
70
83
|
return type(self)
|
|
71
84
|
|
|
72
85
|
@abstractmethod
|
|
73
86
|
def get_type(self) -> Type[T]:
|
|
74
|
-
|
|
87
|
+
"""
|
|
88
|
+
return the type of the created instance
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Type[T: the type]
|
|
92
|
+
"""
|
|
75
93
|
|
|
76
94
|
@abstractmethod
|
|
77
95
|
def is_eager(self) -> bool:
|
|
78
|
-
|
|
96
|
+
"""
|
|
97
|
+
return True, if the provider will eagerly construct instances
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
bool: eager flag
|
|
101
|
+
"""
|
|
79
102
|
|
|
80
103
|
@abstractmethod
|
|
81
104
|
def get_scope(self) -> str:
|
|
82
|
-
|
|
105
|
+
"""
|
|
106
|
+
return the scope name
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
str: the scope name
|
|
110
|
+
"""
|
|
111
|
+
|
|
83
112
|
|
|
84
113
|
def get_dependencies(self) -> (list[Type],int):
|
|
114
|
+
"""
|
|
115
|
+
return the types that i depend on ( for constructor or setter injection ).
|
|
116
|
+
The second tuple element is the number of parameters that a construction injection will require
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
(list[Type],int): the type array and the number of parameters
|
|
120
|
+
"""
|
|
85
121
|
return [],1
|
|
86
122
|
|
|
87
123
|
@abstractmethod
|
|
88
|
-
def create(self, environment: Environment, *args):
|
|
89
|
-
|
|
124
|
+
def create(self, environment: Environment, *args) -> T:
|
|
125
|
+
"""
|
|
126
|
+
Create a new instance.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
environment: the Environment
|
|
130
|
+
*args: the required arguments
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
T: the instance
|
|
134
|
+
"""
|
|
90
135
|
|
|
91
136
|
def report(self) -> str:
|
|
92
137
|
return str(self)
|
|
@@ -159,6 +204,13 @@ class SingletonScopeInstanceProvider(InstanceProvider):
|
|
|
159
204
|
def create(self, environment: Environment, *args):
|
|
160
205
|
return SingletonScope()
|
|
161
206
|
|
|
207
|
+
class EnvironmentScopeInstanceProvider(InstanceProvider):
|
|
208
|
+
def __init__(self):
|
|
209
|
+
super().__init__(SingletonScopeInstanceProvider, SingletonScope, False, "request")
|
|
210
|
+
|
|
211
|
+
def create(self, environment: Environment, *args):
|
|
212
|
+
return EnvironmentScope()
|
|
213
|
+
|
|
162
214
|
class RequestScopeInstanceProvider(InstanceProvider):
|
|
163
215
|
def __init__(self):
|
|
164
216
|
super().__init__(RequestScopeInstanceProvider, RequestScope, False, "singleton")
|
|
@@ -166,7 +218,6 @@ class RequestScopeInstanceProvider(InstanceProvider):
|
|
|
166
218
|
def create(self, environment: Environment, *args):
|
|
167
219
|
return RequestScope()
|
|
168
220
|
|
|
169
|
-
|
|
170
221
|
class AmbiguousProvider(AbstractInstanceProvider):
|
|
171
222
|
"""
|
|
172
223
|
An AmbiguousProvider covers all cases, where fetching a class would lead to an ambiguity exception.
|
|
@@ -354,7 +405,7 @@ class ClassInstanceProvider(InstanceProvider):
|
|
|
354
405
|
for param in method.param_types:
|
|
355
406
|
types.append(param)
|
|
356
407
|
|
|
357
|
-
return
|
|
408
|
+
return types, self.params
|
|
358
409
|
|
|
359
410
|
def create(self, environment: Environment, *args):
|
|
360
411
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
@@ -441,6 +492,12 @@ class FactoryInstanceProvider(InstanceProvider):
|
|
|
441
492
|
class Lifecycle(Enum):
|
|
442
493
|
"""
|
|
443
494
|
This enum defines the lifecycle phases that can be processed by lifecycle processors.
|
|
495
|
+
Phases are:
|
|
496
|
+
|
|
497
|
+
- ON_INJECT
|
|
498
|
+
- ON_INIT
|
|
499
|
+
- ON_RUNNING
|
|
500
|
+
- ON_DESTROY
|
|
444
501
|
"""
|
|
445
502
|
|
|
446
503
|
__slots__ = []
|
|
@@ -591,7 +648,7 @@ class Providers:
|
|
|
591
648
|
Providers.check.clear()
|
|
592
649
|
|
|
593
650
|
@classmethod
|
|
594
|
-
def filter(cls, environment: Environment) -> Dict[Type,AbstractInstanceProvider]:
|
|
651
|
+
def filter(cls, environment: Environment, provider_filter: Callable) -> Dict[Type,AbstractInstanceProvider]:
|
|
595
652
|
cache: Dict[Type,AbstractInstanceProvider] = {}
|
|
596
653
|
|
|
597
654
|
context: ConditionContext = {
|
|
@@ -610,12 +667,18 @@ class Providers:
|
|
|
610
667
|
if result is not None:
|
|
611
668
|
raise ProviderCollisionException(f"type {clazz.__name__} already registered", result, provider)
|
|
612
669
|
|
|
613
|
-
|
|
614
|
-
result = provider
|
|
670
|
+
result = provider
|
|
615
671
|
|
|
616
672
|
return result
|
|
617
673
|
|
|
618
674
|
def provider_applies(provider: AbstractInstanceProvider) -> bool:
|
|
675
|
+
# is it in the right module?
|
|
676
|
+
|
|
677
|
+
if not provider_filter(provider):
|
|
678
|
+
return False
|
|
679
|
+
|
|
680
|
+
# check conditionals
|
|
681
|
+
|
|
619
682
|
descriptor = TypeDescriptor.for_type(provider.get_host())
|
|
620
683
|
if descriptor.has_decorator(conditional):
|
|
621
684
|
conditions: list[Condition] = [*descriptor.get_decorator(conditional).args]
|
|
@@ -658,7 +721,7 @@ class Providers:
|
|
|
658
721
|
|
|
659
722
|
# filter conditional providers and fill base classes as well
|
|
660
723
|
|
|
661
|
-
for provider_type,
|
|
724
|
+
for provider_type, _ in Providers.providers.items():
|
|
662
725
|
matching_provider = filter_type(provider_type)
|
|
663
726
|
if matching_provider is not None:
|
|
664
727
|
cache_provider_for_type(matching_provider, provider_type)
|
|
@@ -677,7 +740,11 @@ class Providers:
|
|
|
677
740
|
|
|
678
741
|
# and resolve
|
|
679
742
|
|
|
680
|
-
|
|
743
|
+
providers = result
|
|
744
|
+
if environment.parent is not None:
|
|
745
|
+
providers = providers | environment.parent.providers # add parent providers
|
|
746
|
+
|
|
747
|
+
provider_context = Providers.ResolveContext(providers)
|
|
681
748
|
for provider in mapped.values():
|
|
682
749
|
provider.resolve(provider_context)
|
|
683
750
|
provider_context.next() # clear dependencies
|
|
@@ -722,6 +789,10 @@ def injectable(eager=True, scope="singleton"):
|
|
|
722
789
|
def factory(eager=True, scope="singleton"):
|
|
723
790
|
"""
|
|
724
791
|
Decorator that needs to be used on a class that implements the Factory interface.
|
|
792
|
+
|
|
793
|
+
Args:
|
|
794
|
+
eager (bool): If True, the corresponding object will be created eagerly when the environment is created.
|
|
795
|
+
scope (str): The scope of the factory, e.g. "singleton", "request", "environment".
|
|
725
796
|
"""
|
|
726
797
|
def decorator(cls):
|
|
727
798
|
Decorators.add(cls, factory)
|
|
@@ -736,6 +807,10 @@ def factory(eager=True, scope="singleton"):
|
|
|
736
807
|
def create(eager=True, scope="singleton"):
|
|
737
808
|
"""
|
|
738
809
|
Any method annotated with @create will be registered as a factory method.
|
|
810
|
+
|
|
811
|
+
Args:
|
|
812
|
+
eager (bool): If True, the corresponding object will be created eagerly when the environment is created.
|
|
813
|
+
scope (str): The scope of the factory, e.g. "singleton", "request", "environment".
|
|
739
814
|
"""
|
|
740
815
|
def decorator(func):
|
|
741
816
|
Decorators.add(func, create, eager, scope)
|
|
@@ -745,7 +820,7 @@ def create(eager=True, scope="singleton"):
|
|
|
745
820
|
|
|
746
821
|
def on_init():
|
|
747
822
|
"""
|
|
748
|
-
Methods annotated with
|
|
823
|
+
Methods annotated with `@on_init` will be called when the instance is created."""
|
|
749
824
|
def decorator(func):
|
|
750
825
|
Decorators.add(func, on_init)
|
|
751
826
|
return func
|
|
@@ -754,7 +829,7 @@ def on_init():
|
|
|
754
829
|
|
|
755
830
|
def on_running():
|
|
756
831
|
"""
|
|
757
|
-
Methods annotated with
|
|
832
|
+
Methods annotated with `@on_running` will be called when the container up and running."""
|
|
758
833
|
def decorator(func):
|
|
759
834
|
Decorators.add(func, on_running)
|
|
760
835
|
return func
|
|
@@ -763,7 +838,7 @@ def on_running():
|
|
|
763
838
|
|
|
764
839
|
def on_destroy():
|
|
765
840
|
"""
|
|
766
|
-
Methods annotated with
|
|
841
|
+
Methods annotated with `@on_destroy` will be called when the instance is destroyed.
|
|
767
842
|
"""
|
|
768
843
|
def decorator(func):
|
|
769
844
|
Decorators.add(func, on_destroy)
|
|
@@ -771,18 +846,19 @@ def on_destroy():
|
|
|
771
846
|
|
|
772
847
|
return decorator
|
|
773
848
|
|
|
774
|
-
def
|
|
849
|
+
def module(imports: Optional[list[Type]] = None):
|
|
775
850
|
"""
|
|
776
|
-
This annotation is used to mark classes that control the
|
|
777
|
-
relative to the module of the class. All
|
|
851
|
+
This annotation is used to mark classes that control the discovery process of injectables based on their location
|
|
852
|
+
relative to the module of the class. All `@injectable`s and `@factory`s that are located in the same or any sub-module will
|
|
778
853
|
be registered and managed accordingly.
|
|
779
|
-
|
|
780
|
-
|
|
854
|
+
|
|
855
|
+
Args:
|
|
856
|
+
imports (Optional[list[Type]]): Optional list of imported module types
|
|
781
857
|
"""
|
|
782
858
|
def decorator(cls):
|
|
783
859
|
Providers.register(ClassInstanceProvider(cls, True))
|
|
784
860
|
|
|
785
|
-
Decorators.add(cls,
|
|
861
|
+
Decorators.add(cls, module, imports)
|
|
786
862
|
Decorators.add(cls, injectable) # do we need that?
|
|
787
863
|
|
|
788
864
|
return cls
|
|
@@ -855,11 +931,28 @@ def conditional(*conditions: Condition):
|
|
|
855
931
|
class Environment:
|
|
856
932
|
"""
|
|
857
933
|
Central class that manages the lifecycle of instances and their dependencies.
|
|
934
|
+
|
|
935
|
+
Usage:
|
|
936
|
+
|
|
937
|
+
```python
|
|
938
|
+
@injectable()
|
|
939
|
+
class Foo:
|
|
940
|
+
def __init__(self):
|
|
941
|
+
|
|
942
|
+
@environment()
|
|
943
|
+
class SimpleEnvironment:
|
|
944
|
+
def __init__(self):
|
|
945
|
+
pass
|
|
946
|
+
|
|
947
|
+
environment = Environment(SimpleEnvironment)
|
|
948
|
+
|
|
949
|
+
foo = environment.get(Foo) # will create an instance of Foo
|
|
950
|
+
```
|
|
858
951
|
"""
|
|
859
952
|
|
|
860
953
|
# static data
|
|
861
954
|
|
|
862
|
-
logger = logging.getLogger(
|
|
955
|
+
logger = logging.getLogger("aspyx.di") # __name__ = module name
|
|
863
956
|
|
|
864
957
|
instance : 'Environment' = None
|
|
865
958
|
|
|
@@ -894,22 +987,35 @@ class Environment:
|
|
|
894
987
|
|
|
895
988
|
self.features = features
|
|
896
989
|
self.providers: Dict[Type, AbstractInstanceProvider] = {}
|
|
990
|
+
self.instances = []
|
|
897
991
|
self.lifecycle_processors: list[LifecycleProcessor] = []
|
|
898
992
|
|
|
899
993
|
if self.parent is not None:
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
994
|
+
# inherit providers from parent
|
|
995
|
+
|
|
996
|
+
for provider_type, inherited_provider in self.parent.providers.items():
|
|
997
|
+
if inherited_provider.get_scope() == "environment":
|
|
998
|
+
# replace with own environment instance provider
|
|
999
|
+
self.providers[provider_type] = EnvironmentInstanceProvider(self, cast(EnvironmentInstanceProvider, inherited_provider).provider)
|
|
1000
|
+
else:
|
|
1001
|
+
self.providers[provider_type] = inherited_provider
|
|
1002
|
+
|
|
1003
|
+
# inherit processors as is unless they have an environment scope
|
|
1004
|
+
|
|
1005
|
+
for processor in self.parent.lifecycle_processors:
|
|
1006
|
+
if self.providers[type(processor)].get_scope() != "environment":
|
|
1007
|
+
self.lifecycle_processors.append(processor)
|
|
1008
|
+
else:
|
|
1009
|
+
# create and remember
|
|
1010
|
+
self.lifecycle_processors.append(self.get(type(processor)))
|
|
1011
|
+
else:
|
|
903
1012
|
self.providers[SingletonScope] = SingletonScopeInstanceProvider()
|
|
904
1013
|
self.providers[RequestScope] = RequestScopeInstanceProvider()
|
|
905
|
-
|
|
906
|
-
self.instances = []
|
|
1014
|
+
self.providers[EnvironmentScope] = EnvironmentScopeInstanceProvider()
|
|
907
1015
|
|
|
908
1016
|
Environment.instance = self
|
|
909
1017
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
overall_providers = Providers.filter(self)
|
|
1018
|
+
prefix_list : list[str] = []
|
|
913
1019
|
|
|
914
1020
|
loaded = set()
|
|
915
1021
|
|
|
@@ -918,6 +1024,36 @@ class Environment:
|
|
|
918
1024
|
|
|
919
1025
|
self.providers[type] = provider
|
|
920
1026
|
|
|
1027
|
+
def get_type_package(type: Type):
|
|
1028
|
+
module_name = type.__module__
|
|
1029
|
+
module = sys.modules[module_name]
|
|
1030
|
+
|
|
1031
|
+
return module.__package__
|
|
1032
|
+
|
|
1033
|
+
def import_package(name: str):
|
|
1034
|
+
"""Import a package and all its submodules recursively."""
|
|
1035
|
+
package = importlib.import_module(name)
|
|
1036
|
+
results = {name: package}
|
|
1037
|
+
|
|
1038
|
+
if hasattr(package, '__path__'): # it's a package, not a single file
|
|
1039
|
+
for finder, name, ispkg in pkgutil.walk_packages(package.__path__, prefix=package.__name__ + "."):
|
|
1040
|
+
try:
|
|
1041
|
+
loaded = sys.modules
|
|
1042
|
+
|
|
1043
|
+
if loaded.get(name, None) is None:
|
|
1044
|
+
Environment.logger.debug("import module %s", name)
|
|
1045
|
+
|
|
1046
|
+
submodule = importlib.import_module(name)
|
|
1047
|
+
results[name] = submodule
|
|
1048
|
+
else:
|
|
1049
|
+
# skip import
|
|
1050
|
+
results[name] = loaded[name]
|
|
1051
|
+
|
|
1052
|
+
except Exception as e:
|
|
1053
|
+
Environment.logger.info("failed to import module %s due to %s", name, str(e))
|
|
1054
|
+
|
|
1055
|
+
return results
|
|
1056
|
+
|
|
921
1057
|
def load_environment(env: Type):
|
|
922
1058
|
if env not in loaded:
|
|
923
1059
|
Environment.logger.debug("load environment %s", env.__qualname__)
|
|
@@ -926,28 +1062,47 @@ class Environment:
|
|
|
926
1062
|
|
|
927
1063
|
# sanity check
|
|
928
1064
|
|
|
929
|
-
decorator = TypeDescriptor.for_type(env).get_decorator(
|
|
1065
|
+
decorator = TypeDescriptor.for_type(env).get_decorator(module)
|
|
930
1066
|
if decorator is None:
|
|
931
1067
|
raise DIRegistrationException(f"{env.__name__} is not an environment class")
|
|
932
1068
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1069
|
+
# package
|
|
1070
|
+
|
|
1071
|
+
package_name = get_type_package(env)
|
|
936
1072
|
|
|
937
1073
|
# recursion
|
|
938
1074
|
|
|
939
1075
|
for import_environment in decorator.args[0] or []:
|
|
940
1076
|
load_environment(import_environment)
|
|
941
1077
|
|
|
1078
|
+
# import package
|
|
1079
|
+
|
|
1080
|
+
if package_name is not None and len(package_name) > 0: # files outside of a package return None pr ""
|
|
1081
|
+
import_package(package_name)
|
|
1082
|
+
|
|
942
1083
|
# filter and load providers according to their module
|
|
943
1084
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1085
|
+
module_prefix = package_name
|
|
1086
|
+
if len(module_prefix) == 0:
|
|
1087
|
+
module_prefix = env.__module__
|
|
1088
|
+
|
|
1089
|
+
prefix_list.append(module_prefix)
|
|
1090
|
+
|
|
947
1091
|
# go
|
|
948
1092
|
|
|
949
1093
|
load_environment(env)
|
|
950
1094
|
|
|
1095
|
+
# filter according to the prefix list
|
|
1096
|
+
|
|
1097
|
+
def filter_provider(provider: AbstractInstanceProvider) -> bool:
|
|
1098
|
+
for prefix in prefix_list:
|
|
1099
|
+
if provider.get_host().__module__.startswith(prefix):
|
|
1100
|
+
return True
|
|
1101
|
+
|
|
1102
|
+
return False
|
|
1103
|
+
|
|
1104
|
+
self.providers.update(Providers.filter(self, filter_provider))
|
|
1105
|
+
|
|
951
1106
|
# construct eager objects for local providers
|
|
952
1107
|
|
|
953
1108
|
for provider in set(self.providers.values()):
|
|
@@ -959,6 +1114,14 @@ class Environment:
|
|
|
959
1114
|
for instance in self.instances:
|
|
960
1115
|
self.execute_processors(Lifecycle.ON_RUNNING, instance)
|
|
961
1116
|
|
|
1117
|
+
def is_registered_type(self, type: Type) -> bool:
|
|
1118
|
+
provider = self.providers.get(type, None)
|
|
1119
|
+
return provider is not None and not isinstance(provider, AmbiguousProvider)
|
|
1120
|
+
|
|
1121
|
+
def registered_types(self, predicate: Callable[[Type], bool]) -> list[Type]:
|
|
1122
|
+
return [provider.get_type() for provider in self.providers.values()
|
|
1123
|
+
if predicate(provider.get_type())]
|
|
1124
|
+
|
|
962
1125
|
# internal
|
|
963
1126
|
|
|
964
1127
|
def has_feature(self, feature: str) -> bool:
|
|
@@ -1048,10 +1211,11 @@ class Environment:
|
|
|
1048
1211
|
"""
|
|
1049
1212
|
Create or return a cached instance for the given type.
|
|
1050
1213
|
|
|
1051
|
-
|
|
1214
|
+
Args:
|
|
1052
1215
|
type (Type): The desired type
|
|
1053
1216
|
|
|
1054
|
-
Returns:
|
|
1217
|
+
Returns:
|
|
1218
|
+
T: The requested instance
|
|
1055
1219
|
"""
|
|
1056
1220
|
provider = self.providers.get(type, None)
|
|
1057
1221
|
if provider is None:
|
|
@@ -1297,6 +1461,19 @@ class SingletonScope(Scope):
|
|
|
1297
1461
|
|
|
1298
1462
|
return self.value
|
|
1299
1463
|
|
|
1464
|
+
@scope("environment", register=False)
|
|
1465
|
+
class EnvironmentScope(SingletonScope):
|
|
1466
|
+
# properties
|
|
1467
|
+
|
|
1468
|
+
__slots__ = [
|
|
1469
|
+
]
|
|
1470
|
+
|
|
1471
|
+
# constructor
|
|
1472
|
+
|
|
1473
|
+
def __init__(self):
|
|
1474
|
+
super().__init__()
|
|
1475
|
+
|
|
1476
|
+
|
|
1300
1477
|
@scope("thread")
|
|
1301
1478
|
class ThreadScope(Scope):
|
|
1302
1479
|
__slots__ = [
|
|
@@ -1312,11 +1489,12 @@ class ThreadScope(Scope):
|
|
|
1312
1489
|
def get(self, provider: AbstractInstanceProvider, environment: Environment, arg_provider: Callable[[], list]):
|
|
1313
1490
|
if not hasattr(self._local, "value"):
|
|
1314
1491
|
self._local.value = provider.create(environment, *arg_provider())
|
|
1492
|
+
|
|
1315
1493
|
return self._local.value
|
|
1316
1494
|
|
|
1317
1495
|
# internal class that is required to import technical instance providers
|
|
1318
1496
|
|
|
1319
|
-
@
|
|
1497
|
+
@module()
|
|
1320
1498
|
class Boot:
|
|
1321
1499
|
# class
|
|
1322
1500
|
|
|
@@ -20,9 +20,7 @@ def synchronized():
|
|
|
20
20
|
return decorator
|
|
21
21
|
|
|
22
22
|
@advice
|
|
23
|
-
class SynchronizeAdvice
|
|
24
|
-
__slots__ = ("locks")
|
|
25
|
-
|
|
23
|
+
class SynchronizeAdvice:
|
|
26
24
|
# constructor
|
|
27
25
|
|
|
28
26
|
def __init__(self):
|
|
@@ -41,6 +39,11 @@ class SynchronizeAdvice():
|
|
|
41
39
|
# around
|
|
42
40
|
|
|
43
41
|
@around(methods().decorated_with(synchronized))
|
|
44
|
-
def
|
|
42
|
+
def synchronize_sync(self, invocation: Invocation):
|
|
43
|
+
with self.get_lock(invocation.args[0]):
|
|
44
|
+
return invocation.proceed()
|
|
45
|
+
|
|
46
|
+
@around(methods().decorated_with(synchronized).that_are_async())
|
|
47
|
+
async def synchronize_async(self, invocation: Invocation):
|
|
45
48
|
with self.get_lock(invocation.args[0]):
|
|
46
|
-
return invocation.
|
|
49
|
+
return await invocation.proceed_async()
|