aspyx 1.0.1__py3-none-any.whl → 1.2.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/di.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- The deoendency injection module provides a framework for managing dependencies and lifecycle of objects in Python applications.
2
+ The dependency injection module provides a framework for managing dependencies and lifecycle of objects in Python applications.
3
3
  """
4
4
  from __future__ import annotations
5
5
 
@@ -7,10 +7,11 @@ import inspect
7
7
  import logging
8
8
 
9
9
  from abc import abstractmethod, ABC
10
- from enum import Enum, auto
10
+ from enum import Enum
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")
@@ -26,11 +27,21 @@ class Factory(ABC, Generic[T]):
26
27
  def create(self) -> T:
27
28
  pass
28
29
 
29
- class InjectorException(Exception):
30
+ class DIException(Exception):
30
31
  """
31
32
  Exception raised for errors in the injector.
32
33
  """
33
34
 
35
+ class DIRegistrationException(DIException):
36
+ """
37
+ Exception raised during the registration of dependencies.
38
+ """
39
+
40
+ class DIRuntimeException(DIException):
41
+ """
42
+ Exception raised during the runtime.
43
+ """
44
+
34
45
  class AbstractInstanceProvider(ABC, Generic[T]):
35
46
  """
36
47
  Interface for instance providers.
@@ -39,6 +50,9 @@ class AbstractInstanceProvider(ABC, Generic[T]):
39
50
  def get_module(self) -> str:
40
51
  pass
41
52
 
53
+ def get_host(self) -> Type[T]:
54
+ return type(self)
55
+
42
56
  @abstractmethod
43
57
  def get_type(self) -> Type[T]:
44
58
  pass
@@ -60,7 +74,10 @@ class AbstractInstanceProvider(ABC, Generic[T]):
60
74
  pass
61
75
 
62
76
  @abstractmethod
63
- def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
77
+ def resolve(self, context: Providers.Context):
78
+ pass
79
+
80
+ def check_factories(self):
64
81
  pass
65
82
 
66
83
 
@@ -85,10 +102,22 @@ class InstanceProvider(AbstractInstanceProvider):
85
102
  self.scope = scope
86
103
  self.dependencies : Optional[list[AbstractInstanceProvider]] = None
87
104
 
105
+ # internal
106
+
107
+ def _is_resolved(self) -> bool:
108
+ return self.dependencies is not None
109
+
88
110
  # implement AbstractInstanceProvider
89
111
 
90
- def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
91
- return self
112
+ def get_host(self):
113
+ return self.host
114
+
115
+ def check_factories(self):
116
+ #register_factories(self.host)
117
+ pass
118
+
119
+ def resolve(self, context: Providers.Context):
120
+ pass
92
121
 
93
122
  def get_module(self) -> str:
94
123
  return self.host.__module__
@@ -178,11 +207,11 @@ class AmbiguousProvider(AbstractInstanceProvider):
178
207
  def get_dependencies(self) -> list[AbstractInstanceProvider]:
179
208
  return []
180
209
 
181
- def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
182
- return self
210
+ def resolve(self, context: Providers.Context):
211
+ pass
183
212
 
184
213
  def create(self, environment: Environment, *args):
185
- raise InjectorException(f"multiple candidates for type {self.type}")
214
+ raise DIException(f"multiple candidates for type {self.type}")
186
215
 
187
216
  def __str__(self):
188
217
  return f"AmbiguousProvider({self.type})"
@@ -198,7 +227,7 @@ class Scopes:
198
227
  def get(cls, scope: str, environment: Environment):
199
228
  scope_type = Scopes.scopes.get(scope, None)
200
229
  if scope_type is None:
201
- raise InjectorException(f"unknown scope type {scope}")
230
+ raise DIRegistrationException(f"unknown scope type {scope}")
202
231
 
203
232
  return environment.get(scope_type)
204
233
 
@@ -245,7 +274,7 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
245
274
 
246
275
  # implement
247
276
 
248
- def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
277
+ def resolve(self, context: Providers.Context):
249
278
  pass # noop
