aspyx 1.0.0__tar.gz → 1.1.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.
Potentially problematic release.
This version of aspyx might be problematic. Click here for more details.
- {aspyx-1.0.0/src/aspyx.egg-info → aspyx-1.1.0}/PKG-INFO +126 -38
- {aspyx-1.0.0 → aspyx-1.1.0}/README.md +124 -36
- {aspyx-1.0.0 → aspyx-1.1.0}/pyproject.toml +2 -2
- aspyx-1.1.0/src/aspyx/di/__init__.py +33 -0
- {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/di/aop/__init__.py +4 -1
- {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/di/aop/aop.py +56 -76
- {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/di/configuration/__init__.py +4 -1
- {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/di/configuration/configuration.py +22 -17
- {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/di/di.py +280 -171
- aspyx-1.1.0/src/aspyx/di/util/__init__.py +8 -0
- aspyx-1.1.0/src/aspyx/di/util/stringbuilder.py +31 -0
- {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/reflection/__init__.py +4 -1
- {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/reflection/proxy.py +10 -7
- {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/reflection/reflection.py +38 -25
- {aspyx-1.0.0 → aspyx-1.1.0/src/aspyx.egg-info}/PKG-INFO +126 -38
- {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx.egg-info/SOURCES.txt +2 -0
- {aspyx-1.0.0 → aspyx-1.1.0}/tests/test_aop.py +14 -12
- {aspyx-1.0.0 → aspyx-1.1.0}/tests/test_configuration.py +12 -12
- {aspyx-1.0.0 → aspyx-1.1.0}/tests/test_di.py +42 -28
- {aspyx-1.0.0 → aspyx-1.1.0}/tests/test_di_cycle.py +7 -5
- {aspyx-1.0.0 → aspyx-1.1.0}/tests/test_proxy.py +4 -1
- {aspyx-1.0.0 → aspyx-1.1.0}/tests/test_reflection.py +10 -14
- aspyx-1.0.0/src/aspyx/di/__init__.py +0 -29
- {aspyx-1.0.0 → aspyx-1.1.0}/LICENSE +0 -0
- {aspyx-1.0.0 → aspyx-1.1.0}/setup.cfg +0 -0
- {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx.egg-info/dependency_links.txt +0 -0
- {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.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
|
|
@@ -25,7 +25,7 @@ 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
|
|
@@ -34,11 +34,14 @@ Dynamic: license-file
|
|
|
34
34
|
|
|
35
35
|

|
|
36
36
|

|
|
37
|
-
|
|
37
|
+

|
|
38
|
+

|
|
39
|
+

|
|
38
40
|
|
|
39
41
|
## Table of Contents
|
|
40
42
|
|
|
41
43
|
- [Introduction](#aspyx)
|
|
44
|
+
- [Installation](#installation)
|
|
42
45
|
- [Registration](#registration)
|
|
43
46
|
- [Class](#class)
|
|
44
47
|
- [Class Factory](#class-factory)
|
|
@@ -53,30 +56,32 @@ Dynamic: license-file
|
|
|
53
56
|
- [Custom scopes](#custom-scopes)
|
|
54
57
|
- [AOP](#aop)
|
|
55
58
|
- [Configuration](#configuration)
|
|
59
|
+
- [Reflection](#reflection)
|
|
60
|
+
- [Version History](#version-history)
|
|
56
61
|
|
|
57
62
|
# Introduction
|
|
58
63
|
|
|
59
64
|
Aspyx is a small python libary, that adds support for both dependency injection and aop.
|
|
60
65
|
|
|
61
66
|
The following features are supported
|
|
62
|
-
- constructor injection
|
|
63
|
-
- method injection
|
|
67
|
+
- constructor and setter injection
|
|
64
68
|
- post processors
|
|
65
69
|
- factory classes and methods
|
|
66
70
|
- support for eager construction
|
|
67
|
-
- support for singleton and
|
|
71
|
+
- support for singleton and request scopes
|
|
68
72
|
- possibilty to add custom scopes
|
|
69
73
|
- lifecycle events methods
|
|
70
74
|
- bundling of injectable object sets by environment classes including recursive imports and inheritance
|
|
71
75
|
- container instances that relate to environment classes and manage the lifecylce of related objects
|
|
72
76
|
- hierarchical environments
|
|
73
77
|
|
|
78
|
+
The library is thread-safe!
|
|
79
|
+
|
|
74
80
|
Let's look at a simple example
|
|
75
81
|
|
|
76
82
|
```python
|
|
77
83
|
from aspyx.di import injectable, on_init, on_destroy, environment, Environment
|
|
78
84
|
|
|
79
|
-
|
|
80
85
|
@injectable()
|
|
81
86
|
class Foo:
|
|
82
87
|
def __init__(self):
|
|
@@ -85,13 +90,12 @@ class Foo:
|
|
|
85
90
|
def hello(msg: str):
|
|
86
91
|
print(f"hello {msg}")
|
|
87
92
|
|
|
88
|
-
|
|
89
93
|
@injectable() # eager and singleton by default
|
|
90
94
|
class Bar:
|
|
91
95
|
def __init__(self, foo: Foo): # will inject the Foo dependency
|
|
92
96
|
self.foo = foo
|
|
93
97
|
|
|
94
|
-
@on_init() # a lifecycle callback called
|
|
98
|
+
@on_init() # a lifecycle callback called after the constructor and all possible injections
|
|
95
99
|
def init(self):
|
|
96
100
|
...
|
|
97
101
|
|
|
@@ -101,41 +105,40 @@ class Bar:
|
|
|
101
105
|
|
|
102
106
|
@environment()
|
|
103
107
|
class SampleEnvironment:
|
|
104
|
-
# constructor
|
|
105
|
-
|
|
106
108
|
def __init__(self):
|
|
107
109
|
pass
|
|
108
110
|
|
|
109
|
-
|
|
110
111
|
# go, forrest
|
|
111
112
|
|
|
112
|
-
environment = SampleEnvironment
|
|
113
|
+
environment = Environment(SampleEnvironment)
|
|
113
114
|
|
|
114
115
|
bar = env.get(Bar)
|
|
115
|
-
|
|
116
|
+
|
|
117
|
+
bar.foo.hello("world")
|
|
116
118
|
```
|
|
117
119
|
|
|
118
|
-
The concepts should be pretty familiar
|
|
120
|
+
The concepts should be pretty familiar as well as the names which are a combination of Spring and Angular names :-)
|
|
119
121
|
|
|
120
122
|
Let's add some aspects...
|
|
121
123
|
|
|
122
124
|
```python
|
|
125
|
+
|
|
123
126
|
@advice
|
|
124
127
|
class SampleAdvice:
|
|
125
|
-
def __init__(self):
|
|
128
|
+
def __init__(self): # could inject additional stuff
|
|
126
129
|
pass
|
|
127
130
|
|
|
128
131
|
@before(methods().named("hello").of_type(Foo))
|
|
129
|
-
def
|
|
132
|
+
def call_before(self, invocation: Invocation):
|
|
130
133
|
print("before Foo.hello(...)")
|
|
131
134
|
|
|
132
135
|
@error(methods().named("hello").of_type(Foo))
|
|
133
|
-
def
|
|
136
|
+
def call_error(self, invocation: Invocation):
|
|
134
137
|
print("error Foo.hello(...)")
|
|
135
138
|
print(invocation.exception)
|
|
136
139
|
|
|
137
140
|
@around(methods().named("hello"))
|
|
138
|
-
def
|
|
141
|
+
def call_around(self, invocation: Invocation):
|
|
139
142
|
print("around Foo.hello()")
|
|
140
143
|
|
|
141
144
|
return invocation.proceed()
|
|
@@ -150,6 +153,14 @@ The invocation parameter stores the complete context of the current execution, w
|
|
|
150
153
|
|
|
151
154
|
Let's look at the details
|
|
152
155
|
|
|
156
|
+
# Installation
|
|
157
|
+
|
|
158
|
+
`pip install aspyx`
|
|
159
|
+
|
|
160
|
+
The library is tested with all Python version > 3.9
|
|
161
|
+
|
|
162
|
+
Ready to go...
|
|
163
|
+
|
|
153
164
|
# Registration
|
|
154
165
|
|
|
155
166
|
Different mechanisms are available that make classes eligible for injection
|
|
@@ -171,12 +182,21 @@ All referenced types will be injected by the environemnt.
|
|
|
171
182
|
|
|
172
183
|
Only eligible types are allowed, of course!
|
|
173
184
|
|
|
185
|
+
The decorator accepts the keyword arguments
|
|
186
|
+
- `eager : boolean`
|
|
187
|
+
if `True`, the container will create the instances automatically while booting the environment. This is the default.
|
|
188
|
+
- `scope: str`
|
|
189
|
+
the name of a - registered - scope which will determine how often instances will be created.
|
|
174
190
|
|
|
175
|
-
The
|
|
176
|
-
- `
|
|
177
|
-
|
|
191
|
+
The following scopes are implemented out of the box:
|
|
192
|
+
- `singleton`
|
|
193
|
+
objects are created once inside an environment and cached. This is the default.
|
|
194
|
+
- `request`
|
|
195
|
+
obejcts are created on every injection request
|
|
196
|
+
- `thread`
|
|
197
|
+
objects are cerated and cached with respect to the current thread.
|
|
178
198
|
|
|
179
|
-
Other scopes can be defined. Please check the corresponding chapter.
|
|
199
|
+
Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
|
|
180
200
|
|
|
181
201
|
## Class Factory
|
|
182
202
|
|
|
@@ -292,7 +312,7 @@ Different decorators are implemented, that call methods with computed values
|
|
|
292
312
|
the method is called with all resolved parameter types ( same as the constructor call)
|
|
293
313
|
- `@inject_environment`
|
|
294
314
|
the method is called with the creating environment as a single parameter
|
|
295
|
-
- `@
|
|
315
|
+
- `@inject_value()`
|
|
296
316
|
the method is called with a resolved configuration value. Check the corresponding chapter
|
|
297
317
|
|
|
298
318
|
**Example**:
|
|
@@ -313,9 +333,11 @@ class Foo:
|
|
|
313
333
|
|
|
314
334
|
## Lifecycle methods
|
|
315
335
|
|
|
316
|
-
It is possible to mark specific
|
|
336
|
+
It is possible to mark specific lifecyle methods.
|
|
317
337
|
- `@on_init()`
|
|
318
338
|
called after the constructor and all other injections.
|
|
339
|
+
- `@on_running()`
|
|
340
|
+
called an environment has initialized all eager objects.
|
|
319
341
|
- `@on_destroy()`
|
|
320
342
|
called during shutdown of the environment
|
|
321
343
|
|
|
@@ -386,17 +408,17 @@ class SampleAdvice:
|
|
|
386
408
|
pass
|
|
387
409
|
|
|
388
410
|
@before(methods().named("hello").of_type(Foo))
|
|
389
|
-
def
|
|
411
|
+
def call_before(self, invocation: Invocation):
|
|
390
412
|
# arguments: invocation.args
|
|
391
413
|
print("before Foo.hello(...)")
|
|
392
414
|
|
|
393
415
|
@error(methods().named("hello").of_type(Foo))
|
|
394
|
-
def
|
|
416
|
+
def call_error(self, invocation: Invocation):
|
|
395
417
|
print("error Foo.hello(...)")
|
|
396
418
|
print(invocation.exception)
|
|
397
419
|
|
|
398
420
|
@around(methods().named("hello"))
|
|
399
|
-
def
|
|
421
|
+
def call_around(self, invocation: Invocation):
|
|
400
422
|
print("around Foo.hello()")
|
|
401
423
|
|
|
402
424
|
return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
|
|
@@ -417,7 +439,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
|
|
|
417
439
|
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.
|
|
418
440
|
If the `proceed` is called with parameters, they will replace the original parameters!
|
|
419
441
|
|
|
420
|
-
The
|
|
442
|
+
The argument list to the corresponding decorators control, how aspects are associated with which methods.
|
|
421
443
|
A fluent interface is used describe the mapping.
|
|
422
444
|
The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
|
|
423
445
|
|
|
@@ -431,11 +453,24 @@ Both add the fluent methods:
|
|
|
431
453
|
- `decorated_with(type: Type)`
|
|
432
454
|
defines decorators on methods or classes
|
|
433
455
|
|
|
434
|
-
The fluent methods `named`, `matches` and `of_type` can be called multiple
|
|
456
|
+
The fluent methods `named`, `matches` and `of_type` can be called multiple times!
|
|
457
|
+
|
|
458
|
+
**Example**:
|
|
459
|
+
|
|
460
|
+
```python
|
|
461
|
+
@injectable()
|
|
462
|
+
class TransactionAdvice:
|
|
463
|
+
def __init__(self):
|
|
464
|
+
pass
|
|
465
|
+
|
|
466
|
+
@around(methods().decorated_with(transactional), classes().decorated_with(transactional))
|
|
467
|
+
def establish_transaction(self, invocation: Invocation):
|
|
468
|
+
...
|
|
469
|
+
```
|
|
435
470
|
|
|
436
471
|
# Configuration
|
|
437
472
|
|
|
438
|
-
It is possible to inject configuration values, by decorating methods with `@value(<name>)` given a configuration key.
|
|
473
|
+
It is possible to inject configuration values, by decorating methods with `@inject-value(<name>)` given a configuration key.
|
|
439
474
|
|
|
440
475
|
```python
|
|
441
476
|
@injectable()
|
|
@@ -452,10 +487,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
|
|
|
452
487
|
|
|
453
488
|
```python
|
|
454
489
|
class ConfigurationSource(ABC):
|
|
455
|
-
def __init__(self
|
|
456
|
-
manager._register(self)
|
|
490
|
+
def __init__(self):
|
|
457
491
|
pass
|
|
458
492
|
|
|
493
|
+
...
|
|
494
|
+
|
|
459
495
|
@abstractmethod
|
|
460
496
|
def load(self) -> dict:
|
|
461
497
|
pass
|
|
@@ -467,14 +503,12 @@ As a default environment variables are already supported.
|
|
|
467
503
|
|
|
468
504
|
Other sources can be added dynamically by just registering them.
|
|
469
505
|
|
|
506
|
+
**Example**:
|
|
470
507
|
```python
|
|
471
508
|
@injectable()
|
|
472
509
|
class SampleConfigurationSource(ConfigurationSource):
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
def __init__(self, manager: ConfigurationManager):
|
|
476
|
-
super().__init__(manager)
|
|
477
|
-
|
|
510
|
+
def __init__(self):
|
|
511
|
+
super().__init__()
|
|
478
512
|
|
|
479
513
|
def load(self) -> dict:
|
|
480
514
|
return {
|
|
@@ -487,7 +521,61 @@ class SampleConfigurationSource(ConfigurationSource):
|
|
|
487
521
|
}
|
|
488
522
|
```
|
|
489
523
|
|
|
524
|
+
# Reflection
|
|
525
|
+
|
|
526
|
+
As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
|
|
527
|
+
|
|
528
|
+
After beeing instatiated with
|
|
529
|
+
|
|
530
|
+
```python
|
|
531
|
+
TypeDescriptor.for_type(<type>)
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
it offers the methods
|
|
535
|
+
- `get_methods(local=False)`
|
|
536
|
+
return a list of either local or overall methods
|
|
537
|
+
- `get_method(name: str, local=False)`
|
|
538
|
+
return a single either local or overall method
|
|
539
|
+
- `has_decorator(decorator: Callable) -> bool`
|
|
540
|
+
return `True`, if the class is decorated with the specified decrator
|
|
541
|
+
- `get_decorator(decorator) -> Optional[DecoratorDescriptor]`
|
|
542
|
+
return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
|
|
543
|
+
|
|
544
|
+
The returned method descriptors offer:
|
|
545
|
+
- `param_types`
|
|
546
|
+
list of arg types
|
|
547
|
+
- `return_type`
|
|
548
|
+
the retur type
|
|
549
|
+
- `has_decorator(decorator: Callable) -> bool`
|
|
550
|
+
return `True`, if the method is decorated with the specified decrator
|
|
551
|
+
- `get_decorator(decorator: Callable) -> Optional[DecoratorDescriptor]`
|
|
552
|
+
return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
|
|
553
|
+
|
|
554
|
+
The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
|
|
555
|
+
|
|
556
|
+
Whenver you define a custom decorator, you will need to register it accordingly.
|
|
557
|
+
|
|
558
|
+
**Example**:
|
|
559
|
+
```python
|
|
560
|
+
def transactional():
|
|
561
|
+
def decorator(func):
|
|
562
|
+
Decorators.add(func, transactional)
|
|
563
|
+
return func
|
|
564
|
+
|
|
565
|
+
return decorator
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
# Version History
|
|
570
|
+
|
|
571
|
+
**1.0.1**
|
|
572
|
+
|
|
573
|
+
- some internal refactorings
|
|
574
|
+
|
|
575
|
+
**1.1.0**
|
|
490
576
|
|
|
577
|
+
- added `@on_running()` callback
|
|
578
|
+
- added `thread` scope
|
|
491
579
|
|
|
492
580
|
|
|
493
581
|
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
|
-
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
6
8
|
|
|
7
9
|
## Table of Contents
|
|
8
10
|
|
|
9
11
|
- [Introduction](#aspyx)
|
|
12
|
+
- [Installation](#installation)
|
|
10
13
|
- [Registration](#registration)
|
|
11
14
|
- [Class](#class)
|
|
12
15
|
- [Class Factory](#class-factory)
|
|
@@ -21,30 +24,32 @@
|
|
|
21
24
|
- [Custom scopes](#custom-scopes)
|
|
22
25
|
- [AOP](#aop)
|
|
23
26
|
- [Configuration](#configuration)
|
|
27
|
+
- [Reflection](#reflection)
|
|
28
|
+
- [Version History](#version-history)
|
|
24
29
|
|
|
25
30
|
# Introduction
|
|
26
31
|
|
|
27
32
|
Aspyx is a small python libary, that adds support for both dependency injection and aop.
|
|
28
33
|
|
|
29
34
|
The following features are supported
|
|
30
|
-
- constructor injection
|
|
31
|
-
- method injection
|
|
35
|
+
- constructor and setter injection
|
|
32
36
|
- post processors
|
|
33
37
|
- factory classes and methods
|
|
34
38
|
- support for eager construction
|
|
35
|
-
- support for singleton and
|
|
39
|
+
- support for singleton and request scopes
|
|
36
40
|
- possibilty to add custom scopes
|
|
37
41
|
- lifecycle events methods
|
|
38
42
|
- bundling of injectable object sets by environment classes including recursive imports and inheritance
|
|
39
43
|
- container instances that relate to environment classes and manage the lifecylce of related objects
|
|
40
44
|
- hierarchical environments
|
|
41
45
|
|
|
46
|
+
The library is thread-safe!
|
|
47
|
+
|
|
42
48
|
Let's look at a simple example
|
|
43
49
|
|
|
44
50
|
```python
|
|
45
51
|
from aspyx.di import injectable, on_init, on_destroy, environment, Environment
|
|
46
52
|
|
|
47
|
-
|
|
48
53
|
@injectable()
|
|
49
54
|
class Foo:
|
|
50
55
|
def __init__(self):
|
|
@@ -53,13 +58,12 @@ class Foo:
|
|
|
53
58
|
def hello(msg: str):
|
|
54
59
|
print(f"hello {msg}")
|
|
55
60
|
|
|
56
|
-
|
|
57
61
|
@injectable() # eager and singleton by default
|
|
58
62
|
class Bar:
|
|
59
63
|
def __init__(self, foo: Foo): # will inject the Foo dependency
|
|
60
64
|
self.foo = foo
|
|
61
65
|
|
|
62
|
-
@on_init() # a lifecycle callback called
|
|
66
|
+
@on_init() # a lifecycle callback called after the constructor and all possible injections
|
|
63
67
|
def init(self):
|
|
64
68
|
...
|
|
65
69
|
|
|
@@ -69,41 +73,40 @@ class Bar:
|
|
|
69
73
|
|
|
70
74
|
@environment()
|
|
71
75
|
class SampleEnvironment:
|
|
72
|
-
# constructor
|
|
73
|
-
|
|
74
76
|
def __init__(self):
|
|
75
77
|
pass
|
|
76
78
|
|
|
77
|
-
|
|
78
79
|
# go, forrest
|
|
79
80
|
|
|
80
|
-
environment = SampleEnvironment
|
|
81
|
+
environment = Environment(SampleEnvironment)
|
|
81
82
|
|
|
82
83
|
bar = env.get(Bar)
|
|
83
|
-
|
|
84
|
+
|
|
85
|
+
bar.foo.hello("world")
|
|
84
86
|
```
|
|
85
87
|
|
|
86
|
-
The concepts should be pretty familiar
|
|
88
|
+
The concepts should be pretty familiar as well as the names which are a combination of Spring and Angular names :-)
|
|
87
89
|
|
|
88
90
|
Let's add some aspects...
|
|
89
91
|
|
|
90
92
|
```python
|
|
93
|
+
|
|
91
94
|
@advice
|
|
92
95
|
class SampleAdvice:
|
|
93
|
-
def __init__(self):
|
|
96
|
+
def __init__(self): # could inject additional stuff
|
|
94
97
|
pass
|
|
95
98
|
|
|
96
99
|
@before(methods().named("hello").of_type(Foo))
|
|
97
|
-
def
|
|
100
|
+
def call_before(self, invocation: Invocation):
|
|
98
101
|
print("before Foo.hello(...)")
|
|
99
102
|
|
|
100
103
|
@error(methods().named("hello").of_type(Foo))
|
|
101
|
-
def
|
|
104
|
+
def call_error(self, invocation: Invocation):
|
|
102
105
|
print("error Foo.hello(...)")
|
|
103
106
|
print(invocation.exception)
|
|
104
107
|
|
|
105
108
|
@around(methods().named("hello"))
|
|
106
|
-
def
|
|
109
|
+
def call_around(self, invocation: Invocation):
|
|
107
110
|
print("around Foo.hello()")
|
|
108
111
|
|
|
109
112
|
return invocation.proceed()
|
|
@@ -118,6 +121,14 @@ The invocation parameter stores the complete context of the current execution, w
|
|
|
118
121
|
|
|
119
122
|
Let's look at the details
|
|
120
123
|
|
|
124
|
+
# Installation
|
|
125
|
+
|
|
126
|
+
`pip install aspyx`
|
|
127
|
+
|
|
128
|
+
The library is tested with all Python version > 3.9
|
|
129
|
+
|
|
130
|
+
Ready to go...
|
|
131
|
+
|
|
121
132
|
# Registration
|
|
122
133
|
|
|
123
134
|
Different mechanisms are available that make classes eligible for injection
|
|
@@ -139,12 +150,21 @@ All referenced types will be injected by the environemnt.
|
|
|
139
150
|
|
|
140
151
|
Only eligible types are allowed, of course!
|
|
141
152
|
|
|
153
|
+
The decorator accepts the keyword arguments
|
|
154
|
+
- `eager : boolean`
|
|
155
|
+
if `True`, the container will create the instances automatically while booting the environment. This is the default.
|
|
156
|
+
- `scope: str`
|
|
157
|
+
the name of a - registered - scope which will determine how often instances will be created.
|
|
142
158
|
|
|
143
|
-
The
|
|
144
|
-
- `
|
|
145
|
-
|
|
159
|
+
The following scopes are implemented out of the box:
|
|
160
|
+
- `singleton`
|
|
161
|
+
objects are created once inside an environment and cached. This is the default.
|
|
162
|
+
- `request`
|
|
163
|
+
obejcts are created on every injection request
|
|
164
|
+
- `thread`
|
|
165
|
+
objects are cerated and cached with respect to the current thread.
|
|
146
166
|
|
|
147
|
-
Other scopes can be defined. Please check the corresponding chapter.
|
|
167
|
+
Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
|
|
148
168
|
|
|
149
169
|
## Class Factory
|
|
150
170
|
|
|
@@ -260,7 +280,7 @@ Different decorators are implemented, that call methods with computed values
|
|
|
260
280
|
the method is called with all resolved parameter types ( same as the constructor call)
|
|
261
281
|
- `@inject_environment`
|
|
262
282
|
the method is called with the creating environment as a single parameter
|
|
263
|
-
- `@
|
|
283
|
+
- `@inject_value()`
|
|
264
284
|
the method is called with a resolved configuration value. Check the corresponding chapter
|
|
265
285
|
|
|
266
286
|
**Example**:
|
|
@@ -281,9 +301,11 @@ class Foo:
|
|
|
281
301
|
|
|
282
302
|
## Lifecycle methods
|
|
283
303
|
|
|
284
|
-
It is possible to mark specific
|
|
304
|
+
It is possible to mark specific lifecyle methods.
|
|
285
305
|
- `@on_init()`
|
|
286
306
|
called after the constructor and all other injections.
|
|
307
|
+
- `@on_running()`
|
|
308
|
+
called an environment has initialized all eager objects.
|
|
287
309
|
- `@on_destroy()`
|
|
288
310
|
called during shutdown of the environment
|
|
289
311
|
|
|
@@ -354,17 +376,17 @@ class SampleAdvice:
|
|
|
354
376
|
pass
|
|
355
377
|
|
|
356
378
|
@before(methods().named("hello").of_type(Foo))
|
|
357
|
-
def
|
|
379
|
+
def call_before(self, invocation: Invocation):
|
|
358
380
|
# arguments: invocation.args
|
|
359
381
|
print("before Foo.hello(...)")
|
|
360
382
|
|
|
361
383
|
@error(methods().named("hello").of_type(Foo))
|
|
362
|
-
def
|
|
384
|
+
def call_error(self, invocation: Invocation):
|
|
363
385
|
print("error Foo.hello(...)")
|
|
364
386
|
print(invocation.exception)
|
|
365
387
|
|
|
366
388
|
@around(methods().named("hello"))
|
|
367
|
-
def
|
|
389
|
+
def call_around(self, invocation: Invocation):
|
|
368
390
|
print("around Foo.hello()")
|
|
369
391
|
|
|
370
392
|
return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
|
|
@@ -385,7 +407,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
|
|
|
385
407
|
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.
|
|
386
408
|
If the `proceed` is called with parameters, they will replace the original parameters!
|
|
387
409
|
|
|
388
|
-
The
|
|
410
|
+
The argument list to the corresponding decorators control, how aspects are associated with which methods.
|
|
389
411
|
A fluent interface is used describe the mapping.
|
|
390
412
|
The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
|
|
391
413
|
|
|
@@ -399,11 +421,24 @@ Both add the fluent methods:
|
|
|
399
421
|
- `decorated_with(type: Type)`
|
|
400
422
|
defines decorators on methods or classes
|
|
401
423
|
|
|
402
|
-
The fluent methods `named`, `matches` and `of_type` can be called multiple
|
|
424
|
+
The fluent methods `named`, `matches` and `of_type` can be called multiple times!
|
|
425
|
+
|
|
426
|
+
**Example**:
|
|
427
|
+
|
|
428
|
+
```python
|
|
429
|
+
@injectable()
|
|
430
|
+
class TransactionAdvice:
|
|
431
|
+
def __init__(self):
|
|
432
|
+
pass
|
|
433
|
+
|
|
434
|
+
@around(methods().decorated_with(transactional), classes().decorated_with(transactional))
|
|
435
|
+
def establish_transaction(self, invocation: Invocation):
|
|
436
|
+
...
|
|
437
|
+
```
|
|
403
438
|
|
|
404
439
|
# Configuration
|
|
405
440
|
|
|
406
|
-
It is possible to inject configuration values, by decorating methods with `@value(<name>)` given a configuration key.
|
|
441
|
+
It is possible to inject configuration values, by decorating methods with `@inject-value(<name>)` given a configuration key.
|
|
407
442
|
|
|
408
443
|
```python
|
|
409
444
|
@injectable()
|
|
@@ -420,10 +455,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
|
|
|
420
455
|
|
|
421
456
|
```python
|
|
422
457
|
class ConfigurationSource(ABC):
|
|
423
|
-
def __init__(self
|
|
424
|
-
manager._register(self)
|
|
458
|
+
def __init__(self):
|
|
425
459
|
pass
|
|
426
460
|
|
|
461
|
+
...
|
|
462
|
+
|
|
427
463
|
@abstractmethod
|
|
428
464
|
def load(self) -> dict:
|
|
429
465
|
pass
|
|
@@ -435,14 +471,12 @@ As a default environment variables are already supported.
|
|
|
435
471
|
|
|
436
472
|
Other sources can be added dynamically by just registering them.
|
|
437
473
|
|
|
474
|
+
**Example**:
|
|
438
475
|
```python
|
|
439
476
|
@injectable()
|
|
440
477
|
class SampleConfigurationSource(ConfigurationSource):
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
def __init__(self, manager: ConfigurationManager):
|
|
444
|
-
super().__init__(manager)
|
|
445
|
-
|
|
478
|
+
def __init__(self):
|
|
479
|
+
super().__init__()
|
|
446
480
|
|
|
447
481
|
def load(self) -> dict:
|
|
448
482
|
return {
|
|
@@ -455,7 +489,61 @@ class SampleConfigurationSource(ConfigurationSource):
|
|
|
455
489
|
}
|
|
456
490
|
```
|
|
457
491
|
|
|
492
|
+
# Reflection
|
|
493
|
+
|
|
494
|
+
As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
|
|
495
|
+
|
|
496
|
+
After beeing instatiated with
|
|
497
|
+
|
|
498
|
+
```python
|
|
499
|
+
TypeDescriptor.for_type(<type>)
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
it offers the methods
|
|
503
|
+
- `get_methods(local=False)`
|
|
504
|
+
return a list of either local or overall methods
|
|
505
|
+
- `get_method(name: str, local=False)`
|
|
506
|
+
return a single either local or overall method
|
|
507
|
+
- `has_decorator(decorator: Callable) -> bool`
|
|
508
|
+
return `True`, if the class is decorated with the specified decrator
|
|
509
|
+
- `get_decorator(decorator) -> Optional[DecoratorDescriptor]`
|
|
510
|
+
return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
|
|
511
|
+
|
|
512
|
+
The returned method descriptors offer:
|
|
513
|
+
- `param_types`
|
|
514
|
+
list of arg types
|
|
515
|
+
- `return_type`
|
|
516
|
+
the retur type
|
|
517
|
+
- `has_decorator(decorator: Callable) -> bool`
|
|
518
|
+
return `True`, if the method is decorated with the specified decrator
|
|
519
|
+
- `get_decorator(decorator: Callable) -> Optional[DecoratorDescriptor]`
|
|
520
|
+
return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
|
|
521
|
+
|
|
522
|
+
The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
|
|
523
|
+
|
|
524
|
+
Whenver you define a custom decorator, you will need to register it accordingly.
|
|
525
|
+
|
|
526
|
+
**Example**:
|
|
527
|
+
```python
|
|
528
|
+
def transactional():
|
|
529
|
+
def decorator(func):
|
|
530
|
+
Decorators.add(func, transactional)
|
|
531
|
+
return func
|
|
532
|
+
|
|
533
|
+
return decorator
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
# Version History
|
|
538
|
+
|
|
539
|
+
**1.0.1**
|
|
540
|
+
|
|
541
|
+
- some internal refactorings
|
|
542
|
+
|
|
543
|
+
**1.1.0**
|
|
458
544
|
|
|
545
|
+
- added `@on_running()` callback
|
|
546
|
+
- added `thread` scope
|
|
459
547
|
|
|
460
548
|
|
|
461
549
|
|
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "aspyx"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.1.0"
|
|
8
8
|
description = "A DI and AOP library for Python"
|
|
9
9
|
authors = [{ name = "Andreas Ernst", email = "andreas.ernst7@gmail.com" }]
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
license = { file = "LICENSE" }
|
|
12
|
-
requires-python = ">=3.
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
13
|
dependencies = []
|
|
14
14
|
|
|
15
15
|
[tool.setuptools]
|