aspyx 1.0.0__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/di.py CHANGED
@@ -1,3 +1,6 @@
1
+ """
2
+ The deoendency injection module provides a framework for managing dependencies and lifecycle of objects in Python applications.
3
+ """
1
4
  from __future__ import annotations
2
5
 
3
6
  import inspect
@@ -8,6 +11,7 @@ from enum import Enum, auto
8
11
  import threading
9
12
  from typing import Type, Dict, TypeVar, Generic, Optional, cast, Callable
10
13
 
14
+ from aspyx.di.util import StringBuilder
11
15
  from aspyx.reflection import Decorators, TypeDescriptor, DecoratorDescriptor
12
16
 
13
17
  T = TypeVar("T")
@@ -25,8 +29,8 @@ class Factory(ABC, Generic[T]):
25
29
 
26
30
  class InjectorException(Exception):
27
31
  """
28
- Exception raised for errors in the injector."""
29
- pass
32
+ Exception raised for errors in the injector.
33
+ """
30
34
 
31
35
  class AbstractInstanceProvider(ABC, Generic[T]):
32
36
  """
@@ -53,11 +57,11 @@ class AbstractInstanceProvider(ABC, Generic[T]):
53
57
  pass
54
58
 
55
59
  @abstractmethod
56
- def create(self, env: Environment, *args):
60
+ def create(self, environment: Environment, *args):
57
61
  pass
58
62
 
59
63
  @abstractmethod
60
- def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
64
+ def resolve(self, context: Providers.Context):
61
65
  pass
62
66
 
63
67
 
@@ -82,10 +86,15 @@ class InstanceProvider(AbstractInstanceProvider):
82
86
  self.scope = scope
83
87
  self.dependencies : Optional[list[AbstractInstanceProvider]] = None
84
88
 
89
+ # internal
90
+
91
+ def _is_resolved(self) -> bool:
92
+ return self.dependencies is not None
93
+
85
94
  # implement AbstractInstanceProvider
86
95
 
87
- def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
88
- return self
96
+ def resolve(self, context: Providers.Context):
97
+ pass
89
98
 
90
99
  def get_module(self) -> str:
91
100
  return self.host.__module__
@@ -175,8 +184,8 @@ class AmbiguousProvider(AbstractInstanceProvider):
175
184
  def get_dependencies(self) -> list[AbstractInstanceProvider]:
176
185
  return []
177
186
 
178
- def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
179
- return self
187
+ def resolve(self, context: Providers.Context):
188
+ pass
180
189
 
181
190
  def create(self, environment: Environment, *args):
182
191
  raise InjectorException(f"multiple candidates for type {self.type}")
@@ -193,15 +202,15 @@ class Scopes:
193
202
 
194
203
  @classmethod
195
204
  def get(cls, scope: str, environment: Environment):
196
- scopeType = Scopes.scopes.get(scope, None)
197
- if scopeType is None:
205
+ scope_type = Scopes.scopes.get(scope, None)
206
+ if scope_type is None:
198
207
  raise InjectorException(f"unknown scope type {scope}")
199
208
 
200
- return environment.get(scopeType)
209
+ return environment.get(scope_type)
201
210
 
202
211
  @classmethod
203
- def register(cls, scopeClass: Type, name: str):
204
- Scopes.scopes[name] = scopeClass
212
+ def register(cls, scope_type: Type, name: str):
213
+ Scopes.scopes[name] = scope_type
205
214
 
206
215
  class Scope:
207
216
  # properties
@@ -216,15 +225,15 @@ class Scope:
216
225
 
217
226
  # public
218
227
 
219
- def get(self, provider: AbstractInstanceProvider, environment: Environment, argProvider: Callable[[],list]):
220
- return provider.create(environment, *argProvider())
228
+ def get(self, provider: AbstractInstanceProvider, environment: Environment, arg_provider: Callable[[],list]):
229
+ return provider.create(environment, *arg_provider())
221
230
 
