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 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, CallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, environment, inject, create, on_init, on_destroy, inject_environment, Factory, PostProcessor
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 aspyx.di.configuration import ConfigurationManager
9
- from aspyx.di.aop import before
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
- "CallableProcessor",
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, CallableProcessor, LifecycleCallable, Lifecycle
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, processor: CallableProcessor, manager: ConfigurationManager):
192
- super().__init__(value, processor, Lifecycle.ON_INIT)
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) -> AbstractInstanceProvider:
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) -> AbstractInstanceProvider:
91
- return self
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) -> AbstractInstanceProvider:
182
- return self
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) -> AbstractInstanceProvider:
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) -> InstanceProvider:
301
- if self.dependencies is None:
302
- self.dependencies = []
306
+ def resolve(self, context: Providers.Context):
307
+ context.add(self)
303
308
 
304
- context.add(self)
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) -> AbstractInstanceProvider:
361
- if self.dependencies is None:
362
- self.dependencies = []
364
+ def resolve(self, context: Providers.Context):
365
+ context.add(self)
363
366
 
364
- context.add(self)
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) -> AbstractInstanceProvider:
405
- if self.dependencies is None:
406
- self.dependencies = []
406
+ def resolve(self, context: Providers.Context):
407
+ context.add(self)
407
408
 
408
- context.add(self)
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
- ON_INIT = auto()
436
- ON_DESTROY = auto()
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" -> {provider.get_type().__name__}"
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 only executed once!
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 ony a single EnvironmentInstanceProvider is created!
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
- return self.execute_processors(Lifecycle.ON_INIT, instance)
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, processor: CallableProcessor, lifecycle: Lifecycle):
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 = TypeDescriptor.for_type(type(self)).get_decorator(order).args[0]
951
+ self.order = TypeDescriptor.for_type(type(self)).get_decorator(order).args[0]
893
952
 
894
- processor.register(self)
953
+ AbstractCallableProcessor.register(self)
895
954
 
896
955
  def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
897
956
  return []
898
957
 
899
- @injectable()
900
- @order(1)
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
- # constructor
982
+ # static data
925
983
 
926
- def __init__(self):
927
- super().__init__()
984
+ callables : Dict[object, LifecycleCallable] = {}
985
+ cache : Dict[Type, list[list[AbstractCallableProcessor.MethodCall]]] = {}
986
+
987
+ # static methods
928
988
 
929
- self.callables : Dict[object,LifecycleCallable] = {}
930
- self.cache : Dict[Type,list[CallableProcessor.MethodCall]] = {}
989
+ @classmethod
990
+ def register(cls, callable: LifecycleCallable):
991
+ AbstractCallableProcessor.callables[callable.decorator] = callable
931
992
 
932
- def compute_callables(self, type: Type) -> list[CallableProcessor.MethodCall] :
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
- if self.callables.get(decorator.decorator) is not None:
940
- result.append(CallableProcessor.MethodCall(method, decorator, self.callables[decorator.decorator]))
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
- def callables_for(self, type: Type)-> list[CallableProcessor.MethodCall]:
951
- callables = self.cache.get(type, None)
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 = self.compute_callables(type)
954
- self.cache[type] = callables
1021
+ callables = AbstractCallableProcessor.compute_callables(type)
1022
+ AbstractCallableProcessor.cache[type] = callables
955
1023
 
956
1024
  return callables
957
1025
 
958
- def register(self, callable: LifecycleCallable):
959
- self.callables[callable.decorator] = callable
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
- callables = self.callables_for(type(instance))
965
- for callable in callables:
966
- if callable.lifecycle_callable.lifecycle is lifecycle:
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, processor: CallableProcessor):
975
- super().__init__(on_init, processor, Lifecycle.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, processor: CallableProcessor):
983
- super().__init__(on_destroy, processor, Lifecycle.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, processor: CallableProcessor):
991
- super().__init__(inject_environment, processor, Lifecycle.ON_INIT)
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, processor: CallableProcessor):
1002
- super().__init__(inject, processor, Lifecycle.ON_INIT)
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.Lock()
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,8 @@
1
+ """
2
+ This module provides utility functions.
3
+ """
4
+ from .stringbuilder import StringBuilder
5
+
6
+ __all__ = [
7
+ "StringBuilder",
8
+ ]
@@ -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)
@@ -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
- descriptor = TypeDescriptor(clazz)
86
- cls._cache[clazz] = descriptor
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.1
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
  ![Pylint](https://github.com/coolsamson7/aspyx/actions/workflows/pylint.yml/badge.svg)
36
36
  ![Build Status](https://github.com/coolsamson7/aspyx/actions/workflows/ci.yml/badge.svg)
37
- ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/aspyx)
37
+ ![Python Versions](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12-blue)
38
38
  ![License](https://img.shields.io/github/license/coolsamson7/aspyx)
39
+ ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
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 reuqest scopes
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.8
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
- `singleton` will create it only once - per environment -, while `request` will recreate it on every injection request. The default is `singleton`
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
- - `@value()`
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 lifecle methods.
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
- - `get_method(name: str, local=False)`
527
- - `has_decorator(decorator) -> bool`
528
- - `get_decorator(decorator)`
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
- - `return_type`
533
- - `has_decorator(decorator)`
534
- - `get_decorator(decorator)`
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,,
@@ -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