250
279
 
251
280
  def get_module(self) -> str:
@@ -266,7 +295,7 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
266
295
  for dependency in self.provider.get_dependencies():
267
296
  instance_provider = providers.get(dependency.get_type(), None)
268
297
  if instance_provider is None:
269
- raise InjectorException(f"missing import for {dependency.get_type()} ")
298
+ raise DIRegistrationException(f"missing import for {dependency.get_type()} ")
270
299
 
271
300
  self.dependencies.append(instance_provider)
272
301
 
@@ -297,22 +326,25 @@ class ClassInstanceProvider(InstanceProvider):
297
326
 
298
327
  # implement
299
328
 
300
- def resolve(self, context: Providers.Context) -> InstanceProvider:
301
- if self.dependencies is None:
302
- self.dependencies = []
329
+ def check_factories(self):
330
+ register_factories(self.host)
331
+
332
+ def resolve(self, context: Providers.Context):
333
+ context.add(self)
303
334
 
304
- context.add(self)
335
+ if not self._is_resolved():
336
+ self.dependencies = []
305
337
 
306
338
  # check constructor
307
339
 
308
340
  init = TypeDescriptor.for_type(self.type).get_method("__init__")
309
341
  if init is None:
310
- raise InjectorException(f"{self.type.__name__} does not implement __init__")
342
+ raise DIRegistrationException(f"{self.type.__name__} does not implement __init__")
311
343
 
312
344
  for param in init.param_types:
313
345
  provider = Providers.get_provider(param)
314
346
  self.params += 1
315
- if self.add_dependency(provider):
347
+ if self.add_dependency(provider): # a dependency can occur multiple times, e.g in __init__ and in an injected method
316
348
  provider.resolve(context)
317
349
 
318
350
  # check @inject
@@ -327,8 +359,6 @@ class ClassInstanceProvider(InstanceProvider):
327
359
  else: # check if the dependencies create a cycle
328
360
  context.add(*self.dependencies)
329
361
 
330
- return self
331
-
332
362
  def create(self, environment: Environment, *args):
333
363
  Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
334
364
 
@@ -357,11 +387,11 @@ class FunctionInstanceProvider(InstanceProvider):
357
387
 
358
388
  # implement
359
389
 
360
- def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
361
- if self.dependencies is None:
362
- self.dependencies = []
390
+ def resolve(self, context: Providers.Context):
391
+ context.add(self)
363
392
 
364
- context.add(self)
393
+ if not self._is_resolved():
394
+ self.dependencies = []
365
395
 
366
396
  provider = Providers.get_provider(self.host)
367
397
  if self.add_dependency(provider):
@@ -369,8 +399,6 @@ class FunctionInstanceProvider(InstanceProvider):
369
399
  else: # check if the dependencies crate a cycle
370
400
  context.add(*self.dependencies)
371
401
 
372
- return self
373
-
374
402
  def create(self, environment: Environment, *args):
375
403
  Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
376
404
 
@@ -401,16 +429,15 @@ class FactoryInstanceProvider(InstanceProvider):
401
429
 
402
430
  # implement
403
431
 
404
- def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
405
- if self.dependencies is None:
406
- self.dependencies = []
432
+ def resolve(self, context: Providers.Context):
433
+ context.add(self)
407
434
 
408
- context.add(self)
435
+ if not self._is_resolved():
436
+ self.dependencies = []
409
437
 
410
438
  provider = Providers.get_provider(self.host)
411
439
  if self.add_dependency(provider):
412
440
  provider.resolve(context)
413
-
414
441
  else: # check if the dependencies crate a cycle
415
442
  context.add(*self.dependencies)
416
443
 
@@ -432,8 +459,10 @@ class Lifecycle(Enum):
432
459
 
433
460
  __slots__ = []
434
461
 
435
- ON_INIT = auto()
436
- ON_DESTROY = auto()
462
+ ON_INJECT = 0
463
+ ON_INIT = 1
464
+ ON_RUNNING = 2
465
+ ON_DESTROY = 3
437
466
 