222
231
  class EnvironmentInstanceProvider(AbstractInstanceProvider):
223
232
  # properties
224
233
 
225
234
  __slots__ = [
226
235
  "environment",
227
- "scopeInstance",
236
+ "scope_instance",
228
237
  "provider",
229
238
  "dependencies",
230
239
  ]
@@ -238,12 +247,11 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
238
247
  self.provider = provider
239
248
  self.dependencies : list[AbstractInstanceProvider] = []
240
249
 
241
- self.scopeInstance = Scopes.get(provider.get_scope(), environment)
242
- print()
250
+ self.scope_instance = Scopes.get(provider.get_scope(), environment)
243
251
 
244
252
  # implement
245
253
 
246
- def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
254
+ def resolve(self, context: Providers.Context):
247
255
  pass # noop
248
256
 
249
257
  def get_module(self) -> str:
@@ -260,21 +268,19 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
260
268
 
261
269
  # custom logic
262
270
 
263
- def tweakDependencies(self, providers: dict[Type, AbstractInstanceProvider]):
271
+ def tweak_dependencies(self, providers: dict[Type, AbstractInstanceProvider]):
264
272
  for dependency in self.provider.get_dependencies():
265
- instanceProvider = providers.get(dependency.get_type(), None)
266
- if instanceProvider is None:
273
+ instance_provider = providers.get(dependency.get_type(), None)
274
+ if instance_provider is None:
267
275
  raise InjectorException(f"missing import for {dependency.get_type()} ")
268
276
 
269
- self.dependencies.append(instanceProvider)
270
- pass
271
- pass
277
+ self.dependencies.append(instance_provider)
272
278
 
273
279
  def get_dependencies(self) -> list[AbstractInstanceProvider]:
274
280
  return self.provider.get_dependencies()
275
281
 
276
- def create(self, env: Environment, *args):
277
- return self.scopeInstance.get(self.provider, self.environment, lambda: [provider.create(env) for provider in self.dependencies] ) # already scope property!
282
+ def create(self, environment: Environment, *args):
283
+ return self.scope_instance.get(self.provider, self.environment, lambda: [provider.create(environment) for provider in self.dependencies]) # already scope property!
278
284
 
279
285
  def __str__(self):
280
286
  return f"EnvironmentInstanceProvider({self.provider})"
@@ -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
 
@@ -309,28 +315,26 @@ class ClassInstanceProvider(InstanceProvider):
309
315
  if init is None:
310
316
  raise InjectorException(f"{self.type.__name__} does not implement __init__")
311
317
 
312
- for param in init.paramTypes:
313
- provider = Providers.getProvider(param)
318
+ for param in init.param_types:
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
319
325
 
320
- for method in TypeDescriptor.for_type(self.type).methods.values():
326
+ for method in TypeDescriptor.for_type(self.type).get_methods():
321
327
  if method.has_decorator(inject):
322
- for param in method.paramTypes:
323
- provider = Providers.getProvider(param)
328
+ for param in method.param_types:
329
+ provider = Providers.get_provider(param)
324
330
 
325
331
  if self.add_dependency(provider):
326
332
  provider.resolve(context)
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
- Environment.logger.debug(f"{self} create class {self.type.__qualname__}")
337
+ Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
334
338
 
335
339
  return environment.created(self.type(*args[:self.params]))
336
340
 
@@ -357,22 +361,20 @@ 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
- provider = Providers.getProvider(self.host)
370
+ provider = Providers.get_provider(self.host)
367
371
  if self.add_dependency(provider):
368
372
  provider.resolve(context)
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
- Environment.logger.debug(f"{self} create class {self.type.__qualname__}")
377
+ Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
376
378
 
377
379
  instance = self.method(*args)
378
380
 
@@ -391,23 +393,23 @@ class FactoryInstanceProvider(InstanceProvider):
391
393
  # class method
392
394
 
393
395
  @classmethod
