aspyx 0.1.0__tar.gz → 1.0.0__tar.gz
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.
- {aspyx-0.1.0/src/aspyx.egg-info → aspyx-1.0.0}/PKG-INFO +54 -11
- {aspyx-0.1.0 → aspyx-1.0.0}/README.md +53 -10
- {aspyx-0.1.0 → aspyx-1.0.0}/pyproject.toml +1 -1
- {aspyx-0.1.0 → aspyx-1.0.0}/src/aspyx/di/aop/aop.py +55 -25
- {aspyx-0.1.0 → aspyx-1.0.0}/src/aspyx/di/configuration/configuration.py +2 -0
- {aspyx-0.1.0 → aspyx-1.0.0}/src/aspyx/di/di.py +49 -6
- {aspyx-0.1.0 → aspyx-1.0.0}/src/aspyx/reflection/reflection.py +8 -3
- {aspyx-0.1.0 → aspyx-1.0.0/src/aspyx.egg-info}/PKG-INFO +54 -11
- {aspyx-0.1.0 → aspyx-1.0.0}/tests/test_aop.py +5 -4
- {aspyx-0.1.0 → aspyx-1.0.0}/tests/test_di.py +3 -1
- {aspyx-0.1.0 → aspyx-1.0.0}/tests/test_reflection.py +19 -2
- {aspyx-0.1.0 → aspyx-1.0.0}/LICENSE +0 -0
- {aspyx-0.1.0 → aspyx-1.0.0}/setup.cfg +0 -0
- {aspyx-0.1.0 → aspyx-1.0.0}/src/aspyx/di/__init__.py +0 -0
- {aspyx-0.1.0 → aspyx-1.0.0}/src/aspyx/di/aop/__init__.py +0 -0
- {aspyx-0.1.0 → aspyx-1.0.0}/src/aspyx/di/configuration/__init__.py +0 -0
- {aspyx-0.1.0 → aspyx-1.0.0}/src/aspyx/reflection/__init__.py +0 -0
- {aspyx-0.1.0 → aspyx-1.0.0}/src/aspyx/reflection/proxy.py +0 -0
- {aspyx-0.1.0 → aspyx-1.0.0}/src/aspyx.egg-info/SOURCES.txt +0 -0
- {aspyx-0.1.0 → aspyx-1.0.0}/src/aspyx.egg-info/dependency_links.txt +0 -0
- {aspyx-0.1.0 → aspyx-1.0.0}/src/aspyx.egg-info/top_level.txt +0 -0
- {aspyx-0.1.0 → aspyx-1.0.0}/tests/test_configuration.py +0 -0
- {aspyx-0.1.0 → aspyx-1.0.0}/tests/test_di_cycle.py +0 -0
- {aspyx-0.1.0 → aspyx-1.0.0}/tests/test_proxy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: A DI and AOP library for Python
|
|
5
5
|
Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -32,8 +32,8 @@ Dynamic: license-file
|
|
|
32
32
|
|
|
33
33
|
# aspyx
|
|
34
34
|
|
|
35
|
-

|
|
36
|
+