438
467
  class LifecycleProcessor(ABC):
439
468
  """
@@ -486,7 +515,7 @@ class Providers:
486
515
  def add(self, *providers: AbstractInstanceProvider):
487
516
  for provider in providers:
488
517
  if next((p for p in self.dependencies if p.get_type() is provider.get_type()), None) is not None:
489
- raise InjectorException(self.cycle_report(provider))
518
+ raise DIRegistrationException(self.cycle_report(provider))
490
519
 
491
520
  self.dependencies.append(provider)
492
521
 
@@ -502,7 +531,7 @@ class Providers:
502
531
 
503
532
  cycle += f"{p.get_type().__name__}"
504
533
 
505
- cycle += f" -> {provider.get_type().__name__}"
534
+ cycle += f" <> {provider.get_type().__name__}"
506
535
 
507
536
  return cycle
508
537
 
@@ -538,13 +567,20 @@ class Providers:
538
567
  return True
539
568
 
540
569
  def cache_provider_for_type(provider: AbstractInstanceProvider, type: Type):
570
+ def location(provider: AbstractInstanceProvider):
571
+ host = provider.get_host()
572
+ file = inspect.getfile(host)
573
+ line = inspect.getsourcelines(host)[1]
574
+
575
+ return f"{file}:{line}"
576
+
541
577
  existing_provider = Providers.cache.get(type)
542
578
  if existing_provider is None:
543
579
  Providers.cache[type] = provider
544
580
 
545
581
  else:
546
582
  if type is provider.get_type():
547
- raise InjectorException(f"{type} already registered")
583
+ raise DIRegistrationException(f"{type} already registered in {location(existing_provider)}, override in {location(provider)}")
548
584
 
549
585
  if isinstance(existing_provider, AmbiguousProvider):
550
586
  cast(AmbiguousProvider, existing_provider).add_provider(provider)
@@ -569,12 +605,14 @@ class Providers:
569
605
 
570
606
  @classmethod
571
607
  def resolve(cls):
572
- for provider in Providers.check:
573
- provider.resolve(Providers.Context())
574
608
 
575
- Providers.check.clear()
609
+ for check in Providers.check:
610
+ check.check_factories()
576
611
 
577
- #Providers.report()
612
+ # in the loop additional providers may be added
613
+ while len(Providers.check) > 0:
614
+ check = Providers.check.pop(0)
615
+ check.resolve(Providers.Context())
578
616
 
579
617
  @classmethod
580
618
  def report(cls):
@@ -585,7 +623,7 @@ class Providers:
585
623
  def get_provider(cls, type: Type) -> AbstractInstanceProvider:
586
624
  provider = Providers.cache.get(type, None)
587
625
  if provider is None:
588
- raise InjectorException(f"{type.__name__} not registered as injectable")
626
+ raise DIException(f"{type.__name__} not registered as injectable")
589
627
 
590
628
  return provider
591
629
 
@@ -595,7 +633,11 @@ def register_factories(cls: Type):
595
633
  for method in descriptor.get_methods():
596
634
  if method.has_decorator(create):
597
635
  create_decorator = method.get_decorator(create)
598
- Providers.register(FunctionInstanceProvider(cls, method.method, method.return_type, create_decorator.args[0],
636
+ return_type = method.return_type
637
+ if return_type is None:
638
+ raise DIRegistrationException(f"{cls.__name__}.{method.method.__name__} expected to have a return type")
639
+
640
+ Providers.register(FunctionInstanceProvider(cls, method.method, return_type, create_decorator.args[0],
599
641
  create_decorator.args[1]))
600
642
  def order(prio = 0):
601
643
  def decorator(cls):
@@ -614,8 +656,6 @@ def injectable(eager=True, scope="singleton"):
614
656
 
615
657
  Providers.register(ClassInstanceProvider(cls, eager, scope))
616
658
 
617
- #TODO registerFactories(cls)
618
-
619
659
  return cls
620
660
 
621
661
  return decorator
@@ -653,6 +693,15 @@ def on_init():
653
693
 
654
694
  return decorator
655
695
 
696
+ def on_running():
697
+ """
698
+ Methods annotated with @on_running will be called when the container up and running."""
699
+ def decorator(func):
700
+ Decorators.add(func, on_running)
701
+ return func
702
+
703
+ return decorator
704
+
656
705
  def on_destroy():
657
706
  """