394
- def getFactoryType(cls, clazz):
395
- return TypeDescriptor.for_type(clazz).get_local_method("create").returnType
396
+ def get_factory_type(cls, clazz):
397
+ return TypeDescriptor.for_type(clazz).get_method("create", local=True).return_type
396
398
 
397
399
  # constructor
398
400
 
399
401
  def __init__(self, factory: Type, eager: bool, scope: str):
400
- super().__init__(factory, FactoryInstanceProvider.getFactoryType(factory), eager, scope)
402
+ super().__init__(factory, FactoryInstanceProvider.get_factory_type(factory), eager, scope)
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
- provider = Providers.getProvider(self.host)
412
+ provider = Providers.get_provider(self.host)
411
413
  if self.add_dependency(provider):
412
414
  provider.resolve(context)
413
415
 
@@ -417,7 +419,7 @@ class FactoryInstanceProvider(InstanceProvider):
417
419
  return self
418
420
 
419
421
  def create(self, environment: Environment, *args):
420
- Environment.logger.debug(f"{self} create class {self.type.__qualname__}")
422
+ Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
421
423
 
422
424
  return environment.created(args[0].create())
423
425
 
@@ -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
  """
@@ -453,7 +457,7 @@ class LifecycleProcessor(ABC):
453
457
  # methods
454
458
 
455
459
  @abstractmethod
456
- def processLifecycle(self, lifecycle: Lifecycle, instance: object, environment: Environment) -> object:
460
+ def process_lifecycle(self, lifecycle: Lifecycle, instance: object, environment: Environment) -> object:
457
461
  pass
458
462
 
459
463
  class PostProcessor(LifecycleProcessor):
@@ -462,15 +466,11 @@ class PostProcessor(LifecycleProcessor):
462
466
  """
463
467
  __slots__ = []
464
468
 
465
- # constructor
466
-
467
- def __init__(self):
468
- super().__init__()
469
469
 
470
470
  def process(self, instance: object, environment: Environment):
471
471
  pass
472
472
 
473
- def processLifecycle(self, lifecycle: Lifecycle, instance: object, environment: Environment) -> object:
473
+ def process_lifecycle(self, lifecycle: Lifecycle, instance: object, environment: Environment) -> object:
474
474
  if lifecycle == Lifecycle.ON_INIT:
475
475
  self.process(instance, environment)
476
476
 
@@ -490,11 +490,11 @@ class Providers:
490
490
  def add(self, *providers: AbstractInstanceProvider):
491
491
  for provider in providers:
492
492
  if next((p for p in self.dependencies if p.get_type() is provider.get_type()), None) is not None:
493
- raise InjectorException(self.cycleReport(provider))
493
+ raise InjectorException(self.cycle_report(provider))
494
494
 
495
495
  self.dependencies.append(provider)
496
496
 
497
- def cycleReport(self, provider: AbstractInstanceProvider):
497
+ def cycle_report(self, provider: AbstractInstanceProvider):
498
498
  cycle = ""
499
499
 
500
500
  first = True
@@ -506,23 +506,23 @@ class Providers:
506
506
 
507
507
  cycle += f"{p.get_type().__name__}"
508
508
 
509
- cycle += f" -> {provider.get_type().__name__}"
509
+ cycle += f" <> {provider.get_type().__name__}"
510
510
 
511
511
  return cycle
512
512
 
513
513
 
514
514
  # class properties
515
515
 
516
- check: list[AbstractInstanceProvider] = list()
516
+ check: list[AbstractInstanceProvider] = []
517
517
 
518
- providers : Dict[Type,AbstractInstanceProvider] = dict()
519
- cache: Dict[Type, AbstractInstanceProvider] = dict()
518
+ providers : Dict[Type,AbstractInstanceProvider] = {}
519
+ cache: Dict[Type, AbstractInstanceProvider] = {}
520
520
 
521
521
  resolved = False
522
522
 
523
523
  @classmethod
