aspyx 0.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 ADDED
@@ -0,0 +1,1055 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import logging
5
+
6
+ from abc import abstractmethod, ABC
7
+ from enum import Enum, auto
8
+ from typing import Type, Dict, TypeVar, Generic, Optional, cast, Callable
9
+
10
+ from aspyx.reflection import Decorators, TypeDescriptor, DecoratorDescriptor
11
+
12
+ T = TypeVar("T")
13
+
14
+ class Factory(ABC, Generic[T]):
15
+ """
16
+ Abstract base class for factories that create instances of type T.
17
+ """
18
+
19
+ __slots__ = []
20
+
21
+ @abstractmethod
22
+ def create(self) -> T:
23
+ pass
24
+
25
+ class InjectorException(Exception):
26
+ """
27
+ Exception raised for errors in the injector."""
28
+ pass
29
+
30
+ class AbstractInstanceProvider(ABC, Generic[T]):
31
+ """
32
+ Interface for instance providers.
33
+ """
34
+ @abstractmethod
35
+ def get_module(self) -> str:
36
+ pass
37
+
38
+ @abstractmethod
39
+ def get_type(self) -> Type[T]:
40
+ pass
41
+
42
+ @abstractmethod
43
+ def is_eager(self) -> bool:
44
+ pass
45
+
46
+ @abstractmethod
47
+ def get_scope(self) -> str:
48
+ pass
49
+
50
+ @abstractmethod
51
+ def get_dependencies(self) -> list[AbstractInstanceProvider]:
52
+ pass
53
+
54
+ @abstractmethod
55
+ def create(self, env: Environment, *args):
56
+ pass
57
+
58
+ @abstractmethod
59
+ def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
60
+ pass
61
+
62
+
63
+ class InstanceProvider(AbstractInstanceProvider):
64
+ """
65
+ An InstanceProvider is able to create instances of type T.
66
+ """
67
+ __slots__ = [
68
+ "host",
69
+ "type",
70
+ "eager",
71
+ "scope",
72
+ "dependencies"
73
+ ]
74
+
75
+ # constructor
76
+
77
+ def __init__(self, host: Type, t: Type[T], eager: bool, scope: str):
78
+ self.host = host
79
+ self.type = t
80
+ self.eager = eager
81
+ self.scope = scope
82
+ self.dependencies : Optional[list[AbstractInstanceProvider]] = None
83
+
84
+ # implement AbstractInstanceProvider
85
+
86
+ def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
87
+ return self
88
+
89
+ def get_module(self) -> str:
90
+ return self.host.__module__
91
+
92
+ def get_type(self) -> Type[T]:
93
+ return self.type
94
+
95
+ def is_eager(self) -> bool:
96
+ return self.eager
97
+
98
+ def get_scope(self) -> str:
99
+ return self.scope
100
+
101
+ def get_dependencies(self) -> list[AbstractInstanceProvider]:
102
+ return self.dependencies
103
+
104
+ # public
105
+
106
+ def module(self) -> str:
107
+ return self.host.__module__
108
+
109
+ def add_dependency(self, provider: AbstractInstanceProvider):
110
+ if any(issubclass(provider.get_type(), dependency.get_type()) for dependency in self.dependencies):
111
+ return False
112
+
113
+ self.dependencies.append(provider)
114
+
115
+ return True
116
+
117
+ @abstractmethod
118
+ def create(self, environment: Environment, *args):
119
+ pass
120
+
121
+ # we need this classes to bootstrap the system...
122
+ class SingletonScopeInstanceProvider(InstanceProvider):
123
+ def __init__(self):
124
+ super().__init__(SingletonScopeInstanceProvider, SingletonScope, False, "request")
125
+
126
+ def create(self, environment: Environment, *args):
127
+ return SingletonScope()
128
+
129
+ class RequestScopeInstanceProvider(InstanceProvider):
130
+ def __init__(self):
131
+ super().__init__(RequestScopeInstanceProvider, RequestScope, False, "singleton")
132
+
133
+ def create(self, environment: Environment, *args):
134
+ return RequestScope()
135
+
136
+
137
+ class AmbiguousProvider(AbstractInstanceProvider):
138
+ """
139
+ An AmbiguousProvider covers all cases, where fetching a class would lead to an ambiguity exception.
140
+ """
141
+
142
+ __slots__ = [
143
+ "type",
144
+ "providers",
145
+ ]
146
+
147
+ # constructor
148
+
149
+ def __init__(self, type: Type, *providers: AbstractInstanceProvider):
150
+ super().__init__()
151
+
152
+ self.type = type
153
+ self.providers = list(providers)
154
+
155
+ # public
156
+
157
+ def add_provider(self, provider: AbstractInstanceProvider):
158
+ self.providers.append(provider)
159
+
160
+ # implement
161
+
162
+ def get_module(self) -> str:
163
+ return self.type.__module__
164
+
165
+ def get_type(self) -> Type[T]:
166
+ return self.type
167
+
168
+ def is_eager(self) -> bool:
169
+ return False
170
+
171
+ def get_scope(self) -> str:
172
+ return "singleton"
173
+
174
+ def get_dependencies(self) -> list[AbstractInstanceProvider]:
175
+ return []
176
+
177
+ def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
178
+ return self
179
+
180
+ def create(self, environment: Environment, *args):
181
+ raise InjectorException(f"multiple candidates for type {self.type}")
182
+
183
+ def __str__(self):
184
+ return f"AmbiguousProvider({self.type})"
185
+
186
+ class Scopes:
187
+ # static data
188
+
189
+ scopes : Dict[str, Type] = {}
190
+
191
+ # class methods
192
+
193
+ @classmethod
194
+ def get(cls, scope: str, environment: Environment):
195
+ scopeType = Scopes.scopes.get(scope, None)
196
+ if scopeType is None:
197
+ raise InjectorException(f"unknown scope type {scope}")
198
+
199
+ return environment.get(scopeType)
200
+
201
+ @classmethod
202
+ def register(cls, scopeClass: Type, name: str):
203
+ Scopes.scopes[name] = scopeClass
204
+
205
+ class Scope:
206
+ # properties
207
+
208
+ __slots__ = [
209
+ ]
210
+
211
+ # constructor
212
+
213
+ def __init__(self):
214
+ pass
215
+
216
+ # public
217
+
218
+ def get(self, provider: AbstractInstanceProvider, environment: Environment, argProvider: Callable[[],list]):
219
+ return provider.create(environment, *argProvider())
220
+
221
+ class EnvironmentInstanceProvider(AbstractInstanceProvider):
222
+ # properties
223
+
224
+ __slots__ = [
225
+ "environment",
226
+ "scopeInstance",
227
+ "provider",
228
+ "dependencies",
229
+ ]
230
+
231
+ # constructor
232
+
233
+ def __init__(self, environment: Environment, provider: AbstractInstanceProvider):
234
+ super().__init__()
235
+
236
+ self.environment = environment
237
+ self.provider = provider
238
+ self.dependencies : list[AbstractInstanceProvider] = []
239
+
240
+ self.scopeInstance = Scopes.get(provider.get_scope(), environment)
241
+ print()
242
+
243
+ # implement
244
+
245
+ def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
246
+ pass # noop
247
+
248
+ def get_module(self) -> str:
249
+ return self.provider.get_module()
250
+
251
+ def get_type(self) -> Type[T]:
252
+ return self.provider.get_type()
253
+
254
+ def is_eager(self) -> bool:
255
+ return self.provider.is_eager()
256
+
257
+ def get_scope(self) -> str:
258
+ return self.provider.get_scope()
259
+
260
+ # custom logic
261
+
262
+ def tweakDependencies(self, providers: dict[Type, AbstractInstanceProvider]):
263
+ for dependency in self.provider.get_dependencies():
264
+ instanceProvider = providers.get(dependency.get_type(), None)
265
+ if instanceProvider is None:
266
+ raise InjectorException(f"missing import for {dependency.get_type()} ")
267
+
268
+ self.dependencies.append(instanceProvider)
269
+ pass
270
+ pass
271
+
272
+ def get_dependencies(self) -> list[AbstractInstanceProvider]:
273
+ return self.provider.get_dependencies()
274
+
275
+ def create(self, env: Environment, *args):
276
+ return self.scopeInstance.get(self.provider, self.environment, lambda: [provider.create(env) for provider in self.dependencies] ) # already scope property!
277
+
278
+ def __str__(self):
279
+ return f"EnvironmentInstanceProvider({self.provider})"
280
+
281
+ class ClassInstanceProvider(InstanceProvider):
282
+ """
283
+ A ClassInstanceProvider is able to create instances of type T by calling the class constructor.
284
+ """
285
+
286
+ __slots__ = [
287
+ "params"
288
+ ]
289
+
290
+ # constructor
291
+
292
+ def __init__(self, t: Type[T], eager: bool, scope = "singleton"):
293
+ super().__init__(t, t, eager, scope)
294
+
295
+ self.params = 0
296
+
297
+ # implement
298
+
299
+ def resolve(self, context: Providers.Context) -> InstanceProvider:
300
+ if self.dependencies is None:
301
+ self.dependencies = []
302
+
303
+ context.add(self)
304
+
305
+ # check constructor
306
+
307
+ init = TypeDescriptor.for_type(self.type).get_method("__init__")
308
+ if init is None:
309
+ raise InjectorException(f"{self.type.__name__} does not implement __init__")
310
+
311
+ for param in init.paramTypes:
312
+ provider = Providers.getProvider(param)
313
+ self.params += 1
314
+ if self.add_dependency(provider):
315
+ provider.resolve(context)
316
+
317
+ # check @inject
318
+
319
+ for method in TypeDescriptor.for_type(self.type).methods.values():
320
+ if method.has_decorator(inject):
321
+ for param in method.paramTypes:
322
+ provider = Providers.getProvider(param)
323
+
324
+ if self.add_dependency(provider):
325
+ provider.resolve(context)
326
+ else: # check if the dependencies create a cycle
327
+ context.add(*self.dependencies)
328
+
329
+ return self
330
+
331
+ def create(self, environment: Environment, *args):
332
+ Environment.logger.debug(f"{self} create class {self.type.__qualname__}")
333
+
334
+ return environment.created(self.type(*args[:self.params]))
335
+
336
+ # object
337
+
338
+ def __str__(self):
339
+ return f"ClassInstanceProvider({self.type.__name__})"
340
+
341
+ class FunctionInstanceProvider(InstanceProvider):
342
+ """
343
+ A FunctionInstanceProvider is able to create instances of type T by calling specific methods annotated with 'create".
344
+ """
345
+
346
+ __slots__ = [
347
+ "method"
348
+ ]
349
+
350
+ # constructor
351
+
352
+ def __init__(self, clazz : Type, method, return_type : Type[T], eager = True, scope = "singleton"):
353
+ super().__init__(clazz, return_type, eager, scope)
354
+
355
+ self.method = method
356
+
357
+ # implement
358
+
359
+ def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
360
+ if self.dependencies is None:
361
+ self.dependencies = []
362
+
363
+ context.add(self)
364
+
365
+ provider = Providers.getProvider(self.host)
366
+ if self.add_dependency(provider):
367
+ provider.resolve(context)
368
+ else: # check if the dependencies crate a cycle
369
+ context.add(*self.dependencies)
370
+
371
+ return self
372
+
373
+ def create(self, environment: Environment, *args):
374
+ Environment.logger.debug(f"{self} create class {self.type.__qualname__}")
375
+
376
+ instance = self.method(*args)
377
+
378
+ return environment.created(instance)
379
+
380
+ def __str__(self):
381
+ return f"FunctionInstanceProvider({self.host.__name__}.{self.method.__name__} -> {self.type.__name__})"
382
+
383
+ class FactoryInstanceProvider(InstanceProvider):
384
+ """
385
+ A FactoryInstanceProvider is able to create instances of type T by calling registered Factory instances.
386
+ """
387
+
388
+ __slots__ = []
389
+
390
+ # class method
391
+
392
+ @classmethod
393
+ def getFactoryType(cls, clazz):
394
+ return TypeDescriptor.for_type(clazz).get_local_method("create").returnType
395
+
396
+ # constructor
397
+
398
+ def __init__(self, factory: Type, eager: bool, scope: str):
399
+ super().__init__(factory, FactoryInstanceProvider.getFactoryType(factory), eager, scope)
400
+
401
+ # implement
402
+
403
+ def resolve(self, context: Providers.Context) -> AbstractInstanceProvider:
404
+ if self.dependencies is None:
405
+ self.dependencies = []
406
+
407
+ context.add(self)
408
+
409
+ provider = Providers.getProvider(self.host)
410
+ if self.add_dependency(provider):
411
+ provider.resolve(context)
412
+
413
+ else: # check if the dependencies crate a cycle
414
+ context.add(*self.dependencies)
415
+
416
+ return self
417
+
418
+ def create(self, environment: Environment, *args):
419
+ Environment.logger.debug(f"{self} create class {self.type.__qualname__}")
420
+
421
+ return environment.created(args[0].create())
422
+
423
+ def __str__(self):
424
+ return f"FactoryInstanceProvider({self.host.__name__} -> {self.type.__name__})"
425
+
426
+
427
+ class Lifecycle(Enum):
428
+ """
429
+ This enum defines the lifecycle events that can be processed by lifecycle processors.
430
+ """
431
+
432
+ __slots__ = []
433
+
434
+ ON_INIT = auto()
435
+ ON_DESTROY = auto()
436
+
437
+ class LifecycleProcessor(ABC):
438
+ """
439
+ A LifecycleProcessor is used to perform any side effects on managed objects during their lifecycle.
440
+ """
441
+ __slots__ = []
442
+
443
+ # constructor
444
+
445
+ def __init__(self):
446
+ pass
447
+
448
+ # methods
449
+
450
+ @abstractmethod
451
+ def processLifecycle(self, lifecycle: Lifecycle, instance: object, environment: Environment) -> object:
452
+ pass
453
+
454
+ class PostProcessor(LifecycleProcessor):
455
+ """
456
+ Base class for custom post processors that are executed after object creation.
457
+ """
458
+ __slots__ = []
459
+
460
+ # constructor
461
+
462
+ def __init__(self):
463
+ super().__init__()
464
+
465
+ def process(self, instance: object, environment: Environment):
466
+ pass
467
+
468
+ def processLifecycle(self, lifecycle: Lifecycle, instance: object, environment: Environment) -> object:
469
+ if lifecycle == Lifecycle.ON_INIT:
470
+ self.process(instance, environment)
471
+
472
+
473
+ class Providers:
474
+ """
475
+ The Providers class is a static class that manages the registration and resolution of InstanceProviders.
476
+ """
477
+ # local class
478
+
479
+ class Context:
480
+ __slots__ = ["dependencies"]
481
+
482
+ def __init__(self):
483
+ self.dependencies : list[AbstractInstanceProvider] = []
484
+
485
+ def add(self, *providers: AbstractInstanceProvider):
486
+ for provider in providers:
487
+ if next((p for p in self.dependencies if p.get_type() is provider.get_type()), None) is not None:
488
+ raise InjectorException(self.cycleReport(provider))
489
+
490
+ self.dependencies.append(provider)
491
+
492
+ def cycleReport(self, provider: AbstractInstanceProvider):
493
+ cycle = ""
494
+
495
+ first = True
496
+ for p in self.dependencies:
497
+ if not first:
498
+ cycle += " -> "
499
+
500
+ first = False
501
+
502
+ cycle += f"{p.get_type().__name__}"
503
+
504
+ cycle += f" -> {provider.get_type().__name__}"
505
+
506
+ return cycle
507
+
508
+
509
+ # class properties
510
+
511
+ check: list[AbstractInstanceProvider] = list()
512
+
513
+ providers : Dict[Type,AbstractInstanceProvider] = dict()
514
+ cache: Dict[Type, AbstractInstanceProvider] = dict()
515
+
516
+ resolved = False
517
+
518
+ @classmethod
519
+ def register(cls, provider: AbstractInstanceProvider):
520
+ Environment.logger.debug(f"register provider {provider.get_type().__qualname__}({provider.get_type().__name__})")
521
+
522
+ # local functions
523
+
524
+ def is_injectable(type: Type) -> bool:
525
+ if type is object:
526
+ return False
527
+
528
+ if inspect.isabstract(type):
529
+ return False
530
+
531
+ #for decorator in Decorators.get(type):
532
+ # if decorator.decorator is injectable:
533
+ # return True
534
+
535
+ # darn
536
+
537
+ return True
538
+
539
+ def cacheProviderForType(provider: AbstractInstanceProvider, type: Type):
540
+ existing_provider = Providers.cache.get(type)
541
+ if existing_provider is None:
542
+ Providers.cache[type] = provider
543
+
544
+ else:
545
+ if type is provider.get_type():
546
+ raise InjectorException(f"{type} already registered")
547
+
548
+ if isinstance(existing_provider, AmbiguousProvider):
549
+ cast(AmbiguousProvider, existing_provider).add_provider(provider)
550
+ else:
551
+ Providers.cache[type] = AmbiguousProvider(type, existing_provider, provider)
552
+
553
+ # recursion
554
+
555
+ for superClass in type.__bases__:
556
+ if is_injectable(superClass):
557
+ cacheProviderForType(provider, superClass)
558
+
559
+ # go
560
+
561
+ Providers.check.append(provider)
562
+
563
+ Providers.providers[provider.get_type()] = provider
564
+
565
+ # cache providers
566
+
567
+ cacheProviderForType(provider, provider.get_type())
568
+
569
+ @classmethod
570
+ def resolve(cls):
571
+ for provider in Providers.check:
572
+ provider.resolve(Providers.Context())
573
+
574
+ Providers.check.clear()
575
+
576
+ #Providers.report()
577
+
578
+ @classmethod
579
+ def report(cls):
580
+ for provider in Providers.cache.values():
581
+ print(f"provider {provider.get_type().__qualname__}")
582
+
583
+ @classmethod
584
+ def getProvider(cls, type: Type) -> AbstractInstanceProvider:
585
+ provider = Providers.cache.get(type, None)
586
+ if provider is None:
587
+ raise InjectorException(f"{type.__name__} not registered as injectable")
588
+
589
+ return provider
590
+
591
+ def registerFactories(cls: Type):
592
+ descriptor = TypeDescriptor.for_type(cls)
593
+
594
+ for method in descriptor.methods.values():
595
+ if method.has_decorator(create):
596
+ create_decorator = method.get_decorator(create)
597
+ Providers.register(FunctionInstanceProvider(cls, method.method, method.returnType, create_decorator.args[0],
598
+ create_decorator.args[1]))
599
+
600
+ def injectable(eager=True, scope="singleton"):
601
+ """
602
+ Instances of classes that are annotated with @injectable can be created by an Environment.
603
+ """
604
+ def decorator(cls):
605
+ Decorators.add(cls, injectable)
606
+
607
+ Providers.register(ClassInstanceProvider(cls, eager, scope))
608
+
609
+ #TODO registerFactories(cls)
610
+
611
+ return cls
612
+
613
+ return decorator
614
+
615
+ def factory(eager=True, scope="singleton"):
616
+ """
617
+ Decorator that needs to be used on a class that implements the Factory interface.
618
+ """
619
+ def decorator(cls):
620
+ Decorators.add(cls, factory)
621
+
622
+ Providers.register(ClassInstanceProvider(cls, eager, scope))
623
+ Providers.register(FactoryInstanceProvider(cls, eager, scope))
624
+
625
+ return cls
626
+
627
+ return decorator
628
+
629
+ def create(eager=True, scope="singleton"):
630
+ """
631
+ Any method annotated with @create will be registered as a factory method.
632
+ """
633
+ def decorator(func):
634
+ Decorators.add(func, create, eager, scope)
635
+ return func
636
+
637
+ return decorator
638
+
639
+ def on_init():
640
+ """
641
+ Methods annotated with @on_init will be called when the instance is created."""
642
+ def decorator(func):
643
+ Decorators.add(func, on_init)
644
+ return func
645
+
646
+ return decorator
647
+
648
+ def on_destroy():
649
+ """
650
+ Methods annotated with @on_destroy will be called when the instance is destroyed.
651
+ """
652
+ def decorator(func):
653
+ Decorators.add(func, on_destroy)
654
+ return func
655
+
656
+ return decorator
657
+
658
+ def environment(imports: Optional[list[Type]] = None):
659
+ """
660
+ This annotation is used to mark classes that control the set of injectables that will be managed based on their location
661
+ relative to the module of the class. All @injectable s and @factory s that are located in the same or any sub-module will
662
+ be registered and managed accordingly.
663
+ Arguments:
664
+ imports (Optional[list[Type]]): Optional list of imported environment types
665
+ """
666
+ def decorator(cls):
667
+ Providers.register(ClassInstanceProvider(cls, True))
668
+
669
+ Decorators.add(cls, environment, imports)
670
+ Decorators.add(cls, injectable) # do we need that?
671
+
672
+ registerFactories(cls)
673
+
674
+ return cls
675
+
676
+ return decorator
677
+
678
+ def inject():
679
+ """
680
+ Methods annotated with @inject will be called with the required dependencies injected.
681
+ """
682
+ def decorator(func):
683
+ Decorators.add(func, inject)
684
+ return func
685
+
686
+ return decorator
687
+
688
+ def inject_environment():
689
+ """
690
+ Methods annotated with @inject_environment will be called with the Environment instance injected.
691
+ """
692
+ def decorator(func):
693
+ Decorators.add(func, inject_environment)
694
+ return func
695
+
696
+ return decorator
697
+
698
+ class Environment:
699
+ """
700
+ Central class that manages the lifecycle of instances and their dependencies.
701
+ """
702
+
703
+ # static data
704
+
705
+ logger = logging.getLogger(__name__) # __name__ = module name
706
+
707
+ instance : 'Environment' = None
708
+
709
+ __slots__ = [
710
+ "type",
711
+ "providers",
712
+ "lifecycleProcessors",
713
+ "parent",
714
+ "instances"
715
+ ]
716
+
717
+ # constructor
718
+ def __init__(self, env: Type, parent : Optional[Environment] = None):
719
+ """
720
+ Creates a new Environment instance.
721
+
722
+ Args:
723
+ env (Type): The environment class that controls the scanning of managed objects.
724
+ parent (Optional[Environment]): Optional parent environment, whose objects are inherited.
725
+ """
726
+ # initialize
727
+
728
+ self.type = env
729
+ self.parent = parent
730
+ if self.parent is None and env is not BootEnvironment:
731
+ self.parent = BootEnvironment.get_instance() # inherit environment including its manged instances!
732
+
733
+ self.providers: Dict[Type, AbstractInstanceProvider] = dict()
734
+ self.lifecycleProcessors: list[LifecycleProcessor] = []
735
+
736
+ if self.parent is not None:
737
+ self.providers |= self.parent.providers
738
+ self.lifecycleProcessors += self.parent.lifecycleProcessors
739
+
740
+ self.instances = []
741
+
742
+ Environment.instance = self
743
+
744
+ # resolve providers on a static basis. This is only executed once!
745
+
746
+ Providers.resolve()
747
+
748
+ loaded = set()
749
+
750
+ def add_provider(type: Type, provider: AbstractInstanceProvider):
751
+ Environment.logger.debug(f"\tadd provider {provider} for {type})")
752
+
753
+ self.providers[type] = provider
754
+
755
+ # bootstrapping hack, they will be overwritten by the "real" providers
756
+
757
+ if env is BootEnvironment:
758
+ add_provider(SingletonScope, SingletonScopeInstanceProvider())
759
+ add_provider(RequestScope, RequestScopeInstanceProvider())
760
+
761
+ def load_environment(env: Type):
762
+ if env not in loaded:
763
+ Environment.logger.debug(f"load environment {env.__qualname__}")
764
+
765
+ loaded.add(env)
766
+
767
+ # sanity check
768
+
769
+ decorator = TypeDescriptor.for_type(env).get_decorator(environment)
770
+ if decorator is None:
771
+ raise InjectorException(f"{env.__name__} is not an environment class")
772
+
773
+ scan = env.__module__
774
+ if "." in scan:
775
+ scan = scan.rsplit('.', 1)[0]
776
+
777
+ # recursion
778
+
779
+ for import_environment in decorator.args[0] or []:
780
+ load_environment(import_environment)
781
+
782
+ # load providers
783
+
784
+ localProviders = {type: provider for type, provider in Providers.cache.items() if provider.get_module().startswith(scan)}
785
+
786
+ # register providers
787
+
788
+ # make sure, that for every type ony a single EnvironmentInstanceProvider is created!
789
+ # otherwise inheritance will fuck it up
790
+
791
+ environmentProviders : dict[AbstractInstanceProvider, EnvironmentInstanceProvider] = {}
792
+
793
+ for type, provider in localProviders.items():
794
+ environmentProvider = environmentProviders.get(provider, None)
795
+ if environmentProvider is None:
796
+ environmentProvider = EnvironmentInstanceProvider(self, provider)
797
+ environmentProviders[provider] = environmentProvider
798
+
799
+ add_provider(type, environmentProvider)
800
+
801
+ # tweak dependencies
802
+
803
+ for type, provider in localProviders.items():
804
+ cast(EnvironmentInstanceProvider, self.providers[type]).tweakDependencies(self.providers)
805
+
806
+ # return local providers
807
+
808
+ return environmentProviders.values()
809
+ else:
810
+ return []
811
+
812
+ # construct eager objects for local providers
813
+
814
+ for provider in load_environment(env):
815
+ if provider.is_eager():
816
+ provider.create(self)
817
+ # internal
818
+
819
+ def executeProcessors(self, lifecycle: Lifecycle, instance: T) -> T:
820
+ for processor in self.lifecycleProcessors:
821
+ processor.processLifecycle(lifecycle, instance, self)
822
+
823
+ return instance
824
+
825
+ def created(self, instance: T) -> T:
826
+ # remember lifecycle processors
827
+
828
+ if isinstance(instance, LifecycleProcessor):
829
+ self.lifecycleProcessors.append(instance)
830
+
831
+ # remember instance
832
+
833
+ self.instances.append(instance)
834
+
835
+ # execute processors
836
+
837
+ return self.executeProcessors(Lifecycle.ON_INIT, instance)
838
+
839
+ # public
840
+
841
+ def destroy(self):
842
+ """
843
+ destroy all managed instances by calling the appropriate lifecycle methods
844
+ """
845
+ for instance in self.instances:
846
+ self.executeProcessors(Lifecycle.ON_DESTROY, instance)
847
+
848
+ self.instances.clear() # make the cy happy
849
+
850
+ def get(self, type: Type[T]) -> T:
851
+ """
852
+ Return and possibly create a new instance of the given type.
853
+
854
+ Arguments:
855
+ type (Type): The desired type
856
+
857
+ Returns: The requested instance
858
+ """
859
+ provider = self.providers.get(type, None)
860
+ if provider is None:
861
+ Environment.logger.error(f"{type} is not supported")
862
+ raise InjectorException(f"{type} is not supported")
863
+
864
+ return provider.create(self)
865
+
866
+ class LifecycleCallable:
867
+ __slots__ = [
868
+ "decorator",
869
+ "lifecycle"
870
+ ]
871
+
872
+ def __init__(self, decorator, processor: CallableProcessor, lifecycle: Lifecycle):
873
+ self.decorator = decorator
874
+ self.lifecycle = lifecycle
875
+
876
+ processor.register(self)
877
+
878
+ def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
879
+ return []
880
+
881
+ @injectable()
882
+ class CallableProcessor(LifecycleProcessor):
883
+ # local classes
884
+
885
+ class MethodCall:
886
+ __slots__ = [
887
+ "decorator",
888
+ "method",
889
+ "lifecycleCallable"
890
+ ]
891
+
892
+ # constructor
893
+
894
+ def __init__(self, method: TypeDescriptor.MethodDescriptor, decorator: DecoratorDescriptor, lifecycleCallable: LifecycleCallable):
895
+ self.decorator = decorator
896
+ self.method = method
897
+ self.lifecycleCallable = lifecycleCallable
898
+
899
+ def execute(self, instance, environment: Environment):
900
+ self.method.method(instance, *self.lifecycleCallable.args(self.decorator, self.method, environment))
901
+
902
+ def __str__(self):
903
+ return f"MethodCall({self.method.method.__name__})"
904
+
905
+ # constructor
906
+
907
+ def __init__(self):
908
+ super().__init__()
909
+
910
+ self.callables : Dict[object,LifecycleCallable] = dict()
911
+ self.cache : Dict[Type,list[CallableProcessor.MethodCall]] = dict()
912
+
913
+ def computeCallables(self, type: Type) -> list[CallableProcessor.MethodCall] :
914
+ descriptor = TypeDescriptor.for_type(type)
915
+
916
+ result = []
917
+
918
+ for method in descriptor.methods.values():
919
+ for decorator in method.decorators:
920
+ if self.callables.get(decorator.decorator) is not None:
921
+ result.append(CallableProcessor.MethodCall(method, decorator, self.callables[decorator.decorator]))
922
+
923
+ return result
924
+
925
+ def callablesFor(self, type: Type)-> list[CallableProcessor.MethodCall]:
926
+ callables = self.cache.get(type, None)
927
+ if callables is None:
928
+ callables = self.computeCallables(type)
929
+ self.cache[type] = callables
930
+
931
+ return callables
932
+
933
+ def register(self, callable: LifecycleCallable):
934
+ self.callables[callable.decorator] = callable
935
+
936
+ # implement
937
+
938
+ def processLifecycle(self, lifecycle: Lifecycle, instance: object, environment: Environment) -> object:
939
+ callables = self.callablesFor(type(instance))
940
+ for callable in callables:
941
+ if callable.lifecycleCallable.lifecycle is lifecycle:
942
+ callable.execute(instance, environment)
943
+
944
+ @injectable()
945
+ class OnInitLifecycleCallable(LifecycleCallable):
946
+ __slots__ = []
947
+
948
+ def __init__(self, processor: CallableProcessor):
949
+ super().__init__(on_init, processor, Lifecycle.ON_INIT)
950
+
951
+ @injectable()
952
+ class OnDestroyLifecycleCallable(LifecycleCallable):
953
+ __slots__ = []
954
+
955
+ def __init__(self, processor: CallableProcessor):
956
+ super().__init__(on_destroy, processor, Lifecycle.ON_DESTROY)
957
+
958
+ @injectable()
959
+ class EnvironmentAwareLifecycleCallable(LifecycleCallable):
960
+ __slots__ = []
961
+
962
+ def __init__(self, processor: CallableProcessor):
963
+ super().__init__(inject_environment, processor, Lifecycle.ON_INIT)
964
+
965
+ def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
966
+ return [environment]
967
+
968
+ @injectable()
969
+ class InjectLifecycleCallable(LifecycleCallable):
970
+ __slots__ = []
971
+
972
+ def __init__(self, processor: CallableProcessor):
973
+ super().__init__(inject, processor, Lifecycle.ON_INIT)
974
+
975
+ # override
976
+
977
+ def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
978
+ return [environment.get(type) for type in method.paramTypes]
979
+
980
+ def scope(name: str):
981
+ def decorator(cls):
982
+ Scopes.register(cls, name)
983
+
984
+ Decorators.add(cls, scope)
985
+ # Decorators.add(cls, injectable)
986
+
987
+ Providers.register(ClassInstanceProvider(cls, eager=True, scope="request"))
988
+
989
+ return cls
990
+
991
+ return decorator
992
+
993
+ @scope("request")
994
+ class RequestScope(Scope):
995
+ # properties
996
+
997
+ __slots__ = [
998
+ ]
999
+
1000
+ # constructor
1001
+
1002
+ def __init__(self):
1003
+ super().__init__()
1004
+
1005
+ # public
1006
+
1007
+ def get(self, provider: AbstractInstanceProvider, environment: Environment, argProvider: Callable[[],list]):
1008
+ return provider.create(environment, *argProvider())
1009
+
1010
+ @scope("singleton")
1011
+ class SingletonScope(Scope):
1012
+ # properties
1013
+
1014
+ __slots__ = [
1015
+ "value"
1016
+ ]
1017
+
1018
+ # constructor
1019
+
1020
+ def __init__(self):
1021
+ super().__init__()
1022
+
1023
+ self.value = None
1024
+
1025
+ # override
1026
+
1027
+ def get(self, provider: AbstractInstanceProvider, environment: Environment, argProvider: Callable[[],list]):
1028
+ if self.value is None: # TODO thread-safe
1029
+ self.value = provider.create(environment, *argProvider())
1030
+
1031
+ return self.value
1032
+
1033
+ # internal class that is required to import technical instance providers
1034
+
1035
+ @environment()
1036
+ class BootEnvironment:
1037
+ # class
1038
+
1039
+ environment = None
1040
+
1041
+ @classmethod
1042
+ def get_instance(cls):
1043
+ if BootEnvironment.environment is None:
1044
+ BootEnvironment.environment = Environment(BootEnvironment)
1045
+
1046
+ return BootEnvironment.environment
1047
+
1048
+ # properties
1049
+
1050
+ __slots__ = []
1051
+
1052
+ # constructor
1053
+
1054
+ def __init__(self):
1055
+ pass