658
707
  Methods annotated with @on_destroy will be called when the instance is destroyed.
@@ -677,8 +726,6 @@ def environment(imports: Optional[list[Type]] = None):
677
726
  Decorators.add(cls, environment, imports)
678
727
  Decorators.add(cls, injectable) # do we need that?
679
728
 
680
- register_factories(cls)
681
-
682
729
  return cls
683
730
 
684
731
  return decorator
@@ -736,8 +783,8 @@ class Environment:
736
783
 
737
784
  self.type = env
738
785
  self.parent = parent
739
- if self.parent is None and env is not BootEnvironment:
740
- self.parent = BootEnvironment.get_instance() # inherit environment including its manged instances!
786
+ if self.parent is None and env is not Boot:
787
+ self.parent = Boot.get_environment() # inherit environment including its manged instances!
741
788
 
742
789
  self.providers: Dict[Type, AbstractInstanceProvider] = {}
743
790
  self.lifecycle_processors: list[LifecycleProcessor] = []
@@ -750,7 +797,7 @@ class Environment:
750
797
 
751
798
  Environment.instance = self
752
799
 
753
- # resolve providers on a static basis. This is only executed once!
800
+ # resolve providers on a static basis. This is executed for all new providers ( in case of new modules multiple times) !
754
801
 
755
802
  Providers.resolve()
756
803
 
@@ -763,7 +810,7 @@ class Environment:
763
810
 
764
811
  # bootstrapping hack, they will be overwritten by the "real" providers
765
812
 
766
- if env is BootEnvironment:
813
+ if env is Boot:
767
814
  add_provider(SingletonScope, SingletonScopeInstanceProvider())
768
815
  add_provider(RequestScope, RequestScopeInstanceProvider())
769
816
 
@@ -777,7 +824,7 @@ class Environment:
777
824
 
778
825
  decorator = TypeDescriptor.for_type(env).get_decorator(environment)
779
826
  if decorator is None:
780
- raise InjectorException(f"{env.__name__} is not an environment class")
827
+ raise DIRegistrationException(f"{env.__name__} is not an environment class")
781
828
 
782
829
  scan = env.__module__
783
830
  if "." in scan:
@@ -794,7 +841,7 @@ class Environment:
794
841
 
795
842
  # register providers
796
843
 
797
- # make sure, that for every type ony a single EnvironmentInstanceProvider is created!
844
+ # make sure, that for every type only a single EnvironmentInstanceProvider is created!
798
845
  # otherwise inheritance will fuck it up
799
846
 
800
847
  environment_providers : dict[AbstractInstanceProvider, EnvironmentInstanceProvider] = {}
@@ -823,6 +870,12 @@ class Environment:
823
870
  for provider in load_environment(env):
824
871
  if provider.is_eager():
825
872
  provider.create(self)
873
+
874
+ # running callback
875
+
876
+ for instance in self.instances:
877
+ self.execute_processors(Lifecycle.ON_RUNNING, instance)
878
+
826
879
  # internal
827
880
 
828
881
  def execute_processors(self, lifecycle: Lifecycle, instance: T) -> T:
@@ -847,10 +900,54 @@ class Environment:
847
900
 
848
901
  # execute processors
849
902
 
850
- return self.execute_processors(Lifecycle.ON_INIT, instance)
903
+ self.execute_processors(Lifecycle.ON_INJECT, instance)
904
+ self.execute_processors(Lifecycle.ON_INIT, instance)
905
+
906
+ return instance
851
907
 
852
908
  # public
853
909
 