524
524
  def register(cls, provider: AbstractInstanceProvider):
525
- Environment.logger.debug(f"register provider {provider.get_type().__qualname__}({provider.get_type().__name__})")
525
+ Environment.logger.debug("register provider %s(%s)", provider.get_type().__qualname__, provider.get_type().__name__)
526
526
 
527
527
  # local functions
528
528
 
@@ -541,7 +541,7 @@ class Providers:
541
541
 
542
542
  return True
543
543
 
544
- def cacheProviderForType(provider: AbstractInstanceProvider, type: Type):
544
+ def cache_provider_for_type(provider: AbstractInstanceProvider, type: Type):
545
545
  existing_provider = Providers.cache.get(type)
546
546
  if existing_provider is None:
547
547
  Providers.cache[type] = provider
@@ -557,9 +557,9 @@ class Providers:
557
557
 
558
558
  # recursion
559
559
 
560
- for superClass in type.__bases__:
561
- if is_injectable(superClass):
562
- cacheProviderForType(provider, superClass)
560
+ for super_class in type.__bases__:
561
+ if is_injectable(super_class):
562
+ cache_provider_for_type(provider, super_class)
563
563
 
564
564
  # go
565
565
 
@@ -569,7 +569,7 @@ class Providers:
569
569
 
570
570
  # cache providers
571
571
 
572
- cacheProviderForType(provider, provider.get_type())
572
+ cache_provider_for_type(provider, provider.get_type())
573
573
 
574
574
  @classmethod
575
575
  def resolve(cls):
@@ -578,28 +578,26 @@ class Providers:
578
578
 
579
579
  Providers.check.clear()
580
580
 
581
- #Providers.report()
582
-
583
581
  @classmethod
584
582
  def report(cls):
585
583
  for provider in Providers.cache.values():
586
584
  print(f"provider {provider.get_type().__qualname__}")
587
585
 
588
586
  @classmethod
589
- def getProvider(cls, type: Type) -> AbstractInstanceProvider:
587
+ def get_provider(cls, type: Type) -> AbstractInstanceProvider:
590
588
  provider = Providers.cache.get(type, None)
591
589
  if provider is None:
592
590
  raise InjectorException(f"{type.__name__} not registered as injectable")
593
591
 
594
592
  return provider
595
593
 
596
- def registerFactories(cls: Type):
594
+ def register_factories(cls: Type):
597
595
  descriptor = TypeDescriptor.for_type(cls)
598
596
 
599
- for method in descriptor.methods.values():
597
+ for method in descriptor.get_methods():
600
598
  if method.has_decorator(create):
601
599
  create_decorator = method.get_decorator(create)