|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
## Table of Contents
|
|
@@ -46,8 +46,10 @@ Dynamic: license-file
|
|
|
46
46
|
- [Environment](#environment)
|
|
47
47
|
- [Definition](#definition)
|
|
48
48
|
- [Retrieval](#retrieval)
|
|
49
|
-
- [
|
|
50
|
-
- [
|
|
49
|
+
- [Instantiation logic](#instantiation-logic)
|
|
50
|
+
- [Injection Methods](#injection-methods)
|
|
51
|
+
- [Lifecycle Methods](#lifecycle-methods)
|
|
52
|
+
- [Post Processors](#post-processors)
|
|
51
53
|
- [Custom scopes](#custom-scopes)
|
|
52
54
|
- [AOP](#aop)
|
|
53
55
|
- [Configuration](#configuration)
|
|
@@ -164,9 +166,10 @@ class Foo:
|
|
|
164
166
|
def __init__(self):
|
|
165
167
|
pass
|
|
166
168
|
```
|
|
167
|
-
|
|
169
|
+
Please make sure, that the class defines a local constructor, as this is required to determine injected instances.
|
|
170
|
+
All referenced types will be injected by the environemnt.
|
|
168
171
|
|
|
169
|
-
|
|
172
|
+
Only eligible types are allowed, of course!
|
|
170
173
|
|
|
171
174
|
|
|
172
175
|
The decorator accepts the keyword arguments
|
|
@@ -268,15 +271,55 @@ In case of ambiguities, it will throw an exception.
|
|
|
268
271
|
|
|
269
272
|
Please be aware, that a base class are not _required_ to be annotated with `@injectable`, as this would mean, that it could be created on its own as well. ( Which is possible as well, btw. )
|
|
270
273
|
|
|
271
|
-
#
|
|
274
|
+
# Instantiation logic
|
|
272
275
|
|
|
273
|
-
|
|
276
|
+
Constructing a new instance involves a number of steps executed in this order
|
|
277
|
+
- Constructor call
|
|
278
|
+
the constructor is called with the resolved parameters
|
|
279
|
+
- Advice injection
|
|
280
|
+
All methods involving aspects are updated
|
|
281
|
+
- Lifecycle methods
|
|
282
|
+
different decorators can mark methods that should be called during the lifecycle ( here the construction ) of an instance.
|
|
283
|
+
These are various injection possibilities as well as an optional final `on_init` call
|
|
284
|
+
- PostProcessors
|
|
285
|
+
Any custom post processors, that can add isde effects or modify the instances
|
|
286
|
+
|
|
287
|
+
## Injection methods
|
|
288
|
+
|
|
289
|
+
Different decorators are implemented, that call methods with computed values
|
|
290
|
+
|
|
291
|
+
- `@inject`
|
|
292
|
+
the method is called with all resolved parameter types ( same as the constructor call)
|
|
293
|
+
- `@inject_environment`
|
|
294
|
+
the method is called with the creating environment as a single parameter
|
|
295
|
+
- `@value()`
|
|
296
|
+
the method is called with a resolved configuration value. Check the corresponding chapter
|
|
297
|
+
|
|
298
|
+
**Example**:
|
|
299
|
+
```python
|
|
300
|
+
@injectable()
|
|
301
|
+
class Foo:
|
|
302
|
+
def __init__(self):
|
|
303
|
+
pass
|
|
304
|
+
|
|
305
|
+
@inject_environment()
|
|
306
|
+
def initEnvironment(self, env: Environment):
|
|
307
|
+
...
|
|
308
|
+
|
|
309
|
+
@inject()
|
|
310
|
+
def set(self, baz: Baz) -> None:
|
|
311
|
+
...
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Lifecycle methods
|
|
315
|
+
|
|
316
|
+
It is possible to mark specific lifecle methods.
|
|
274
317
|
- `@on_init()`
|
|
275
318
|
called after the constructor and all other injections.
|
|
276
319
|
- `@on_destroy()`
|
|
277
|
-
called
|
|
320
|
+
called during shutdown of the environment
|
|
278
321
|
|
|
279
|
-
|
|
322
|
+
## Post Processors
|
|
280
323
|
|
|
281
324
|
As part of the instantiation logic it is possible to define post processors that execute any side effect on newly created instances.
|
|
282
325
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# aspyx
|
|
2
2
|
|
|
3
|
-

|
|
4
|
+

|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
## Table of Contents
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
- [Environment](#environment)
|
|
15
15
|
- [Definition](#definition)
|
|
16
16
|
- [Retrieval](#retrieval)
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
17
|
+
- [Instantiation logic](#instantiation-logic)
|
|
18
|
+
- [Injection Methods](#injection-methods)
|
|
19
|
+
- [Lifecycle Methods](#lifecycle-methods)
|
|
20
|
+
- [Post Processors](#post-processors)
|
|
19
21
|
- [Custom scopes](#custom-scopes)
|
|
20
22
|
- [AOP](#aop)
|
|
21
23
|
- [Configuration](#configuration)
|
|
@@ -132,9 +134,10 @@ class Foo:
|
|
|
132
134
|
def __init__(self):
|
|
133
135
|
pass
|
|
134
136
|
```
|
|
135
|
-
|
|
137
|
+
Please make sure, that the class defines a local constructor, as this is required to determine injected instances.
|
|
138
|
+
All referenced types will be injected by the environemnt.
|
|
136
139
|
|
|
137
|
-
|
|
140
|
+
Only eligible types are allowed, of course!
|
|
138
141
|
|
|
139
142
|
|
|
140
143
|
The decorator accepts the keyword arguments
|
|
@@ -236,15 +239,55 @@ In case of ambiguities, it will throw an exception.
|
|
|
236
239
|
|
|
237
240
|
Please be aware, that a base class are not _required_ to be annotated with `@injectable`, as this would mean, that it could be created on its own as well. ( Which is possible as well, btw. )
|
|
238
241
|
|
|
239
|
-
#
|
|
242
|
+
# Instantiation logic
|
|
240
243
|
|
|
241
|
-
|
|
244
|
+
Constructing a new instance involves a number of steps executed in this order
|
|
245
|
+
- Constructor call
|
|
246
|
+
the constructor is called with the resolved parameters
|
|
247
|
+
- Advice injection
|
|
248
|
+
All methods involving aspects are updated
|
|
249
|
+
- Lifecycle methods
|
|
250
|
+
different decorators can mark methods that should be called during the lifecycle ( here the construction ) of an instance.
|
|
251
|
+
These are various injection possibilities as well as an optional final `on_init` call
|
|
252
|
+
- PostProcessors
|
|
253
|
+
Any custom post processors, that can add isde effects or modify the instances
|
|
254
|
+
|
|
255
|
+
## Injection methods
|
|
256
|
+
|
|
257
|
+
Different decorators are implemented, that call methods with computed values
|
|
258
|
+
|
|
259
|
+
- `@inject`
|
|
260
|
+
the method is called with all resolved parameter types ( same as the constructor call)
|
|
261
|
+
- `@inject_environment`
|
|
262
|
+
the method is called with the creating environment as a single parameter
|
|
263
|
+
- `@value()`
|
|
264
|
+
the method is called with a resolved configuration value. Check the corresponding chapter
|
|
265
|
+
|
|
266
|
+
**Example**:
|
|
267
|
+
```python
|
|
268
|
+
@injectable()
|
|
269
|
+
class Foo:
|
|
270
|
+
def __init__(self):
|
|
271
|
+
pass
|
|
272
|
+
|
|
273
|
+
@inject_environment()
|
|
274
|
+
def initEnvironment(self, env: Environment):
|
|
275
|
+
...
|
|
276
|
+
|
|
277
|
+
@inject()
|
|
278
|
+
def set(self, baz: Baz) -> None:
|
|
279
|
+
...
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Lifecycle methods
|
|
283
|
+
|
|
284
|
+
It is possible to mark specific lifecle methods.
|
|
242
285
|
- `@on_init()`
|
|
243
286
|
called after the constructor and all other injections.
|
|
244
287
|
- `@on_destroy()`
|
|
245
|
-
called
|
|
288
|
+
called during shutdown of the environment
|
|
246
289
|
|
|
247
|
-
|
|
290
|
+
## Post Processors
|
|
248
291
|
|
|
249
292
|
As part of the instantiation logic it is possible to define post processors that execute any side effect on newly created instances.
|
|
250
293
|
|
|
@@ -3,11 +3,13 @@ from __future__ import annotations
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
import inspect
|
|
5
5
|
import re
|
|
6
|
+
import threading
|
|
6
7
|
import types
|
|
7
8
|
from dataclasses import dataclass
|
|
8
9
|
from enum import auto, Enum
|
|
9
10
|
from typing import Optional, Dict, Type, Callable
|
|
10
11
|
|
|
12
|
+
from aspyx.di.di import order
|
|
11
13
|
from aspyx.reflection import Decorators, TypeDescriptor
|
|
12
14
|
from aspyx.di import injectable, Providers, ClassInstanceProvider, Environment, PostProcessor
|
|
13
15
|
|
|
@@ -51,6 +53,7 @@ class AspectTarget(ABC):
|
|
|
51
53
|
"names",
|
|
52
54
|
"patterns",
|
|
53
55
|
"types",
|
|
56
|
+
"other",
|
|
54
57
|
"decorators",
|
|
55
58
|
]
|
|
56
59
|
|
|
@@ -65,14 +68,32 @@ class AspectTarget(ABC):
|
|
|
65
68
|
self.types = []
|
|
66
69
|
self.decorators = []
|
|
67
70
|
|
|
71
|
+
self.other : list[AspectTarget] = []
|
|
72
|
+
|
|
68
73
|
pass
|
|
69
74
|
|
|
70
75
|
# abstract
|
|
71
76
|
|
|
72
|
-
@abstractmethod
|
|
73
77
|
def _matches(self, clazz : Type, func):
|
|
78
|
+
if not self._matchesSelf(clazz, func):
|
|
79
|
+
for target in self.other:
|
|
80
|
+
if target._matches(clazz, func):
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
@abstractmethod
|
|
88
|
+
def _matchesSelf(self, clazz: Type, func):
|
|
74
89
|
pass
|
|
75
90
|
|
|
91
|
+
# protected
|
|
92
|
+
|
|
93
|
+
def _add(self, target: AspectTarget):
|
|
94
|
+
self.other.append(target)
|
|
95
|
+
return self
|
|
96
|
+
|
|
76
97
|
# fluent
|
|
77
98
|
|
|
78
99
|
def function(self, func):
|
|
@@ -83,7 +104,7 @@ class AspectTarget(ABC):
|
|
|
83
104
|
self._type = type
|
|
84
105
|
|
|
85
106
|
return self
|
|
86
|
-
|
|
107
|
+
|
|
87
108
|
def of_type(self, type: Type):
|
|
88
109
|
self.types.append(type)
|
|
89
110
|
return self
|
|
@@ -118,9 +139,9 @@ class ClassAspectTarget(AspectTarget):
|
|
|
118
139
|
|
|
119
140
|
# public
|
|
120
141
|
|
|
121
|
-
def
|
|
122
|
-
|
|
123
|
-
|
|
142
|
+
def _matchesSelf(self, clazz : Type, func):
|
|
143
|
+
classDescriptor = TypeDescriptor.for_type(clazz)
|
|
144
|
+
#descriptor = TypeDescriptor.for_type(func)
|
|
124
145
|
# type
|
|
125
146
|
|
|
126
147
|
if len(self.types) > 0:
|
|
@@ -130,7 +151,7 @@ class ClassAspectTarget(AspectTarget):
|
|
|
130
151
|
# decorators
|
|
131
152
|
|
|
132
153
|
if len(self.decorators) > 0:
|
|
133
|
-
if next((decorator for decorator in self.decorators if
|
|
154
|
+
if next((decorator for decorator in self.decorators if classDescriptor.has_decorator(decorator)), None) is None:
|
|
134
155
|
return False
|
|
135
156
|
|
|
136
157
|
# names
|
|
@@ -144,8 +165,6 @@ class ClassAspectTarget(AspectTarget):
|
|
|
144
165
|
if len(self.patterns) > 0:
|
|
145
166
|
if next((pattern for pattern in self.patterns if re.fullmatch(pattern, clazz.__name__) is not None), None) is None:
|
|
146
167
|
return False
|
|
147
|
-
|
|
148
|
-
# yipee
|
|
149
168
|
|
|
150
169
|
return True
|
|
151
170
|
|
|
@@ -174,7 +193,7 @@ class MethodAspectTarget(AspectTarget):
|
|
|
174
193
|
|
|
175
194
|
# public
|
|
176
195
|
|
|
177
|
-
def
|
|
196
|
+
def _matchesSelf(self, clazz : Type, func):
|
|
178
197
|
descriptor = TypeDescriptor.for_type(clazz)
|
|
179
198
|
|
|
180
199
|
methodDescriptor = descriptor.get_method(func.__name__)
|
|
@@ -349,12 +368,14 @@ class Advice:
|
|
|
349
368
|
|
|
350
369
|
__slots__ = [
|
|
351
370
|
"cache",
|
|
371
|
+
"lock"
|
|
352
372
|
]
|
|
353
373
|
|
|
354
374
|
# constructor
|
|
355
375
|
|
|
356
376
|
def __init__(self):
|
|
357
377
|
self.cache : Dict[Type, Dict[Callable,JoinPoints]] = dict()
|
|
378
|
+
self.lock = threading.RLock()
|
|
358
379
|
|
|
359
380
|
# methods
|
|
360
381
|
|
|
@@ -370,20 +391,23 @@ class Advice:
|
|
|
370
391
|
|
|
371
392
|
return aspects
|
|
372
393
|
|
|
373
|
-
# TODO thread-safe
|
|
374
394
|
def joinPoints4(self, instance, environment: Environment) -> Dict[Callable,JoinPoints]:
|
|
375
395
|
clazz = type(instance)
|
|
376
396
|
|
|
377
397
|
result = self.cache.get(clazz, None)
|
|
378
398
|
if result is None:
|
|
379
|
-
|
|
399
|
+
with self.lock:
|
|
400
|
+
result = self.cache.get(clazz, None)
|
|
401
|
+
|
|
402
|
+
if result is None:
|
|
403
|
+
result = dict()
|
|
380
404
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
405
|
+
for name, member in inspect.getmembers(clazz, predicate=inspect.isfunction):
|
|
406
|
+
joinPoints = self.computeJoinPoints(clazz, member, environment)
|
|
407
|
+
if joinPoints is not None:
|
|
408
|
+
result[member] = joinPoints
|
|
385
409
|
|
|
386
|
-
|
|
410
|
+
self.cache[clazz] = result
|
|
387
411
|
|
|
388
412
|
# add around methods
|
|
389
413
|
|
|
@@ -453,57 +477,63 @@ def advice(cls):
|
|
|
453
477
|
|
|
454
478
|
# decorators
|
|
455
479
|
|
|
456
|
-
def _register(decorator,
|
|
480
|
+
def _register(decorator, targets: list[AspectTarget], func, aspectType: AspectType):
|
|
481
|
+
target = targets[0]
|
|
482
|
+
|
|
483
|
+
for i in range(1, len(targets)):
|
|
484
|
+
target._add(targets[i])
|
|
485
|
+
|
|
457
486
|
target.function(func).type(aspectType)
|
|
458
487
|
|
|
459
488
|
Decorators.add(func, decorator, target)
|
|
460
489
|
|
|
461
|
-
def before(
|
|
490
|
+
def before(*targets: AspectTarget):
|
|
462
491
|
"""
|
|
463
492
|
Methods decorated with @before will be executed before the target method is invoked.
|
|
464
493
|
"""
|
|
465
494
|
def decorator(func):
|
|
466
|
-
_register(before,
|
|
495
|
+
_register(before, targets, func, AspectType.BEFORE)
|
|
467
496
|
|
|
468
497
|
return func
|
|
469
498
|
|
|
470
499
|
return decorator
|
|
471
500
|
|
|
472
|
-
def error(
|
|
501
|
+
def error(*targets: AspectTarget):
|
|
473
502
|
"""
|
|
474
503
|
Methods decorated with @error will be executed if the target method raises an exception."""
|
|
475
504
|
def decorator(func):
|
|
476
|
-
_register(error,
|
|
505
|
+
_register(error, targets, func, AspectType.ERROR)
|
|
477
506
|
|
|
478
507
|
return func
|
|
479
508
|
|
|
480
509
|
return decorator
|
|
481
510
|
|
|
482
|
-
def after(
|
|
511
|
+
def after(*targets: AspectTarget):
|
|
483
512
|
"""
|
|
484
513
|
Methods decorated with @after will be executed after the target method is invoked.
|
|
485
514
|
"""
|
|
486
515
|
def decorator(func):
|
|
487
|
-
_register(after,
|
|
516
|
+
_register(after, targets, func, AspectType.AFTER)
|
|
488
517
|
|
|
489
518
|
return func
|
|
490
519
|
|
|
491
520
|
return decorator
|
|
492
521
|
|
|
493
|
-
def around(
|
|
522
|
+
def around(*targets: AspectTarget):
|
|
494
523
|
"""
|
|
495
524
|
Methods decorated with @around will be executed around the target method.
|
|
496
525
|
Every around method must accept a single parameter of type Invocation and needs to call proceed
|
|
497
526
|
on this parameter to proceed to the next around method.
|
|
498
527
|
"""
|
|
499
528
|
def decorator(func):
|
|
500
|
-
_register(around,
|
|
529
|
+
_register(around, targets, func, AspectType.AROUND)
|
|
501
530
|
|
|
502
531
|
return func
|
|
503
532
|
|
|
504
533
|
return decorator
|
|
505
534
|
|
|
506
535
|
@injectable()
|
|
536
|
+
@order(0)
|
|
507
537
|
class AdviceProcessor(PostProcessor):
|
|
508
538
|
# properties
|
|
509
539
|
|
|
@@ -6,6 +6,7 @@ from typing import Optional, Type, TypeVar
|
|
|
6
6
|
from dotenv import load_dotenv
|
|
7
7
|
|
|
8
8
|
from aspyx.di import injectable, Environment, CallableProcessor, LifecycleCallable, Lifecycle, environment
|
|
9
|
+
from aspyx.di.di import order
|
|
9
10
|
from aspyx.reflection import Decorators, DecoratorDescriptor, TypeDescriptor
|
|
10
11
|
|
|
11
12
|
T = TypeVar("T")
|
|
@@ -180,6 +181,7 @@ def value(key: str, default=None):
|
|
|
180
181
|
return decorator
|
|
181
182
|
|
|
182
183
|
@injectable()
|
|
184
|
+
@order(9)
|
|
183
185
|
class ConfigurationLifecycleCallable(LifecycleCallable):
|
|
184
186
|
def __init__(self, processor: CallableProcessor, manager: ConfigurationManager):
|
|
185
187
|
super().__init__(value, processor, Lifecycle.ON_INIT)
|
|
@@ -5,6 +5,7 @@ import logging
|
|
|
5
5
|
|
|
6
6
|
from abc import abstractmethod, ABC
|
|
7
7
|
from enum import Enum, auto
|
|
8
|
+
import threading
|
|
8
9
|
from typing import Type, Dict, TypeVar, Generic, Optional, cast, Callable
|
|
9
10
|
|
|
10
11
|
from aspyx.reflection import Decorators, TypeDescriptor, DecoratorDescriptor
|
|
@@ -438,12 +439,16 @@ class LifecycleProcessor(ABC):
|
|
|
438
439
|
"""
|
|
439
440
|
A LifecycleProcessor is used to perform any side effects on managed objects during their lifecycle.
|
|
440
441
|
"""
|
|
441
|
-
__slots__ = [
|
|
442
|
+
__slots__ = [
|
|
443
|
+
"order"
|
|
444
|
+
]
|
|
442
445
|
|
|
443
446
|
# constructor
|
|
444
447
|
|
|
445
448
|
def __init__(self):
|
|
446
|
-
|
|
449
|
+
self.order = 0
|
|
450
|
+
if TypeDescriptor.for_type(type(self)).has_decorator(order):
|
|
451
|
+
self.order = TypeDescriptor.for_type(type(self)).get_decorator(order).args[0]
|
|
447
452
|
|
|
448
453
|
# methods
|
|
449
454
|
|
|
@@ -596,6 +601,13 @@ def registerFactories(cls: Type):
|
|
|
596
601
|
create_decorator = method.get_decorator(create)
|
|
597
602
|
Providers.register(FunctionInstanceProvider(cls, method.method, method.returnType, create_decorator.args[0],
|
|
598
603
|
create_decorator.args[1]))
|
|
604
|
+
def order(prio = 0):
|
|
605
|
+
def decorator(cls):
|
|
606
|
+
Decorators.add(cls, order, prio)
|
|
607
|
+
|
|
608
|
+
return cls
|
|
609
|
+
|
|
610
|
+
return decorator
|
|
599
611
|
|
|
600
612
|
def injectable(eager=True, scope="singleton"):
|
|
601
613
|
"""
|
|
@@ -715,6 +727,7 @@ class Environment:
|
|
|
715
727
|
]
|
|
716
728
|
|
|
717
729
|
# constructor
|
|
730
|
+
|
|
718
731
|
def __init__(self, env: Type, parent : Optional[Environment] = None):
|
|
719
732
|
"""
|
|
720
733
|
Creates a new Environment instance.
|
|
@@ -823,11 +836,21 @@ class Environment:
|
|
|
823
836
|
return instance
|
|
824
837
|
|
|
825
838
|
def created(self, instance: T) -> T:
|
|
839
|
+
def get_order(type: TypeDescriptor) -> int:
|
|
840
|
+
if type.has_decorator(order):
|
|
841
|
+
return type.get_decorator(order).args[0]
|
|
842
|
+
else:
|
|
843
|
+
return 10
|
|
844
|
+
|
|
826
845
|
# remember lifecycle processors
|
|
827
846
|
|
|
828
847
|
if isinstance(instance, LifecycleProcessor):
|
|
829
848
|
self.lifecycleProcessors.append(instance)
|
|
830
849
|
|
|
850
|
+
# sort immediately
|
|
851
|
+
|
|
852
|
+
self.lifecycleProcessors.sort(key=lambda processor: processor.order)
|
|
853
|
+
|
|
831
854
|
# remember instance
|
|
832
855
|
|
|
833
856
|
self.instances.append(instance)
|
|
@@ -866,12 +889,17 @@ class Environment:
|
|
|
866
889
|
class LifecycleCallable:
|
|
867
890
|
__slots__ = [
|
|
868
891
|
"decorator",
|
|
869
|
-
"lifecycle"
|
|
892
|
+
"lifecycle",
|
|
893
|
+
"order"
|
|
870
894
|
]
|
|
871
895
|
|
|
872
896
|
def __init__(self, decorator, processor: CallableProcessor, lifecycle: Lifecycle):
|
|
873
897
|
self.decorator = decorator
|
|
874
898
|
self.lifecycle = lifecycle
|
|
899
|
+
self.order = 0
|
|
900
|
+
|
|
901
|
+
if TypeDescriptor.for_type(type(self)).has_decorator(order):
|
|
902
|
+
self.order = TypeDescriptor.for_type(type(self)).get_decorator(order).args[0]
|
|
875
903
|
|
|
876
904
|
processor.register(self)
|
|
877
905
|
|
|
@@ -879,6 +907,7 @@ class LifecycleCallable:
|
|
|
879
907
|
return []
|
|
880
908
|
|
|
881
909
|
@injectable()
|
|
910
|
+
@order(1)
|
|
882
911
|
class CallableProcessor(LifecycleProcessor):
|
|
883
912
|
# local classes
|
|
884
913
|
|
|
@@ -920,6 +949,12 @@ class CallableProcessor(LifecycleProcessor):
|
|
|
920
949
|
if self.callables.get(decorator.decorator) is not None:
|
|
921
950
|
result.append(CallableProcessor.MethodCall(method, decorator, self.callables[decorator.decorator]))
|
|
922
951
|
|
|
952
|
+
# sort according to order
|
|
953
|
+
|
|
954
|
+
result.sort(key=lambda call: call.lifecycleCallable.order)
|
|
955
|
+
|
|
956
|
+
# done
|
|
957
|
+
|
|
923
958
|
return result
|
|
924
959
|
|
|
925
960
|
def callablesFor(self, type: Type)-> list[CallableProcessor.MethodCall]:
|
|
@@ -942,6 +977,7 @@ class CallableProcessor(LifecycleProcessor):
|
|
|
942
977
|
callable.execute(instance, environment)
|
|
943
978
|
|
|
944
979
|
@injectable()
|
|
980
|
+
@order(1000)
|
|
945
981
|
class OnInitLifecycleCallable(LifecycleCallable):
|
|
946
982
|
__slots__ = []
|
|
947
983
|
|
|
@@ -949,6 +985,7 @@ class OnInitLifecycleCallable(LifecycleCallable):
|
|
|
949
985
|
super().__init__(on_init, processor, Lifecycle.ON_INIT)
|
|
950
986
|
|
|
951
987
|
@injectable()
|
|
988
|
+
@order(1001)
|
|
952
989
|
class OnDestroyLifecycleCallable(LifecycleCallable):
|
|
953
990
|
__slots__ = []
|
|
954
991
|
|
|
@@ -956,6 +993,7 @@ class OnDestroyLifecycleCallable(LifecycleCallable):
|
|
|
956
993
|
super().__init__(on_destroy, processor, Lifecycle.ON_DESTROY)
|
|
957
994
|
|
|
958
995
|
@injectable()
|
|
996
|
+
@order(9)
|
|
959
997
|
class EnvironmentAwareLifecycleCallable(LifecycleCallable):
|
|
960
998
|
__slots__ = []
|
|
961
999
|
|
|
@@ -966,6 +1004,7 @@ class EnvironmentAwareLifecycleCallable(LifecycleCallable):
|
|
|
966
1004
|
return [environment]
|
|
967
1005
|
|
|
968
1006
|
@injectable()
|
|
1007
|
+
@order(10)
|
|
969
1008
|
class InjectLifecycleCallable(LifecycleCallable):
|
|
970
1009
|
__slots__ = []
|
|
971
1010
|
|
|
@@ -1012,7 +1051,8 @@ class SingletonScope(Scope):
|
|
|
1012
1051
|
# properties
|
|
1013
1052
|
|
|
1014
1053
|
__slots__ = [
|
|
1015
|
-
"value"
|
|
1054
|
+
"value",
|
|
1055
|
+
"lock"
|
|
1016
1056
|
]
|
|
1017
1057
|
|
|
1018
1058
|
# constructor
|
|
@@ -1021,12 +1061,15 @@ class SingletonScope(Scope):
|
|
|
1021
1061
|
super().__init__()
|
|
1022
1062
|
|
|
1023
1063
|
self.value = None
|
|
1064
|
+
self.lock = threading.Lock()
|
|
1024
1065
|
|
|
1025
1066
|
# override
|
|
1026
1067
|
|
|
1027
1068
|
def get(self, provider: AbstractInstanceProvider, environment: Environment, argProvider: Callable[[],list]):
|
|
1028
|
-
if self.value is None:
|
|
1029
|
-
self.
|
|
1069
|
+
if self.value is None:
|
|
1070
|
+
with self.lock:
|
|
1071
|
+
if self.value is None:
|
|
1072
|
+
self.value = provider.create(environment, *argProvider())
|
|
1030
1073
|
|
|
1031
1074
|
return self.value
|
|
1032
1075
|
|
|
@@ -6,6 +6,11 @@ from typing import Callable, get_type_hints, Type, Dict, Optional
|
|
|
6
6
|
from weakref import WeakKeyDictionary
|
|
7
7
|
|
|
8
8
|
class DecoratorDescriptor:
|
|
9
|
+
__slots__ = [
|
|
10
|
+
"decorator",
|
|
11
|
+
"args"
|
|
12
|
+
]
|
|
13
|
+
|
|
9
14
|
def __init__(self, decorator, *args):
|
|
10
15
|
self.decorator = decorator
|
|
11
16
|
self.args = args
|
|
@@ -47,14 +52,14 @@ class TypeDescriptor:
|
|
|
47
52
|
|
|
48
53
|
def get_decorator(self, decorator):
|
|
49
54
|
for dec in self.decorators:
|
|
50
|
-
if dec.decorator
|
|
55
|
+
if dec.decorator is decorator:
|
|
51
56
|
return dec
|
|
52
57
|
|
|
53
58
|
return None
|
|
54
59
|
|
|
55
60
|
def has_decorator(self, decorator):
|
|
56
61
|
for dec in self.decorators:
|
|
57
|
-
if dec.decorator
|
|
62
|
+
if dec.decorator is decorator:
|
|
58
63
|
return True
|
|
59
64
|
|
|
60
65
|
return False
|
|
@@ -122,7 +127,7 @@ class TypeDescriptor:
|
|
|
122
127
|
|
|
123
128
|
def has_decorator(self, decorator) -> bool:
|
|
124
129
|
for dec in self.decorators:
|
|
125
|
-
if dec.decorator
|
|
130
|
+
if dec.decorator.__name__ == decorator.__name__:
|
|
126
131
|
return True
|
|
127
132
|
|
|
128
133
|
return False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: A DI and AOP library for Python
|
|
5
5
|
Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -32,8 +32,8 @@ Dynamic: license-file
|
|
|
32
32
|
|
|
33
33
|
# aspyx
|
|
34
34
|
|
|
35
|
-

|
|
36
|
+

|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
## Table of Contents
|
|
@@ -46,8 +46,10 @@ Dynamic: license-file
|
|
|
46
46
|
- [Environment](#environment)
|
|
47
47
|
- [Definition](#definition)
|
|
48
48
|
- [Retrieval](#retrieval)
|
|
49
|
-
- [
|
|
50
|
-
- [
|
|
49
|
+
- [Instantiation logic](#instantiation-logic)
|
|
50
|
+
- [Injection Methods](#injection-methods)
|
|
51
|
+
- [Lifecycle Methods](#lifecycle-methods)
|
|
52
|
+
- [Post Processors](#post-processors)
|
|
51
53
|
- [Custom scopes](#custom-scopes)
|
|
52
54
|
- [AOP](#aop)
|
|
53
55
|
- [Configuration](#configuration)
|
|
@@ -164,9 +166,10 @@ class Foo:
|
|
|
164
166
|
def __init__(self):
|
|
165
167
|
pass
|
|
166
168
|
```
|
|
167
|
-
|
|
169
|
+
Please make sure, that the class defines a local constructor, as this is required to determine injected instances.
|
|
170
|
+
All referenced types will be injected by the environemnt.
|
|
168
171
|
|
|
169
|
-
|
|
172
|
+
Only eligible types are allowed, of course!
|
|
170
173
|
|
|
171
174
|
|
|
172
175
|
The decorator accepts the keyword arguments
|
|
@@ -268,15 +271,55 @@ In case of ambiguities, it will throw an exception.
|
|
|
268
271
|
|
|
269
272
|
Please be aware, that a base class are not _required_ to be annotated with `@injectable`, as this would mean, that it could be created on its own as well. ( Which is possible as well, btw. )
|
|
270
273
|
|
|
271
|
-
#
|
|
274
|
+
# Instantiation logic
|
|
272
275
|
|
|
273
|
-
|
|
276
|
+
Constructing a new instance involves a number of steps executed in this order
|
|
277
|
+
- Constructor call
|
|
278
|
+
the constructor is called with the resolved parameters
|
|
279
|
+
- Advice injection
|
|
280
|
+
All methods involving aspects are updated
|
|
281
|
+
- Lifecycle methods
|
|
282
|
+
different decorators can mark methods that should be called during the lifecycle ( here the construction ) of an instance.
|
|
283
|
+
These are various injection possibilities as well as an optional final `on_init` call
|
|
284
|
+
- PostProcessors
|
|
285
|
+
Any custom post processors, that can add isde effects or modify the instances
|
|
286
|
+
|
|
287
|
+
## Injection methods
|
|
288
|
+
|
|
289
|
+
Different decorators are implemented, that call methods with computed values
|
|
290
|
+
|
|
291
|
+
- `@inject`
|
|
292
|
+
the method is called with all resolved parameter types ( same as the constructor call)
|
|
293
|
+
- `@inject_environment`
|
|
294
|
+
the method is called with the creating environment as a single parameter
|
|
295
|
+
- `@value()`
|
|
296
|
+
the method is called with a resolved configuration value. Check the corresponding chapter
|
|
297
|
+
|
|
298
|
+
**Example**:
|
|
299
|
+
```python
|
|
300
|
+
@injectable()
|
|
301
|
+
class Foo:
|
|
302
|
+
def __init__(self):
|
|
303
|
+
pass
|
|
304
|
+
|
|
305
|
+
@inject_environment()
|
|
306
|
+
def initEnvironment(self, env: Environment):
|
|
307
|
+
...
|
|
308
|
+
|
|
309
|
+
@inject()
|
|
310
|
+
def set(self, baz: Baz) -> None:
|
|
311
|
+
...
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Lifecycle methods
|
|
315
|
+
|
|
316
|
+
It is possible to mark specific lifecle methods.
|
|
274
317
|
- `@on_init()`
|
|
275
318
|
called after the constructor and all other injections.
|
|
276
319
|
- `@on_destroy()`
|
|
277
|
-
called
|
|
320
|
+
called during shutdown of the environment
|
|
278
321
|
|
|
279
|
-
|
|
322
|
+
## Post Processors
|
|
280
323
|
|
|
281
324
|
As part of the instantiation logic it is possible to define post processors that execute any side effect on newly created instances.
|
|
282
325
|
|
|
@@ -5,7 +5,7 @@ import unittest
|
|
|
5
5
|
|
|
6
6
|
from aspyx.reflection import Decorators
|
|
7
7
|
from aspyx.di import injectable, Environment, environment
|
|
8
|
-
from aspyx.di.aop import advice, before, after, around, methods, Invocation, error
|
|
8
|
+
from aspyx.di.aop import advice, before, after, around, methods, Invocation, error, classes
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def transactional():
|
|
@@ -22,11 +22,12 @@ class TestEnvironment:
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
@injectable()
|
|
25
|
+
@transactional()
|
|
25
26
|
class Bar:
|
|
26
27
|
def __init__(self):
|
|
27
28
|
pass
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
#@transactional()
|
|
30
31
|
def say(self, hello: str):
|
|
31
32
|
return hello
|
|
32
33
|
|
|
@@ -89,7 +90,7 @@ class SampleAdvice:
|
|
|
89
90
|
|
|
90
91
|
return invocation.proceed()
|
|
91
92
|
|
|
92
|
-
@around(methods().decorated_with(transactional))
|
|
93
|
+
@around(methods().decorated_with(transactional), classes().decorated_with(transactional))
|
|
93
94
|
def callTransactional1(self, invocation: Invocation):
|
|
94
95
|
self.around_calls += 1
|
|
95
96
|
|
|
@@ -101,7 +102,7 @@ class SampleAdvice:
|
|
|
101
102
|
|
|
102
103
|
return invocation.proceed()
|
|
103
104
|
|
|
104
|
-
logging.basicConfig(level=logging.DEBUG)
|
|
105
|
+
#logging.basicConfig(level=logging.DEBUG)
|
|
105
106
|
|
|
106
107
|
class TestAdvice(unittest.TestCase):
|
|
107
108
|
testEnvironment = Environment(TestEnvironment)
|
|
@@ -6,6 +6,7 @@ import unittest
|
|
|
6
6
|
from typing import Dict
|
|
7
7
|
|
|
8
8
|
from aspyx.di import InjectorException, injectable, on_init, on_destroy, inject_environment, inject, Factory, create, environment, Environment, PostProcessor, factory
|
|
9
|
+
from aspyx.di.di import order
|
|
9
10
|
from di_import import ImportedEnvironment, ImportedClass
|
|
10
11
|
|
|
11
12
|
# not here
|
|
@@ -27,6 +28,7 @@ configure_logging({
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
@injectable()
|
|
31
|
+
@order(10)
|
|
30
32
|
class SamplePostProcessor(PostProcessor):
|
|
31
33
|
def process(self, instance: object, environment: Environment):
|
|
32
34
|
pass #print(f"created a {instance}")
|
|
@@ -220,7 +222,7 @@ class TestInject(unittest.TestCase):
|
|
|
220
222
|
|
|
221
223
|
self.assertIsNot(ns, ns1)
|
|
222
224
|
|
|
223
|
-
#def test_import_configurations(self):
|
|
225
|
+
#def test_import_configurations(self): TODO
|
|
224
226
|
# env = Environment(TestEnvironment)#
|
|
225
227
|
|
|
226
228
|
# imported = env.get(ImportedClass)
|
|
@@ -2,12 +2,23 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import unittest
|
|
4
4
|
|
|
5
|
-
from aspyx.
|
|
5
|
+
from aspyx.di import injectable
|
|
6
|
+
from aspyx.reflection import TypeDescriptor, Decorators
|
|
6
7
|
|
|
8
|
+
|
|
9
|
+
def transactional():
|
|
10
|
+
def decorator(func):
|
|
11
|
+
Decorators.add(func, transactional)
|
|
12
|
+
return func #
|
|
13
|
+
|
|
14
|
+
return decorator
|
|
15
|
+
|
|
16
|
+
@transactional()
|
|
7
17
|
class Base:
|
|
8
18
|
def __init__(self):
|
|
9
19
|
pass
|
|
10
20
|
|
|
21
|
+
@transactional()
|
|
11
22
|
def base(self, message: str) -> str:
|
|
12
23
|
pass
|
|
13
24
|
|
|
@@ -26,7 +37,13 @@ class Derived(Base):
|
|
|
26
37
|
pass
|
|
27
38
|
|
|
28
39
|
class TestReflection(unittest.TestCase):
|
|
29
|
-
def
|
|
40
|
+
def test_decorators(self):
|
|
41
|
+
baseDescriptor = TypeDescriptor.for_type(Base)
|
|
42
|
+
|
|
43
|
+
self.assertTrue(baseDescriptor.has_decorator(transactional))
|
|
44
|
+
self.assertTrue( baseDescriptor.get_method("base").has_decorator(transactional))
|
|
45
|
+
|
|
46
|
+
def test_methods(self):
|
|
30
47
|
derivedDescriptor = TypeDescriptor.for_type(Derived)
|
|
31
48
|
|
|
32
49
|
self.assertIsNotNone(derivedDescriptor.get_method("derived").returnType, str)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|