910
+ def report(self):
911
+ builder = StringBuilder()
912
+
913
+ builder.append(f"Environment {self.type.__name__}")
914
+
915
+ if self.parent is not None:
916
+ builder.append(f" parent {self.parent.type.__name__}")
917
+
918
+ builder.append("\n")
919
+
920
+ # post processors
921
+
922
+ builder.append("Processors \n")
923
+ for processor in self.lifecycle_processors:
924
+ builder.append(f"- {processor.__class__.__name__}\n")
925
+
926
+ # providers
927
+
928
+ builder.append("Providers \n")
929
+ for result_type, provider in self.providers.items():
930
+ if cast(EnvironmentInstanceProvider, provider).environment is self:
931
+ builder.append(f"- {result_type.__name__}: {cast(EnvironmentInstanceProvider, provider).provider}\n")
932
+
933
+ # instances
934
+
935
+ builder.append("Instances \n")
936
+
937
+ result = {}
938
+ for obj in self.instances:
939
+ cls = type(obj)
940
+ result[cls] = result.get(cls, 0) + 1
941
+
942
+ for cls, count in result.items():
943
+ builder.append(f"- {cls.__name__}: {count} \n")
944
+
945
+ # done
946
+
947
+ result = str(builder)
948
+
949
+ return result
950
+
854
951
  def destroy(self):
855
952
  """
856
953
  destroy all managed instances by calling the appropriate lifecycle methods
@@ -862,7 +959,7 @@ class Environment:
862
959
 
863
960
  def get(self, type: Type[T]) -> T:
864
961
  """
865
- Return and possibly create a new instance of the given type.
962
+ Create or return a cached instance for the given type.
866
963
 
867
964
  Arguments:
868
965
  type (Type): The desired type
@@ -872,7 +969,7 @@ class Environment:
872
969
  provider = self.providers.get(type, None)
873
970
  if provider is None:
874
971
  Environment.logger.error("%s is not supported", type)
875
- raise InjectorException(f"{type} is not supported")
972
+ raise DIRuntimeException(f"{type} is not supported")
876
973
 
877
974
  return provider.create(self)
878
975
 
@@ -883,22 +980,21 @@ class LifecycleCallable:
883
980
  "order"
884
981
  ]
885
982
 
886
- def __init__(self, decorator, processor: CallableProcessor, lifecycle: Lifecycle):
983
+ def __init__(self, decorator, lifecycle: Lifecycle):
887
984
  self.decorator = decorator
888
985
  self.lifecycle = lifecycle
889
986
  self.order = 0
890
987
 
891
988
  if TypeDescriptor.for_type(type(self)).has_decorator(order):
892
- self.order = TypeDescriptor.for_type(type(self)).get_decorator(order).args[0]
989
+ self.order = TypeDescriptor.for_type(type(self)).get_decorator(order).args[0]
893
990
 
894
- processor.register(self)
991
+ AbstractCallableProcessor.register(self)
895
992
 
896
993
  def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
897
994
  return []
898
995
 
899
- @injectable()
900
- @order(1)
901
- class CallableProcessor(LifecycleProcessor):
996
+
997
+ class AbstractCallableProcessor(LifecycleProcessor):
902
998
  # local classes
903
999
 
904
1000
  class MethodCall:
@@ -921,74 +1017,127 @@ class CallableProcessor(LifecycleProcessor):
921
1017
  def __str__(self):
922
1018
  return f"MethodCall({self.method.method.__name__})"
923
1019
 
924
- # constructor
1020
+ # static data
925
1021
 
926
- def __init__(self):
927
- super().__init__()
1022
+ lock = threading.RLock()
1023
+ callables : Dict[object, LifecycleCallable] = {}
1024
+ cache : Dict[Type, list[list[AbstractCallableProcessor.MethodCall]]] = {}
1025
+
1026
+ # static methods
928
1027
 
929
- self.callables : Dict[object,LifecycleCallable] = {}
930
- self.cache : Dict[Type,list[CallableProcessor.MethodCall]] = {}
1028
+ @classmethod
1029
+ def register(cls, callable: LifecycleCallable):
1030
+ AbstractCallableProcessor.callables[callable.decorator] = callable
931
1031
 