602
- Providers.register(FunctionInstanceProvider(cls, method.method, method.returnType, create_decorator.args[0],
600
+ Providers.register(FunctionInstanceProvider(cls, method.method, method.return_type, create_decorator.args[0],
603
601
  create_decorator.args[1]))
604
602
  def order(prio = 0):
605
603
  def decorator(cls):
@@ -618,8 +616,6 @@ def injectable(eager=True, scope="singleton"):
618
616
 
619
617
  Providers.register(ClassInstanceProvider(cls, eager, scope))
620
618
 
621
- #TODO registerFactories(cls)
622
-
623
619
  return cls
624
620
 
625
621
  return decorator
@@ -657,6 +653,15 @@ def on_init():
657
653
 
658
654
  return decorator
659
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
+
660
665
  def on_destroy():
661
666
  """
662
667
  Methods annotated with @on_destroy will be called when the instance is destroyed.
@@ -681,7 +686,7 @@ def environment(imports: Optional[list[Type]] = None):
681
686
  Decorators.add(cls, environment, imports)
682
687
  Decorators.add(cls, injectable) # do we need that?
683
688
 
684
- registerFactories(cls)
689
+ register_factories(cls)
685
690
 
686
691
  return cls
687
692
 
@@ -721,7 +726,7 @@ class Environment:
721
726
  __slots__ = [
722
727
  "type",
723
728
  "providers",
724
- "lifecycleProcessors",
729
+ "lifecycle_processors",
725
730
  "parent",
726
731
  "instances"
727
732
  ]
@@ -743,25 +748,25 @@ class Environment:
743
748
  if self.parent is None and env is not BootEnvironment:
744
749
  self.parent = BootEnvironment.get_instance() # inherit environment including its manged instances!
745
750
 
746
- self.providers: Dict[Type, AbstractInstanceProvider] = dict()
747
- self.lifecycleProcessors: list[LifecycleProcessor] = []
751
+ self.providers: Dict[Type, AbstractInstanceProvider] = {}
752
+ self.lifecycle_processors: list[LifecycleProcessor] = []
748
753
 
749
754
  if self.parent is not None:
750
755
  self.providers |= self.parent.providers
751
- self.lifecycleProcessors += self.parent.lifecycleProcessors
756
+ self.lifecycle_processors += self.parent.lifecycle_processors
752
757
 
753
758
  self.instances = []
754
759
 
755
760
  Environment.instance = self
756
761
 
757
- # 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) !
758
763
 
759
764
  Providers.resolve()
760
765
 
761
766
  loaded = set()
762
767
 
763
768
  def add_provider(type: Type, provider: AbstractInstanceProvider):
764
- Environment.logger.debug(f"\tadd provider {provider} for {type})")
769
+ Environment.logger.debug("\tadd provider %s for %s", provider, type)
765
770
 
766
771
  self.providers[type] = provider
767
772
 
@@ -773,7 +778,7 @@ class Environment:
773
778
 
774
779
  def load_environment(env: Type):
775
780
  if env not in loaded:
776
- Environment.logger.debug(f"load environment {env.__qualname__}")
781
+ Environment.logger.debug("load environment %s", env.__qualname__)
777
782
 
778
783
  loaded.add(env)
779
784
 
@@ -794,31 +799,31 @@ class Environment:
794
799
 
795
800
  # load providers
796
801
 
797
- localProviders = {type: provider for type, provider in Providers.cache.items() if provider.get_module().startswith(scan)}
802
+ local_providers = {type: provider for type, provider in Providers.cache.items() if provider.get_module().startswith(scan)}
798
803
 
799
804
  # register providers
800
805
 
801
- # 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!
802
807
  # otherwise inheritance will fuck it up
803
808
 
804
- environmentProviders : dict[AbstractInstanceProvider, EnvironmentInstanceProvider] = {}
809
+ environment_providers : dict[AbstractInstanceProvider, EnvironmentInstanceProvider] = {}
805
810
 
806
- for type, provider in localProviders.items():
807
- environmentProvider = environmentProviders.get(provider, None)
808
- if environmentProvider is None:
809
- environmentProvider = EnvironmentInstanceProvider(self, provider)
810
- environmentProviders[provider] = environmentProvider
811
+ for type, provider in local_providers.items():
812
+ environment_provider = environment_providers.get(provider, None)
813
+ if environment_provider is None:
814
+ environment_provider = EnvironmentInstanceProvider(self, provider)
815
+ environment_providers[provider] = environment_provider
811
816
 
812
- add_provider(type, environmentProvider)
817
+ add_provider(type, environment_provider)
813
818
 
814
819
  # tweak dependencies
815
820
 
816
- for type, provider in localProviders.items():
817
- cast(EnvironmentInstanceProvider, self.providers[type]).tweakDependencies(self.providers)
821
+ for type, provider in local_providers.items():
822
+ cast(EnvironmentInstanceProvider, self.providers[type]).tweak_dependencies(self.providers)
818
823
 
819
824
  # return local providers
820
825
 
821
- return environmentProviders.values()
826
+ return environment_providers.values()
822
827
  else:
823
828
  return []
824
829
 
@@ -827,29 +832,29 @@ class Environment:
827
832
  for provider in load_environment(env):
828
833
  if provider.is_eager():
829
834
  provider.create(self)
835
+
836
+ # running callback
837
+
838
+ for instance in self.instances:
839
+ self.execute_processors(Lifecycle.ON_RUNNING, instance)
840
+
830
841
  # internal
831
842
 
832
- def executeProcessors(self, lifecycle: Lifecycle, instance: T) -> T:
833
- for processor in self.lifecycleProcessors:
834
- processor.processLifecycle(lifecycle, instance, self)
843
+ def execute_processors(self, lifecycle: Lifecycle, instance: T) -> T:
844
+ for processor in self.lifecycle_processors:
845
+ processor.process_lifecycle(lifecycle, instance, self)
835
846
 
836
847
  return instance
837
848
 
838
849
  def created(self, instance: T) -> T:
839
- def get_order(type: TypeDescriptor) -> int:
840
- if type.has_decorator(order):
841
- return type.get_decorator(order).args[0]
842
- else:
843
- return 10
844
-
845
850
  # remember lifecycle processors
846
851
 
847
852
  if isinstance(instance, LifecycleProcessor):
848
- self.lifecycleProcessors.append(instance)
853
+ self.lifecycle_processors.append(instance)
849
854
 
850
855
  # sort immediately
851
856
 
852
- self.lifecycleProcessors.sort(key=lambda processor: processor.order)
857
+ self.lifecycle_processors.sort(key=lambda processor: processor.order)
853
858
 
854
859
  # remember instance
855
860
 
@@ -857,16 +862,60 @@ class Environment:
857
862
 
858
863
  # execute processors
859
864
 
860
- return self.executeProcessors(Lifecycle.ON_INIT, instance)
865
+ self.execute_processors(Lifecycle.ON_INJECT, instance)
866
+ self.execute_processors(Lifecycle.ON_INIT, instance)
867
+
868
+ return instance
861
869
 
862
870
  # public
863
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
+
864
913
  def destroy(self):
865
914
  """
866
915
  destroy all managed instances by calling the appropriate lifecycle methods
867
916
  """
868
917
  for instance in self.instances:
869
- self.executeProcessors(Lifecycle.ON_DESTROY, instance)
918
+ self.execute_processors(Lifecycle.ON_DESTROY, instance)
870
919
 
871
920
  self.instances.clear() # make the cy happy
872
921
 
@@ -881,7 +930,7 @@ class Environment:
881
930
  """
882
931
  provider = self.providers.get(type, None)
883
932
  if provider is None:
884
- Environment.logger.error(f"{type} is not supported")
933
+ Environment.logger.error("%s is not supported", type)
885
934
  raise InjectorException(f"{type} is not supported")
886
935
 
887
936
  return provider.create(self)
@@ -893,112 +942,160 @@ class LifecycleCallable:
893
942
  "order"
894
943
  ]
895
944
 
896
- def __init__(self, decorator, processor: CallableProcessor, lifecycle: Lifecycle):
945
+ def __init__(self, decorator, lifecycle: Lifecycle):
897
946
  self.decorator = decorator
898
947
  self.lifecycle = lifecycle
899
948
  self.order = 0
900
949
 
901
950
  if TypeDescriptor.for_type(type(self)).has_decorator(order):
902
- 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]
903
952
 
