aspyx 1.1.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 +10 -5
- aspyx/di/aop/aop.py +86 -4
- aspyx/di/configuration/__init__.py +4 -1
- aspyx/di/configuration/configuration.py +1 -51
- aspyx/di/configuration/env_configuration_source.py +55 -0
- aspyx/di/configuration/yaml_configuration_source.py +26 -0
- aspyx/di/di.py +339 -207
- aspyx/di/threading/__init__.py +11 -0
- aspyx/di/threading/synchronized.py +46 -0
- aspyx/reflection/reflection.py +33 -1
- {aspyx-1.1.0.dist-info → aspyx-1.3.0.dist-info}/METADATA +150 -29
- aspyx-1.3.0.dist-info/RECORD +20 -0
- aspyx-1.1.0.dist-info/RECORD +0 -16
- {aspyx-1.1.0.dist-info → aspyx-1.3.0.dist-info}/WHEEL +0 -0
- {aspyx-1.1.0.dist-info → aspyx-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {aspyx-1.1.0.dist-info → aspyx-1.3.0.dist-info}/top_level.txt +0 -0
aspyx/di/di.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
The
|
|
2
|
+
The dependency injection module provides a framework for managing dependencies and lifecycle of objects in Python applications.
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
@@ -7,9 +7,10 @@ import inspect
|
|
|
7
7
|
import logging
|
|
8
8
|
|
|
9
9
|
from abc import abstractmethod, ABC
|
|
10
|
-
from enum import Enum
|
|
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
|
|
@@ -27,10 +28,35 @@ class Factory(ABC, Generic[T]):
|
|
|
27
28
|
def create(self) -> T:
|
|
28
29
|
pass
|
|
29
30
|
|
|
30
|
-
class
|
|
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)
|
|
37
|
+
|
|
38
|
+
class DIRegistrationException(DIException):
|
|
39
|
+
"""
|
|
40
|
+
Exception raised during the registration of dependencies.
|
|
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()}"
|
|
53
|
+
|
|
54
|
+
class DIRuntimeException(DIException):
|
|
55
|
+
"""
|
|
56
|
+
Exception raised during the runtime.
|
|
57
|
+
"""
|
|
58
|
+
def __init__(self, message: str):
|
|
59
|
+
super().__init__(message)
|
|
34
60
|
|
|
35
61
|
class AbstractInstanceProvider(ABC, Generic[T]):
|
|
36
62
|
"""
|
|
@@ -40,6 +66,9 @@ class AbstractInstanceProvider(ABC, Generic[T]):
|
|
|
40
66
|
def get_module(self) -> str:
|
|
41
67
|
pass
|
|
42
68
|
|
|
69
|
+
def get_host(self) -> Type[T]:
|
|
70
|
+
return type(self)
|
|
71
|
+
|
|
43
72
|
@abstractmethod
|
|
44
73
|
def get_type(self) -> Type[T]:
|
|
45
74
|
pass
|
|
@@ -52,16 +81,25 @@ class AbstractInstanceProvider(ABC, Generic[T]):
|
|
|
52
81
|
def get_scope(self) -> str:
|
|
53
82
|
pass
|
|
54
83
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
pass
|
|
84
|
+
def get_dependencies(self) -> (list[Type],int):
|
|
85
|
+
return [],1
|
|
58
86
|
|
|
59
87
|
@abstractmethod
|
|
60
88
|
def create(self, environment: Environment, *args):
|
|
61
89
|
pass
|
|
62
90
|
|
|
63
|
-
|
|
64
|
-
|
|
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}"
|
|
101
|
+
|
|
102
|
+
def check_factories(self):
|
|
65
103
|
pass
|
|
66
104
|
|
|
67
105
|
|
|
@@ -73,8 +111,7 @@ class InstanceProvider(AbstractInstanceProvider):
|
|
|
73
111
|
"host",
|
|
74
112
|
"type",
|
|
75
113
|
"eager",
|
|
76
|
-
"scope"
|
|
77
|
-
"dependencies"
|
|
114
|
+
"scope"
|
|
78
115
|
]
|
|
79
116
|
|
|
80
117
|
# constructor
|
|
@@ -84,16 +121,13 @@ class InstanceProvider(AbstractInstanceProvider):
|
|
|
84
121
|
self.type = t
|
|
85
122
|
self.eager = eager
|
|
86
123
|
self.scope = scope
|
|
87
|
-
self.dependencies : Optional[list[AbstractInstanceProvider]] = None
|
|
88
|
-
|
|
89
|
-
# internal
|
|
90
|
-
|
|
91
|
-
def _is_resolved(self) -> bool:
|
|
92
|
-
return self.dependencies is not None
|
|
93
124
|
|
|
94
125
|
# implement AbstractInstanceProvider
|
|
95
126
|
|
|
96
|
-
def
|
|
127
|
+
def get_host(self):
|
|
128
|
+
return self.host
|
|
129
|
+
|
|
130
|
+
def check_factories(self):
|
|
97
131
|
pass
|
|
98
132
|
|
|
99
133
|
def get_module(self) -> str:
|
|
@@ -108,22 +142,11 @@ class InstanceProvider(AbstractInstanceProvider):
|
|
|
108
142
|
def get_scope(self) -> str:
|
|
109
143
|
return self.scope
|
|
110
144
|
|
|
111
|
-
def get_dependencies(self) -> list[AbstractInstanceProvider]:
|
|
112
|
-
return self.dependencies
|
|
113
|
-
|
|
114
145
|
# public
|
|
115
146
|
|
|
116
147
|
def module(self) -> str:
|
|
117
148
|
return self.host.__module__
|
|
118
149
|
|
|
119
|
-
def add_dependency(self, provider: AbstractInstanceProvider):
|
|
120
|
-
if any(issubclass(provider.get_type(), dependency.get_type()) for dependency in self.dependencies):
|
|
121
|
-
return False
|
|
122
|
-
|
|
123
|
-
self.dependencies.append(provider)
|
|
124
|
-
|
|
125
|
-
return True
|
|
126
|
-
|
|
127
150
|
@abstractmethod
|
|
128
151
|
def create(self, environment: Environment, *args):
|
|
129
152
|
pass
|
|
@@ -181,14 +204,11 @@ class AmbiguousProvider(AbstractInstanceProvider):
|
|
|
181
204
|
def get_scope(self) -> str:
|
|
182
205
|
return "singleton"
|
|
183
206
|
|
|
184
|
-
def get_dependencies(self) -> list[AbstractInstanceProvider]:
|
|
185
|
-
return []
|
|
186
|
-
|
|
187
|
-
def resolve(self, context: Providers.Context):
|
|
188
|
-
pass
|
|
189
|
-
|
|
190
207
|
def create(self, environment: Environment, *args):
|
|
191
|
-
raise
|
|
208
|
+
raise DIException(f"multiple candidates for type {self.type}")
|
|
209
|
+
|
|
210
|
+
def report(self) -> str:
|
|
211
|
+
return "ambiguous: " + ",".join([provider.report() for provider in self.providers])
|
|
192
212
|
|
|
193
213
|
def __str__(self):
|
|
194
214
|
return f"AmbiguousProvider({self.type})"
|
|
@@ -204,7 +224,7 @@ class Scopes:
|
|
|
204
224
|
def get(cls, scope: str, environment: Environment):
|
|
205
225
|
scope_type = Scopes.scopes.get(scope, None)
|
|
206
226
|
if scope_type is None:
|
|
207
|
-
raise
|
|
227
|
+
raise DIRegistrationException(f"unknown scope type {scope}")
|
|
208
228
|
|
|
209
229
|
return environment.get(scope_type)
|
|
210
230
|
|
|
@@ -234,8 +254,8 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
234
254
|
__slots__ = [
|
|
235
255
|
"environment",
|
|
236
256
|
"scope_instance",
|
|
237
|
-
"provider",
|
|
238
257
|
"dependencies",
|
|
258
|
+
"provider"
|
|
239
259
|
]
|
|
240
260
|
|
|
241
261
|
# constructor
|
|
@@ -245,14 +265,30 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
245
265
|
|
|
246
266
|
self.environment = environment
|
|
247
267
|
self.provider = provider
|
|
248
|
-
self.dependencies
|
|
249
|
-
|
|
268
|
+
self.dependencies = []
|
|
250
269
|
self.scope_instance = Scopes.get(provider.get_scope(), environment)
|
|
251
270
|
|
|
252
271
|
# implement
|
|
253
272
|
|
|
254
|
-
def resolve(self, context: Providers.
|
|
255
|
-
|
|
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))
|
|
256
292
|
|
|
257
293
|
def get_module(self) -> str:
|
|
258
294
|
return self.provider.get_module()
|
|
@@ -266,18 +302,10 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
266
302
|
def get_scope(self) -> str:
|
|
267
303
|
return self.provider.get_scope()
|
|
268
304
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def tweak_dependencies(self, providers: dict[Type, AbstractInstanceProvider]):
|
|
272
|
-
for dependency in self.provider.get_dependencies():
|
|
273
|
-
instance_provider = providers.get(dependency.get_type(), None)
|
|
274
|
-
if instance_provider is None:
|
|
275
|
-
raise InjectorException(f"missing import for {dependency.get_type()} ")
|
|
305
|
+
def report(self) -> str:
|
|
306
|
+
return self.provider.report()
|
|
276
307
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
def get_dependencies(self) -> list[AbstractInstanceProvider]:
|
|
280
|
-
return self.provider.get_dependencies()
|
|
308
|
+
# own logic
|
|
281
309
|
|
|
282
310
|
def create(self, environment: Environment, *args):
|
|
283
311
|
return self.scope_instance.get(self.provider, self.environment, lambda: [provider.create(environment) for provider in self.dependencies]) # already scope property!
|
|
@@ -303,41 +331,39 @@ class ClassInstanceProvider(InstanceProvider):
|
|
|
303
331
|
|
|
304
332
|
# implement
|
|
305
333
|
|
|
306
|
-
def
|
|
307
|
-
|
|
334
|
+
def check_factories(self):
|
|
335
|
+
register_factories(self.host)
|
|
308
336
|
|
|
309
|
-
|
|
310
|
-
|
|
337
|
+
def get_dependencies(self) -> (list[Type],int):
|
|
338
|
+
types : list[Type] = []
|
|
311
339
|
|
|
312
|
-
|
|
340
|
+
# check constructor
|
|
313
341
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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__")
|
|
317
345
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if self.add_dependency(provider): # a dependency can occur multiple times, e.g in __init__ and in an injected method
|
|
322
|
-
provider.resolve(context)
|
|
346
|
+
self.params = len(init.param_types)
|
|
347
|
+
for param in init.param_types:
|
|
348
|
+
types.append(param)
|
|
323
349
|
|
|
324
|
-
|
|
350
|
+
# check @inject
|
|
325
351
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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)
|
|
330
356
|
|
|
331
|
-
|
|
332
|
-
provider.resolve(context)
|
|
333
|
-
else: # check if the dependencies create a cycle
|
|
334
|
-
context.add(*self.dependencies)
|
|
357
|
+
return (types, self.params)
|
|
335
358
|
|
|
336
359
|
def create(self, environment: Environment, *args):
|
|
337
360
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
338
361
|
|
|
339
362
|
return environment.created(self.type(*args[:self.params]))
|
|
340
363
|
|
|
364
|
+
def report(self) -> str:
|
|
365
|
+
return f"{self.host.__name__}.__init__"
|
|
366
|
+
|
|
341
367
|
# object
|
|
342
368
|
|
|
343
369
|
def __str__(self):
|
|
@@ -361,25 +387,19 @@ class FunctionInstanceProvider(InstanceProvider):
|
|
|
361
387
|
|
|
362
388
|
# implement
|
|
363
389
|
|
|
364
|
-
def
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if not self._is_resolved():
|
|
368
|
-
self.dependencies = []
|
|
369
|
-
|
|
370
|
-
provider = Providers.get_provider(self.host)
|
|
371
|
-
if self.add_dependency(provider):
|
|
372
|
-
provider.resolve(context)
|
|
373
|
-
else: # check if the dependencies crate a cycle
|
|
374
|
-
context.add(*self.dependencies)
|
|
390
|
+
def get_dependencies(self) -> (list[Type],int):
|
|
391
|
+
return [self.host], 1
|
|
375
392
|
|
|
376
393
|
def create(self, environment: Environment, *args):
|
|
377
394
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
378
395
|
|
|
379
|
-
instance = self.method(*args)
|
|
396
|
+
instance = self.method(*args) # args[0]=self
|
|
380
397
|
|
|
381
398
|
return environment.created(instance)
|
|
382
399
|
|
|
400
|
+
def report(self) -> str:
|
|
401
|
+
return f"{self.host.__name__}.{self.method.__name__}"
|
|
402
|
+
|
|
383
403
|
def __str__(self):
|
|
384
404
|
return f"FunctionInstanceProvider({self.host.__name__}.{self.method.__name__} -> {self.type.__name__})"
|
|
385
405
|
|
|
@@ -403,33 +423,24 @@ class FactoryInstanceProvider(InstanceProvider):
|
|
|
403
423
|
|
|
404
424
|
# implement
|
|
405
425
|
|
|
406
|
-
def
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if not self._is_resolved():
|
|
410
|
-
self.dependencies = []
|
|
411
|
-
|
|
412
|
-
provider = Providers.get_provider(self.host)
|
|
413
|
-
if self.add_dependency(provider):
|
|
414
|
-
provider.resolve(context)
|
|
415
|
-
|
|
416
|
-
else: # check if the dependencies crate a cycle
|
|
417
|
-
context.add(*self.dependencies)
|
|
418
|
-
|
|
419
|
-
return self
|
|
426
|
+
def get_dependencies(self) -> (list[Type],int):
|
|
427
|
+
return [self.host],1
|
|
420
428
|
|
|
421
429
|
def create(self, environment: Environment, *args):
|
|
422
430
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
423
431
|
|
|
424
432
|
return environment.created(args[0].create())
|
|
425
433
|
|
|
434
|
+
def report(self) -> str:
|
|
435
|
+
return f"{self.host.__name__}.create"
|
|
436
|
+
|
|
426
437
|
def __str__(self):
|
|
427
438
|
return f"FactoryInstanceProvider({self.host.__name__} -> {self.type.__name__})"
|
|
428
439
|
|
|
429
440
|
|
|
430
441
|
class Lifecycle(Enum):
|
|
431
442
|
"""
|
|
432
|
-
This enum defines the lifecycle
|
|
443
|
+
This enum defines the lifecycle phases that can be processed by lifecycle processors.
|
|
433
444
|
"""
|
|
434
445
|
|
|
435
446
|
__slots__ = []
|
|
@@ -441,7 +452,7 @@ class Lifecycle(Enum):
|
|
|
441
452
|
|
|
442
453
|
class LifecycleProcessor(ABC):
|
|
443
454
|
"""
|
|
444
|
-
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.
|
|
445
456
|
"""
|
|
446
457
|
__slots__ = [
|
|
447
458
|
"order"
|
|
@@ -481,16 +492,57 @@ class Providers:
|
|
|
481
492
|
"""
|
|
482
493
|
# local class
|
|
483
494
|
|
|
484
|
-
class
|
|
485
|
-
__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)
|
|
486
524
|
|
|
487
|
-
|
|
488
|
-
|
|
525
|
+
if any(issubclass(provider.get_type(), dependency.get_type()) for dependency in provider_dependencies):
|
|
526
|
+
return None
|
|
489
527
|
|
|
490
|
-
|
|
528
|
+
provider_dependencies.append(provider)
|
|
529
|
+
|
|
530
|
+
return provider
|
|
531
|
+
|
|
532
|
+
def next(self):
|
|
533
|
+
self.dependencies.clear()
|
|
534
|
+
|
|
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):
|
|
491
543
|
for provider in providers:
|
|
492
544
|
if next((p for p in self.dependencies if p.get_type() is provider.get_type()), None) is not None:
|
|
493
|
-
raise
|
|
545
|
+
raise DIRegistrationException(self.cycle_report(provider))
|
|
494
546
|
|
|
495
547
|
self.dependencies.append(provider)
|
|
496
548
|
|
|
@@ -504,19 +556,17 @@ class Providers:
|
|
|
504
556
|
|
|
505
557
|
first = False
|
|
506
558
|
|
|
507
|
-
cycle += f"{p.
|
|
559
|
+
cycle += f"{p.report()}"
|
|
508
560
|
|
|
509
|
-
cycle += f" <> {provider.
|
|
561
|
+
cycle += f" <> {provider.report()}"
|
|
510
562
|
|
|
511
563
|
return cycle
|
|
512
564
|
|
|
513
565
|
|
|
514
566
|
# class properties
|
|
515
567
|
|
|
516
|
-
check: list[AbstractInstanceProvider] = []
|
|
517
|
-
|
|
518
|
-
providers : Dict[Type,AbstractInstanceProvider] = {}
|
|
519
|
-
cache: Dict[Type, AbstractInstanceProvider] = {}
|
|
568
|
+
check : list[AbstractInstanceProvider] = []
|
|
569
|
+
providers : Dict[Type,list[AbstractInstanceProvider]] = {}
|
|
520
570
|
|
|
521
571
|
resolved = False
|
|
522
572
|
|
|
@@ -524,7 +574,58 @@ class Providers:
|
|
|
524
574
|
def register(cls, provider: AbstractInstanceProvider):
|
|
525
575
|
Environment.logger.debug("register provider %s(%s)", provider.get_type().__qualname__, provider.get_type().__name__)
|
|
526
576
|
|
|
527
|
-
|
|
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
|
|
528
629
|
|
|
529
630
|
def is_injectable(type: Type) -> bool:
|
|
530
631
|
if type is object:
|
|
@@ -533,27 +634,21 @@ class Providers:
|
|
|
533
634
|
if inspect.isabstract(type):
|
|
534
635
|
return False
|
|
535
636
|
|
|
536
|
-
#for decorator in Decorators.get(type):
|
|
537
|
-
# if decorator.decorator is injectable:
|
|
538
|
-
# return True
|
|
539
|
-
|
|
540
|
-
# darn
|
|
541
|
-
|
|
542
637
|
return True
|
|
543
638
|
|
|
544
639
|
def cache_provider_for_type(provider: AbstractInstanceProvider, type: Type):
|
|
545
|
-
existing_provider =
|
|
640
|
+
existing_provider = cache.get(type)
|
|
546
641
|
if existing_provider is None:
|
|
547
|
-
|
|
642
|
+
cache[type] = provider
|
|
548
643
|
|
|
549
644
|
else:
|
|
550
645
|
if type is provider.get_type():
|
|
551
|
-
raise
|
|
646
|
+
raise ProviderCollisionException(f"type {type.__name__} already registered", existing_provider, provider)
|
|
552
647
|
|
|
553
648
|
if isinstance(existing_provider, AmbiguousProvider):
|
|
554
649
|
cast(AmbiguousProvider, existing_provider).add_provider(provider)
|
|
555
650
|
else:
|
|
556
|
-
|
|
651
|
+
cache[type] = AmbiguousProvider(type, existing_provider, provider)
|
|
557
652
|
|
|
558
653
|
# recursion
|
|
559
654
|
|
|
@@ -561,35 +656,35 @@ class Providers:
|
|
|
561
656
|
if is_injectable(super_class):
|
|
562
657
|
cache_provider_for_type(provider, super_class)
|
|
563
658
|
|
|
564
|
-
#
|
|
659
|
+
# filter conditional providers and fill base classes as well
|
|
565
660
|
|
|
566
|
-
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)
|
|
567
665
|
|
|
568
|
-
|
|
666
|
+
# replace by EnvironmentInstanceProvider
|
|
569
667
|
|
|
570
|
-
|
|
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
|
|
571
675
|
|
|
572
|
-
|
|
676
|
+
result[provider_type] = environment_provider
|
|
573
677
|
|
|
574
|
-
|
|
575
|
-
def resolve(cls):
|
|
576
|
-
for provider in Providers.check:
|
|
577
|
-
provider.resolve(Providers.Context())
|
|
678
|
+
# and resolve
|
|
578
679
|
|
|
579
|
-
Providers.
|
|
680
|
+
provider_context = Providers.ResolveContext(result)
|
|
681
|
+
for provider in mapped.values():
|
|
682
|
+
provider.resolve(provider_context)
|
|
683
|
+
provider_context.next() # clear dependencies
|
|
580
684
|
|
|
581
|
-
|
|
582
|
-
def report(cls):
|
|
583
|
-
for provider in Providers.cache.values():
|
|
584
|
-
print(f"provider {provider.get_type().__qualname__}")
|
|
585
|
-
|
|
586
|
-
@classmethod
|
|
587
|
-
def get_provider(cls, type: Type) -> AbstractInstanceProvider:
|
|
588
|
-
provider = Providers.cache.get(type, None)
|
|
589
|
-
if provider is None:
|
|
590
|
-
raise InjectorException(f"{type.__name__} not registered as injectable")
|
|
685
|
+
# done
|
|
591
686
|
|
|
592
|
-
return
|
|
687
|
+
return result
|
|
593
688
|
|
|
594
689
|
def register_factories(cls: Type):
|
|
595
690
|
descriptor = TypeDescriptor.for_type(cls)
|
|
@@ -597,7 +692,11 @@ def register_factories(cls: Type):
|
|
|
597
692
|
for method in descriptor.get_methods():
|
|
598
693
|
if method.has_decorator(create):
|
|
599
694
|
create_decorator = method.get_decorator(create)
|
|
600
|
-
|
|
695
|
+
return_type = method.return_type
|
|
696
|
+
if return_type is None:
|
|
697
|
+
raise DIRegistrationException(f"{cls.__name__}.{method.method.__name__} expected to have a return type")
|
|
698
|
+
|
|
699
|
+
Providers.register(FunctionInstanceProvider(cls, method.method, return_type, create_decorator.args[0],
|
|
601
700
|
create_decorator.args[1]))
|
|
602
701
|
def order(prio = 0):
|
|
603
702
|
def decorator(cls):
|
|
@@ -686,8 +785,6 @@ def environment(imports: Optional[list[Type]] = None):
|
|
|
686
785
|
Decorators.add(cls, environment, imports)
|
|
687
786
|
Decorators.add(cls, injectable) # do we need that?
|
|
688
787
|
|
|
689
|
-
register_factories(cls)
|
|
690
|
-
|
|
691
788
|
return cls
|
|
692
789
|
|
|
693
790
|
return decorator
|
|
@@ -712,6 +809,49 @@ def inject_environment():
|
|
|
712
809
|
|
|
713
810
|
return decorator
|
|
714
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
|
+
|
|
715
855
|
class Environment:
|
|
716
856
|
"""
|
|
717
857
|
Central class that manages the lifecycle of instances and their dependencies.
|
|
@@ -728,12 +868,13 @@ class Environment:
|
|
|
728
868
|
"providers",
|
|
729
869
|
"lifecycle_processors",
|
|
730
870
|
"parent",
|
|
871
|
+
"features",
|
|
731
872
|
"instances"
|
|
732
873
|
]
|
|
733
874
|
|
|
734
875
|
# constructor
|
|
735
876
|
|
|
736
|
-
def __init__(self, env: Type, parent : Optional[Environment] = None):
|
|
877
|
+
def __init__(self, env: Type, features: list[str] = [], parent : Optional[Environment] = None):
|
|
737
878
|
"""
|
|
738
879
|
Creates a new Environment instance.
|
|
739
880
|
|
|
@@ -741,27 +882,34 @@ class Environment:
|
|
|
741
882
|
env (Type): The environment class that controls the scanning of managed objects.
|
|
742
883
|
parent (Optional[Environment]): Optional parent environment, whose objects are inherited.
|
|
743
884
|
"""
|
|
885
|
+
|
|
886
|
+
Environment.logger.debug("create environment for class %s", env.__qualname__)
|
|
887
|
+
|
|
744
888
|
# initialize
|
|
745
889
|
|
|
746
890
|
self.type = env
|
|
747
891
|
self.parent = parent
|
|
748
|
-
if self.parent is None and env is not
|
|
749
|
-
self.parent =
|
|
892
|
+
if self.parent is None and env is not Boot:
|
|
893
|
+
self.parent = Boot.get_environment() # inherit environment including its manged instances!
|
|
750
894
|
|
|
895
|
+
self.features = features
|
|
751
896
|
self.providers: Dict[Type, AbstractInstanceProvider] = {}
|
|
752
897
|
self.lifecycle_processors: list[LifecycleProcessor] = []
|
|
753
898
|
|
|
754
899
|
if self.parent is not None:
|
|
755
900
|
self.providers |= self.parent.providers
|
|
756
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()
|
|
757
905
|
|
|
758
906
|
self.instances = []
|
|
759
907
|
|
|
760
908
|
Environment.instance = self
|
|
761
909
|
|
|
762
|
-
#
|
|
910
|
+
# filter conditional providers
|
|
763
911
|
|
|
764
|
-
Providers.
|
|
912
|
+
overall_providers = Providers.filter(self)
|
|
765
913
|
|
|
766
914
|
loaded = set()
|
|
767
915
|
|
|
@@ -770,12 +918,6 @@ class Environment:
|
|
|
770
918
|
|
|
771
919
|
self.providers[type] = provider
|
|
772
920
|
|
|
773
|
-
# bootstrapping hack, they will be overwritten by the "real" providers
|
|
774
|
-
|
|
775
|
-
if env is BootEnvironment:
|
|
776
|
-
add_provider(SingletonScope, SingletonScopeInstanceProvider())
|
|
777
|
-
add_provider(RequestScope, RequestScopeInstanceProvider())
|
|
778
|
-
|
|
779
921
|
def load_environment(env: Type):
|
|
780
922
|
if env not in loaded:
|
|
781
923
|
Environment.logger.debug("load environment %s", env.__qualname__)
|
|
@@ -786,7 +928,7 @@ class Environment:
|
|
|
786
928
|
|
|
787
929
|
decorator = TypeDescriptor.for_type(env).get_decorator(environment)
|
|
788
930
|
if decorator is None:
|
|
789
|
-
raise
|
|
931
|
+
raise DIRegistrationException(f"{env.__name__} is not an environment class")
|
|
790
932
|
|
|
791
933
|
scan = env.__module__
|
|
792
934
|
if "." in scan:
|
|
@@ -797,39 +939,18 @@ class Environment:
|
|
|
797
939
|
for import_environment in decorator.args[0] or []:
|
|
798
940
|
load_environment(import_environment)
|
|
799
941
|
|
|
800
|
-
# load providers
|
|
801
|
-
|
|
802
|
-
local_providers = {type: provider for type, provider in Providers.cache.items() if provider.get_module().startswith(scan)}
|
|
803
|
-
|
|
804
|
-
# register providers
|
|
805
|
-
|
|
806
|
-
# make sure, that for every type only a single EnvironmentInstanceProvider is created!
|
|
807
|
-
# otherwise inheritance will fuck it up
|
|
808
|
-
|
|
809
|
-
environment_providers : dict[AbstractInstanceProvider, EnvironmentInstanceProvider] = {}
|
|
810
|
-
|
|
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
|
|
816
|
-
|
|
817
|
-
add_provider(type, environment_provider)
|
|
942
|
+
# filter and load providers according to their module
|
|
818
943
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
# 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
|
|
825
948
|
|
|
826
|
-
|
|
827
|
-
else:
|
|
828
|
-
return []
|
|
949
|
+
load_environment(env)
|
|
829
950
|
|
|
830
951
|
# construct eager objects for local providers
|
|
831
952
|
|
|
832
|
-
for provider in
|
|
953
|
+
for provider in set(self.providers.values()):
|
|
833
954
|
if provider.is_eager():
|
|
834
955
|
provider.create(self)
|
|
835
956
|
|
|
@@ -840,6 +961,9 @@ class Environment:
|
|
|
840
961
|
|
|
841
962
|
# internal
|
|
842
963
|
|
|
964
|
+
def has_feature(self, feature: str) -> bool:
|
|
965
|
+
return feature in self.features
|
|
966
|
+
|
|
843
967
|
def execute_processors(self, lifecycle: Lifecycle, instance: T) -> T:
|
|
844
968
|
for processor in self.lifecycle_processors:
|
|
845
969
|
processor.process_lifecycle(lifecycle, instance, self)
|
|
@@ -889,8 +1013,9 @@ class Environment:
|
|
|
889
1013
|
|
|
890
1014
|
builder.append("Providers \n")
|
|
891
1015
|
for result_type, provider in self.providers.items():
|
|
892
|
-
if
|
|
893
|
-
|
|
1016
|
+
if isinstance(provider, EnvironmentInstanceProvider):
|
|
1017
|
+
if cast(EnvironmentInstanceProvider, provider).environment is self:
|
|
1018
|
+
builder.append(f"- {result_type.__name__}: {provider.report()}\n")
|
|
894
1019
|
|
|
895
1020
|
# instances
|
|
896
1021
|
|
|
@@ -921,7 +1046,7 @@ class Environment:
|
|
|
921
1046
|
|
|
922
1047
|
def get(self, type: Type[T]) -> T:
|
|
923
1048
|
"""
|
|
924
|
-
|
|
1049
|
+
Create or return a cached instance for the given type.
|
|
925
1050
|
|
|
926
1051
|
Arguments:
|
|
927
1052
|
type (Type): The desired type
|
|
@@ -931,10 +1056,13 @@ class Environment:
|
|
|
931
1056
|
provider = self.providers.get(type, None)
|
|
932
1057
|
if provider is None:
|
|
933
1058
|
Environment.logger.error("%s is not supported", type)
|
|
934
|
-
raise
|
|
1059
|
+
raise DIRuntimeException(f"{type} is not supported")
|
|
935
1060
|
|
|
936
1061
|
return provider.create(self)
|
|
937
1062
|
|
|
1063
|
+
def __str__(self):
|
|
1064
|
+
return f"Environment({self.type.__name__})"
|
|
1065
|
+
|
|
938
1066
|
class LifecycleCallable:
|
|
939
1067
|
__slots__ = [
|
|
940
1068
|
"decorator",
|
|
@@ -981,6 +1109,7 @@ class AbstractCallableProcessor(LifecycleProcessor):
|
|
|
981
1109
|
|
|
982
1110
|
# static data
|
|
983
1111
|
|
|
1112
|
+
lock = threading.RLock()
|
|
984
1113
|
callables : Dict[object, LifecycleCallable] = {}
|
|
985
1114
|
cache : Dict[Type, list[list[AbstractCallableProcessor.MethodCall]]] = {}
|
|
986
1115
|
|
|
@@ -1018,8 +1147,11 @@ class AbstractCallableProcessor(LifecycleProcessor):
|
|
|
1018
1147
|
def callables_for(cls, type: Type) -> list[list[AbstractCallableProcessor.MethodCall]]:
|
|
1019
1148
|
callables = AbstractCallableProcessor.cache.get(type, None)
|
|
1020
1149
|
if callables is None:
|
|
1021
|
-
|
|
1022
|
-
|
|
1150
|
+
with AbstractCallableProcessor.lock:
|
|
1151
|
+
callables = AbstractCallableProcessor.cache.get(type, None)
|
|
1152
|
+
if callables is None:
|
|
1153
|
+
callables = AbstractCallableProcessor.compute_callables(type)
|
|
1154
|
+
AbstractCallableProcessor.cache[type] = callables
|
|
1023
1155
|
|
|
1024
1156
|
return callables
|
|
1025
1157
|
|
|
@@ -1113,20 +1245,20 @@ class InjectLifecycleCallable(LifecycleCallable):
|
|
|
1113
1245
|
def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
|
|
1114
1246
|
return [environment.get(type) for type in method.param_types]
|
|
1115
1247
|
|
|
1116
|
-
def scope(name: str):
|
|
1248
|
+
def scope(name: str, register=True):
|
|
1117
1249
|
def decorator(cls):
|
|
1118
1250
|
Scopes.register(cls, name)
|
|
1119
1251
|
|
|
1120
1252
|
Decorators.add(cls, scope)
|
|
1121
|
-
# Decorators.add(cls, injectable)
|
|
1122
1253
|
|
|
1123
|
-
|
|
1254
|
+
if register:
|
|
1255
|
+
Providers.register(ClassInstanceProvider(cls, eager=True, scope="request"))
|
|
1124
1256
|
|
|
1125
1257
|
return cls
|
|
1126
1258
|
|
|
1127
1259
|
return decorator
|
|
1128
1260
|
|
|
1129
|
-
@scope("request")
|
|
1261
|
+
@scope("request", register=False)
|
|
1130
1262
|
class RequestScope(Scope):
|
|
1131
1263
|
# properties
|
|
1132
1264
|
|
|
@@ -1138,7 +1270,7 @@ class RequestScope(Scope):
|
|
|
1138
1270
|
def get(self, provider: AbstractInstanceProvider, environment: Environment, arg_provider: Callable[[],list]):
|
|
1139
1271
|
return provider.create(environment, *arg_provider())
|
|
1140
1272
|
|
|
1141
|
-
@scope("singleton")
|
|
1273
|
+
@scope("singleton", register=False)
|
|
1142
1274
|
class SingletonScope(Scope):
|
|
1143
1275
|
# properties
|
|
1144
1276
|
|
|
@@ -1164,7 +1296,7 @@ class SingletonScope(Scope):
|
|
|
1164
1296
|
self.value = provider.create(environment, *arg_provider())
|
|
1165
1297
|
|
|
1166
1298
|
return self.value
|
|
1167
|
-
|
|
1299
|
+
|
|
1168
1300
|
@scope("thread")
|
|
1169
1301
|
class ThreadScope(Scope):
|
|
1170
1302
|
__slots__ = [
|
|
@@ -1185,17 +1317,17 @@ class ThreadScope(Scope):
|
|
|
1185
1317
|
# internal class that is required to import technical instance providers
|
|
1186
1318
|
|
|
1187
1319
|
@environment()
|
|
1188
|
-
class
|
|
1320
|
+
class Boot:
|
|
1189
1321
|
# class
|
|
1190
1322
|
|
|
1191
1323
|
environment = None
|
|
1192
1324
|
|
|
1193
1325
|
@classmethod
|
|
1194
|
-
def
|
|
1195
|
-
if
|
|
1196
|
-
|
|
1326
|
+
def get_environment(cls):
|
|
1327
|
+
if Boot.environment is None:
|
|
1328
|
+
Boot.environment = Environment(Boot)
|
|
1197
1329
|
|
|
1198
|
-
return
|
|
1330
|
+
return Boot.environment
|
|
1199
1331
|
|
|
1200
1332
|
# properties
|
|
1201
1333
|
|