932
- def compute_callables(self, type: Type) -> list[CallableProcessor.MethodCall] :
1032
+ @classmethod
1033
+ def compute_callables(cls, type: Type) -> list[list[AbstractCallableProcessor.MethodCall]]:
933
1034
  descriptor = TypeDescriptor.for_type(type)
934
1035
 
935
- result = []
1036
+ result = [[], [], [], []] # per lifecycle
936
1037
 
937
1038
  for method in descriptor.get_methods():
938
1039
  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]))
1040
+ callable = AbstractCallableProcessor.callables.get(decorator.decorator)
1041
+ if callable is not None: # any callable for this decorator?
1042
+ result[callable.lifecycle.value].append(
1043
+ AbstractCallableProcessor.MethodCall(method, decorator, callable))
941
1044
 
942
1045
  # sort according to order
943
1046
 
944
- result.sort(key=lambda call: call.lifecycle_callable.order)
1047
+ result[0].sort(key=lambda call: call.lifecycle_callable.order)
1048
+ result[1].sort(key=lambda call: call.lifecycle_callable.order)
1049
+ result[2].sort(key=lambda call: call.lifecycle_callable.order)
1050
+ result[3].sort(key=lambda call: call.lifecycle_callable.order)
945
1051
 
946
1052
  # done
947
1053
 
948
1054
  return result
949
1055
 
950
- def callables_for(self, type: Type)-> list[CallableProcessor.MethodCall]:
951
- callables = self.cache.get(type, None)
1056
+ @classmethod
1057
+ def callables_for(cls, type: Type) -> list[list[AbstractCallableProcessor.MethodCall]]:
1058
+ callables = AbstractCallableProcessor.cache.get(type, None)
952
1059
  if callables is None:
953
- callables = self.compute_callables(type)
954
- self.cache[type] = callables
1060
+ with AbstractCallableProcessor.lock:
1061
+ callables = AbstractCallableProcessor.cache.get(type, None)
1062
+ if callables is None:
1063
+ callables = AbstractCallableProcessor.compute_callables(type)
1064
+ AbstractCallableProcessor.cache[type] = callables
955
1065
 
956
1066
  return callables
957
1067
 
958
- def register(self, callable: LifecycleCallable):
959
- self.callables[callable.decorator] = callable
1068
+ # constructor
1069
+
1070
+ def __init__(self, lifecycle: Lifecycle):
1071
+ super().__init__()
1072
+
1073
+ self.lifecycle = lifecycle
960
1074
 
961
1075
  # implement
962
1076
 
963
1077
  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:
1078
+ if lifecycle is self.lifecycle:
1079
+ callables = self.callables_for(type(instance))
1080
+
1081
+ for callable in callables[lifecycle.value]:
967
1082
  callable.execute(instance, environment)
968
1083
 
1084
+ @injectable()
1085
+ @order(1)
1086
+ class OnInjectCallableProcessor(AbstractCallableProcessor):
1087
+ def __init__(self):
1088
+ super().__init__(Lifecycle.ON_INJECT)
1089
+
1090
+ @injectable()
1091
+ @order(2)
1092
+ class OnInitCallableProcessor(AbstractCallableProcessor):
1093
+ def __init__(self):
1094
+ super().__init__(Lifecycle.ON_INIT)
1095
+
1096
+ @injectable()
1097
+ @order(3)
1098
+ class OnRunningCallableProcessor(AbstractCallableProcessor):
1099
+ def __init__(self):
1100
+ super().__init__(Lifecycle.ON_RUNNING)
1101
+
1102
+ @injectable()
1103
+ @order(4)
1104
+ class OnDestroyCallableProcessor(AbstractCallableProcessor):
1105
+ def __init__(self):
1106
+ super().__init__(Lifecycle.ON_DESTROY)
1107
+
1108
+ # the callables
1109
+
969
1110
  @injectable()
970
1111
  @order(1000)
971
1112
  class OnInitLifecycleCallable(LifecycleCallable):