904
- processor.register(self)
953
+ AbstractCallableProcessor.register(self)
905
954
 
906
955
  def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
907
956
  return []
908
957
 
909
- @injectable()
910
- @order(1)
911
- class CallableProcessor(LifecycleProcessor):
958
+
959
+ class AbstractCallableProcessor(LifecycleProcessor):
912
960
  # local classes
913
961
 
914
962
  class MethodCall:
915
963
  __slots__ = [
916
964
  "decorator",
917
965
  "method",
918
- "lifecycleCallable"
966
+ "lifecycle_callable"
919
967
  ]
920
968
 
921
969
  # constructor
922
970
 
923
- def __init__(self, method: TypeDescriptor.MethodDescriptor, decorator: DecoratorDescriptor, lifecycleCallable: LifecycleCallable):
971
+ def __init__(self, method: TypeDescriptor.MethodDescriptor, decorator: DecoratorDescriptor, lifecycle_callable: LifecycleCallable):
924
972
  self.decorator = decorator
925
973
  self.method = method
926
- self.lifecycleCallable = lifecycleCallable
974
+ self.lifecycle_callable = lifecycle_callable
927
975
 
928
976
  def execute(self, instance, environment: Environment):
929
- self.method.method(instance, *self.lifecycleCallable.args(self.decorator, self.method, environment))
977
+ self.method.method(instance, *self.lifecycle_callable.args(self.decorator, self.method, environment))
930
978
 
