aspyx 1.0.1__py3-none-any.whl → 1.1.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.
Potentially problematic release.
This version of aspyx might be problematic. Click here for more details.
- aspyx/di/__init__.py +5 -4
- aspyx/di/configuration/configuration.py +3 -3
- aspyx/di/di.py +191 -67
- aspyx/di/util/__init__.py +8 -0
- aspyx/di/util/stringbuilder.py +31 -0
- aspyx/reflection/reflection.py +11 -6
- {aspyx-1.0.1.dist-info → aspyx-1.1.0.dist-info}/METADATA +46 -19
- aspyx-1.1.0.dist-info/RECORD +16 -0
- aspyx-1.0.1.dist-info/RECORD +0 -14
- {aspyx-1.0.1.dist-info → aspyx-1.1.0.dist-info}/WHEEL +0 -0
- {aspyx-1.0.1.dist-info → aspyx-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {aspyx-1.0.1.dist-info → aspyx-1.1.0.dist-info}/top_level.txt +0 -0
aspyx/di/__init__.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module provides dependency injection and aop capabilities for Python applications.
|
|
3
3
|
"""
|
|
4
|
-
from .di import InjectorException,
|
|
4
|
+
from .di import InjectorException, AbstractCallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, environment, inject, create, on_init, on_running, on_destroy, inject_environment, Factory, PostProcessor
|
|
5
5
|
|
|
6
6
|
# import something from the subpackages, so that teh decorators are executed
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from
|
|
8
|
+
from .configuration import ConfigurationManager
|
|
9
|
+
from .aop import before
|
|
10
10
|
|
|
11
11
|
imports = [ConfigurationManager, before]
|
|
12
12
|
|
|
@@ -21,11 +21,12 @@ __all__ = [
|
|
|
21
21
|
"create",
|
|
22
22
|
|
|
23
23
|
"on_init",
|
|
24
|
+
"on_running",
|
|
24
25
|
"on_destroy",
|
|
25
26
|
"inject_environment",
|
|
26
27
|
"Factory",
|
|
27
28
|
"PostProcessor",
|
|
28
|
-
"
|
|
29
|
+
"AbstractCallableProcessor",
|
|
29
30
|
"LifecycleCallable",
|
|
30
31
|
"InjectorException",
|
|
31
32
|
"Lifecycle"
|
|
@@ -8,7 +8,7 @@ import os
|
|
|
8
8
|
from typing import Optional, Type, TypeVar
|
|
9
9
|
from dotenv import load_dotenv
|
|
10
10
|
|
|
11
|
-
from aspyx.di import injectable, Environment,
|
|
11
|
+
from aspyx.di import injectable, Environment, LifecycleCallable, Lifecycle
|
|
12
12
|
from aspyx.di.di import order, inject
|
|
13
13
|
from aspyx.reflection import Decorators, DecoratorDescriptor, TypeDescriptor
|
|
14
14
|
|
|
@@ -188,8 +188,8 @@ def value(key: str, default=None):
|
|
|
188
188
|
@injectable()
|
|
189
189
|
@order(9)
|
|
190
190
|
class ConfigurationLifecycleCallable(LifecycleCallable):
|
|
191
|
-
def __init__(self,
|
|
192
|
-
super().__init__(value,
|
|
191
|
+
def __init__(self, manager: ConfigurationManager):
|
|
192
|
+
super().__init__(value, Lifecycle.ON_INJECT)
|
|
193
193
|
|
|
194
194
|
self.manager = manager
|
|
195
195
|
|
aspyx/di/di.py
CHANGED
|
@@ -11,6 +11,7 @@ from enum import Enum, auto
|
|
|
11
11
|
import threading
|
|
12
12
|
from typing import Type, Dict, TypeVar, Generic, Optional, cast, Callable
|
|
13
13
|
|
|
14
|
+
from aspyx.di.util import StringBuilder
|
|
14
15
|
from aspyx.reflection import Decorators, TypeDescriptor, DecoratorDescriptor
|
|
15
16
|
|
|
16
17
|
T = TypeVar("T")
|
|
@@ -60,7 +61,7 @@ class AbstractInstanceProvider(ABC, Generic[T]):
|
|
|
60
61
|
pass
|
|
61
62
|
|
|
62
63
|
@abstractmethod
|
|
63
|
-
def resolve(self, context: Providers.Context)
|
|
64
|
+
def resolve(self, context: Providers.Context):
|
|
64
65
|
pass
|
|
65
66
|
|
|
66
67
|
|
|
@@ -85,10 +86,15 @@ class InstanceProvider(AbstractInstanceProvider):
|
|
|
85
86
|
self.scope = scope
|
|
86
87
|
self.dependencies : Optional[list[AbstractInstanceProvider]] = None
|
|
87
88
|
|
|
89
|
+
# internal
|
|
90
|
+
|
|
91
|
+
def _is_resolved(self) -> bool:
|
|
92
|
+
return self.dependencies is not None
|
|
93
|
+
|
|
88
94
|
# implement AbstractInstanceProvider
|
|
89
95
|
|
|
90
|
-
def resolve(self, context: Providers.Context)
|
|
91
|
-
|
|
96
|
+
def resolve(self, context: Providers.Context):
|
|
97
|
+
pass
|
|
92
98
|
|
|
93
99
|
def get_module(self) -> str:
|
|
94
100
|
return self.host.__module__
|
|
@@ -178,8 +184,8 @@ class AmbiguousProvider(AbstractInstanceProvider):
|
|
|
178
184
|
def get_dependencies(self) -> list[AbstractInstanceProvider]:
|
|
179
185
|
return []
|
|
180
186
|
|
|
181
|
-
def resolve(self, context: Providers.Context)
|
|
182
|
-
|
|
187
|
+
def resolve(self, context: Providers.Context):
|
|
188
|
+
pass
|
|
183
189
|
|
|
184
190
|
def create(self, environment: Environment, *args):
|
|
185
191
|
raise InjectorException(f"multiple candidates for type {self.type}")
|
|
@@ -245,7 +251,7 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
245
251
|
|
|
246
252
|
# implement
|
|
247
253
|
|
|
248
|
-
def resolve(self, context: Providers.Context)
|
|
254
|
+
def resolve(self, context: Providers.Context):
|
|
249
255
|
pass # noop
|
|
250
256
|
|
|
251
257
|
def get_module(self) -> str:
|
|
@@ -297,11 +303,11 @@ class ClassInstanceProvider(InstanceProvider):
|
|
|
297
303
|
|
|
298
304
|
# implement
|
|
299
305
|
|
|
300
|
-
def resolve(self, context: Providers.Context)
|
|
301
|
-
|
|
302
|
-
self.dependencies = []
|
|
306
|
+
def resolve(self, context: Providers.Context):
|
|
307
|
+
context.add(self)
|
|
303
308
|
|
|
304
|
-
|
|
309
|
+
if not self._is_resolved():
|
|
310
|
+
self.dependencies = []
|
|
305
311
|
|
|
306
312
|
# check constructor
|
|
307
313
|
|
|
@@ -312,7 +318,7 @@ class ClassInstanceProvider(InstanceProvider):
|
|
|
312
318
|
for param in init.param_types:
|
|
313
319
|
provider = Providers.get_provider(param)
|
|
314
320
|
self.params += 1
|
|
315
|
-
if self.add_dependency(provider):
|
|
321
|
+
if self.add_dependency(provider): # a dependency can occur multiple times, e.g in __init__ and in an injected method
|
|
316
322
|
provider.resolve(context)
|
|
317
323
|
|
|
318
324
|
# check @inject
|
|
@@ -327,8 +333,6 @@ class ClassInstanceProvider(InstanceProvider):
|
|
|
327
333
|
else: # check if the dependencies create a cycle
|
|
328
334
|
context.add(*self.dependencies)
|
|
329
335
|
|
|
330
|
-
return self
|
|
331
|
-
|
|
332
336
|
def create(self, environment: Environment, *args):
|
|
333
337
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
334
338
|
|
|
@@ -357,11 +361,11 @@ class FunctionInstanceProvider(InstanceProvider):
|
|
|
357
361
|
|
|
358
362
|
# implement
|
|
359
363
|
|
|
360
|
-
def resolve(self, context: Providers.Context)
|
|
361
|
-
|
|
362
|
-
self.dependencies = []
|
|
364
|
+
def resolve(self, context: Providers.Context):
|
|
365
|
+
context.add(self)
|
|
363
366
|
|
|
364
|
-
|
|
367
|
+
if not self._is_resolved():
|
|
368
|
+
self.dependencies = []
|
|
365
369
|
|
|
366
370
|
provider = Providers.get_provider(self.host)
|
|
367
371
|
if self.add_dependency(provider):
|
|
@@ -369,8 +373,6 @@ class FunctionInstanceProvider(InstanceProvider):
|
|
|
369
373
|
else: # check if the dependencies crate a cycle
|
|
370
374
|
context.add(*self.dependencies)
|
|
371
375
|
|
|
372
|
-
return self
|
|
373
|
-
|
|
374
376
|
def create(self, environment: Environment, *args):
|
|
375
377
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
376
378
|
|
|
@@ -401,11 +403,11 @@ class FactoryInstanceProvider(InstanceProvider):
|
|
|
401
403
|
|
|
402
404
|
# implement
|
|
403
405
|
|
|
404
|
-
def resolve(self, context: Providers.Context)
|
|
405
|
-
|
|
406
|
-
self.dependencies = []
|
|
406
|
+
def resolve(self, context: Providers.Context):
|
|
407
|
+
context.add(self)
|
|
407
408
|
|
|
408
|
-
|
|
409
|
+
if not self._is_resolved():
|
|
410
|
+
self.dependencies = []
|
|
409
411
|
|
|
410
412
|
provider = Providers.get_provider(self.host)
|
|
411
413
|
if self.add_dependency(provider):
|
|
@@ -432,8 +434,10 @@ class Lifecycle(Enum):
|
|
|
432
434
|
|
|
433
435
|
__slots__ = []
|
|
434
436
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
+
ON_INJECT = 0
|
|
438
|
+
ON_INIT = 1
|
|
439
|
+
ON_RUNNING = 2
|
|
440
|
+
ON_DESTROY = 3
|
|
437
441
|
|
|
438
442
|
class LifecycleProcessor(ABC):
|
|
439
443
|
"""
|
|
@@ -502,7 +506,7 @@ class Providers:
|
|
|
502
506
|
|
|
503
507
|
cycle += f"{p.get_type().__name__}"
|
|
504
508
|
|
|
505
|
-
cycle += f"
|
|
509
|
+
cycle += f" <> {provider.get_type().__name__}"
|
|
506
510
|
|
|
507
511
|
return cycle
|
|
508
512
|
|
|
@@ -574,8 +578,6 @@ class Providers:
|
|
|
574
578
|
|
|
575
579
|
Providers.check.clear()
|
|
576
580
|
|
|
577
|
-
#Providers.report()
|
|
578
|
-
|
|
579
581
|
@classmethod
|
|
580
582
|
def report(cls):
|
|
581
583
|
for provider in Providers.cache.values():
|
|
@@ -614,8 +616,6 @@ def injectable(eager=True, scope="singleton"):
|
|
|
614
616
|
|
|
615
617
|
Providers.register(ClassInstanceProvider(cls, eager, scope))
|
|
616
618
|
|
|
617
|
-
#TODO registerFactories(cls)
|
|
618
|
-
|
|
619
619
|
return cls
|
|
620
620
|
|
|
621
621
|
return decorator
|
|
@@ -653,6 +653,15 @@ def on_init():
|
|
|
653
653
|
|
|
654
654
|
return decorator
|
|
655
655
|
|
|
656
|
+
def on_running():
|
|
657
|
+
"""
|
|
658
|
+
Methods annotated with @on_running will be called when the container up and running."""
|
|
659
|
+
def decorator(func):
|
|
660
|
+
Decorators.add(func, on_running)
|
|
661
|
+
return func
|
|
662
|
+
|
|
663
|
+
return decorator
|
|
664
|
+
|
|
656
665
|
def on_destroy():
|
|
657
666
|
"""
|
|
658
667
|
Methods annotated with @on_destroy will be called when the instance is destroyed.
|
|
@@ -750,7 +759,7 @@ class Environment:
|
|
|
750
759
|
|
|
751
760
|
Environment.instance = self
|
|
752
761
|
|
|
753
|
-
# resolve providers on a static basis. This is
|
|
762
|
+
# resolve providers on a static basis. This is executed for all new providers ( in case of new modules multiple times) !
|
|
754
763
|
|
|
755
764
|
Providers.resolve()
|
|
756
765
|
|
|
@@ -794,7 +803,7 @@ class Environment:
|
|
|
794
803
|
|
|
795
804
|
# register providers
|
|
796
805
|
|
|
797
|
-
# make sure, that for every type
|
|
806
|
+
# make sure, that for every type only a single EnvironmentInstanceProvider is created!
|
|
798
807
|
# otherwise inheritance will fuck it up
|
|
799
808
|
|
|
800
809
|
environment_providers : dict[AbstractInstanceProvider, EnvironmentInstanceProvider] = {}
|
|
@@ -823,6 +832,12 @@ class Environment:
|
|
|
823
832
|
for provider in load_environment(env):
|
|
824
833
|
if provider.is_eager():
|
|
825
834
|
provider.create(self)
|
|
835
|
+
|
|
836
|
+
# running callback
|
|
837
|
+
|
|
838
|
+
for instance in self.instances:
|
|
839
|
+
self.execute_processors(Lifecycle.ON_RUNNING, instance)
|
|
840
|
+
|
|
826
841
|
# internal
|
|
827
842
|
|
|
828
843
|
def execute_processors(self, lifecycle: Lifecycle, instance: T) -> T:
|
|
@@ -847,10 +862,54 @@ class Environment:
|
|
|
847
862
|
|
|
848
863
|
# execute processors
|
|
849
864
|
|
|
850
|
-
|
|
865
|
+
self.execute_processors(Lifecycle.ON_INJECT, instance)
|
|
866
|
+
self.execute_processors(Lifecycle.ON_INIT, instance)
|
|
867
|
+
|
|
868
|
+
return instance
|
|
851
869
|
|
|
852
870
|
# public
|
|
853
871
|
|
|
872
|
+
def report(self):
|
|
873
|
+
builder = StringBuilder()
|
|
874
|
+
|
|
875
|
+
builder.append(f"Environment {self.type.__name__}")
|
|
876
|
+
|
|
877
|
+
if self.parent is not None:
|
|
878
|
+
builder.append(f" parent {self.parent.type.__name__}")
|
|
879
|
+
|
|
880
|
+
builder.append("\n")
|
|
881
|
+
|
|
882
|
+
# post processors
|
|
883
|
+
|
|
884
|
+
builder.append("Processors \n")
|
|
885
|
+
for processor in self.lifecycle_processors:
|
|
886
|
+
builder.append(f"- {processor.__class__.__name__}\n")
|
|
887
|
+
|
|
888
|
+
# providers
|
|
889
|
+
|
|
890
|
+
builder.append("Providers \n")
|
|
891
|
+
for result_type, provider in self.providers.items():
|
|
892
|
+
if cast(EnvironmentInstanceProvider, provider).environment is self:
|
|
893
|
+
builder.append(f"- {result_type.__name__}: {cast(EnvironmentInstanceProvider, provider).provider}\n")
|
|
894
|
+
|
|
895
|
+
# instances
|
|
896
|
+
|
|
897
|
+
builder.append("Instances \n")
|
|
898
|
+
|
|
899
|
+
result = {}
|
|
900
|
+
for obj in self.instances:
|
|
901
|
+
cls = type(obj)
|
|
902
|
+
result[cls] = result.get(cls, 0) + 1
|
|
903
|
+
|
|
904
|
+
for cls, count in result.items():
|
|
905
|
+
builder.append(f"- {cls.__name__}: {count} \n")
|
|
906
|
+
|
|
907
|
+
# done
|
|
908
|
+
|
|
909
|
+
result = str(builder)
|
|
910
|
+
|
|
911
|
+
return result
|
|
912
|
+
|
|
854
913
|
def destroy(self):
|
|
855
914
|
"""
|
|
856
915
|
destroy all managed instances by calling the appropriate lifecycle methods
|
|
@@ -883,22 +942,21 @@ class LifecycleCallable:
|
|
|
883
942
|
"order"
|
|
884
943
|
]
|
|
885
944
|
|
|
886
|
-
def __init__(self, decorator,
|
|
945
|
+
def __init__(self, decorator, lifecycle: Lifecycle):
|
|
887
946
|
self.decorator = decorator
|
|
888
947
|
self.lifecycle = lifecycle
|
|
889
948
|
self.order = 0
|
|
890
949
|
|
|
891
950
|
if TypeDescriptor.for_type(type(self)).has_decorator(order):
|
|
892
|
-
self.order =
|
|
951
|
+
self.order = TypeDescriptor.for_type(type(self)).get_decorator(order).args[0]
|
|
893
952
|
|
|
894
|
-
|
|
953
|
+
AbstractCallableProcessor.register(self)
|
|
895
954
|
|
|
896
955
|
def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
|
|
897
956
|
return []
|
|
898
957
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
class CallableProcessor(LifecycleProcessor):
|
|
958
|
+
|
|
959
|
+
class AbstractCallableProcessor(LifecycleProcessor):
|
|
902
960
|
# local classes
|
|
903
961
|
|
|
904
962
|
class MethodCall:
|
|
@@ -921,74 +979,123 @@ class CallableProcessor(LifecycleProcessor):
|
|
|
921
979
|
def __str__(self):
|
|
922
980
|
return f"MethodCall({self.method.method.__name__})"
|
|
923
981
|
|
|
924
|
-
#
|
|
982
|
+
# static data
|
|
925
983
|
|
|
926
|
-
|
|
927
|
-
|
|
984
|
+
callables : Dict[object, LifecycleCallable] = {}
|
|
985
|
+
cache : Dict[Type, list[list[AbstractCallableProcessor.MethodCall]]] = {}
|
|
986
|
+
|
|
987
|
+
# static methods
|
|
928
988
|
|
|
929
|
-
|
|
930
|
-
|
|
989
|
+
@classmethod
|
|
990
|
+
def register(cls, callable: LifecycleCallable):
|
|
991
|
+
AbstractCallableProcessor.callables[callable.decorator] = callable
|
|
931
992
|
|
|
932
|
-
|
|
993
|
+
@classmethod
|
|
994
|
+
def compute_callables(cls, type: Type) -> list[list[AbstractCallableProcessor.MethodCall]]:
|
|
933
995
|
descriptor = TypeDescriptor.for_type(type)
|
|
934
996
|
|
|
935
|
-
result = []
|
|
997
|
+
result = [[], [], [], []] # per lifecycle
|
|
936
998
|
|
|
937
999
|
for method in descriptor.get_methods():
|
|
938
1000
|
for decorator in method.decorators:
|
|
939
|
-
|
|
940
|
-
|
|
1001
|
+
callable = AbstractCallableProcessor.callables.get(decorator.decorator)
|
|
1002
|
+
if callable is not None: # any callable for this decorator?
|
|
1003
|
+
result[callable.lifecycle.value].append(
|
|
1004
|
+
AbstractCallableProcessor.MethodCall(method, decorator, callable))
|
|
941
1005
|
|
|
942
1006
|
# sort according to order
|
|
943
1007
|
|
|
944
|
-
result.sort(key=lambda call: call.lifecycle_callable.order)
|
|
1008
|
+
result[0].sort(key=lambda call: call.lifecycle_callable.order)
|
|
1009
|
+
result[1].sort(key=lambda call: call.lifecycle_callable.order)
|
|
1010
|
+
result[2].sort(key=lambda call: call.lifecycle_callable.order)
|
|
1011
|
+
result[3].sort(key=lambda call: call.lifecycle_callable.order)
|
|
945
1012
|
|
|
946
1013
|
# done
|
|
947
1014
|
|
|
948
1015
|
return result
|
|
949
1016
|
|
|
950
|
-
|
|
951
|
-
|
|
1017
|
+
@classmethod
|
|
1018
|
+
def callables_for(cls, type: Type) -> list[list[AbstractCallableProcessor.MethodCall]]:
|
|
1019
|
+
callables = AbstractCallableProcessor.cache.get(type, None)
|
|
952
1020
|
if callables is None:
|
|
953
|
-
callables =
|
|
954
|
-
|
|
1021
|
+
callables = AbstractCallableProcessor.compute_callables(type)
|
|
1022
|
+
AbstractCallableProcessor.cache[type] = callables
|
|
955
1023
|
|
|
956
1024
|
return callables
|
|
957
1025
|
|
|
958
|
-
|
|
959
|
-
|
|
1026
|
+
# constructor
|
|
1027
|
+
|
|
1028
|
+
def __init__(self, lifecycle: Lifecycle):
|
|
1029
|
+
super().__init__()
|
|
1030
|
+
|
|
1031
|
+
self.lifecycle = lifecycle
|
|
960
1032
|
|
|
961
1033
|
# implement
|
|
962
1034
|
|
|
963
1035
|
def process_lifecycle(self, lifecycle: Lifecycle, instance: object, environment: Environment) -> object:
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1036
|
+
if lifecycle is self.lifecycle:
|
|
1037
|
+
callables = self.callables_for(type(instance))
|
|
1038
|
+
|
|
1039
|
+
for callable in callables[lifecycle.value]:
|
|
967
1040
|
callable.execute(instance, environment)
|
|
968
1041
|
|
|
1042
|
+
@injectable()
|
|
1043
|
+
@order(1)
|
|
1044
|
+
class OnInjectCallableProcessor(AbstractCallableProcessor):
|
|
1045
|
+
def __init__(self):
|
|
1046
|
+
super().__init__(Lifecycle.ON_INJECT)
|
|
1047
|
+
|
|
1048
|
+
@injectable()
|
|
1049
|
+
@order(2)
|
|
1050
|
+
class OnInitCallableProcessor(AbstractCallableProcessor):
|
|
1051
|
+
def __init__(self):
|
|
1052
|
+
super().__init__(Lifecycle.ON_INIT)
|
|
1053
|
+
|
|
1054
|
+
@injectable()
|
|
1055
|
+
@order(3)
|
|
1056
|
+
class OnRunningCallableProcessor(AbstractCallableProcessor):
|
|
1057
|
+
def __init__(self):
|
|
1058
|
+
super().__init__(Lifecycle.ON_RUNNING)
|
|
1059
|
+
|
|
1060
|
+
@injectable()
|
|
1061
|
+
@order(4)
|
|
1062
|
+
class OnDestroyCallableProcessor(AbstractCallableProcessor):
|
|
1063
|
+
def __init__(self):
|
|
1064
|
+
super().__init__(Lifecycle.ON_DESTROY)
|
|
1065
|
+
|
|
1066
|
+
# the callables
|
|
1067
|
+
|
|
969
1068
|
@injectable()
|
|
970
1069
|
@order(1000)
|
|
971
1070
|
class OnInitLifecycleCallable(LifecycleCallable):
|
|
972
1071
|
__slots__ = []
|
|
973
1072
|
|
|
974
|
-
def __init__(self
|
|
975
|
-
super().__init__(on_init,
|
|
1073
|
+
def __init__(self):
|
|
1074
|
+
super().__init__(on_init, Lifecycle.ON_INIT)
|
|
976
1075
|
|
|
977
1076
|
@injectable()
|
|
978
1077
|
@order(1001)
|
|
979
1078
|
class OnDestroyLifecycleCallable(LifecycleCallable):
|
|
980
1079
|
__slots__ = []
|
|
981
1080
|
|
|
982
|
-
def __init__(self
|
|
983
|
-
super().__init__(on_destroy,
|
|
1081
|
+
def __init__(self):
|
|
1082
|
+
super().__init__(on_destroy, Lifecycle.ON_DESTROY)
|
|
1083
|
+
|
|
1084
|
+
@injectable()
|
|
1085
|
+
@order(1002)
|
|
1086
|
+
class OnRunningLifecycleCallable(LifecycleCallable):
|
|
1087
|
+
__slots__ = []
|
|
1088
|
+
|
|
1089
|
+
def __init__(self):
|
|
1090
|
+
super().__init__(on_running, Lifecycle.ON_RUNNING)
|
|
984
1091
|
|
|
985
1092
|
@injectable()
|
|
986
1093
|
@order(9)
|
|
987
1094
|
class EnvironmentAwareLifecycleCallable(LifecycleCallable):
|
|
988
1095
|
__slots__ = []
|
|
989
1096
|
|
|
990
|
-
def __init__(self
|
|
991
|
-
super().__init__(inject_environment,
|
|
1097
|
+
def __init__(self):
|
|
1098
|
+
super().__init__(inject_environment, Lifecycle.ON_INJECT)
|
|
992
1099
|
|
|
993
1100
|
def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
|
|
994
1101
|
return [environment]
|
|
@@ -998,8 +1105,8 @@ class EnvironmentAwareLifecycleCallable(LifecycleCallable):
|
|
|
998
1105
|
class InjectLifecycleCallable(LifecycleCallable):
|
|
999
1106
|
__slots__ = []
|
|
1000
1107
|
|
|
1001
|
-
def __init__(self
|
|
1002
|
-
super().__init__(inject,
|
|
1108
|
+
def __init__(self):
|
|
1109
|
+
super().__init__(inject, Lifecycle.ON_INJECT)
|
|
1003
1110
|
|
|
1004
1111
|
# override
|
|
1005
1112
|
|
|
@@ -1046,7 +1153,7 @@ class SingletonScope(Scope):
|
|
|
1046
1153
|
super().__init__()
|
|
1047
1154
|
|
|
1048
1155
|
self.value = None
|
|
1049
|
-
self.lock = threading.
|
|
1156
|
+
self.lock = threading.RLock()
|
|
1050
1157
|
|
|
1051
1158
|
# override
|
|
1052
1159
|
|
|
@@ -1057,6 +1164,23 @@ class SingletonScope(Scope):
|
|
|
1057
1164
|
self.value = provider.create(environment, *arg_provider())
|
|
1058
1165
|
|
|
1059
1166
|
return self.value
|
|
1167
|
+
|
|
1168
|
+
@scope("thread")
|
|
1169
|
+
class ThreadScope(Scope):
|
|
1170
|
+
__slots__ = [
|
|
1171
|
+
"_local"
|
|
1172
|
+
]
|
|
1173
|
+
|
|
1174
|
+
# constructor
|
|
1175
|
+
|
|
1176
|
+
def __init__(self):
|
|
1177
|
+
super().__init__()
|
|
1178
|
+
self._local = threading.local()
|
|
1179
|
+
|
|
1180
|
+
def get(self, provider: AbstractInstanceProvider, environment: Environment, arg_provider: Callable[[], list]):
|
|
1181
|
+
if not hasattr(self._local, "value"):
|
|
1182
|
+
self._local.value = provider.create(environment, *arg_provider())
|
|
1183
|
+
return self._local.value
|
|
1060
1184
|
|
|
1061
1185
|
# internal class that is required to import technical instance providers
|
|
1062
1186
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility class for Java lovers
|
|
3
|
+
"""
|
|
4
|
+
class StringBuilder:
|
|
5
|
+
___slots__ = ("_parts",)
|
|
6
|
+
|
|
7
|
+
# constructor
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self._parts = []
|
|
11
|
+
|
|
12
|
+
# public
|
|
13
|
+
|
|
14
|
+
def append(self, s: str) -> "StringBuilder":
|
|
15
|
+
self._parts.append(str(s))
|
|
16
|
+
|
|
17
|
+
return self
|
|
18
|
+
|
|
19
|
+
def extend(self, iterable) -> "StringBuilder":
|
|
20
|
+
for s in iterable:
|
|
21
|
+
self._parts.append(str(s))
|
|
22
|
+
|
|
23
|
+
return self
|
|
24
|
+
|
|
25
|
+
def clear(self):
|
|
26
|
+
self._parts.clear()
|
|
27
|
+
|
|
28
|
+
# object
|
|
29
|
+
|
|
30
|
+
def __str__(self):
|
|
31
|
+
return ''.join(self._parts)
|
aspyx/reflection/reflection.py
CHANGED
|
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import inspect
|
|
8
8
|
from inspect import signature, getmembers
|
|
9
|
+
import threading
|
|
9
10
|
from typing import Callable, get_type_hints, Type, Dict, Optional
|
|
10
11
|
from weakref import WeakKeyDictionary
|
|
11
12
|
|
|
@@ -55,14 +56,14 @@ class TypeDescriptor:
|
|
|
55
56
|
|
|
56
57
|
self.return_type = type_hints.get('return', None)
|
|
57
58
|
|
|
58
|
-
def get_decorator(self, decorator) -> Optional[DecoratorDescriptor]:
|
|
59
|
+
def get_decorator(self, decorator: Callable) -> Optional[DecoratorDescriptor]:
|
|
59
60
|
for dec in self.decorators:
|
|
60
61
|
if dec.decorator is decorator:
|
|
61
62
|
return dec
|
|
62
63
|
|
|
63
64
|
return None
|
|
64
65
|
|
|
65
|
-
def has_decorator(self, decorator) -> bool:
|
|
66
|
+
def has_decorator(self, decorator: Callable) -> bool:
|
|
66
67
|
for dec in self.decorators:
|
|
67
68
|
if dec.decorator is decorator:
|
|
68
69
|
return True
|
|
@@ -75,6 +76,7 @@ class TypeDescriptor:
|
|
|
75
76
|
# class properties
|
|
76
77
|
|
|
77
78
|
_cache = WeakKeyDictionary()
|
|
79
|
+
_lock = threading.RLock()
|
|
78
80
|
|
|
79
81
|
# class methods
|
|
80
82
|
|
|
@@ -82,8 +84,11 @@ class TypeDescriptor:
|
|
|
82
84
|
def for_type(cls, clazz: Type) -> TypeDescriptor:
|
|
83
85
|
descriptor = cls._cache.get(clazz)
|
|
84
86
|
if descriptor is None:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
with cls._lock:
|
|
88
|
+
descriptor = cls._cache.get(clazz)
|
|
89
|
+
if descriptor is None:
|
|
90
|
+
descriptor = TypeDescriptor(clazz)
|
|
91
|
+
cls._cache[clazz] = descriptor
|
|
87
92
|
|
|
88
93
|
return descriptor
|
|
89
94
|
|
|
@@ -120,14 +125,14 @@ class TypeDescriptor:
|
|
|
120
125
|
|
|
121
126
|
# public
|
|
122
127
|
|
|
123
|
-
def get_decorator(self, decorator) -> Optional[DecoratorDescriptor]:
|
|
128
|
+
def get_decorator(self, decorator: Callable) -> Optional[DecoratorDescriptor]:
|
|
124
129
|
for dec in self.decorators:
|
|
125
130
|
if dec.decorator is decorator:
|
|
126
131
|
return dec
|
|
127
132
|
|
|
128
133
|
return None
|
|
129
134
|
|
|
130
|
-
def has_decorator(self, decorator) -> bool:
|
|
135
|
+
def has_decorator(self, decorator: Callable) -> bool:
|
|
131
136
|
for dec in self.decorators:
|
|
132
137
|
if dec.decorator is decorator:
|
|
133
138
|
return True
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: A DI and AOP library for Python
|
|
5
5
|
Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -34,8 +34,9 @@ Dynamic: license-file
|
|
|
34
34
|
|
|
35
35
|

|
|
36
36
|

|
|
37
|
-

|
|
38
38
|

|
|
39
|
+

|
|
39
40
|
|
|
40
41
|
## Table of Contents
|
|
41
42
|
|
|
@@ -56,18 +57,18 @@ Dynamic: license-file
|
|
|
56
57
|
- [AOP](#aop)
|
|
57
58
|
- [Configuration](#configuration)
|
|
58
59
|
- [Reflection](#reflection)
|
|
60
|
+
- [Version History](#version-history)
|
|
59
61
|
|
|
60
62
|
# Introduction
|
|
61
63
|
|
|
62
64
|
Aspyx is a small python libary, that adds support for both dependency injection and aop.
|
|
63
65
|
|
|
64
66
|
The following features are supported
|
|
65
|
-
- constructor injection
|
|
66
|
-
- method injection
|
|
67
|
+
- constructor and setter injection
|
|
67
68
|
- post processors
|
|
68
69
|
- factory classes and methods
|
|
69
70
|
- support for eager construction
|
|
70
|
-
- support for singleton and
|
|
71
|
+
- support for singleton and request scopes
|
|
71
72
|
- possibilty to add custom scopes
|
|
72
73
|
- lifecycle events methods
|
|
73
74
|
- bundling of injectable object sets by environment classes including recursive imports and inheritance
|
|
@@ -156,7 +157,7 @@ Let's look at the details
|
|
|
156
157
|
|
|
157
158
|
`pip install aspyx`
|
|
158
159
|
|
|
159
|
-
The library is tested with Python version > 3.
|
|
160
|
+
The library is tested with all Python version > 3.9
|
|
160
161
|
|
|
161
162
|
Ready to go...
|
|
162
163
|
|
|
@@ -185,8 +186,15 @@ The decorator accepts the keyword arguments
|
|
|
185
186
|
- `eager : boolean`
|
|
186
187
|
if `True`, the container will create the instances automatically while booting the environment. This is the default.
|
|
187
188
|
- `scope: str`
|
|
188
|
-
the name of a scope which will determine how often instances will be created.
|
|
189
|
-
|
|
189
|
+
the name of a - registered - scope which will determine how often instances will be created.
|
|
190
|
+
|
|
191
|
+
The following scopes are implemented out of the box:
|
|
192
|
+
- `singleton`
|
|
193
|
+
objects are created once inside an environment and cached. This is the default.
|
|
194
|
+
- `request`
|
|
195
|
+
obejcts are created on every injection request
|
|
196
|
+
- `thread`
|
|
197
|
+
objects are cerated and cached with respect to the current thread.
|
|
190
198
|
|
|
191
199
|
Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
|
|
192
200
|
|
|
@@ -304,7 +312,7 @@ Different decorators are implemented, that call methods with computed values
|
|
|
304
312
|
the method is called with all resolved parameter types ( same as the constructor call)
|
|
305
313
|
- `@inject_environment`
|
|
306
314
|
the method is called with the creating environment as a single parameter
|
|
307
|
-
- `@
|
|
315
|
+
- `@inject_value()`
|
|
308
316
|
the method is called with a resolved configuration value. Check the corresponding chapter
|
|
309
317
|
|
|
310
318
|
**Example**:
|
|
@@ -325,9 +333,11 @@ class Foo:
|
|
|
325
333
|
|
|
326
334
|
## Lifecycle methods
|
|
327
335
|
|
|
328
|
-
It is possible to mark specific
|
|
336
|
+
It is possible to mark specific lifecyle methods.
|
|
329
337
|
- `@on_init()`
|
|
330
338
|
called after the constructor and all other injections.
|
|
339
|
+
- `@on_running()`
|
|
340
|
+
called an environment has initialized all eager objects.
|
|
331
341
|
- `@on_destroy()`
|
|
332
342
|
called during shutdown of the environment
|
|
333
343
|
|
|
@@ -460,7 +470,7 @@ class TransactionAdvice:
|
|
|
460
470
|
|
|
461
471
|
# Configuration
|
|
462
472
|
|
|
463
|
-
It is possible to inject configuration values, by decorating methods with `@value(<name>)` given a configuration key.
|
|
473
|
+
It is possible to inject configuration values, by decorating methods with `@inject-value(<name>)` given a configuration key.
|
|
464
474
|
|
|
465
475
|
```python
|
|
466
476
|
@injectable()
|
|
@@ -522,16 +532,24 @@ TypeDescriptor.for_type(<type>)
|
|
|
522
532
|
```
|
|
523
533
|
|
|
524
534
|
it offers the methods
|
|
525
|
-
- `get_methods(local=False)`
|
|
526
|
-
|
|
527
|
-
- `
|
|
528
|
-
|
|
535
|
+
- `get_methods(local=False)`
|
|
536
|
+
return a list of either local or overall methods
|
|
537
|
+
- `get_method(name: str, local=False)`
|
|
538
|
+
return a single either local or overall method
|
|
539
|
+
- `has_decorator(decorator: Callable) -> bool`
|
|
540
|
+
return `True`, if the class is decorated with the specified decrator
|
|
541
|
+
- `get_decorator(decorator) -> Optional[DecoratorDescriptor]`
|
|
542
|
+
return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
|
|
529
543
|
|
|
530
544
|
The returned method descriptors offer:
|
|
531
|
-
- `param_types`
|
|
532
|
-
|
|
533
|
-
- `
|
|
534
|
-
|
|
545
|
+
- `param_types`
|
|
546
|
+
list of arg types
|
|
547
|
+
- `return_type`
|
|
548
|
+
the retur type
|
|
549
|
+
- `has_decorator(decorator: Callable) -> bool`
|
|
550
|
+
return `True`, if the method is decorated with the specified decrator
|
|
551
|
+
- `get_decorator(decorator: Callable) -> Optional[DecoratorDescriptor]`
|
|
552
|
+
return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
|
|
535
553
|
|
|
536
554
|
The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
|
|
537
555
|
|
|
@@ -548,7 +566,16 @@ def transactional():
|
|
|
548
566
|
```
|
|
549
567
|
|
|
550
568
|
|
|
569
|
+
# Version History
|
|
570
|
+
|
|
571
|
+
**1.0.1**
|
|
572
|
+
|
|
573
|
+
- some internal refactorings
|
|
574
|
+
|
|
575
|
+
**1.1.0**
|
|
551
576
|
|
|
577
|
+
- added `@on_running()` callback
|
|
578
|
+
- added `thread` scope
|
|
552
579
|
|
|
553
580
|
|
|
554
581
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
aspyx/di/__init__.py,sha256=U2-4JnCmSO_IXCnN1jeig9nuWfJN__z9yRzl1WexQJk,927
|
|
2
|
+
aspyx/di/di.py,sha256=VVLOXhTKJer2cV0rNEOqMLyMigsNMEQ7xoc-6M7a8wU,34296
|
|
3
|
+
aspyx/di/aop/__init__.py,sha256=nOABex49zSyMZ2w1ezwX3Q3yrOcQRSDjDtSj0DwKVbQ,233
|
|
4
|
+
aspyx/di/aop/aop.py,sha256=1RUgijk8RsiXWTizfNQX1IfHF7b96kCPdUgahX_J4Io,14457
|
|
5
|
+
aspyx/di/configuration/__init__.py,sha256=Zw7h-OlbJD7LyJvzkgyF0EmVqra6oN_Pt0HuUdjTPTA,249
|
|
6
|
+
aspyx/di/configuration/configuration.py,sha256=jJJSHRG2wOIvle23Xc8CQ4WM7lp_xCL8-ENVZO603uQ,5682
|
|
7
|
+
aspyx/di/util/__init__.py,sha256=8H2yKkXu3nkRGeTerb8ialzKGfvzUx44XUWFUYcYuQM,125
|
|
8
|
+
aspyx/di/util/stringbuilder.py,sha256=JywkLxZfaQUUWjSB5wvqA6a6Cfs3sW1jbaZ1z4U0-CQ,540
|
|
9
|
+
aspyx/reflection/__init__.py,sha256=r2sNJrfHDpuqaIYu4fTYsoo046gpgn4VTd7bsS3mQJY,282
|
|
10
|
+
aspyx/reflection/proxy.py,sha256=zJ6Psd6zWfFABdrKOf4cULt3gibyqCRdcR6z8WKIkzE,1982
|
|
11
|
+
aspyx/reflection/reflection.py,sha256=HfPFd_FehTGmgF6NA-IJ_aHePE6GXM1ag7K3h8YIXjI,4600
|
|
12
|
+
aspyx-1.1.0.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
|
|
13
|
+
aspyx-1.1.0.dist-info/METADATA,sha256=rdeICMYyQSJmSZDJZrsdw6l5ZVrQrxvAPS_z47ObM-Y,17989
|
|
14
|
+
aspyx-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
+
aspyx-1.1.0.dist-info/top_level.txt,sha256=A_ZwhBY_ybIgjZlztd44eaOrWqkJAndiqjGlbJ3tR_I,6
|
|
16
|
+
aspyx-1.1.0.dist-info/RECORD,,
|
aspyx-1.0.1.dist-info/RECORD
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
aspyx/di/__init__.py,sha256=G13Cz1MMElBKNWsrhgTEIvhmIKj5MX4uqK_Apke4w8I,897
|
|
2
|
-
aspyx/di/di.py,sha256=elT3XJokxYxPJ9mx528MAU-pmm9lIZF8_AXKGkjyhBo,31014
|
|
3
|
-
aspyx/di/aop/__init__.py,sha256=nOABex49zSyMZ2w1ezwX3Q3yrOcQRSDjDtSj0DwKVbQ,233
|
|
4
|
-
aspyx/di/aop/aop.py,sha256=1RUgijk8RsiXWTizfNQX1IfHF7b96kCPdUgahX_J4Io,14457
|
|
5
|
-
aspyx/di/configuration/__init__.py,sha256=Zw7h-OlbJD7LyJvzkgyF0EmVqra6oN_Pt0HuUdjTPTA,249
|
|
6
|
-
aspyx/di/configuration/configuration.py,sha256=dHYoM3jC43QeDVlT6-mGDj05Sz6-Nm91LNE2TfQT1UU,5740
|
|
7
|
-
aspyx/reflection/__init__.py,sha256=r2sNJrfHDpuqaIYu4fTYsoo046gpgn4VTd7bsS3mQJY,282
|
|
8
|
-
aspyx/reflection/proxy.py,sha256=zJ6Psd6zWfFABdrKOf4cULt3gibyqCRdcR6z8WKIkzE,1982
|
|
9
|
-
aspyx/reflection/reflection.py,sha256=NkBK94kjJ7rQpPsK4mzHzX5TLSlWRM3mbNVzkwOZo4o,4379
|
|
10
|
-
aspyx-1.0.1.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
|
|
11
|
-
aspyx-1.0.1.dist-info/METADATA,sha256=69OakKuzM1UK0YTgH-y6-473YWhylxQvt8v993WsqiU,16798
|
|
12
|
-
aspyx-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
aspyx-1.0.1.dist-info/top_level.txt,sha256=A_ZwhBY_ybIgjZlztd44eaOrWqkJAndiqjGlbJ3tR_I,6
|
|
14
|
-
aspyx-1.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|