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