931
979
  def __str__(self):
932
980
  return f"MethodCall({self.method.method.__name__})"
933
981
 
934
- # constructor
982
+ # static data
935
983
 
936
- def __init__(self):
937
- super().__init__()
984
+ callables : Dict[object, LifecycleCallable] = {}
985
+ cache : Dict[Type, list[list[AbstractCallableProcessor.MethodCall]]] = {}
938
986
 
939
- self.callables : Dict[object,LifecycleCallable] = dict()
940
- self.cache : Dict[Type,list[CallableProcessor.MethodCall]] = dict()
987
+ # static methods
988
+
989
+ @classmethod
990
+ def register(cls, callable: LifecycleCallable):
991
+ AbstractCallableProcessor.callables[callable.decorator] = callable
941
992
 
942
- def computeCallables(self, type: Type) -> list[CallableProcessor.MethodCall] :
993
+ @classmethod
994
+ def compute_callables(cls, type: Type) -> list[list[AbstractCallableProcessor.MethodCall]]:
943
995
  descriptor = TypeDescriptor.for_type(type)
944
996
 
945
- result = []
997
+ result = [[], [], [], []] # per lifecycle
946
998
 
947
- for method in descriptor.methods.values():
999
+ for method in descriptor.get_methods():
948
1000
  for decorator in method.decorators:
949
- if self.callables.get(decorator.decorator) is not None:
950
- 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))
951
1005
 
952
1006
  # sort according to order
953
1007
 
954
- result.sort(key=lambda call: call.lifecycleCallable.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)
955
1012
 
956
1013
  # done
957
1014
 
958
1015
  return result
959
1016
 
960
- def callablesFor(self, type: Type)-> list[CallableProcessor.MethodCall]:
961
- 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)
962
1020
  if callables is None:
963
- callables = self.computeCallables(type)
964
- self.cache[type] = callables
1021
+ callables = AbstractCallableProcessor.compute_callables(type)
1022
+ AbstractCallableProcessor.cache[type] = callables
965
1023
 
966
1024
  return callables
967
1025
 
968
- def register(self, callable: LifecycleCallable):
969
- self.callables[callable.decorator] = callable
1026
+ # constructor
1027
+
1028
+ def __init__(self, lifecycle: Lifecycle):
1029
+ super().__init__()
1030
+
1031
+ self.lifecycle = lifecycle
970
1032
 
971
1033
  # implement
972
1034
 
973
- def processLifecycle(self, lifecycle: Lifecycle, instance: object, environment: Environment) -> object:
974
- callables = self.callablesFor(type(instance))
975
- for callable in callables:
976
- if callable.lifecycleCallable.lifecycle is lifecycle:
1035
+ def process_lifecycle(self, lifecycle: Lifecycle, instance: object, environment: Environment) -> object:
1036
+ if lifecycle is self.lifecycle:
1037
+ callables = self.callables_for(type(instance))
1038
+
1039
+ for callable in callables[lifecycle.value]:
977
1040
  callable.execute(instance, environment)
978
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
+
979
1068
  @injectable()
980
1069
  @order(1000)
