aspyx 0.1.0__tar.gz → 1.0.1__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.
Potentially problematic release.
This version of aspyx might be problematic. Click here for more details.
- {aspyx-0.1.0/src/aspyx.egg-info → aspyx-1.0.1}/PKG-INFO +147 -43
- {aspyx-0.1.0 → aspyx-1.0.1}/README.md +145 -41
- {aspyx-0.1.0 → aspyx-1.0.1}/pyproject.toml +2 -2
- {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/di/__init__.py +4 -1
- {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/di/aop/__init__.py +4 -1
- {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/di/aop/aop.py +98 -88
- {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/di/configuration/__init__.py +4 -1
- {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/di/configuration/configuration.py +21 -14
- {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/di/di.py +137 -109
- {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/reflection/__init__.py +4 -1
- {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/reflection/proxy.py +10 -7
- {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/reflection/reflection.py +35 -22
- {aspyx-0.1.0 → aspyx-1.0.1/src/aspyx.egg-info}/PKG-INFO +147 -43
- {aspyx-0.1.0 → aspyx-1.0.1}/tests/test_aop.py +5 -4
- {aspyx-0.1.0 → aspyx-1.0.1}/tests/test_configuration.py +5 -7
- {aspyx-0.1.0 → aspyx-1.0.1}/tests/test_di.py +3 -1
- {aspyx-0.1.0 → aspyx-1.0.1}/tests/test_reflection.py +20 -3
- {aspyx-0.1.0 → aspyx-1.0.1}/LICENSE +0 -0
- {aspyx-0.1.0 → aspyx-1.0.1}/setup.cfg +0 -0
- {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx.egg-info/SOURCES.txt +0 -0
- {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx.egg-info/dependency_links.txt +0 -0
- {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx.egg-info/top_level.txt +0 -0
- {aspyx-0.1.0 → aspyx-1.0.1}/tests/test_di_cycle.py +0 -0
- {aspyx-0.1.0 → aspyx-1.0.1}/tests/test_proxy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 1.0.1
|
|
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
|
|
@@ -25,20 +25,22 @@ License: MIT License
|
|
|
25
25
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
|
|
28
|
-
Requires-Python: >=3.
|
|
28
|
+
Requires-Python: >=3.9
|
|
29
29
|
Description-Content-Type: text/markdown
|
|
30
30
|
License-File: LICENSE
|
|
31
31
|
Dynamic: license-file
|
|
32
32
|
|
|
33
33
|
# aspyx
|
|
34
34
|
|
|
35
|
-

|
|
36
|
+

|
|
37
|
+

|
|
38
|
+

|
|
38
39
|
|
|
39
40
|
## Table of Contents
|
|
40
41
|
|
|
41
42
|
- [Introduction](#aspyx)
|
|
43
|
+
- [Installation](#installation)
|
|
42
44
|
- [Registration](#registration)
|
|
43
45
|
- [Class](#class)
|
|
44
46
|
- [Class Factory](#class-factory)
|
|
@@ -46,11 +48,14 @@ Dynamic: license-file
|
|
|
46
48
|
- [Environment](#environment)
|
|
47
49
|
- [Definition](#definition)
|
|
48
50
|
- [Retrieval](#retrieval)
|
|
49
|
-
- [
|
|
50
|
-
- [
|
|
51
|
+
- [Instantiation logic](#instantiation-logic)
|
|
52
|
+
- [Injection Methods](#injection-methods)
|
|
53
|
+
- [Lifecycle Methods](#lifecycle-methods)
|
|
54
|
+
- [Post Processors](#post-processors)
|
|
51
55
|
- [Custom scopes](#custom-scopes)
|
|
52
56
|
- [AOP](#aop)
|
|
53
57
|
- [Configuration](#configuration)
|
|
58
|
+
- [Reflection](#reflection)
|
|
54
59
|
|
|
55
60
|
# Introduction
|
|
56
61
|
|
|
@@ -69,12 +74,13 @@ The following features are supported
|
|
|
69
74
|
- container instances that relate to environment classes and manage the lifecylce of related objects
|
|
70
75
|
- hierarchical environments
|
|
71
76
|
|
|
77
|
+
The library is thread-safe!
|
|
78
|
+
|
|
72
79
|
Let's look at a simple example
|
|
73
80
|
|
|
74
81
|
```python
|
|
75
82
|
from aspyx.di import injectable, on_init, on_destroy, environment, Environment
|
|
76
83
|
|
|
77
|
-
|
|
78
84
|
@injectable()
|
|
79
85
|
class Foo:
|
|
80
86
|
def __init__(self):
|
|
@@ -83,13 +89,12 @@ class Foo:
|
|
|
83
89
|
def hello(msg: str):
|
|
84
90
|
print(f"hello {msg}")
|
|
85
91
|
|
|
86
|
-
|
|
87
92
|
@injectable() # eager and singleton by default
|
|
88
93
|
class Bar:
|
|
89
94
|
def __init__(self, foo: Foo): # will inject the Foo dependency
|
|
90
95
|
self.foo = foo
|
|
91
96
|
|
|
92
|
-
@on_init() # a lifecycle callback called
|
|
97
|
+
@on_init() # a lifecycle callback called after the constructor and all possible injections
|
|
93
98
|
def init(self):
|
|
94
99
|
...
|
|
95
100
|
|
|
@@ -99,41 +104,40 @@ class Bar:
|
|
|
99
104
|
|
|
100
105
|
@environment()
|
|
101
106
|
class SampleEnvironment:
|
|
102
|
-
# constructor
|
|
103
|
-
|
|
104
107
|
def __init__(self):
|
|
105
108
|
pass
|
|
106
109
|
|
|
107
|
-
|
|
108
110
|
# go, forrest
|
|
109
111
|
|
|
110
|
-
environment = SampleEnvironment
|
|
112
|
+
environment = Environment(SampleEnvironment)
|
|
111
113
|
|
|
112
114
|
bar = env.get(Bar)
|
|
113
|
-
|
|
115
|
+
|
|
116
|
+
bar.foo.hello("world")
|
|
114
117
|
```
|
|
115
118
|
|
|
116
|
-
The concepts should be pretty familiar
|
|
119
|
+
The concepts should be pretty familiar as well as the names which are a combination of Spring and Angular names :-)
|
|
117
120
|
|
|
118
121
|
Let's add some aspects...
|
|
119
122
|
|
|
120
123
|
```python
|
|
124
|
+
|
|
121
125
|
@advice
|
|
122
126
|
class SampleAdvice:
|
|
123
|
-
def __init__(self):
|
|
127
|
+
def __init__(self): # could inject additional stuff
|
|
124
128
|
pass
|
|
125
129
|
|
|
126
130
|
@before(methods().named("hello").of_type(Foo))
|
|
127
|
-
def
|
|
131
|
+
def call_before(self, invocation: Invocation):
|
|
128
132
|
print("before Foo.hello(...)")
|
|
129
133
|
|
|
130
134
|
@error(methods().named("hello").of_type(Foo))
|
|
131
|
-
def
|
|
135
|
+
def call_error(self, invocation: Invocation):
|
|
132
136
|
print("error Foo.hello(...)")
|
|
133
137
|
print(invocation.exception)
|
|
134
138
|
|
|
135
139
|
@around(methods().named("hello"))
|
|
136
|
-
def
|
|
140
|
+
def call_around(self, invocation: Invocation):
|
|
137
141
|
print("around Foo.hello()")
|
|
138
142
|
|
|
139
143
|
return invocation.proceed()
|
|
@@ -148,6 +152,14 @@ The invocation parameter stores the complete context of the current execution, w
|
|
|
148
152
|
|
|
149
153
|
Let's look at the details
|
|
150
154
|
|
|
155
|
+
# Installation
|
|
156
|
+
|
|
157
|
+
`pip install aspyx`
|
|
158
|
+
|
|
159
|
+
The library is tested with Python version > 3.8
|
|
160
|
+
|
|
161
|
+
Ready to go...
|
|
162
|
+
|
|
151
163
|
# Registration
|
|
152
164
|
|
|
153
165
|
Different mechanisms are available that make classes eligible for injection
|
|
@@ -164,16 +176,19 @@ class Foo:
|
|
|
164
176
|
def __init__(self):
|
|
165
177
|
pass
|
|
166
178
|
```
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
The constructor can only define parameter types that are known as well to the container!
|
|
179
|
+
Please make sure, that the class defines a local constructor, as this is required to determine injected instances.
|
|
180
|
+
All referenced types will be injected by the environemnt.
|
|
170
181
|
|
|
182
|
+
Only eligible types are allowed, of course!
|
|
171
183
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
184
|
+
The decorator accepts the keyword arguments
|
|
185
|
+
- `eager : boolean`
|
|
186
|
+
if `True`, the container will create the instances automatically while booting the environment. This is the default.
|
|
187
|
+
- `scope: str`
|
|
188
|
+
the name of a scope which will determine how often instances will be created.
|
|
189
|
+
`singleton` will create it only once - per environment -, while `request` will recreate it on every injection request. The default is `singleton`
|
|
175
190
|
|
|
176
|
-
Other scopes can be defined. Please check the corresponding chapter.
|
|
191
|
+
Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
|
|
177
192
|
|
|
178
193
|
## Class Factory
|
|
179
194
|
|
|
@@ -268,15 +283,55 @@ In case of ambiguities, it will throw an exception.
|
|
|
268
283
|
|
|
269
284
|
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
285
|
|
|
271
|
-
#
|
|
286
|
+
# Instantiation logic
|
|
287
|
+
|
|
288
|
+
Constructing a new instance involves a number of steps executed in this order
|
|
289
|
+
- Constructor call
|
|
290
|
+
the constructor is called with the resolved parameters
|
|
291
|
+
- Advice injection
|
|
292
|
+
All methods involving aspects are updated
|
|
293
|
+
- Lifecycle methods
|
|
294
|
+
different decorators can mark methods that should be called during the lifecycle ( here the construction ) of an instance.
|
|
295
|
+
These are various injection possibilities as well as an optional final `on_init` call
|
|
296
|
+
- PostProcessors
|
|
297
|
+
Any custom post processors, that can add isde effects or modify the instances
|
|
298
|
+
|
|
299
|
+
## Injection methods
|
|
300
|
+
|
|
301
|
+
Different decorators are implemented, that call methods with computed values
|
|
302
|
+
|
|
303
|
+
- `@inject`
|
|
304
|
+
the method is called with all resolved parameter types ( same as the constructor call)
|
|
305
|
+
- `@inject_environment`
|
|
306
|
+
the method is called with the creating environment as a single parameter
|
|
307
|
+
- `@value()`
|
|
308
|
+
the method is called with a resolved configuration value. Check the corresponding chapter
|
|
309
|
+
|
|
310
|
+
**Example**:
|
|
311
|
+
```python
|
|
312
|
+
@injectable()
|
|
313
|
+
class Foo:
|
|
314
|
+
def __init__(self):
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
@inject_environment()
|
|
318
|
+
def initEnvironment(self, env: Environment):
|
|
319
|
+
...
|
|
320
|
+
|
|
321
|
+
@inject()
|
|
322
|
+
def set(self, baz: Baz) -> None:
|
|
323
|
+
...
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Lifecycle methods
|
|
272
327
|
|
|
273
|
-
It is possible to
|
|
328
|
+
It is possible to mark specific lifecle methods.
|
|
274
329
|
- `@on_init()`
|
|
275
330
|
called after the constructor and all other injections.
|
|
276
331
|
- `@on_destroy()`
|
|
277
|
-
called
|
|
332
|
+
called during shutdown of the environment
|
|
278
333
|
|
|
279
|
-
|
|
334
|
+
## Post Processors
|
|
280
335
|
|
|
281
336
|
As part of the instantiation logic it is possible to define post processors that execute any side effect on newly created instances.
|
|
282
337
|
|
|
@@ -343,17 +398,17 @@ class SampleAdvice:
|
|
|
343
398
|
pass
|
|
344
399
|
|
|
345
400
|
@before(methods().named("hello").of_type(Foo))
|
|
346
|
-
def
|
|
401
|
+
def call_before(self, invocation: Invocation):
|
|
347
402
|
# arguments: invocation.args
|
|
348
403
|
print("before Foo.hello(...)")
|
|
349
404
|
|
|
350
405
|
@error(methods().named("hello").of_type(Foo))
|
|
351
|
-
def
|
|
406
|
+
def call_error(self, invocation: Invocation):
|
|
352
407
|
print("error Foo.hello(...)")
|
|
353
408
|
print(invocation.exception)
|
|
354
409
|
|
|
355
410
|
@around(methods().named("hello"))
|
|
356
|
-
def
|
|
411
|
+
def call_around(self, invocation: Invocation):
|
|
357
412
|
print("around Foo.hello()")
|
|
358
413
|
|
|
359
414
|
return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
|
|
@@ -374,7 +429,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
|
|
|
374
429
|
It is essential for `around` methods to call `proceed()` on the invocation, which will call the next around method in the chain and finally the original method.
|
|
375
430
|
If the `proceed` is called with parameters, they will replace the original parameters!
|
|
376
431
|
|
|
377
|
-
The
|
|
432
|
+
The argument list to the corresponding decorators control, how aspects are associated with which methods.
|
|
378
433
|
A fluent interface is used describe the mapping.
|
|
379
434
|
The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
|
|
380
435
|
|
|
@@ -388,7 +443,20 @@ Both add the fluent methods:
|
|
|
388
443
|
- `decorated_with(type: Type)`
|
|
389
444
|
defines decorators on methods or classes
|
|
390
445
|
|
|
391
|
-
The fluent methods `named`, `matches` and `of_type` can be called multiple
|
|
446
|
+
The fluent methods `named`, `matches` and `of_type` can be called multiple times!
|
|
447
|
+
|
|
448
|
+
**Example**:
|
|
449
|
+
|
|
450
|
+
```python
|
|
451
|
+
@injectable()
|
|
452
|
+
class TransactionAdvice:
|
|
453
|
+
def __init__(self):
|
|
454
|
+
pass
|
|
455
|
+
|
|
456
|
+
@around(methods().decorated_with(transactional), classes().decorated_with(transactional))
|
|
457
|
+
def establish_transaction(self, invocation: Invocation):
|
|
458
|
+
...
|
|
459
|
+
```
|
|
392
460
|
|
|
393
461
|
# Configuration
|
|
394
462
|
|
|
@@ -409,10 +477,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
|
|
|
409
477
|
|
|
410
478
|
```python
|
|
411
479
|
class ConfigurationSource(ABC):
|
|
412
|
-
def __init__(self
|
|
413
|
-
manager._register(self)
|
|
480
|
+
def __init__(self):
|
|
414
481
|
pass
|
|
415
482
|
|
|
483
|
+
...
|
|
484
|
+
|
|
416
485
|
@abstractmethod
|
|
417
486
|
def load(self) -> dict:
|
|
418
487
|
pass
|
|
@@ -424,14 +493,12 @@ As a default environment variables are already supported.
|
|
|
424
493
|
|
|
425
494
|
Other sources can be added dynamically by just registering them.
|
|
426
495
|
|
|
496
|
+
**Example**:
|
|
427
497
|
```python
|
|
428
498
|
@injectable()
|
|
429
499
|
class SampleConfigurationSource(ConfigurationSource):
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
def __init__(self, manager: ConfigurationManager):
|
|
433
|
-
super().__init__(manager)
|
|
434
|
-
|
|
500
|
+
def __init__(self):
|
|
501
|
+
super().__init__()
|
|
435
502
|
|
|
436
503
|
def load(self) -> dict:
|
|
437
504
|
return {
|
|
@@ -444,6 +511,43 @@ class SampleConfigurationSource(ConfigurationSource):
|
|
|
444
511
|
}
|
|
445
512
|
```
|
|
446
513
|
|
|
514
|
+
# Reflection
|
|
515
|
+
|
|
516
|
+
As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
|
|
517
|
+
|
|
518
|
+
After beeing instatiated with
|
|
519
|
+
|
|
520
|
+
```python
|
|
521
|
+
TypeDescriptor.for_type(<type>)
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
it offers the methods
|
|
525
|
+
- `get_methods(local=False)`
|
|
526
|
+
- `get_method(name: str, local=False)`
|
|
527
|
+
- `has_decorator(decorator) -> bool`
|
|
528
|
+
- `get_decorator(decorator)`
|
|
529
|
+
|
|
530
|
+
The returned method descriptors offer:
|
|
531
|
+
- `param_types`
|
|
532
|
+
- `return_type`
|
|
533
|
+
- `has_decorator(decorator)`
|
|
534
|
+
- `get_decorator(decorator)`
|
|
535
|
+
|
|
536
|
+
The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
|
|
537
|
+
|
|
538
|
+
Whenver you define a custom decorator, you will need to register it accordingly.
|
|
539
|
+
|
|
540
|
+
**Example**:
|
|
541
|
+
```python
|
|
542
|
+
def transactional():
|
|
543
|
+
def decorator(func):
|
|
544
|
+
Decorators.add(func, transactional)
|
|
545
|
+
return func
|
|
546
|
+
|
|
547
|
+
return decorator
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
|
|
447
551
|
|
|
448
552
|
|
|
449
553
|
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# aspyx
|
|
2
2
|
|
|
3
|
-

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
6
7
|
|
|
7
8
|
## Table of Contents
|
|
8
9
|
|
|
9
10
|
- [Introduction](#aspyx)
|
|
11
|
+
- [Installation](#installation)
|
|
10
12
|
- [Registration](#registration)
|
|
11
13
|
- [Class](#class)
|
|
12
14
|
- [Class Factory](#class-factory)
|
|
@@ -14,11 +16,14 @@
|
|
|
14
16
|
- [Environment](#environment)
|
|
15
17
|
- [Definition](#definition)
|
|
16
18
|
- [Retrieval](#retrieval)
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
+
- [Instantiation logic](#instantiation-logic)
|
|
20
|
+
- [Injection Methods](#injection-methods)
|
|
21
|
+
- [Lifecycle Methods](#lifecycle-methods)
|
|
22
|
+
- [Post Processors](#post-processors)
|
|
19
23
|
- [Custom scopes](#custom-scopes)
|
|
20
24
|
- [AOP](#aop)
|
|
21
25
|
- [Configuration](#configuration)
|
|
26
|
+
- [Reflection](#reflection)
|
|
22
27
|
|
|
23
28
|
# Introduction
|
|
24
29
|
|
|
@@ -37,12 +42,13 @@ The following features are supported
|
|
|
37
42
|
- container instances that relate to environment classes and manage the lifecylce of related objects
|
|
38
43
|
- hierarchical environments
|
|
39
44
|
|
|
45
|
+
The library is thread-safe!
|
|
46
|
+
|
|
40
47
|
Let's look at a simple example
|
|
41
48
|
|
|
42
49
|
```python
|
|
43
50
|
from aspyx.di import injectable, on_init, on_destroy, environment, Environment
|
|
44
51
|
|
|
45
|
-
|
|
46
52
|
@injectable()
|
|
47
53
|
class Foo:
|
|
48
54
|
def __init__(self):
|
|
@@ -51,13 +57,12 @@ class Foo:
|
|
|
51
57
|
def hello(msg: str):
|
|
52
58
|
print(f"hello {msg}")
|
|
53
59
|
|
|
54
|
-
|
|
55
60
|
@injectable() # eager and singleton by default
|
|
56
61
|
class Bar:
|
|
57
62
|
def __init__(self, foo: Foo): # will inject the Foo dependency
|
|
58
63
|
self.foo = foo
|
|
59
64
|
|
|
60
|
-
@on_init() # a lifecycle callback called
|
|
65
|
+
@on_init() # a lifecycle callback called after the constructor and all possible injections
|
|
61
66
|
def init(self):
|
|
62
67
|
...
|
|
63
68
|
|
|
@@ -67,41 +72,40 @@ class Bar:
|
|
|
67
72
|
|
|
68
73
|
@environment()
|
|
69
74
|
class SampleEnvironment:
|
|
70
|
-
# constructor
|
|
71
|
-
|
|
72
75
|
def __init__(self):
|
|
73
76
|
pass
|
|
74
77
|
|
|
75
|
-
|
|
76
78
|
# go, forrest
|
|
77
79
|
|
|
78
|
-
environment = SampleEnvironment
|
|
80
|
+
environment = Environment(SampleEnvironment)
|
|
79
81
|
|
|
80
82
|
bar = env.get(Bar)
|
|
81
|
-
|
|
83
|
+
|
|
84
|
+
bar.foo.hello("world")
|
|
82
85
|
```
|
|
83
86
|
|
|
84
|
-
The concepts should be pretty familiar
|
|
87
|
+
The concepts should be pretty familiar as well as the names which are a combination of Spring and Angular names :-)
|
|
85
88
|
|
|
86
89
|
Let's add some aspects...
|
|
87
90
|
|
|
88
91
|
```python
|
|
92
|
+
|
|
89
93
|
@advice
|
|
90
94
|
class SampleAdvice:
|
|
91
|
-
def __init__(self):
|
|
95
|
+
def __init__(self): # could inject additional stuff
|
|
92
96
|
pass
|
|
93
97
|
|
|
94
98
|
@before(methods().named("hello").of_type(Foo))
|
|
95
|
-
def
|
|
99
|
+
def call_before(self, invocation: Invocation):
|
|
96
100
|
print("before Foo.hello(...)")
|
|
97
101
|
|
|
98
102
|
@error(methods().named("hello").of_type(Foo))
|
|
99
|
-
def
|
|
103
|
+
def call_error(self, invocation: Invocation):
|
|
100
104
|
print("error Foo.hello(...)")
|
|
101
105
|
print(invocation.exception)
|
|
102
106
|
|
|
103
107
|
@around(methods().named("hello"))
|
|
104
|
-
def
|
|
108
|
+
def call_around(self, invocation: Invocation):
|
|
105
109
|
print("around Foo.hello()")
|
|
106
110
|
|
|
107
111
|
return invocation.proceed()
|
|
@@ -116,6 +120,14 @@ The invocation parameter stores the complete context of the current execution, w
|
|
|
116
120
|
|
|
117
121
|
Let's look at the details
|
|
118
122
|
|
|
123
|
+
# Installation
|
|
124
|
+
|
|
125
|
+
`pip install aspyx`
|
|
126
|
+
|
|
127
|
+
The library is tested with Python version > 3.8
|
|
128
|
+
|
|
129
|
+
Ready to go...
|
|
130
|
+
|
|
119
131
|
# Registration
|
|
120
132
|
|
|
121
133
|
Different mechanisms are available that make classes eligible for injection
|
|
@@ -132,16 +144,19 @@ class Foo:
|
|
|
132
144
|
def __init__(self):
|
|
133
145
|
pass
|
|
134
146
|
```
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
The constructor can only define parameter types that are known as well to the container!
|
|
147
|
+
Please make sure, that the class defines a local constructor, as this is required to determine injected instances.
|
|
148
|
+
All referenced types will be injected by the environemnt.
|
|
138
149
|
|
|
150
|
+
Only eligible types are allowed, of course!
|
|
139
151
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
152
|
+
The decorator accepts the keyword arguments
|
|
153
|
+
- `eager : boolean`
|
|
154
|
+
if `True`, the container will create the instances automatically while booting the environment. This is the default.
|
|
155
|
+
- `scope: str`
|
|
156
|
+
the name of a scope which will determine how often instances will be created.
|
|
157
|
+
`singleton` will create it only once - per environment -, while `request` will recreate it on every injection request. The default is `singleton`
|
|
143
158
|
|
|
144
|
-
Other scopes can be defined. Please check the corresponding chapter.
|
|
159
|
+
Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
|
|
145
160
|
|
|
146
161
|
## Class Factory
|
|
147
162
|
|
|
@@ -236,15 +251,55 @@ In case of ambiguities, it will throw an exception.
|
|
|
236
251
|
|
|
237
252
|
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
253
|
|
|
239
|
-
#
|
|
254
|
+
# Instantiation logic
|
|
255
|
+
|
|
256
|
+
Constructing a new instance involves a number of steps executed in this order
|
|
257
|
+
- Constructor call
|
|
258
|
+
the constructor is called with the resolved parameters
|
|
259
|
+
- Advice injection
|
|
260
|
+
All methods involving aspects are updated
|
|
261
|
+
- Lifecycle methods
|
|
262
|
+
different decorators can mark methods that should be called during the lifecycle ( here the construction ) of an instance.
|
|
263
|
+
These are various injection possibilities as well as an optional final `on_init` call
|
|
264
|
+
- PostProcessors
|
|
265
|
+
Any custom post processors, that can add isde effects or modify the instances
|
|
266
|
+
|
|
267
|
+
## Injection methods
|
|
268
|
+
|
|
269
|
+
Different decorators are implemented, that call methods with computed values
|
|
270
|
+
|
|
271
|
+
- `@inject`
|
|
272
|
+
the method is called with all resolved parameter types ( same as the constructor call)
|
|
273
|
+
- `@inject_environment`
|
|
274
|
+
the method is called with the creating environment as a single parameter
|
|
275
|
+
- `@value()`
|
|
276
|
+
the method is called with a resolved configuration value. Check the corresponding chapter
|
|
277
|
+
|
|
278
|
+
**Example**:
|
|
279
|
+
```python
|
|
280
|
+
@injectable()
|
|
281
|
+
class Foo:
|
|
282
|
+
def __init__(self):
|
|
283
|
+
pass
|
|
284
|
+
|
|
285
|
+
@inject_environment()
|
|
286
|
+
def initEnvironment(self, env: Environment):
|
|
287
|
+
...
|
|
288
|
+
|
|
289
|
+
@inject()
|
|
290
|
+
def set(self, baz: Baz) -> None:
|
|
291
|
+
...
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Lifecycle methods
|
|
240
295
|
|
|
241
|
-
It is possible to
|
|
296
|
+
It is possible to mark specific lifecle methods.
|
|
242
297
|
- `@on_init()`
|
|
243
298
|
called after the constructor and all other injections.
|
|
244
299
|
- `@on_destroy()`
|
|
245
|
-
called
|
|
300
|
+
called during shutdown of the environment
|
|
246
301
|
|
|
247
|
-
|
|
302
|
+
## Post Processors
|
|
248
303
|
|
|
249
304
|
As part of the instantiation logic it is possible to define post processors that execute any side effect on newly created instances.
|
|
250
305
|
|
|
@@ -311,17 +366,17 @@ class SampleAdvice:
|
|
|
311
366
|
pass
|
|
312
367
|
|
|
313
368
|
@before(methods().named("hello").of_type(Foo))
|
|
314
|
-
def
|
|
369
|
+
def call_before(self, invocation: Invocation):
|
|
315
370
|
# arguments: invocation.args
|
|
316
371
|
print("before Foo.hello(...)")
|
|
317
372
|
|
|
318
373
|
@error(methods().named("hello").of_type(Foo))
|
|
319
|
-
def
|
|
374
|
+
def call_error(self, invocation: Invocation):
|
|
320
375
|
print("error Foo.hello(...)")
|
|
321
376
|
print(invocation.exception)
|
|
322
377
|
|
|
323
378
|
@around(methods().named("hello"))
|
|
324
|
-
def
|
|
379
|
+
def call_around(self, invocation: Invocation):
|
|
325
380
|
print("around Foo.hello()")
|
|
326
381
|
|
|
327
382
|
return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
|
|
@@ -342,7 +397,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
|
|
|
342
397
|
It is essential for `around` methods to call `proceed()` on the invocation, which will call the next around method in the chain and finally the original method.
|
|
343
398
|
If the `proceed` is called with parameters, they will replace the original parameters!
|
|
344
399
|
|
|
345
|
-
The
|
|
400
|
+
The argument list to the corresponding decorators control, how aspects are associated with which methods.
|
|
346
401
|
A fluent interface is used describe the mapping.
|
|
347
402
|
The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
|
|
348
403
|
|
|
@@ -356,7 +411,20 @@ Both add the fluent methods:
|
|
|
356
411
|
- `decorated_with(type: Type)`
|
|
357
412
|
defines decorators on methods or classes
|
|
358
413
|
|
|
359
|
-
The fluent methods `named`, `matches` and `of_type` can be called multiple
|
|
414
|
+
The fluent methods `named`, `matches` and `of_type` can be called multiple times!
|
|
415
|
+
|
|
416
|
+
**Example**:
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
@injectable()
|
|
420
|
+
class TransactionAdvice:
|
|
421
|
+
def __init__(self):
|
|
422
|
+
pass
|
|
423
|
+
|
|
424
|
+
@around(methods().decorated_with(transactional), classes().decorated_with(transactional))
|
|
425
|
+
def establish_transaction(self, invocation: Invocation):
|
|
426
|
+
...
|
|
427
|
+
```
|
|
360
428
|
|
|
361
429
|
# Configuration
|
|
362
430
|
|
|
@@ -377,10 +445,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
|
|
|
377
445
|
|
|
378
446
|
```python
|
|
379
447
|
class ConfigurationSource(ABC):
|
|
380
|
-
def __init__(self
|
|
381
|
-
manager._register(self)
|
|
448
|
+
def __init__(self):
|
|
382
449
|
pass
|
|
383
450
|
|
|
451
|
+
...
|
|
452
|
+
|
|
384
453
|
@abstractmethod
|
|
385
454
|
def load(self) -> dict:
|
|
386
455
|
pass
|
|
@@ -392,14 +461,12 @@ As a default environment variables are already supported.
|
|
|
392
461
|
|
|
393
462
|
Other sources can be added dynamically by just registering them.
|
|
394
463
|
|
|
464
|
+
**Example**:
|
|
395
465
|
```python
|
|
396
466
|
@injectable()
|
|
397
467
|
class SampleConfigurationSource(ConfigurationSource):
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
def __init__(self, manager: ConfigurationManager):
|
|
401
|
-
super().__init__(manager)
|
|
402
|
-
|
|
468
|
+
def __init__(self):
|
|
469
|
+
super().__init__()
|
|
403
470
|
|
|
404
471
|
def load(self) -> dict:
|
|
405
472
|
return {
|
|
@@ -412,6 +479,43 @@ class SampleConfigurationSource(ConfigurationSource):
|
|
|
412
479
|
}
|
|
413
480
|
```
|
|
414
481
|
|
|
482
|
+
# Reflection
|
|
483
|
+
|
|
484
|
+
As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
|
|
485
|
+
|
|
486
|
+
After beeing instatiated with
|
|
487
|
+
|
|
488
|
+
```python
|
|
489
|
+
TypeDescriptor.for_type(<type>)
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
it offers the methods
|
|
493
|
+
- `get_methods(local=False)`
|
|
494
|
+
- `get_method(name: str, local=False)`
|
|
495
|
+
- `has_decorator(decorator) -> bool`
|
|
496
|
+
- `get_decorator(decorator)`
|
|
497
|
+
|
|
498
|
+
The returned method descriptors offer:
|
|
499
|
+
- `param_types`
|
|
500
|
+
- `return_type`
|
|
501
|
+
- `has_decorator(decorator)`
|
|
502
|
+
- `get_decorator(decorator)`
|
|
503
|
+
|
|
504
|
+
The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
|
|
505
|
+
|
|
506
|
+
Whenver you define a custom decorator, you will need to register it accordingly.
|
|
507
|
+
|
|
508
|
+
**Example**:
|
|
509
|
+
```python
|
|
510
|
+
def transactional():
|
|
511
|
+
def decorator(func):
|
|
512
|
+
Decorators.add(func, transactional)
|
|
513
|
+
return func
|
|
514
|
+
|
|
515
|
+
return decorator
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
|
|
415
519
|
|
|
416
520
|
|
|
417
521
|
|