aspyx 1.2.0__py3-none-any.whl → 1.4.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/__init__.py +0 -0
- aspyx/di/__init__.py +8 -4
- aspyx/di/aop/aop.py +181 -74
- aspyx/di/configuration/env_configuration_source.py +1 -1
- aspyx/di/configuration/yaml_configuration_source.py +1 -1
- aspyx/di/di.py +389 -198
- aspyx/di/threading/__init__.py +11 -0
- aspyx/di/threading/synchronized.py +49 -0
- aspyx/exception/__init__.py +10 -0
- aspyx/exception/exception_manager.py +168 -0
- aspyx/reflection/reflection.py +41 -2
- aspyx/threading/__init__.py +10 -0
- aspyx/threading/thread_local.py +47 -0
- aspyx/{di/util → util}/stringbuilder.py +11 -0
- {aspyx-1.2.0.dist-info → aspyx-1.4.0.dist-info}/METADATA +254 -52
- aspyx-1.4.0.dist-info/RECORD +25 -0
- aspyx-1.2.0.dist-info/RECORD +0 -18
- /aspyx/{di/util → util}/__init__.py +0 -0
- {aspyx-1.2.0.dist-info → aspyx-1.4.0.dist-info}/WHEEL +0 -0
- {aspyx-1.2.0.dist-info → aspyx-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {aspyx-1.2.0.dist-info → aspyx-1.4.0.dist-info}/top_level.txt +0 -0
aspyx/di/di.py
CHANGED
|
@@ -5,13 +5,16 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
import inspect
|
|
7
7
|
import logging
|
|
8
|
+
import importlib
|
|
9
|
+
import pkgutil
|
|
10
|
+
import sys
|
|
8
11
|
|
|
9
12
|
from abc import abstractmethod, ABC
|
|
10
13
|
from enum import Enum
|
|
11
14
|
import threading
|
|
12
|
-
from typing import Type, Dict, TypeVar, Generic, Optional, cast, Callable
|
|
15
|
+
from typing import Type, Dict, TypeVar, Generic, Optional, cast, Callable, TypedDict
|
|
13
16
|
|
|
14
|
-
from aspyx.
|
|
17
|
+
from aspyx.util import StringBuilder
|
|
15
18
|
from aspyx.reflection import Decorators, TypeDescriptor, DecoratorDescriptor
|
|
16
19
|
|
|
17
20
|
T = TypeVar("T")
|
|
@@ -31,16 +34,31 @@ class DIException(Exception):
|
|
|
31
34
|
"""
|
|
32
35
|
Exception raised for errors in the injector.
|
|
33
36
|
"""
|
|
37
|
+
def __init__(self, message: str):
|
|
38
|
+
super().__init__(message)
|
|
34
39
|
|
|
35
40
|
class DIRegistrationException(DIException):
|
|
36
41
|
"""
|
|
37
42
|
Exception raised during the registration of dependencies.
|
|
38
43
|
"""
|
|
44
|
+
def __init__(self, message: str):
|
|
45
|
+
super().__init__(message)
|
|
46
|
+
|
|
47
|
+
class ProviderCollisionException(DIRegistrationException):
|
|
48
|
+
def __init__(self, message: str, *providers: AbstractInstanceProvider):
|
|
49
|
+
super().__init__(message)
|
|
50
|
+
|
|
51
|
+
self.providers = providers
|
|
52
|
+
|
|
53
|
+
def __str__(self):
|
|
54
|
+
return f"[{self.args[0]} {self.providers[1].location()} collides with {self.providers[0].location()}"
|
|
39
55
|
|
|
40
56
|
class DIRuntimeException(DIException):
|
|
41
57
|
"""
|
|
42
58
|
Exception raised during the runtime.
|
|
43
59
|
"""
|
|
60
|
+
def __init__(self, message: str):
|
|
61
|
+
super().__init__(message)
|
|
44
62
|
|
|
45
63
|
class AbstractInstanceProvider(ABC, Generic[T]):
|
|
46
64
|
"""
|
|
@@ -65,17 +83,23 @@ class AbstractInstanceProvider(ABC, Generic[T]):
|
|
|
65
83
|
def get_scope(self) -> str:
|
|
66
84
|
pass
|
|
67
85
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
pass
|
|
86
|
+
def get_dependencies(self) -> (list[Type],int):
|
|
87
|
+
return [],1
|
|
71
88
|
|
|
72
89
|
@abstractmethod
|
|
73
90
|
def create(self, environment: Environment, *args):
|
|
74
91
|
pass
|
|
75
92
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
def report(self) -> str:
|
|
94
|
+
return str(self)
|
|
95
|
+
|
|
96
|
+
def location(self) -> str:
|
|
97
|
+
host = self.get_host()
|
|
98
|
+
|
|
99
|
+
file = inspect.getfile(host)
|
|
100
|
+
line = inspect.getsourcelines(host)[1]
|
|
101
|
+
|
|
102
|
+
return f"{file}:{line}"
|
|
79
103
|
|
|
80
104
|
def check_factories(self):
|
|
81
105
|
pass
|
|
@@ -89,8 +113,7 @@ class InstanceProvider(AbstractInstanceProvider):
|
|
|
89
113
|
"host",
|
|
90
114
|
"type",
|
|
91
115
|
"eager",
|
|
92
|
-
"scope"
|
|
93
|
-
"dependencies"
|
|
116
|
+
"scope"
|
|
94
117
|
]
|
|
95
118
|
|
|
96
119
|
# constructor
|
|
@@ -100,12 +123,6 @@ class InstanceProvider(AbstractInstanceProvider):
|
|
|
100
123
|
self.type = t
|
|
101
124
|
self.eager = eager
|
|
102
125
|
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
126
|
|
|
110
127
|
# implement AbstractInstanceProvider
|
|
111
128
|
|
|
@@ -113,10 +130,6 @@ class InstanceProvider(AbstractInstanceProvider):
|
|
|
113
130
|
return self.host
|
|
114
131
|
|
|
115
132
|
def check_factories(self):
|
|
116
|
-
#register_factories(self.host)
|
|
117
|
-
pass
|
|
118
|
-
|
|
119
|
-
def resolve(self, context: Providers.Context):
|
|
120
133
|
pass
|
|
121
134
|
|
|
122
135
|
def get_module(self) -> str:
|
|
@@ -131,22 +144,11 @@ class InstanceProvider(AbstractInstanceProvider):
|
|
|
131
144
|
def get_scope(self) -> str:
|
|
132
145
|
return self.scope
|
|
133
146
|
|
|
134
|
-
def get_dependencies(self) -> list[AbstractInstanceProvider]:
|
|
135
|
-
return self.dependencies
|
|
136
|
-
|
|
137
147
|
# public
|
|
138
148
|
|
|
139
149
|
def module(self) -> str:
|
|
140
150
|
return self.host.__module__
|
|
141
151
|
|
|
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
152
|
@abstractmethod
|
|
151
153
|
def create(self, environment: Environment, *args):
|
|
152
154
|
pass
|
|
@@ -159,6 +161,13 @@ class SingletonScopeInstanceProvider(InstanceProvider):
|
|
|
159
161
|
def create(self, environment: Environment, *args):
|
|
160
162
|
return SingletonScope()
|
|
161
163
|
|
|
164
|
+
class EnvironmentScopeInstanceProvider(InstanceProvider):
|
|
165
|
+
def __init__(self):
|
|
166
|
+
super().__init__(SingletonScopeInstanceProvider, SingletonScope, False, "request") # TODO?
|
|
167
|
+
|
|
168
|
+
def create(self, environment: Environment, *args):
|
|
169
|
+
return EnvironmentScope()
|
|
170
|
+
|
|
162
171
|
class RequestScopeInstanceProvider(InstanceProvider):
|
|
163
172
|
def __init__(self):
|
|
164
173
|
super().__init__(RequestScopeInstanceProvider, RequestScope, False, "singleton")
|
|
@@ -166,7 +175,6 @@ class RequestScopeInstanceProvider(InstanceProvider):
|
|
|
166
175
|
def create(self, environment: Environment, *args):
|
|
167
176
|
return RequestScope()
|
|
168
177
|
|
|
169
|
-
|
|
170
178
|
class AmbiguousProvider(AbstractInstanceProvider):
|
|
171
179
|
"""
|
|
172
180
|
An AmbiguousProvider covers all cases, where fetching a class would lead to an ambiguity exception.
|
|
@@ -204,15 +212,12 @@ class AmbiguousProvider(AbstractInstanceProvider):
|
|
|
204
212
|
def get_scope(self) -> str:
|
|
205
213
|
return "singleton"
|
|
206
214
|
|
|
207
|
-
def get_dependencies(self) -> list[AbstractInstanceProvider]:
|
|
208
|
-
return []
|
|
209
|
-
|
|
210
|
-
def resolve(self, context: Providers.Context):
|
|
211
|
-
pass
|
|
212
|
-
|
|
213
215
|
def create(self, environment: Environment, *args):
|
|
214
216
|
raise DIException(f"multiple candidates for type {self.type}")
|
|
215
217
|
|
|
218
|
+
def report(self) -> str:
|
|
219
|
+
return "ambiguous: " + ",".join([provider.report() for provider in self.providers])
|
|
220
|
+
|
|
216
221
|
def __str__(self):
|
|
217
222
|
return f"AmbiguousProvider({self.type})"
|
|
218
223
|
|
|
@@ -257,8 +262,8 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
257
262
|
__slots__ = [
|
|
258
263
|
"environment",
|
|
259
264
|
"scope_instance",
|
|
260
|
-
"provider",
|
|
261
265
|
"dependencies",
|
|
266
|
+
"provider"
|
|
262
267
|
]
|
|
263
268
|
|
|
264
269
|
# constructor
|
|
@@ -268,14 +273,30 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
268
273
|
|
|
269
274
|
self.environment = environment
|
|
270
275
|
self.provider = provider
|
|
271
|
-
self.dependencies
|
|
272
|
-
|
|
276
|
+
self.dependencies = []
|
|
273
277
|
self.scope_instance = Scopes.get(provider.get_scope(), environment)
|
|
274
278
|
|
|
275
279
|
# implement
|
|
276
280
|
|
|
277
|
-
def resolve(self, context: Providers.
|
|
278
|
-
|
|
281
|
+
def resolve(self, context: Providers.ResolveContext):
|
|
282
|
+
context.add(self)
|
|
283
|
+
|
|
284
|
+
if not context.is_resolved(self):
|
|
285
|
+
context.provider_dependencies[self] = [] #?
|
|
286
|
+
|
|
287
|
+
type_and_params = self.provider.get_dependencies()
|
|
288
|
+
params = type_and_params[1]
|
|
289
|
+
for type in type_and_params[0]:
|
|
290
|
+
if params > 0:
|
|
291
|
+
params -= 1
|
|
292
|
+
self.dependencies.append(context.get_provider(type))
|
|
293
|
+
|
|
294
|
+
provider = context.add_provider_dependency(self, type)
|
|
295
|
+
if provider is not None:
|
|
296
|
+
provider.resolve(context)
|
|
297
|
+
|
|
298
|
+
else:
|
|
299
|
+
context.add(*context.get_provider_dependencies(self))
|
|
279
300
|
|
|
280
301
|
def get_module(self) -> str:
|
|
281
302
|
return self.provider.get_module()
|
|
@@ -289,18 +310,10 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
289
310
|
def get_scope(self) -> str:
|
|
290
311
|
return self.provider.get_scope()
|
|
291
312
|
|
|
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()} ")
|
|
313
|
+
def report(self) -> str:
|
|
314
|
+
return self.provider.report()
|
|
299
315
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
def get_dependencies(self) -> list[AbstractInstanceProvider]:
|
|
303
|
-
return self.provider.get_dependencies()
|
|
316
|
+
# own logic
|
|
304
317
|
|
|
305
318
|
def create(self, environment: Environment, *args):
|
|
306
319
|
return self.scope_instance.get(self.provider, self.environment, lambda: [provider.create(environment) for provider in self.dependencies]) # already scope property!
|
|
@@ -329,41 +342,36 @@ class ClassInstanceProvider(InstanceProvider):
|
|
|
329
342
|
def check_factories(self):
|
|
330
343
|
register_factories(self.host)
|
|
331
344
|
|
|
332
|
-
def
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if not self._is_resolved():
|
|
336
|
-
self.dependencies = []
|
|
345
|
+
def get_dependencies(self) -> (list[Type],int):
|
|
346
|
+
types : list[Type] = []
|
|
337
347
|
|
|
338
|
-
|
|
348
|
+
# check constructor
|
|
339
349
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
350
|
+
init = TypeDescriptor.for_type(self.type).get_method("__init__")
|
|
351
|
+
if init is None:
|
|
352
|
+
raise DIRegistrationException(f"{self.type.__name__} does not implement __init__")
|
|
343
353
|
|
|
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)
|
|
354
|
+
self.params = len(init.param_types)
|
|
355
|
+
for param in init.param_types:
|
|
356
|
+
types.append(param)
|
|
349
357
|
|
|
350
|
-
|
|
358
|
+
# check @inject
|
|
351
359
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
360
|
+
for method in TypeDescriptor.for_type(self.type).get_methods():
|
|
361
|
+
if method.has_decorator(inject):
|
|
362
|
+
for param in method.param_types:
|
|
363
|
+
types.append(param)
|
|
356
364
|
|
|
357
|
-
|
|
358
|
-
provider.resolve(context)
|
|
359
|
-
else: # check if the dependencies create a cycle
|
|
360
|
-
context.add(*self.dependencies)
|
|
365
|
+
return (types, self.params)
|
|
361
366
|
|
|
362
367
|
def create(self, environment: Environment, *args):
|
|
363
368
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
364
369
|
|
|
365
370
|
return environment.created(self.type(*args[:self.params]))
|
|
366
371
|
|
|
372
|
+
def report(self) -> str:
|
|
373
|
+
return f"{self.host.__name__}.__init__"
|
|
374
|
+
|
|
367
375
|
# object
|
|
368
376
|
|
|
369
377
|
def __str__(self):
|
|
@@ -387,25 +395,19 @@ class FunctionInstanceProvider(InstanceProvider):
|
|
|
387
395
|
|
|
388
396
|
# implement
|
|
389
397
|
|
|
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)
|
|
398
|
+
def get_dependencies(self) -> (list[Type],int):
|
|
399
|
+
return [self.host], 1
|
|
401
400
|
|
|
402
401
|
def create(self, environment: Environment, *args):
|
|
403
402
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
404
403
|
|
|
405
|
-
instance = self.method(*args)
|
|
404
|
+
instance = self.method(*args) # args[0]=self
|
|
406
405
|
|
|
407
406
|
return environment.created(instance)
|
|
408
407
|
|
|
408
|
+
def report(self) -> str:
|
|
409
|
+
return f"{self.host.__name__}.{self.method.__name__}"
|
|
410
|
+
|
|
409
411
|
def __str__(self):
|
|
410
412
|
return f"FunctionInstanceProvider({self.host.__name__}.{self.method.__name__} -> {self.type.__name__})"
|
|
411
413
|
|
|
@@ -429,32 +431,24 @@ class FactoryInstanceProvider(InstanceProvider):
|
|
|
429
431
|
|
|
430
432
|
# implement
|
|
431
433
|
|
|
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
|
|
434
|
+
def get_dependencies(self) -> (list[Type],int):
|
|
435
|
+
return [self.host],1
|
|
445
436
|
|
|
446
437
|
def create(self, environment: Environment, *args):
|
|
447
438
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
448
439
|
|
|
449
440
|
return environment.created(args[0].create())
|
|
450
441
|
|
|
442
|
+
def report(self) -> str:
|
|
443
|
+
return f"{self.host.__name__}.create"
|
|
444
|
+
|
|
451
445
|
def __str__(self):
|
|
452
446
|
return f"FactoryInstanceProvider({self.host.__name__} -> {self.type.__name__})"
|
|
453
447
|
|
|
454
448
|
|
|
455
449
|
class Lifecycle(Enum):
|
|
456
450
|
"""
|
|
457
|
-
This enum defines the lifecycle
|
|
451
|
+
This enum defines the lifecycle phases that can be processed by lifecycle processors.
|
|
458
452
|
"""
|
|
459
453
|
|
|
460
454
|
__slots__ = []
|
|
@@ -466,7 +460,7 @@ class Lifecycle(Enum):
|
|
|
466
460
|
|
|
467
461
|
class LifecycleProcessor(ABC):
|
|
468
462
|
"""
|
|
469
|
-
A LifecycleProcessor is used to perform any side effects on managed objects during
|
|
463
|
+
A LifecycleProcessor is used to perform any side effects on managed objects during different lifecycle phases.
|
|
470
464
|
"""
|
|
471
465
|
__slots__ = [
|
|
472
466
|
"order"
|
|
@@ -506,13 +500,54 @@ class Providers:
|
|
|
506
500
|
"""
|
|
507
501
|
# local class
|
|
508
502
|
|
|
509
|
-
class
|
|
510
|
-
__slots__ = [
|
|
503
|
+
class ResolveContext:
|
|
504
|
+
__slots__ = [
|
|
505
|
+
"dependencies",
|
|
506
|
+
"providers",
|
|
507
|
+
"provider_dependencies"
|
|
508
|
+
]
|
|
509
|
+
|
|
510
|
+
# constructor
|
|
511
|
+
|
|
512
|
+
def __init__(self, providers: Dict[Type, EnvironmentInstanceProvider]):
|
|
513
|
+
self.dependencies : list[EnvironmentInstanceProvider] = []
|
|
514
|
+
self.providers = providers
|
|
515
|
+
self.provider_dependencies : dict[EnvironmentInstanceProvider, list[EnvironmentInstanceProvider]] = {}
|
|
516
|
+
|
|
517
|
+
# public
|
|
518
|
+
|
|
519
|
+
def is_resolved(self, provider: EnvironmentInstanceProvider) -> bool:
|
|
520
|
+
return self.provider_dependencies.get(provider, None) is not None
|
|
521
|
+
|
|
522
|
+
def get_provider_dependencies(self, provider: EnvironmentInstanceProvider) -> list[EnvironmentInstanceProvider]:
|
|
523
|
+
return self.provider_dependencies[provider]
|
|
524
|
+
|
|
525
|
+
def add_provider_dependency(self, provider: EnvironmentInstanceProvider, type: Type) -> Optional[EnvironmentInstanceProvider]:
|
|
526
|
+
provider_dependencies = self.provider_dependencies.get(provider, None)
|
|
527
|
+
if provider_dependencies is None:
|
|
528
|
+
provider_dependencies = []
|
|
529
|
+
self.provider_dependencies[provider] = provider_dependencies
|
|
511
530
|
|
|
512
|
-
|
|
513
|
-
self.dependencies : list[AbstractInstanceProvider] = []
|
|
531
|
+
provider = self.get_provider(type)
|
|
514
532
|
|
|
515
|
-
|
|
533
|
+
if any(issubclass(provider.get_type(), dependency.get_type()) for dependency in provider_dependencies):
|
|
534
|
+
return None
|
|
535
|
+
|
|
536
|
+
provider_dependencies.append(provider)
|
|
537
|
+
|
|
538
|
+
return provider
|
|
539
|
+
|
|
540
|
+
def next(self):
|
|
541
|
+
self.dependencies.clear()
|
|
542
|
+
|
|
543
|
+
def get_provider(self, type: Type) -> EnvironmentInstanceProvider:
|
|
544
|
+
provider = self.providers.get(type, None)
|
|
545
|
+
if provider is None:
|
|
546
|
+
raise DIRegistrationException(f"Provider for {type} is not defined")
|
|
547
|
+
|
|
548
|
+
return provider
|
|
549
|
+
|
|
550
|
+
def add(self, *providers: EnvironmentInstanceProvider):
|
|
516
551
|
for provider in providers:
|
|
517
552
|
if next((p for p in self.dependencies if p.get_type() is provider.get_type()), None) is not None:
|
|
518
553
|
raise DIRegistrationException(self.cycle_report(provider))
|
|
@@ -529,19 +564,17 @@ class Providers:
|
|
|
529
564
|
|
|
530
565
|
first = False
|
|
531
566
|
|
|
532
|
-
cycle += f"{p.
|
|
567
|
+
cycle += f"{p.report()}"
|
|
533
568
|
|
|
534
|
-
cycle += f" <> {provider.
|
|
569
|
+
cycle += f" <> {provider.report()}"
|
|
535
570
|
|
|
536
571
|
return cycle
|
|
537
572
|
|
|
538
573
|
|
|
539
574
|
# class properties
|
|
540
575
|
|
|
541
|
-
check: list[AbstractInstanceProvider] = []
|
|
542
|
-
|
|
543
|
-
providers : Dict[Type,AbstractInstanceProvider] = {}
|
|
544
|
-
cache: Dict[Type, AbstractInstanceProvider] = {}
|
|
576
|
+
check : list[AbstractInstanceProvider] = []
|
|
577
|
+
providers : Dict[Type,list[AbstractInstanceProvider]] = {}
|
|
545
578
|
|
|
546
579
|
resolved = False
|
|
547
580
|
|
|
@@ -549,7 +582,64 @@ class Providers:
|
|
|
549
582
|
def register(cls, provider: AbstractInstanceProvider):
|
|
550
583
|
Environment.logger.debug("register provider %s(%s)", provider.get_type().__qualname__, provider.get_type().__name__)
|
|
551
584
|
|
|
552
|
-
|
|
585
|
+
Providers.check.append(provider)
|
|
586
|
+
candidates = Providers.providers.get(provider.get_type(), None)
|
|
587
|
+
if candidates is None:
|
|
588
|
+
Providers.providers[provider.get_type()] = [provider]
|
|
589
|
+
else:
|
|
590
|
+
candidates.append(provider)
|
|
591
|
+
|
|
592
|
+
# add factories lazily
|
|
593
|
+
|
|
594
|
+
@classmethod
|
|
595
|
+
def check_factories(cls):
|
|
596
|
+
for check in Providers.check:
|
|
597
|
+
check.check_factories()
|
|
598
|
+
|
|
599
|
+
Providers.check.clear()
|
|
600
|
+
|
|
601
|
+
@classmethod
|
|
602
|
+
def filter(cls, environment: Environment, provider_filter: Callable) -> Dict[Type,AbstractInstanceProvider]:
|
|
603
|
+
cache: Dict[Type,AbstractInstanceProvider] = {}
|
|
604
|
+
|
|
605
|
+
context: ConditionContext = {
|
|
606
|
+
"requires_feature": lambda feature : environment.has_feature(feature),
|
|
607
|
+
"requires_class": lambda clazz : cache.get(clazz, None) is not None # ? only works if the class is in the cache already?
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
Providers.check_factories() # check for additional factories
|
|
611
|
+
|
|
612
|
+
# local methods
|
|
613
|
+
|
|
614
|
+
def filter_type(clazz: Type) -> Optional[AbstractInstanceProvider]:
|
|
615
|
+
result = None
|
|
616
|
+
for provider in Providers.providers[clazz]:
|
|
617
|
+
if provider_applies(provider):
|
|
618
|
+
if result is not None:
|
|
619
|
+
raise ProviderCollisionException(f"type {clazz.__name__} already registered", result, provider)
|
|
620
|
+
|
|
621
|
+
result = provider
|
|
622
|
+
|
|
623
|
+
return result
|
|
624
|
+
|
|
625
|
+
def provider_applies(provider: AbstractInstanceProvider) -> bool:
|
|
626
|
+
# is it in the right module?
|
|
627
|
+
|
|
628
|
+
if not provider_filter(provider):
|
|
629
|
+
return False
|
|
630
|
+
|
|
631
|
+
# check conditionals
|
|
632
|
+
|
|
633
|
+
descriptor = TypeDescriptor.for_type(provider.get_host())
|
|
634
|
+
if descriptor.has_decorator(conditional):
|
|
635
|
+
conditions: list[Condition] = [*descriptor.get_decorator(conditional).args]
|
|
636
|
+
for condition in conditions:
|
|
637
|
+
if not condition.apply(context):
|
|
638
|
+
return False
|
|
639
|
+
|
|
640
|
+
return True
|
|
641
|
+
|
|
642
|
+
return True
|
|
553
643
|
|
|
554
644
|
def is_injectable(type: Type) -> bool:
|
|
555
645
|
if type is object:
|
|
@@ -558,34 +648,21 @@ class Providers:
|
|
|
558
648
|
if inspect.isabstract(type):
|
|
559
649
|
return False
|
|
560
650
|
|
|
561
|
-
#for decorator in Decorators.get(type):
|
|
562
|
-
# if decorator.decorator is injectable:
|
|
563
|
-
# return True
|
|
564
|
-
|
|
565
|
-
# darn
|
|
566
|
-
|
|
567
651
|
return True
|
|
568
652
|
|
|
569
653
|
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)
|
|
654
|
+
existing_provider = cache.get(type)
|
|
578
655
|
if existing_provider is None:
|
|
579
|
-
|
|
656
|
+
cache[type] = provider
|
|
580
657
|
|
|
581
658
|
else:
|
|
582
659
|
if type is provider.get_type():
|
|
583
|
-
raise
|
|
660
|
+
raise ProviderCollisionException(f"type {type.__name__} already registered", existing_provider, provider)
|
|
584
661
|
|
|
585
662
|
if isinstance(existing_provider, AmbiguousProvider):
|
|
586
663
|
cast(AmbiguousProvider, existing_provider).add_provider(provider)
|
|
587
664
|
else:
|
|
588
|
-
|
|
665
|
+
cache[type] = AmbiguousProvider(type, existing_provider, provider)
|
|
589
666
|
|
|
590
667
|
# recursion
|
|
591
668
|
|
|
@@ -593,39 +670,39 @@ class Providers:
|
|
|
593
670
|
if is_injectable(super_class):
|
|
594
671
|
cache_provider_for_type(provider, super_class)
|
|
595
672
|
|
|
596
|
-
#
|
|
597
|
-
|
|
598
|
-
Providers.check.append(provider)
|
|
673
|
+
# filter conditional providers and fill base classes as well
|
|
599
674
|
|
|
600
|
-
Providers.providers
|
|
675
|
+
for provider_type, _ in Providers.providers.items():
|
|
676
|
+
matching_provider = filter_type(provider_type)
|
|
677
|
+
if matching_provider is not None:
|
|
678
|
+
cache_provider_for_type(matching_provider, provider_type)
|
|
601
679
|
|
|
602
|
-
#
|
|
680
|
+
# replace by EnvironmentInstanceProvider
|
|
603
681
|
|
|
604
|
-
|
|
682
|
+
mapped = {}
|
|
683
|
+
result = {}
|
|
684
|
+
for provider_type, provider in cache.items():
|
|
685
|
+
environment_provider = mapped.get(provider, None)
|
|
686
|
+
if environment_provider is None:
|
|
687
|
+
environment_provider = EnvironmentInstanceProvider(environment, provider)
|
|
688
|
+
mapped[provider] = environment_provider
|
|
605
689
|
|
|
606
|
-
|
|
607
|
-
def resolve(cls):
|
|
690
|
+
result[provider_type] = environment_provider
|
|
608
691
|
|
|
609
|
-
|
|
610
|
-
check.check_factories()
|
|
692
|
+
# and resolve
|
|
611
693
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
check.resolve(Providers.Context())
|
|
694
|
+
providers = result
|
|
695
|
+
if environment.parent is not None:
|
|
696
|
+
providers = providers | environment.parent.providers # add parent providers
|
|
616
697
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
698
|
+
provider_context = Providers.ResolveContext(providers)
|
|
699
|
+
for provider in mapped.values():
|
|
700
|
+
provider.resolve(provider_context)
|
|
701
|
+
provider_context.next() # clear dependencies
|
|
621
702
|
|
|
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")
|
|
703
|
+
# done
|
|
627
704
|
|
|
628
|
-
return
|
|
705
|
+
return result
|
|
629
706
|
|
|
630
707
|
def register_factories(cls: Type):
|
|
631
708
|
descriptor = TypeDescriptor.for_type(cls)
|
|
@@ -750,6 +827,49 @@ def inject_environment():
|
|
|
750
827
|
|
|
751
828
|
return decorator
|
|
752
829
|
|
|
830
|
+
# conditional stuff
|
|
831
|
+
|
|
832
|
+
class ConditionContext(TypedDict):
|
|
833
|
+
requires_feature: Callable[[str], bool]
|
|
834
|
+
requires_class: Callable[[Type], bool]
|
|
835
|
+
|
|
836
|
+
class Condition(ABC):
|
|
837
|
+
@abstractmethod
|
|
838
|
+
def apply(self, context: ConditionContext) -> bool:
|
|
839
|
+
pass
|
|
840
|
+
|
|
841
|
+
class FeatureCondition(Condition):
|
|
842
|
+
def __init__(self, feature: str):
|
|
843
|
+
super().__init__()
|
|
844
|
+
|
|
845
|
+
self.feature = feature
|
|
846
|
+
|
|
847
|
+
def apply(self, context: ConditionContext) -> bool:
|
|
848
|
+
return context["requires_feature"](self.feature)
|
|
849
|
+
|
|
850
|
+
class ClassCondition(Condition):
|
|
851
|
+
def __init__(self, clazz: Type):
|
|
852
|
+
super().__init__()
|
|
853
|
+
|
|
854
|
+
self.clazz = clazz
|
|
855
|
+
|
|
856
|
+
def apply(self, context: ConditionContext) -> bool:
|
|
857
|
+
return context["requires_class"](self.clazz)
|
|
858
|
+
|
|
859
|
+
def requires_feature(feature: str):
|
|
860
|
+
return FeatureCondition(feature)
|
|
861
|
+
|
|
862
|
+
def requires_class(clazz: Type):
|
|
863
|
+
return ClassCondition(clazz)
|
|
864
|
+
|
|
865
|
+
def conditional(*conditions: Condition):
|
|
866
|
+
def decorator(cls):
|
|
867
|
+
Decorators.add(cls, conditional, *conditions)
|
|
868
|
+
|
|
869
|
+
return cls
|
|
870
|
+
|
|
871
|
+
return decorator
|
|
872
|
+
|
|
753
873
|
class Environment:
|
|
754
874
|
"""
|
|
755
875
|
Central class that manages the lifecycle of instances and their dependencies.
|
|
@@ -766,12 +886,13 @@ class Environment:
|
|
|
766
886
|
"providers",
|
|
767
887
|
"lifecycle_processors",
|
|
768
888
|
"parent",
|
|
889
|
+
"features",
|
|
769
890
|
"instances"
|
|
770
891
|
]
|
|
771
892
|
|
|
772
893
|
# constructor
|
|
773
894
|
|
|
774
|
-
def __init__(self, env: Type, parent : Optional[Environment] = None):
|
|
895
|
+
def __init__(self, env: Type, features: list[str] = [], parent : Optional[Environment] = None):
|
|
775
896
|
"""
|
|
776
897
|
Creates a new Environment instance.
|
|
777
898
|
|
|
@@ -779,6 +900,9 @@ class Environment:
|
|
|
779
900
|
env (Type): The environment class that controls the scanning of managed objects.
|
|
780
901
|
parent (Optional[Environment]): Optional parent environment, whose objects are inherited.
|
|
781
902
|
"""
|
|
903
|
+
|
|
904
|
+
Environment.logger.debug("create environment for class %s", env.__qualname__)
|
|
905
|
+
|
|
782
906
|
# initialize
|
|
783
907
|
|
|
784
908
|
self.type = env
|
|
@@ -786,20 +910,37 @@ class Environment:
|
|
|
786
910
|
if self.parent is None and env is not Boot:
|
|
787
911
|
self.parent = Boot.get_environment() # inherit environment including its manged instances!
|
|
788
912
|
|
|
913
|
+
self.features = features
|
|
789
914
|
self.providers: Dict[Type, AbstractInstanceProvider] = {}
|
|
915
|
+
self.instances = []
|
|
790
916
|
self.lifecycle_processors: list[LifecycleProcessor] = []
|
|
791
917
|
|
|
792
918
|
if self.parent is not None:
|
|
793
|
-
|
|
794
|
-
self.lifecycle_processors += self.parent.lifecycle_processors
|
|
919
|
+
# inherit providers from parent
|
|
795
920
|
|
|
796
|
-
|
|
921
|
+
for provider_type, inherited_provider in self.parent.providers.items():
|
|
922
|
+
if inherited_provider.get_scope() == "environment":
|
|
923
|
+
# replace with own environment instance provider
|
|
924
|
+
self.providers[provider_type] = EnvironmentInstanceProvider(self, cast(EnvironmentInstanceProvider, inherited_provider).provider)
|
|
925
|
+
else:
|
|
926
|
+
self.providers[provider_type] = inherited_provider
|
|
797
927
|
|
|
798
|
-
|
|
928
|
+
# inherit processors as is unless they have an environment scope
|
|
799
929
|
|
|
800
|
-
|
|
930
|
+
for processor in self.parent.lifecycle_processors:
|
|
931
|
+
if self.providers[type(processor)].get_scope() != "environment":
|
|
932
|
+
self.lifecycle_processors.append(processor)
|
|
933
|
+
else:
|
|
934
|
+
# create and remember
|
|
935
|
+
self.lifecycle_processors.append(self.get(type(processor)))
|
|
936
|
+
else:
|
|
937
|
+
self.providers[SingletonScope] = SingletonScopeInstanceProvider()
|
|
938
|
+
self.providers[RequestScope] = RequestScopeInstanceProvider()
|
|
939
|
+
self.providers[EnvironmentScope] = EnvironmentScopeInstanceProvider()
|
|
940
|
+
|
|
941
|
+
Environment.instance = self
|
|
801
942
|
|
|
802
|
-
|
|
943
|
+
prefix_list : list[str] = []
|
|
803
944
|
|
|
804
945
|
loaded = set()
|
|
805
946
|
|
|
@@ -808,11 +949,35 @@ class Environment:
|
|
|
808
949
|
|
|
809
950
|
self.providers[type] = provider
|
|
810
951
|
|
|
811
|
-
|
|
952
|
+
def get_type_package(type: Type):
|
|
953
|
+
module_name = type.__module__
|
|
954
|
+
module = sys.modules[module_name]
|
|
955
|
+
|
|
956
|
+
return module.__package__
|
|
957
|
+
|
|
958
|
+
def import_package(name: str):
|
|
959
|
+
"""Import a package and all its submodules recursively."""
|
|
960
|
+
package = importlib.import_module(name)
|
|
961
|
+
results = {name: package}
|
|
812
962
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
963
|
+
if hasattr(package, '__path__'): # it's a package, not a single file
|
|
964
|
+
for finder, name, ispkg in pkgutil.walk_packages(package.__path__, prefix=package.__name__ + "."):
|
|
965
|
+
try:
|
|
966
|
+
loaded = sys.modules
|
|
967
|
+
|
|
968
|
+
if loaded.get(name, None) is None:
|
|
969
|
+
Environment.logger.debug("import module %s", name)
|
|
970
|
+
|
|
971
|
+
submodule = importlib.import_module(name)
|
|
972
|
+
results[name] = submodule
|
|
973
|
+
else:
|
|
974
|
+
# skip import
|
|
975
|
+
results[name] = loaded[name]
|
|
976
|
+
|
|
977
|
+
except Exception as e:
|
|
978
|
+
Environment.logger.info("failed to import module %s due to %s", name, str(e))
|
|
979
|
+
|
|
980
|
+
return results
|
|
816
981
|
|
|
817
982
|
def load_environment(env: Type):
|
|
818
983
|
if env not in loaded:
|
|
@@ -826,48 +991,46 @@ class Environment:
|
|
|
826
991
|
if decorator is None:
|
|
827
992
|
raise DIRegistrationException(f"{env.__name__} is not an environment class")
|
|
828
993
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
994
|
+
# package
|
|
995
|
+
|
|
996
|
+
package_name = get_type_package(env)
|
|
832
997
|
|
|
833
998
|
# recursion
|
|
834
999
|
|
|
835
1000
|
for import_environment in decorator.args[0] or []:
|
|
836
1001
|
load_environment(import_environment)
|
|
837
1002
|
|
|
838
|
-
#
|
|
1003
|
+
# import package
|
|
839
1004
|
|
|
840
|
-
|
|
1005
|
+
if package_name is not None and len(package_name) > 0: # files outside of a package return None pr ""
|
|
1006
|
+
import_package(package_name)
|
|
841
1007
|
|
|
842
|
-
#
|
|
1008
|
+
# filter and load providers according to their module
|
|
843
1009
|
|
|
844
|
-
|
|
845
|
-
|
|
1010
|
+
module_prefix = package_name
|
|
1011
|
+
if len(module_prefix) == 0:
|
|
1012
|
+
module_prefix = env.__module__
|
|
846
1013
|
|
|
847
|
-
|
|
1014
|
+
prefix_list.append(module_prefix)
|
|
848
1015
|
|
|
849
|
-
|
|
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
|
|
1016
|
+
# go
|
|
854
1017
|
|
|
855
|
-
|
|
1018
|
+
load_environment(env)
|
|
856
1019
|
|
|
857
|
-
|
|
1020
|
+
# filter according to the prefix list
|
|
858
1021
|
|
|
859
|
-
|
|
860
|
-
|
|
1022
|
+
def filter_provider(provider: AbstractInstanceProvider) -> bool:
|
|
1023
|
+
for prefix in prefix_list:
|
|
1024
|
+
if provider.get_host().__module__.startswith(prefix):
|
|
1025
|
+
return True
|
|
861
1026
|
|
|
862
|
-
|
|
1027
|
+
return False
|
|
863
1028
|
|
|
864
|
-
|
|
865
|
-
else:
|
|
866
|
-
return []
|
|
1029
|
+
self.providers.update(Providers.filter(self, filter_provider))
|
|
867
1030
|
|
|
868
1031
|
# construct eager objects for local providers
|
|
869
1032
|
|
|
870
|
-
for provider in
|
|
1033
|
+
for provider in set(self.providers.values()):
|
|
871
1034
|
if provider.is_eager():
|
|
872
1035
|
provider.create(self)
|
|
873
1036
|
|
|
@@ -876,8 +1039,19 @@ class Environment:
|
|
|
876
1039
|
for instance in self.instances:
|
|
877
1040
|
self.execute_processors(Lifecycle.ON_RUNNING, instance)
|
|
878
1041
|
|
|
1042
|
+
def is_registered_type(self, type: Type) -> bool:
|
|
1043
|
+
provider = self.providers.get(type, None)
|
|
1044
|
+
return provider is not None and not isinstance(provider, AmbiguousProvider)
|
|
1045
|
+
|
|
1046
|
+
def registered_types(self, predicate: Callable[[Type], bool]) -> list[Type]:
|
|
1047
|
+
return [provider.get_type() for provider in self.providers.values()
|
|
1048
|
+
if predicate(provider.get_type())]
|
|
1049
|
+
|
|
879
1050
|
# internal
|
|
880
1051
|
|
|
1052
|
+
def has_feature(self, feature: str) -> bool:
|
|
1053
|
+
return feature in self.features
|
|
1054
|
+
|
|
881
1055
|
def execute_processors(self, lifecycle: Lifecycle, instance: T) -> T:
|
|
882
1056
|
for processor in self.lifecycle_processors:
|
|
883
1057
|
processor.process_lifecycle(lifecycle, instance, self)
|
|
@@ -927,8 +1101,9 @@ class Environment:
|
|
|
927
1101
|
|
|
928
1102
|
builder.append("Providers \n")
|
|
929
1103
|
for result_type, provider in self.providers.items():
|
|
930
|
-
if
|
|
931
|
-
|
|
1104
|
+
if isinstance(provider, EnvironmentInstanceProvider):
|
|
1105
|
+
if cast(EnvironmentInstanceProvider, provider).environment is self:
|
|
1106
|
+
builder.append(f"- {result_type.__name__}: {provider.report()}\n")
|
|
932
1107
|
|
|
933
1108
|
# instances
|
|
934
1109
|
|
|
@@ -973,6 +1148,9 @@ class Environment:
|
|
|
973
1148
|
|
|
974
1149
|
return provider.create(self)
|
|
975
1150
|
|
|
1151
|
+
def __str__(self):
|
|
1152
|
+
return f"Environment({self.type.__name__})"
|
|
1153
|
+
|
|
976
1154
|
class LifecycleCallable:
|
|
977
1155
|
__slots__ = [
|
|
978
1156
|
"decorator",
|
|
@@ -1155,20 +1333,20 @@ class InjectLifecycleCallable(LifecycleCallable):
|
|
|
1155
1333
|
def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
|
|
1156
1334
|
return [environment.get(type) for type in method.param_types]
|
|
1157
1335
|
|
|
1158
|
-
def scope(name: str):
|
|
1336
|
+
def scope(name: str, register=True):
|
|
1159
1337
|
def decorator(cls):
|
|
1160
1338
|
Scopes.register(cls, name)
|
|
1161
1339
|
|
|
1162
1340
|
Decorators.add(cls, scope)
|
|
1163
|
-
# Decorators.add(cls, injectable)
|
|
1164
1341
|
|
|
1165
|
-
|
|
1342
|
+
if register:
|
|
1343
|
+
Providers.register(ClassInstanceProvider(cls, eager=True, scope="request"))
|
|
1166
1344
|
|
|
1167
1345
|
return cls
|
|
1168
1346
|
|
|
1169
1347
|
return decorator
|
|
1170
1348
|
|
|
1171
|
-
@scope("request")
|
|
1349
|
+
@scope("request", register=False)
|
|
1172
1350
|
class RequestScope(Scope):
|
|
1173
1351
|
# properties
|
|
1174
1352
|
|
|
@@ -1180,7 +1358,7 @@ class RequestScope(Scope):
|
|
|
1180
1358
|
def get(self, provider: AbstractInstanceProvider, environment: Environment, arg_provider: Callable[[],list]):
|
|
1181
1359
|
return provider.create(environment, *arg_provider())
|
|
1182
1360
|
|
|
1183
|
-
@scope("singleton")
|
|
1361
|
+
@scope("singleton", register=False)
|
|
1184
1362
|
class SingletonScope(Scope):
|
|
1185
1363
|
# properties
|
|
1186
1364
|
|
|
@@ -1207,6 +1385,19 @@ class SingletonScope(Scope):
|
|
|
1207
1385
|
|
|
1208
1386
|
return self.value
|
|
1209
1387
|
|
|
1388
|
+
@scope("environment", register=False)
|
|
1389
|
+
class EnvironmentScope(SingletonScope):
|
|
1390
|
+
# properties
|
|
1391
|
+
|
|
1392
|
+
__slots__ = [
|
|
1393
|
+
]
|
|
1394
|
+
|
|
1395
|
+
# constructor
|
|
1396
|
+
|
|
1397
|
+
def __init__(self):
|
|
1398
|
+
super().__init__()
|
|
1399
|
+
|
|
1400
|
+
|
|
1210
1401
|
@scope("thread")
|
|
1211
1402
|
class ThreadScope(Scope):
|
|
1212
1403
|
__slots__ = [
|