981
1070
  class OnInitLifecycleCallable(LifecycleCallable):
982
1071
  __slots__ = []
983
1072
 
984
- def __init__(self, processor: CallableProcessor):
985
- super().__init__(on_init, processor, Lifecycle.ON_INIT)
1073
+ def __init__(self):
1074
+ super().__init__(on_init, Lifecycle.ON_INIT)
986
1075
 
987
1076
  @injectable()
988
1077
  @order(1001)
989
1078
  class OnDestroyLifecycleCallable(LifecycleCallable):
990
1079
  __slots__ = []
991
1080
 
992
- def __init__(self, processor: CallableProcessor):
993
- 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)
994
1091
 
995
1092
  @injectable()
996
1093
  @order(9)
997
1094
  class EnvironmentAwareLifecycleCallable(LifecycleCallable):
998
1095
  __slots__ = []
999
1096
 
1000
- def __init__(self, processor: CallableProcessor):
1001
- super().__init__(inject_environment, processor, Lifecycle.ON_INIT)
1097
+ def __init__(self):
1098
+ super().__init__(inject_environment, Lifecycle.ON_INJECT)
1002
1099
 
1003
1100
  def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
1004
1101
  return [environment]
@@ -1008,13 +1105,13 @@ class EnvironmentAwareLifecycleCallable(LifecycleCallable):
1008
1105
  class InjectLifecycleCallable(LifecycleCallable):
1009
1106
  __slots__ = []
1010
1107
 
1011
- def __init__(self, processor: CallableProcessor):
1012
- super().__init__(inject, processor, Lifecycle.ON_INIT)
1108
+ def __init__(self):
1109
+ super().__init__(inject, Lifecycle.ON_INJECT)
1013
1110
 
1014
1111
  # override
1015
1112
 
1016
1113
  def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
1017
- return [environment.get(type) for type in method.paramTypes]
1114
+ return [environment.get(type) for type in method.param_types]
1018
1115
 
1019
1116
  def scope(name: str):
1020
1117
  def decorator(cls):
@@ -1036,15 +1133,10 @@ class RequestScope(Scope):
1036
1133
  __slots__ = [
1037
1134
  ]
1038
1135
 
1039
- # constructor
1040
-
1041
- def __init__(self):
1042
- super().__init__()
1043
-
1044
1136
  # public
1045
1137
 
1046
- def get(self, provider: AbstractInstanceProvider, environment: Environment, argProvider: Callable[[],list]):
1047
- return provider.create(environment, *argProvider())
1138
+ def get(self, provider: AbstractInstanceProvider, environment: Environment, arg_provider: Callable[[],list]):
1139
+ return provider.create(environment, *arg_provider())
1048
1140
 
1049
1141
  @scope("singleton")
1050
1142
  class SingletonScope(Scope):
@@ -1061,17 +1153,34 @@ class SingletonScope(Scope):
1061
1153
  super().__init__()
1062
1154
 
1063
1155
  self.value = None
1064
- self.lock = threading.Lock()
1156
+ self.lock = threading.RLock()
1065
1157
 
1066
1158
  # override
1067
1159
 
1068
- def get(self, provider: AbstractInstanceProvider, environment: Environment, argProvider: Callable[[],list]):
1160
+ def get(self, provider: AbstractInstanceProvider, environment: Environment, arg_provider: Callable[[],list]):
1069
1161
  if self.value is None:
1070
1162
  with self.lock:
1071
- if self.value is None:
1072
- self.value = provider.create(environment, *argProvider())
1163
+ if self.value is None:
1164
+ self.value = provider.create(environment, *arg_provider())
1073
1165
 
1074
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
1075
1184
 
1076
1185
  # internal class that is required to import technical instance providers
1077
1186
 
@@ -1095,4 +1204,4 @@ class BootEnvironment:
1095
1204
  # constructor
1096
1205
 
1097
1206
  def __init__(self):
1098
- pass
1207
+ pass