972
1113
  __slots__ = []
973
1114
 
974
- def __init__(self, processor: CallableProcessor):
975
- super().__init__(on_init, processor, Lifecycle.ON_INIT)
1115
+ def __init__(self):
1116
+ super().__init__(on_init, Lifecycle.ON_INIT)
976
1117
 
977
1118
  @injectable()
978
1119
  @order(1001)
979
1120
  class OnDestroyLifecycleCallable(LifecycleCallable):
980
1121
  __slots__ = []
981
1122
 
982
- def __init__(self, processor: CallableProcessor):
983
- super().__init__(on_destroy, processor, Lifecycle.ON_DESTROY)
1123
+ def __init__(self):
1124
+ super().__init__(on_destroy, Lifecycle.ON_DESTROY)
1125
+
1126
+ @injectable()
1127
+ @order(1002)
1128
+ class OnRunningLifecycleCallable(LifecycleCallable):
1129
+ __slots__ = []
1130
+
1131
+ def __init__(self):
1132
+ super().__init__(on_running, Lifecycle.ON_RUNNING)
984
1133
 
985
1134
  @injectable()
986
1135
  @order(9)
987
1136
  class EnvironmentAwareLifecycleCallable(LifecycleCallable):
988
1137
  __slots__ = []
989
1138
 
990
- def __init__(self, processor: CallableProcessor):
991
- super().__init__(inject_environment, processor, Lifecycle.ON_INIT)
1139
+ def __init__(self):
1140
+ super().__init__(inject_environment, Lifecycle.ON_INJECT)
992
1141
 
993
1142
  def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
994
1143
  return [environment]
@@ -998,8 +1147,8 @@ class EnvironmentAwareLifecycleCallable(LifecycleCallable):
998
1147
  class InjectLifecycleCallable(LifecycleCallable):
999
1148
  __slots__ = []
1000
1149
 
1001
- def __init__(self, processor: CallableProcessor):
1002
- super().__init__(inject, processor, Lifecycle.ON_INIT)
1150
+ def __init__(self):
1151
+ super().__init__(inject, Lifecycle.ON_INJECT)
1003
1152
 
1004
1153
  # override
1005
1154
 
@@ -1046,7 +1195,7 @@ class SingletonScope(Scope):
1046
1195
  super().__init__()
1047
1196
 
1048
1197
  self.value = None
1049
- self.lock = threading.Lock()
1198
+ self.lock = threading.RLock()
1050
1199
 
1051
1200
  # override
1052
1201
 
@@ -1058,20 +1207,37 @@ class SingletonScope(Scope):
1058
1207
 
1059
1208
  return self.value
1060
1209
 
1210
+ @scope("thread")
1211
+ class ThreadScope(Scope):
1212
+ __slots__ = [
1213
+ "_local"
1214
+ ]
1215
+
1216
+ # constructor
1217
+
1218
+ def __init__(self):
1219
+ super().__init__()
1220
+ self._local = threading.local()
1221
+
1222
+ def get(self, provider: AbstractInstanceProvider, environment: Environment, arg_provider: Callable[[], list]):
1223
+ if not hasattr(self._local, "value"):
1224
+ self._local.value = provider.create(environment, *arg_provider())
1225
+ return self._local.value
1226
+
1061
1227
  # internal class that is required to import technical instance providers
1062
1228
 
1063
1229
  @environment()
1064
- class BootEnvironment:
1230
+ class Boot:
1065
1231
  # class
1066
1232
 
1067
1233
  environment = None
1068
1234
 
1069
1235
  @classmethod
1070
- def get_instance(cls):
1071
- if BootEnvironment.environment is None:
1072
- BootEnvironment.environment = Environment(BootEnvironment)
1236
+ def get_environment(cls):
1237
+ if Boot.environment is None:
1238
+ Boot.environment = Environment(Boot)
1073
1239
 
1074
- return BootEnvironment.environment
1240
+ return Boot.environment
1075
1241
 
1076
1242
  # properties
1077
1243