aspyx 1.2.0__py3-none-any.whl → 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aspyx might be problematic. Click here for more details.
- aspyx/di/__init__.py +8 -4
- aspyx/di/aop/aop.py +87 -5
- aspyx/di/configuration/env_configuration_source.py +1 -1
- aspyx/di/configuration/yaml_configuration_source.py +1 -1
- aspyx/di/di.py +290 -200
- aspyx/di/threading/__init__.py +11 -0
- aspyx/di/threading/synchronized.py +46 -0
- aspyx/reflection/reflection.py +4 -1
- {aspyx-1.2.0.dist-info → aspyx-1.3.0.dist-info}/METADATA +96 -16
- aspyx-1.3.0.dist-info/RECORD +20 -0
- aspyx-1.2.0.dist-info/RECORD +0 -18
- {aspyx-1.2.0.dist-info → aspyx-1.3.0.dist-info}/WHEEL +0 -0
- {aspyx-1.2.0.dist-info → aspyx-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {aspyx-1.2.0.dist-info → aspyx-1.3.0.dist-info}/top_level.txt +0 -0
aspyx/di/di.py
CHANGED
|
@@ -9,7 +9,8 @@ import logging
|
|
|
9
9
|
from abc import abstractmethod, ABC
|
|
10
10
|
from enum import Enum
|
|
11
11
|
import threading
|
|
12
|
-
from
|
|
12
|
+
from operator import truediv
|
|
13
|
+
from typing import Type, Dict, TypeVar, Generic, Optional, cast, Callable, TypedDict
|
|
13
14
|
|
|
14
15
|
from aspyx.di.util import StringBuilder
|
|
15
16
|
from aspyx.reflection import Decorators, TypeDescriptor, DecoratorDescriptor
|
|
@@ -31,16 +32,31 @@ class DIException(Exception):
|
|
|
31
32
|
"""
|
|
32
33
|
Exception raised for errors in the injector.
|
|
33
34
|
"""
|
|
35
|
+
def __init__(self, message: str):
|
|
36
|
+
super().__init__(message)
|
|
34
37
|
|
|
35
38
|
class DIRegistrationException(DIException):
|
|
36
39
|
"""
|
|
37
40
|
Exception raised during the registration of dependencies.
|
|
38
41
|
"""
|
|
42
|
+
def __init__(self, message: str):
|
|
43
|
+
super().__init__(message)
|
|
44
|
+
|
|
45
|
+
class ProviderCollisionException(DIRegistrationException):
|
|
46
|
+
def __init__(self, message: str, *providers: AbstractInstanceProvider):
|
|
47
|
+
super().__init__(message)
|
|
48
|
+
|
|
49
|
+
self.providers = providers
|
|
50
|
+
|
|
51
|
+
def __str__(self):
|
|
52
|
+
return f"[{self.args[0]} {self.providers[1].location()} collides with {self.providers[0].location()}"
|
|
39
53
|
|
|
40
54
|
class DIRuntimeException(DIException):
|
|
41
55
|
"""
|
|
42
56
|
Exception raised during the runtime.
|
|
43
57
|
"""
|
|
58
|
+
def __init__(self, message: str):
|
|
59
|
+
super().__init__(message)
|
|
44
60
|
|
|
45
61
|
class AbstractInstanceProvider(ABC, Generic[T]):
|
|
46
62
|
"""
|
|
@@ -65,17 +81,23 @@ class AbstractInstanceProvider(ABC, Generic[T]):
|
|
|
65
81
|
def get_scope(self) -> str:
|
|
66
82
|
pass
|
|
67
83
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
pass
|
|
84
|
+
def get_dependencies(self) -> (list[Type],int):
|
|
85
|
+
return [],1
|
|
71
86
|
|
|
72
87
|
@abstractmethod
|
|
73
88
|
def create(self, environment: Environment, *args):
|
|
74
89
|
pass
|
|
75
90
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
91
|
+
def report(self) -> str:
|
|
92
|
+
return str(self)
|
|
93
|
+
|
|
94
|
+
def location(self) -> str:
|
|
95
|
+
host = self.get_host()
|
|
96
|
+
|
|
97
|
+
file = inspect.getfile(host)
|
|
98
|
+
line = inspect.getsourcelines(host)[1]
|
|
99
|
+
|
|
100
|
+
return f"{file}:{line}"
|
|
79
101
|
|
|
80
102
|
def check_factories(self):
|
|
81
103
|
pass
|
|
@@ -89,8 +111,7 @@ class InstanceProvider(AbstractInstanceProvider):
|
|
|
89
111
|
"host",
|
|
90
112
|
"type",
|
|
91
113
|
"eager",
|
|
92
|
-
"scope"
|
|
93
|
-
"dependencies"
|
|
114
|
+
"scope"
|
|
94
115
|
]
|
|
95
116
|
|
|
96
117
|
# constructor
|
|
@@ -100,12 +121,6 @@ class InstanceProvider(AbstractInstanceProvider):
|
|
|
100
121
|
self.type = t
|
|
101
122
|
self.eager = eager
|
|
102
123
|
self.scope = scope
|
|
103
|
-
self.dependencies : Optional[list[AbstractInstanceProvider]] = None
|
|
104
|
-
|
|
105
|
-
# internal
|
|
106
|
-
|
|
107
|
-
def _is_resolved(self) -> bool:
|
|
108
|
-
return self.dependencies is not None
|
|
109
124
|
|
|
110
125
|
# implement AbstractInstanceProvider
|
|
111
126
|
|
|
@@ -113,10 +128,6 @@ class InstanceProvider(AbstractInstanceProvider):
|
|
|
113
128
|
return self.host
|
|
114
129
|
|
|
115
130
|
def check_factories(self):
|
|
116
|
-
#register_factories(self.host)
|
|
117
|
-
pass
|
|
118
|
-
|
|
119
|
-
def resolve(self, context: Providers.Context):
|
|
120
131
|
pass
|
|
121
132
|
|
|
122
133
|
def get_module(self) -> str:
|
|
@@ -131,22 +142,11 @@ class InstanceProvider(AbstractInstanceProvider):
|
|
|
131
142
|
def get_scope(self) -> str:
|
|
132
143
|
return self.scope
|
|
133
144
|
|
|
134
|
-
def get_dependencies(self) -> list[AbstractInstanceProvider]:
|
|
135
|
-
return self.dependencies
|
|
136
|
-
|
|
137
145
|
# public
|
|
138
146
|
|
|
139
147
|
def module(self) -> str:
|
|
140
148
|
return self.host.__module__
|
|
141
149
|
|
|
142
|
-
def add_dependency(self, provider: AbstractInstanceProvider):
|
|
143
|
-
if any(issubclass(provider.get_type(), dependency.get_type()) for dependency in self.dependencies):
|
|
144
|
-
return False
|
|
145
|
-
|
|
146
|
-
self.dependencies.append(provider)
|
|
147
|
-
|
|
148
|
-
return True
|
|
149
|
-
|
|
150
150
|
@abstractmethod
|
|
151
151
|
def create(self, environment: Environment, *args):
|
|
152
152
|
pass
|
|
@@ -204,15 +204,12 @@ class AmbiguousProvider(AbstractInstanceProvider):
|
|
|
204
204
|
def get_scope(self) -> str:
|
|
205
205
|
return "singleton"
|
|
206
206
|
|
|
207
|
-
def get_dependencies(self) -> list[AbstractInstanceProvider]:
|
|
208
|
-
return []
|
|
209
|
-
|
|
210
|
-
def resolve(self, context: Providers.Context):
|
|
211
|
-
pass
|
|
212
|
-
|
|
213
207
|
def create(self, environment: Environment, *args):
|
|
214
208
|
raise DIException(f"multiple candidates for type {self.type}")
|
|
215
209
|
|
|
210
|
+
def report(self) -> str:
|
|
211
|
+
return "ambiguous: " + ",".join([provider.report() for provider in self.providers])
|
|
212
|
+
|
|
216
213
|
def __str__(self):
|
|
217
214
|
return f"AmbiguousProvider({self.type})"
|
|
218
215
|
|
|
@@ -257,8 +254,8 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
257
254
|
__slots__ = [
|
|
258
255
|
"environment",
|
|
259
256
|
"scope_instance",
|
|
260
|
-
"provider",
|
|
261
257
|
"dependencies",
|
|
258
|
+
"provider"
|
|
262
259
|
]
|
|
263
260
|
|
|
264
261
|
# constructor
|
|
@@ -268,14 +265,30 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
268
265
|
|
|
269
266
|
self.environment = environment
|
|
270
267
|
self.provider = provider
|
|
271
|
-
self.dependencies
|
|
272
|
-
|
|
268
|
+
self.dependencies = []
|
|
273
269
|
self.scope_instance = Scopes.get(provider.get_scope(), environment)
|
|
274
270
|
|
|
275
271
|
# implement
|
|
276
272
|
|
|
277
|
-
def resolve(self, context: Providers.
|
|
278
|
-
|
|
273
|
+
def resolve(self, context: Providers.ResolveContext):
|
|
274
|
+
context.add(self)
|
|
275
|
+
|
|
276
|
+
if not context.is_resolved(self):
|
|
277
|
+
context.provider_dependencies[self] = [] #?
|
|
278
|
+
|
|
279
|
+
type_and_params = self.provider.get_dependencies()
|
|
280
|
+
params = type_and_params[1]
|
|
281
|
+
for type in type_and_params[0]:
|
|
282
|
+
if params > 0:
|
|
283
|
+
params -= 1
|
|
284
|
+
self.dependencies.append(context.get_provider(type))
|
|
285
|
+
|
|
286
|
+
provider = context.add_provider_dependency(self, type)
|
|
287
|
+
if provider is not None:
|
|
288
|
+
provider.resolve(context)
|
|
289
|
+
|
|
290
|
+
else:
|
|
291
|
+
context.add(*context.get_provider_dependencies(self))
|
|
279
292
|
|
|
280
293
|
def get_module(self) -> str:
|
|
281
294
|
return self.provider.get_module()
|
|
@@ -289,18 +302,10 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
289
302
|
def get_scope(self) -> str:
|
|
290
303
|
return self.provider.get_scope()
|
|
291
304
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
def tweak_dependencies(self, providers: dict[Type, AbstractInstanceProvider]):
|
|
295
|
-
for dependency in self.provider.get_dependencies():
|
|
296
|
-
instance_provider = providers.get(dependency.get_type(), None)
|
|
297
|
-
if instance_provider is None:
|
|
298
|
-
raise DIRegistrationException(f"missing import for {dependency.get_type()} ")
|
|
299
|
-
|
|
300
|
-
self.dependencies.append(instance_provider)
|
|
305
|
+
def report(self) -> str:
|
|
306
|
+
return self.provider.report()
|
|
301
307
|
|
|
302
|
-
|
|
303
|
-
return self.provider.get_dependencies()
|
|
308
|
+
# own logic
|
|
304
309
|
|
|
305
310
|
def create(self, environment: Environment, *args):
|
|
306
311
|
return self.scope_instance.get(self.provider, self.environment, lambda: [provider.create(environment) for provider in self.dependencies]) # already scope property!
|
|
@@ -329,41 +334,36 @@ class ClassInstanceProvider(InstanceProvider):
|
|
|
329
334
|
def check_factories(self):
|
|
330
335
|
register_factories(self.host)
|
|
331
336
|
|
|
332
|
-
def
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if not self._is_resolved():
|
|
336
|
-
self.dependencies = []
|
|
337
|
+
def get_dependencies(self) -> (list[Type],int):
|
|
338
|
+
types : list[Type] = []
|
|
337
339
|
|
|
338
|
-
|
|
340
|
+
# check constructor
|
|
339
341
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
342
|
+
init = TypeDescriptor.for_type(self.type).get_method("__init__")
|
|
343
|
+
if init is None:
|
|
344
|
+
raise DIRegistrationException(f"{self.type.__name__} does not implement __init__")
|
|
343
345
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if self.add_dependency(provider): # a dependency can occur multiple times, e.g in __init__ and in an injected method
|
|
348
|
-
provider.resolve(context)
|
|
346
|
+
self.params = len(init.param_types)
|
|
347
|
+
for param in init.param_types:
|
|
348
|
+
types.append(param)
|
|
349
349
|
|
|
350
|
-
|
|
350
|
+
# check @inject
|
|
351
351
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
352
|
+
for method in TypeDescriptor.for_type(self.type).get_methods():
|
|
353
|
+
if method.has_decorator(inject):
|
|
354
|
+
for param in method.param_types:
|
|
355
|
+
types.append(param)
|
|
356
356
|
|
|
357
|
-
|
|
358
|
-
provider.resolve(context)
|
|
359
|
-
else: # check if the dependencies create a cycle
|
|
360
|
-
context.add(*self.dependencies)
|
|
357
|
+
return (types, self.params)
|
|
361
358
|
|
|
362
359
|
def create(self, environment: Environment, *args):
|
|
363
360
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
364
361
|
|
|
365
362
|
return environment.created(self.type(*args[:self.params]))
|
|
366
363
|
|
|
364
|
+
def report(self) -> str:
|
|
365
|
+
return f"{self.host.__name__}.__init__"
|
|
366
|
+
|
|
367
367
|
# object
|
|
368
368
|
|
|
369
369
|
def __str__(self):
|
|
@@ -387,25 +387,19 @@ class FunctionInstanceProvider(InstanceProvider):
|
|
|
387
387
|
|
|
388
388
|
# implement
|
|
389
389
|
|
|
390
|
-
def
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
if not self._is_resolved():
|
|
394
|
-
self.dependencies = []
|
|
395
|
-
|
|
396
|
-
provider = Providers.get_provider(self.host)
|
|
397
|
-
if self.add_dependency(provider):
|
|
398
|
-
provider.resolve(context)
|
|
399
|
-
else: # check if the dependencies crate a cycle
|
|
400
|
-
context.add(*self.dependencies)
|
|
390
|
+
def get_dependencies(self) -> (list[Type],int):
|
|
391
|
+
return [self.host], 1
|
|
401
392
|
|
|
402
393
|
def create(self, environment: Environment, *args):
|
|
403
394
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
404
395
|
|
|
405
|
-
instance = self.method(*args)
|
|
396
|
+
instance = self.method(*args) # args[0]=self
|
|
406
397
|
|
|
407
398
|
return environment.created(instance)
|
|
408
399
|
|
|
400
|
+
def report(self) -> str:
|
|
401
|
+
return f"{self.host.__name__}.{self.method.__name__}"
|
|
402
|
+
|
|
409
403
|
def __str__(self):
|
|
410
404
|
return f"FunctionInstanceProvider({self.host.__name__}.{self.method.__name__} -> {self.type.__name__})"
|
|
411
405
|
|
|
@@ -429,32 +423,24 @@ class FactoryInstanceProvider(InstanceProvider):
|
|
|
429
423
|
|
|
430
424
|
# implement
|
|
431
425
|
|
|
432
|
-
def
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if not self._is_resolved():
|
|
436
|
-
self.dependencies = []
|
|
437
|
-
|
|
438
|
-
provider = Providers.get_provider(self.host)
|
|
439
|
-
if self.add_dependency(provider):
|
|
440
|
-
provider.resolve(context)
|
|
441
|
-
else: # check if the dependencies crate a cycle
|
|
442
|
-
context.add(*self.dependencies)
|
|
443
|
-
|
|
444
|
-
return self
|
|
426
|
+
def get_dependencies(self) -> (list[Type],int):
|
|
427
|
+
return [self.host],1
|
|
445
428
|
|
|
446
429
|
def create(self, environment: Environment, *args):
|
|
447
430
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
448
431
|
|
|
449
432
|
return environment.created(args[0].create())
|
|
450
433
|
|
|
434
|
+
def report(self) -> str:
|
|
435
|
+
return f"{self.host.__name__}.create"
|
|
436
|
+
|
|
451
437
|
def __str__(self):
|
|
452
438
|
return f"FactoryInstanceProvider({self.host.__name__} -> {self.type.__name__})"
|
|
453
439
|
|
|
454
440
|
|
|
455
441
|
class Lifecycle(Enum):
|
|
456
442
|
"""
|
|
457
|
-
This enum defines the lifecycle
|
|
443
|
+
This enum defines the lifecycle phases that can be processed by lifecycle processors.
|
|
458
444
|
"""
|
|
459
445
|
|
|
460
446
|
__slots__ = []
|
|
@@ -466,7 +452,7 @@ class Lifecycle(Enum):
|
|
|
466
452
|
|
|
467
453
|
class LifecycleProcessor(ABC):
|
|
468
454
|
"""
|
|
469
|
-
A LifecycleProcessor is used to perform any side effects on managed objects during
|
|
455
|
+
A LifecycleProcessor is used to perform any side effects on managed objects during different lifecycle phases.
|
|
470
456
|
"""
|
|
471
457
|
__slots__ = [
|
|
472
458
|
"order"
|
|
@@ -506,13 +492,54 @@ class Providers:
|
|
|
506
492
|
"""
|
|
507
493
|
# local class
|
|
508
494
|
|
|
509
|
-
class
|
|
510
|
-
__slots__ = [
|
|
495
|
+
class ResolveContext:
|
|
496
|
+
__slots__ = [
|
|
497
|
+
"dependencies",
|
|
498
|
+
"providers",
|
|
499
|
+
"provider_dependencies"
|
|
500
|
+
]
|
|
501
|
+
|
|
502
|
+
# constructor
|
|
503
|
+
|
|
504
|
+
def __init__(self, providers: Dict[Type, EnvironmentInstanceProvider]):
|
|
505
|
+
self.dependencies : list[EnvironmentInstanceProvider] = []
|
|
506
|
+
self.providers = providers
|
|
507
|
+
self.provider_dependencies : dict[EnvironmentInstanceProvider, list[EnvironmentInstanceProvider]] = {}
|
|
508
|
+
|
|
509
|
+
# public
|
|
510
|
+
|
|
511
|
+
def is_resolved(self, provider: EnvironmentInstanceProvider) -> bool:
|
|
512
|
+
return self.provider_dependencies.get(provider, None) is not None
|
|
513
|
+
|
|
514
|
+
def get_provider_dependencies(self, provider: EnvironmentInstanceProvider) -> list[EnvironmentInstanceProvider]:
|
|
515
|
+
return self.provider_dependencies[provider]
|
|
516
|
+
|
|
517
|
+
def add_provider_dependency(self, provider: EnvironmentInstanceProvider, type: Type) -> Optional[EnvironmentInstanceProvider]:
|
|
518
|
+
provider_dependencies = self.provider_dependencies.get(provider, None)
|
|
519
|
+
if provider_dependencies is None:
|
|
520
|
+
provider_dependencies = []
|
|
521
|
+
self.provider_dependencies[provider] = provider_dependencies
|
|
522
|
+
|
|
523
|
+
provider = self.get_provider(type)
|
|
524
|
+
|
|
525
|
+
if any(issubclass(provider.get_type(), dependency.get_type()) for dependency in provider_dependencies):
|
|
526
|
+
return None
|
|
527
|
+
|
|
528
|
+
provider_dependencies.append(provider)
|
|
529
|
+
|
|
530
|
+
return provider
|
|
511
531
|
|
|
512
|
-
def
|
|
513
|
-
self.dependencies
|
|
532
|
+
def next(self):
|
|
533
|
+
self.dependencies.clear()
|
|
514
534
|
|
|
515
|
-
def
|
|
535
|
+
def get_provider(self, type: Type) -> EnvironmentInstanceProvider:
|
|
536
|
+
provider = self.providers.get(type, None)
|
|
537
|
+
if provider is None:
|
|
538
|
+
raise DIRegistrationException(f"Provider for {type} is not defined")
|
|
539
|
+
|
|
540
|
+
return provider
|
|
541
|
+
|
|
542
|
+
def add(self, *providers: EnvironmentInstanceProvider):
|
|
516
543
|
for provider in providers:
|
|
517
544
|
if next((p for p in self.dependencies if p.get_type() is provider.get_type()), None) is not None:
|
|
518
545
|
raise DIRegistrationException(self.cycle_report(provider))
|
|
@@ -529,19 +556,17 @@ class Providers:
|
|
|
529
556
|
|
|
530
557
|
first = False
|
|
531
558
|
|
|
532
|
-
cycle += f"{p.
|
|
559
|
+
cycle += f"{p.report()}"
|
|
533
560
|
|
|
534
|
-
cycle += f" <> {provider.
|
|
561
|
+
cycle += f" <> {provider.report()}"
|
|
535
562
|
|
|
536
563
|
return cycle
|
|
537
564
|
|
|
538
565
|
|
|
539
566
|
# class properties
|
|
540
567
|
|
|
541
|
-
check: list[AbstractInstanceProvider] = []
|
|
542
|
-
|
|
543
|
-
providers : Dict[Type,AbstractInstanceProvider] = {}
|
|
544
|
-
cache: Dict[Type, AbstractInstanceProvider] = {}
|
|
568
|
+
check : list[AbstractInstanceProvider] = []
|
|
569
|
+
providers : Dict[Type,list[AbstractInstanceProvider]] = {}
|
|
545
570
|
|
|
546
571
|
resolved = False
|
|
547
572
|
|
|
@@ -549,7 +574,58 @@ class Providers:
|
|
|
549
574
|
def register(cls, provider: AbstractInstanceProvider):
|
|
550
575
|
Environment.logger.debug("register provider %s(%s)", provider.get_type().__qualname__, provider.get_type().__name__)
|
|
551
576
|
|
|
552
|
-
|
|
577
|
+
Providers.check.append(provider)
|
|
578
|
+
candidates = Providers.providers.get(provider.get_type(), None)
|
|
579
|
+
if candidates is None:
|
|
580
|
+
Providers.providers[provider.get_type()] = [provider]
|
|
581
|
+
else:
|
|
582
|
+
candidates.append(provider)
|
|
583
|
+
|
|
584
|
+
# add factories lazily
|
|
585
|
+
|
|
586
|
+
@classmethod
|
|
587
|
+
def check_factories(cls):
|
|
588
|
+
for check in Providers.check:
|
|
589
|
+
check.check_factories()
|
|
590
|
+
|
|
591
|
+
Providers.check.clear()
|
|
592
|
+
|
|
593
|
+
@classmethod
|
|
594
|
+
def filter(cls, environment: Environment) -> Dict[Type,AbstractInstanceProvider]:
|
|
595
|
+
cache: Dict[Type,AbstractInstanceProvider] = {}
|
|
596
|
+
|
|
597
|
+
context: ConditionContext = {
|
|
598
|
+
"requires_feature": lambda feature : environment.has_feature(feature),
|
|
599
|
+
"requires_class": lambda clazz : cache.get(clazz, None) is not None # ? only works if the class is in the cache already?
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
Providers.check_factories() # check for additional factories
|
|
603
|
+
|
|
604
|
+
# local methods
|
|
605
|
+
|
|
606
|
+
def filter_type(clazz: Type) -> Optional[AbstractInstanceProvider]:
|
|
607
|
+
result = None
|
|
608
|
+
for provider in Providers.providers[clazz]:
|
|
609
|
+
if provider_applies(provider):
|
|
610
|
+
if result is not None:
|
|
611
|
+
raise ProviderCollisionException(f"type {clazz.__name__} already registered", result, provider)
|
|
612
|
+
|
|
613
|
+
else:
|
|
614
|
+
result = provider
|
|
615
|
+
|
|
616
|
+
return result
|
|
617
|
+
|
|
618
|
+
def provider_applies(provider: AbstractInstanceProvider) -> bool:
|
|
619
|
+
descriptor = TypeDescriptor.for_type(provider.get_host())
|
|
620
|
+
if descriptor.has_decorator(conditional):
|
|
621
|
+
conditions: list[Condition] = [*descriptor.get_decorator(conditional).args]
|
|
622
|
+
for condition in conditions:
|
|
623
|
+
if not condition.apply(context):
|
|
624
|
+
return False
|
|
625
|
+
|
|
626
|
+
return True
|
|
627
|
+
|
|
628
|
+
return True
|
|
553
629
|
|
|
554
630
|
def is_injectable(type: Type) -> bool:
|
|
555
631
|
if type is object:
|
|
@@ -558,34 +634,21 @@ class Providers:
|
|
|
558
634
|
if inspect.isabstract(type):
|
|
559
635
|
return False
|
|
560
636
|
|
|
561
|
-
#for decorator in Decorators.get(type):
|
|
562
|
-
# if decorator.decorator is injectable:
|
|
563
|
-
# return True
|
|
564
|
-
|
|
565
|
-
# darn
|
|
566
|
-
|
|
567
637
|
return True
|
|
568
638
|
|
|
569
639
|
def cache_provider_for_type(provider: AbstractInstanceProvider, type: Type):
|
|
570
|
-
|
|
571
|
-
host = provider.get_host()
|
|
572
|
-
file = inspect.getfile(host)
|
|
573
|
-
line = inspect.getsourcelines(host)[1]
|
|
574
|
-
|
|
575
|
-
return f"{file}:{line}"
|
|
576
|
-
|
|
577
|
-
existing_provider = Providers.cache.get(type)
|
|
640
|
+
existing_provider = cache.get(type)
|
|
578
641
|
if existing_provider is None:
|
|
579
|
-
|
|
642
|
+
cache[type] = provider
|
|
580
643
|
|
|
581
644
|
else:
|
|
582
645
|
if type is provider.get_type():
|
|
583
|
-
raise
|
|
646
|
+
raise ProviderCollisionException(f"type {type.__name__} already registered", existing_provider, provider)
|
|
584
647
|
|
|
585
648
|
if isinstance(existing_provider, AmbiguousProvider):
|
|
586
649
|
cast(AmbiguousProvider, existing_provider).add_provider(provider)
|
|
587
650
|
else:
|
|
588
|
-
|
|
651
|
+
cache[type] = AmbiguousProvider(type, existing_provider, provider)
|
|
589
652
|
|
|
590
653
|
# recursion
|
|
591
654
|
|
|
@@ -593,39 +656,35 @@ class Providers:
|
|
|
593
656
|
if is_injectable(super_class):
|
|
594
657
|
cache_provider_for_type(provider, super_class)
|
|
595
658
|
|
|
596
|
-
#
|
|
597
|
-
|
|
598
|
-
Providers.check.append(provider)
|
|
659
|
+
# filter conditional providers and fill base classes as well
|
|
599
660
|
|
|
600
|
-
Providers.providers
|
|
661
|
+
for provider_type, providers in Providers.providers.items():
|
|
662
|
+
matching_provider = filter_type(provider_type)
|
|
663
|
+
if matching_provider is not None:
|
|
664
|
+
cache_provider_for_type(matching_provider, provider_type)
|
|
601
665
|
|
|
602
|
-
#
|
|
666
|
+
# replace by EnvironmentInstanceProvider
|
|
603
667
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
668
|
+
mapped = {}
|
|
669
|
+
result = {}
|
|
670
|
+
for provider_type, provider in cache.items():
|
|
671
|
+
environment_provider = mapped.get(provider, None)
|
|
672
|
+
if environment_provider is None:
|
|
673
|
+
environment_provider = EnvironmentInstanceProvider(environment, provider)
|
|
674
|
+
mapped[provider] = environment_provider
|
|
608
675
|
|
|
609
|
-
|
|
610
|
-
check.check_factories()
|
|
676
|
+
result[provider_type] = environment_provider
|
|
611
677
|
|
|
612
|
-
#
|
|
613
|
-
while len(Providers.check) > 0:
|
|
614
|
-
check = Providers.check.pop(0)
|
|
615
|
-
check.resolve(Providers.Context())
|
|
678
|
+
# and resolve
|
|
616
679
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
680
|
+
provider_context = Providers.ResolveContext(result)
|
|
681
|
+
for provider in mapped.values():
|
|
682
|
+
provider.resolve(provider_context)
|
|
683
|
+
provider_context.next() # clear dependencies
|
|
621
684
|
|
|
622
|
-
|
|
623
|
-
def get_provider(cls, type: Type) -> AbstractInstanceProvider:
|
|
624
|
-
provider = Providers.cache.get(type, None)
|
|
625
|
-
if provider is None:
|
|
626
|
-
raise DIException(f"{type.__name__} not registered as injectable")
|
|
685
|
+
# done
|
|
627
686
|
|
|
628
|
-
return
|
|
687
|
+
return result
|
|
629
688
|
|
|
630
689
|
def register_factories(cls: Type):
|
|
631
690
|
descriptor = TypeDescriptor.for_type(cls)
|
|
@@ -750,6 +809,49 @@ def inject_environment():
|
|
|
750
809
|
|
|
751
810
|
return decorator
|
|
752
811
|
|
|
812
|
+
# conditional stuff
|
|
813
|
+
|
|
814
|
+
class ConditionContext(TypedDict):
|
|
815
|
+
requires_feature: Callable[[str], bool]
|
|
816
|
+
requires_class: Callable[[Type], bool]
|
|
817
|
+
|
|
818
|
+
class Condition(ABC):
|
|
819
|
+
@abstractmethod
|
|
820
|
+
def apply(self, context: ConditionContext) -> bool:
|
|
821
|
+
pass
|
|
822
|
+
|
|
823
|
+
class FeatureCondition(Condition):
|
|
824
|
+
def __init__(self, feature: str):
|
|
825
|
+
super().__init__()
|
|
826
|
+
|
|
827
|
+
self.feature = feature
|
|
828
|
+
|
|
829
|
+
def apply(self, context: ConditionContext) -> bool:
|
|
830
|
+
return context["requires_feature"](self.feature)
|
|
831
|
+
|
|
832
|
+
class ClassCondition(Condition):
|
|
833
|
+
def __init__(self, clazz: Type):
|
|
834
|
+
super().__init__()
|
|
835
|
+
|
|
836
|
+
self.clazz = clazz
|
|
837
|
+
|
|
838
|
+
def apply(self, context: ConditionContext) -> bool:
|
|
839
|
+
return context["requires_class"](self.clazz)
|
|
840
|
+
|
|
841
|
+
def requires_feature(feature: str):
|
|
842
|
+
return FeatureCondition(feature)
|
|
843
|
+
|
|
844
|
+
def requires_class(clazz: Type):
|
|
845
|
+
return ClassCondition(clazz)
|
|
846
|
+
|
|
847
|
+
def conditional(*conditions: Condition):
|
|
848
|
+
def decorator(cls):
|
|
849
|
+
Decorators.add(cls, conditional, *conditions)
|
|
850
|
+
|
|
851
|
+
return cls
|
|
852
|
+
|
|
853
|
+
return decorator
|
|
854
|
+
|
|
753
855
|
class Environment:
|
|
754
856
|
"""
|
|
755
857
|
Central class that manages the lifecycle of instances and their dependencies.
|
|
@@ -766,12 +868,13 @@ class Environment:
|
|
|
766
868
|
"providers",
|
|
767
869
|
"lifecycle_processors",
|
|
768
870
|
"parent",
|
|
871
|
+
"features",
|
|
769
872
|
"instances"
|
|
770
873
|
]
|
|
771
874
|
|
|
772
875
|
# constructor
|
|
773
876
|
|
|
774
|
-
def __init__(self, env: Type, parent : Optional[Environment] = None):
|
|
877
|
+
def __init__(self, env: Type, features: list[str] = [], parent : Optional[Environment] = None):
|
|
775
878
|
"""
|
|
776
879
|
Creates a new Environment instance.
|
|
777
880
|
|
|
@@ -779,6 +882,9 @@ class Environment:
|
|
|
779
882
|
env (Type): The environment class that controls the scanning of managed objects.
|
|
780
883
|
parent (Optional[Environment]): Optional parent environment, whose objects are inherited.
|
|
781
884
|
"""
|
|
885
|
+
|
|
886
|
+
Environment.logger.debug("create environment for class %s", env.__qualname__)
|
|
887
|
+
|
|
782
888
|
# initialize
|
|
783
889
|
|
|
784
890
|
self.type = env
|
|
@@ -786,20 +892,24 @@ class Environment:
|
|
|
786
892
|
if self.parent is None and env is not Boot:
|
|
787
893
|
self.parent = Boot.get_environment() # inherit environment including its manged instances!
|
|
788
894
|
|
|
895
|
+
self.features = features
|
|
789
896
|
self.providers: Dict[Type, AbstractInstanceProvider] = {}
|
|
790
897
|
self.lifecycle_processors: list[LifecycleProcessor] = []
|
|
791
898
|
|
|
792
899
|
if self.parent is not None:
|
|
793
900
|
self.providers |= self.parent.providers
|
|
794
901
|
self.lifecycle_processors += self.parent.lifecycle_processors
|
|
902
|
+
else: #if self.type is Boot:
|
|
903
|
+
self.providers[SingletonScope] = SingletonScopeInstanceProvider()
|
|
904
|
+
self.providers[RequestScope] = RequestScopeInstanceProvider()
|
|
795
905
|
|
|
796
906
|
self.instances = []
|
|
797
907
|
|
|
798
908
|
Environment.instance = self
|
|
799
909
|
|
|
800
|
-
#
|
|
910
|
+
# filter conditional providers
|
|
801
911
|
|
|
802
|
-
Providers.
|
|
912
|
+
overall_providers = Providers.filter(self)
|
|
803
913
|
|
|
804
914
|
loaded = set()
|
|
805
915
|
|
|
@@ -808,12 +918,6 @@ class Environment:
|
|
|
808
918
|
|
|
809
919
|
self.providers[type] = provider
|
|
810
920
|
|
|
811
|
-
# bootstrapping hack, they will be overwritten by the "real" providers
|
|
812
|
-
|
|
813
|
-
if env is Boot:
|
|
814
|
-
add_provider(SingletonScope, SingletonScopeInstanceProvider())
|
|
815
|
-
add_provider(RequestScope, RequestScopeInstanceProvider())
|
|
816
|
-
|
|
817
921
|
def load_environment(env: Type):
|
|
818
922
|
if env not in loaded:
|
|
819
923
|
Environment.logger.debug("load environment %s", env.__qualname__)
|
|
@@ -835,39 +939,18 @@ class Environment:
|
|
|
835
939
|
for import_environment in decorator.args[0] or []:
|
|
836
940
|
load_environment(import_environment)
|
|
837
941
|
|
|
838
|
-
# load providers
|
|
839
|
-
|
|
840
|
-
local_providers = {type: provider for type, provider in Providers.cache.items() if provider.get_module().startswith(scan)}
|
|
841
|
-
|
|
842
|
-
# register providers
|
|
843
|
-
|
|
844
|
-
# make sure, that for every type only a single EnvironmentInstanceProvider is created!
|
|
845
|
-
# otherwise inheritance will fuck it up
|
|
846
|
-
|
|
847
|
-
environment_providers : dict[AbstractInstanceProvider, EnvironmentInstanceProvider] = {}
|
|
848
|
-
|
|
849
|
-
for type, provider in local_providers.items():
|
|
850
|
-
environment_provider = environment_providers.get(provider, None)
|
|
851
|
-
if environment_provider is None:
|
|
852
|
-
environment_provider = EnvironmentInstanceProvider(self, provider)
|
|
853
|
-
environment_providers[provider] = environment_provider
|
|
942
|
+
# filter and load providers according to their module
|
|
854
943
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
for type, provider in local_providers.items():
|
|
860
|
-
cast(EnvironmentInstanceProvider, self.providers[type]).tweak_dependencies(self.providers)
|
|
861
|
-
|
|
862
|
-
# return local providers
|
|
944
|
+
for type, provider in overall_providers.items():
|
|
945
|
+
if provider.get_module().startswith(scan):
|
|
946
|
+
add_provider(type, provider)
|
|
947
|
+
# go
|
|
863
948
|
|
|
864
|
-
|
|
865
|
-
else:
|
|
866
|
-
return []
|
|
949
|
+
load_environment(env)
|
|
867
950
|
|
|
868
951
|
# construct eager objects for local providers
|
|
869
952
|
|
|
870
|
-
for provider in
|
|
953
|
+
for provider in set(self.providers.values()):
|
|
871
954
|
if provider.is_eager():
|
|
872
955
|
provider.create(self)
|
|
873
956
|
|
|
@@ -878,6 +961,9 @@ class Environment:
|
|
|
878
961
|
|
|
879
962
|
# internal
|
|
880
963
|
|
|
964
|
+
def has_feature(self, feature: str) -> bool:
|
|
965
|
+
return feature in self.features
|
|
966
|
+
|
|
881
967
|
def execute_processors(self, lifecycle: Lifecycle, instance: T) -> T:
|
|
882
968
|
for processor in self.lifecycle_processors:
|
|
883
969
|
processor.process_lifecycle(lifecycle, instance, self)
|
|
@@ -927,8 +1013,9 @@ class Environment:
|
|
|
927
1013
|
|
|
928
1014
|
builder.append("Providers \n")
|
|
929
1015
|
for result_type, provider in self.providers.items():
|
|
930
|
-
if
|
|
931
|
-
|
|
1016
|
+
if isinstance(provider, EnvironmentInstanceProvider):
|
|
1017
|
+
if cast(EnvironmentInstanceProvider, provider).environment is self:
|
|
1018
|
+
builder.append(f"- {result_type.__name__}: {provider.report()}\n")
|
|
932
1019
|
|
|
933
1020
|
# instances
|
|
934
1021
|
|
|
@@ -973,6 +1060,9 @@ class Environment:
|
|
|
973
1060
|
|
|
974
1061
|
return provider.create(self)
|
|
975
1062
|
|
|
1063
|
+
def __str__(self):
|
|
1064
|
+
return f"Environment({self.type.__name__})"
|
|
1065
|
+
|
|
976
1066
|
class LifecycleCallable:
|
|
977
1067
|
__slots__ = [
|
|
978
1068
|
"decorator",
|
|
@@ -1155,20 +1245,20 @@ class InjectLifecycleCallable(LifecycleCallable):
|
|
|
1155
1245
|
def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
|
|
1156
1246
|
return [environment.get(type) for type in method.param_types]
|
|
1157
1247
|
|
|
1158
|
-
def scope(name: str):
|
|
1248
|
+
def scope(name: str, register=True):
|
|
1159
1249
|
def decorator(cls):
|
|
1160
1250
|
Scopes.register(cls, name)
|
|
1161
1251
|
|
|
1162
1252
|
Decorators.add(cls, scope)
|
|
1163
|
-
# Decorators.add(cls, injectable)
|
|
1164
1253
|
|
|
1165
|
-
|
|
1254
|
+
if register:
|
|
1255
|
+
Providers.register(ClassInstanceProvider(cls, eager=True, scope="request"))
|
|
1166
1256
|
|
|
1167
1257
|
return cls
|
|
1168
1258
|
|
|
1169
1259
|
return decorator
|
|
1170
1260
|
|
|
1171
|
-
@scope("request")
|
|
1261
|
+
@scope("request", register=False)
|
|
1172
1262
|
class RequestScope(Scope):
|
|
1173
1263
|
# properties
|
|
1174
1264
|
|
|
@@ -1180,7 +1270,7 @@ class RequestScope(Scope):
|
|
|
1180
1270
|
def get(self, provider: AbstractInstanceProvider, environment: Environment, arg_provider: Callable[[],list]):
|
|
1181
1271
|
return provider.create(environment, *arg_provider())
|
|
1182
1272
|
|
|
1183
|
-
@scope("singleton")
|
|
1273
|
+
@scope("singleton", register=False)
|
|
1184
1274
|
class SingletonScope(Scope):
|
|
1185
1275
|
# properties
|
|
1186
1276
|
|