aspyx 1.0.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-1.0.0/src/aspyx.egg-info → aspyx-1.0.1}/PKG-INFO +94 -33
- {aspyx-1.0.0 → aspyx-1.0.1}/README.md +92 -31
- {aspyx-1.0.0 → aspyx-1.0.1}/pyproject.toml +2 -2
- {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/di/__init__.py +4 -1
- {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/di/aop/__init__.py +4 -1
- {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/di/aop/aop.py +56 -76
- {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/di/configuration/__init__.py +4 -1
- {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/di/configuration/configuration.py +20 -15
- {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/di/di.py +98 -113
- {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/reflection/__init__.py +4 -1
- {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/reflection/proxy.py +10 -7
- {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/reflection/reflection.py +29 -21
- {aspyx-1.0.0 → aspyx-1.0.1/src/aspyx.egg-info}/PKG-INFO +94 -33
- {aspyx-1.0.0 → aspyx-1.0.1}/tests/test_configuration.py +5 -7
- {aspyx-1.0.0 → aspyx-1.0.1}/tests/test_reflection.py +1 -1
- {aspyx-1.0.0 → aspyx-1.0.1}/LICENSE +0 -0
- {aspyx-1.0.0 → aspyx-1.0.1}/setup.cfg +0 -0
- {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx.egg-info/SOURCES.txt +0 -0
- {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx.egg-info/dependency_links.txt +0 -0
- {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx.egg-info/top_level.txt +0 -0
- {aspyx-1.0.0 → aspyx-1.0.1}/tests/test_aop.py +0 -0
- {aspyx-1.0.0 → aspyx-1.0.1}/tests/test_di.py +0 -0
- {aspyx-1.0.0 → aspyx-1.0.1}/tests/test_di_cycle.py +0 -0
- {aspyx-1.0.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: 1.0.
|
|
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,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,13 @@ Dynamic: license-file
|
|
|
34
34
|
|
|
35
35
|

|
|
36
36
|

|
|
37
|
-
|
|
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)
|
|
@@ -53,6 +55,7 @@ Dynamic: license-file
|
|
|
53
55
|
- [Custom scopes](#custom-scopes)
|
|
54
56
|
- [AOP](#aop)
|
|
55
57
|
- [Configuration](#configuration)
|
|
58
|
+
- [Reflection](#reflection)
|
|
56
59
|
|
|
57
60
|
# Introduction
|
|
58
61
|
|
|
@@ -71,12 +74,13 @@ The following features are supported
|
|
|
71
74
|
- container instances that relate to environment classes and manage the lifecylce of related objects
|
|
72
75
|
- hierarchical environments
|
|
73
76
|
|
|
77
|
+
The library is thread-safe!
|
|
78
|
+
|
|
74
79
|
Let's look at a simple example
|
|
75
80
|
|
|
76
81
|
```python
|
|
77
82
|
from aspyx.di import injectable, on_init, on_destroy, environment, Environment
|
|
78
83
|
|
|
79
|
-
|
|
80
84
|
@injectable()
|
|
81
85
|
class Foo:
|
|
82
86
|
def __init__(self):
|
|
@@ -85,13 +89,12 @@ class Foo:
|
|
|
85
89
|
def hello(msg: str):
|
|
86
90
|
print(f"hello {msg}")
|
|
87
91
|
|
|
88
|
-
|
|
89
92
|
@injectable() # eager and singleton by default
|
|
90
93
|
class Bar:
|
|
91
94
|
def __init__(self, foo: Foo): # will inject the Foo dependency
|
|
92
95
|
self.foo = foo
|
|
93
96
|
|
|
94
|
-
@on_init() # a lifecycle callback called
|
|
97
|
+
@on_init() # a lifecycle callback called after the constructor and all possible injections
|
|
95
98
|
def init(self):
|
|
96
99
|
...
|
|
97
100
|
|
|
@@ -101,41 +104,40 @@ class Bar:
|
|
|
101
104
|
|
|
102
105
|
@environment()
|
|
103
106
|
class SampleEnvironment:
|
|
104
|
-
# constructor
|
|
105
|
-
|
|
106
107
|
def __init__(self):
|
|
107
108
|
pass
|
|
108
109
|
|
|
109
|
-
|
|
110
110
|
# go, forrest
|
|
111
111
|
|
|
112
|
-
environment = SampleEnvironment
|
|
112
|
+
environment = Environment(SampleEnvironment)
|
|
113
113
|
|
|
114
114
|
bar = env.get(Bar)
|
|
115
|
-
|
|
115
|
+
|
|
116
|
+
bar.foo.hello("world")
|
|
116
117
|
```
|
|
117
118
|
|
|
118
|
-
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 :-)
|
|
119
120
|
|
|
120
121
|
Let's add some aspects...
|
|
121
122
|
|
|
122
123
|
```python
|
|
124
|
+
|
|
123
125
|
@advice
|
|
124
126
|
class SampleAdvice:
|
|
125
|
-
def __init__(self):
|
|
127
|
+
def __init__(self): # could inject additional stuff
|
|
126
128
|
pass
|
|
127
129
|
|
|
128
130
|
@before(methods().named("hello").of_type(Foo))
|
|
129
|
-
def
|
|
131
|
+
def call_before(self, invocation: Invocation):
|
|
130
132
|
print("before Foo.hello(...)")
|
|
131
133
|
|
|
132
134
|
@error(methods().named("hello").of_type(Foo))
|
|
133
|
-
def
|
|
135
|
+
def call_error(self, invocation: Invocation):
|
|
134
136
|
print("error Foo.hello(...)")
|
|
135
137
|
print(invocation.exception)
|
|
136
138
|
|
|
137
139
|
@around(methods().named("hello"))
|
|
138
|
-
def
|
|
140
|
+
def call_around(self, invocation: Invocation):
|
|
139
141
|
print("around Foo.hello()")
|
|
140
142
|
|
|
141
143
|
return invocation.proceed()
|
|
@@ -150,6 +152,14 @@ The invocation parameter stores the complete context of the current execution, w
|
|
|
150
152
|
|
|
151
153
|
Let's look at the details
|
|
152
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
|
+
|
|
153
163
|
# Registration
|
|
154
164
|
|
|
155
165
|
Different mechanisms are available that make classes eligible for injection
|
|
@@ -171,12 +181,14 @@ All referenced types will be injected by the environemnt.
|
|
|
171
181
|
|
|
172
182
|
Only eligible types are allowed, of course!
|
|
173
183
|
|
|
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`
|
|
174
190
|
|
|
175
|
-
|
|
176
|
-
- `eager=True` if `True`, the container will create the instances automatically while booting the environment
|
|
177
|
-
- `scope="singleton"` defines how often instances will be created. `singleton` will create it only once - per environment -, while `request` will recreate it on every injection request
|
|
178
|
-
|
|
179
|
-
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.
|
|
180
192
|
|
|
181
193
|
## Class Factory
|
|
182
194
|
|
|
@@ -386,17 +398,17 @@ class SampleAdvice:
|
|
|
386
398
|
pass
|
|
387
399
|
|
|
388
400
|
@before(methods().named("hello").of_type(Foo))
|
|
389
|
-
def
|
|
401
|
+
def call_before(self, invocation: Invocation):
|
|
390
402
|
# arguments: invocation.args
|
|
391
403
|
print("before Foo.hello(...)")
|
|
392
404
|
|
|
393
405
|
@error(methods().named("hello").of_type(Foo))
|
|
394
|
-
def
|
|
406
|
+
def call_error(self, invocation: Invocation):
|
|
395
407
|
print("error Foo.hello(...)")
|
|
396
408
|
print(invocation.exception)
|
|
397
409
|
|
|
398
410
|
@around(methods().named("hello"))
|
|
399
|
-
def
|
|
411
|
+
def call_around(self, invocation: Invocation):
|
|
400
412
|
print("around Foo.hello()")
|
|
401
413
|
|
|
402
414
|
return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
|
|
@@ -417,7 +429,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
|
|
|
417
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.
|
|
418
430
|
If the `proceed` is called with parameters, they will replace the original parameters!
|
|
419
431
|
|
|
420
|
-
The
|
|
432
|
+
The argument list to the corresponding decorators control, how aspects are associated with which methods.
|
|
421
433
|
A fluent interface is used describe the mapping.
|
|
422
434
|
The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
|
|
423
435
|
|
|
@@ -431,7 +443,20 @@ Both add the fluent methods:
|
|
|
431
443
|
- `decorated_with(type: Type)`
|
|
432
444
|
defines decorators on methods or classes
|
|
433
445
|
|
|
434
|
-
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
|
+
```
|
|
435
460
|
|
|
436
461
|
# Configuration
|
|
437
462
|
|
|
@@ -452,10 +477,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
|
|
|
452
477
|
|
|
453
478
|
```python
|
|
454
479
|
class ConfigurationSource(ABC):
|
|
455
|
-
def __init__(self
|
|
456
|
-
manager._register(self)
|
|
480
|
+
def __init__(self):
|
|
457
481
|
pass
|
|
458
482
|
|
|
483
|
+
...
|
|
484
|
+
|
|
459
485
|
@abstractmethod
|
|
460
486
|
def load(self) -> dict:
|
|
461
487
|
pass
|
|
@@ -467,14 +493,12 @@ As a default environment variables are already supported.
|
|
|
467
493
|
|
|
468
494
|
Other sources can be added dynamically by just registering them.
|
|
469
495
|
|
|
496
|
+
**Example**:
|
|
470
497
|
```python
|
|
471
498
|
@injectable()
|
|
472
499
|
class SampleConfigurationSource(ConfigurationSource):
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
def __init__(self, manager: ConfigurationManager):
|
|
476
|
-
super().__init__(manager)
|
|
477
|
-
|
|
500
|
+
def __init__(self):
|
|
501
|
+
super().__init__()
|
|
478
502
|
|
|
479
503
|
def load(self) -> dict:
|
|
480
504
|
return {
|
|
@@ -487,6 +511,43 @@ class SampleConfigurationSource(ConfigurationSource):
|
|
|
487
511
|
}
|
|
488
512
|
```
|
|
489
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
|
+
|
|
490
551
|
|
|
491
552
|
|
|
492
553
|
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
|
-
|
|
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)
|
|
@@ -21,6 +23,7 @@
|
|
|
21
23
|
- [Custom scopes](#custom-scopes)
|
|
22
24
|
- [AOP](#aop)
|
|
23
25
|
- [Configuration](#configuration)
|
|
26
|
+
- [Reflection](#reflection)
|
|
24
27
|
|
|
25
28
|
# Introduction
|
|
26
29
|
|
|
@@ -39,12 +42,13 @@ The following features are supported
|
|
|
39
42
|
- container instances that relate to environment classes and manage the lifecylce of related objects
|
|
40
43
|
- hierarchical environments
|
|
41
44
|
|
|
45
|
+
The library is thread-safe!
|
|
46
|
+
|
|
42
47
|
Let's look at a simple example
|
|
43
48
|
|
|
44
49
|
```python
|
|
45
50
|
from aspyx.di import injectable, on_init, on_destroy, environment, Environment
|
|
46
51
|
|
|
47
|
-
|
|
48
52
|
@injectable()
|
|
49
53
|
class Foo:
|
|
50
54
|
def __init__(self):
|
|
@@ -53,13 +57,12 @@ class Foo:
|
|
|
53
57
|
def hello(msg: str):
|
|
54
58
|
print(f"hello {msg}")
|
|
55
59
|
|
|
56
|
-
|
|
57
60
|
@injectable() # eager and singleton by default
|
|
58
61
|
class Bar:
|
|
59
62
|
def __init__(self, foo: Foo): # will inject the Foo dependency
|
|
60
63
|
self.foo = foo
|
|
61
64
|
|
|
62
|
-
@on_init() # a lifecycle callback called
|
|
65
|
+
@on_init() # a lifecycle callback called after the constructor and all possible injections
|
|
63
66
|
def init(self):
|
|
64
67
|
...
|
|
65
68
|
|
|
@@ -69,41 +72,40 @@ class Bar:
|
|
|
69
72
|
|
|
70
73
|
@environment()
|
|
71
74
|
class SampleEnvironment:
|
|
72
|
-
# constructor
|
|
73
|
-
|
|
74
75
|
def __init__(self):
|
|
75
76
|
pass
|
|
76
77
|
|
|
77
|
-
|
|
78
78
|
# go, forrest
|
|
79
79
|
|
|
80
|
-
environment = SampleEnvironment
|
|
80
|
+
environment = Environment(SampleEnvironment)
|
|
81
81
|
|
|
82
82
|
bar = env.get(Bar)
|
|
83
|
-
|
|
83
|
+
|
|
84
|
+
bar.foo.hello("world")
|
|
84
85
|
```
|
|
85
86
|
|
|
86
|
-
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 :-)
|
|
87
88
|
|
|
88
89
|
Let's add some aspects...
|
|
89
90
|
|
|
90
91
|
```python
|
|
92
|
+
|
|
91
93
|
@advice
|
|
92
94
|
class SampleAdvice:
|
|
93
|
-
def __init__(self):
|
|
95
|
+
def __init__(self): # could inject additional stuff
|
|
94
96
|
pass
|
|
95
97
|
|
|
96
98
|
@before(methods().named("hello").of_type(Foo))
|
|
97
|
-
def
|
|
99
|
+
def call_before(self, invocation: Invocation):
|
|
98
100
|
print("before Foo.hello(...)")
|
|
99
101
|
|
|
100
102
|
@error(methods().named("hello").of_type(Foo))
|
|
101
|
-
def
|
|
103
|
+
def call_error(self, invocation: Invocation):
|
|
102
104
|
print("error Foo.hello(...)")
|
|
103
105
|
print(invocation.exception)
|
|
104
106
|
|
|
105
107
|
@around(methods().named("hello"))
|
|
106
|
-
def
|
|
108
|
+
def call_around(self, invocation: Invocation):
|
|
107
109
|
print("around Foo.hello()")
|
|
108
110
|
|
|
109
111
|
return invocation.proceed()
|
|
@@ -118,6 +120,14 @@ The invocation parameter stores the complete context of the current execution, w
|
|
|
118
120
|
|
|
119
121
|
Let's look at the details
|
|
120
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
|
+
|
|
121
131
|
# Registration
|
|
122
132
|
|
|
123
133
|
Different mechanisms are available that make classes eligible for injection
|
|
@@ -139,12 +149,14 @@ All referenced types will be injected by the environemnt.
|
|
|
139
149
|
|
|
140
150
|
Only eligible types are allowed, of course!
|
|
141
151
|
|
|
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`
|
|
142
158
|
|
|
143
|
-
|
|
144
|
-
- `eager=True` if `True`, the container will create the instances automatically while booting the environment
|
|
145
|
-
- `scope="singleton"` defines how often instances will be created. `singleton` will create it only once - per environment -, while `request` will recreate it on every injection request
|
|
146
|
-
|
|
147
|
-
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.
|
|
148
160
|
|
|
149
161
|
## Class Factory
|
|
150
162
|
|
|
@@ -354,17 +366,17 @@ class SampleAdvice:
|
|
|
354
366
|
pass
|
|
355
367
|
|
|
356
368
|
@before(methods().named("hello").of_type(Foo))
|
|
357
|
-
def
|
|
369
|
+
def call_before(self, invocation: Invocation):
|
|
358
370
|
# arguments: invocation.args
|
|
359
371
|
print("before Foo.hello(...)")
|
|
360
372
|
|
|
361
373
|
@error(methods().named("hello").of_type(Foo))
|
|
362
|
-
def
|
|
374
|
+
def call_error(self, invocation: Invocation):
|
|
363
375
|
print("error Foo.hello(...)")
|
|
364
376
|
print(invocation.exception)
|
|
365
377
|
|
|
366
378
|
@around(methods().named("hello"))
|
|
367
|
-
def
|
|
379
|
+
def call_around(self, invocation: Invocation):
|
|
368
380
|
print("around Foo.hello()")
|
|
369
381
|
|
|
370
382
|
return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
|
|
@@ -385,7 +397,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
|
|
|
385
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.
|
|
386
398
|
If the `proceed` is called with parameters, they will replace the original parameters!
|
|
387
399
|
|
|
388
|
-
The
|
|
400
|
+
The argument list to the corresponding decorators control, how aspects are associated with which methods.
|
|
389
401
|
A fluent interface is used describe the mapping.
|
|
390
402
|
The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
|
|
391
403
|
|
|
@@ -399,7 +411,20 @@ Both add the fluent methods:
|
|
|
399
411
|
- `decorated_with(type: Type)`
|
|
400
412
|
defines decorators on methods or classes
|
|
401
413
|
|
|
402
|
-
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
|
+
```
|
|
403
428
|
|
|
404
429
|
# Configuration
|
|
405
430
|
|
|
@@ -420,10 +445,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
|
|
|
420
445
|
|
|
421
446
|
```python
|
|
422
447
|
class ConfigurationSource(ABC):
|
|
423
|
-
def __init__(self
|
|
424
|
-
manager._register(self)
|
|
448
|
+
def __init__(self):
|
|
425
449
|
pass
|
|
426
450
|
|
|
451
|
+
...
|
|
452
|
+
|
|
427
453
|
@abstractmethod
|
|
428
454
|
def load(self) -> dict:
|
|
429
455
|
pass
|
|
@@ -435,14 +461,12 @@ As a default environment variables are already supported.
|
|
|
435
461
|
|
|
436
462
|
Other sources can be added dynamically by just registering them.
|
|
437
463
|
|
|
464
|
+
**Example**:
|
|
438
465
|
```python
|
|
439
466
|
@injectable()
|
|
440
467
|
class SampleConfigurationSource(ConfigurationSource):
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
def __init__(self, manager: ConfigurationManager):
|
|
444
|
-
super().__init__(manager)
|
|
445
|
-
|
|
468
|
+
def __init__(self):
|
|
469
|
+
super().__init__()
|
|
446
470
|
|
|
447
471
|
def load(self) -> dict:
|
|
448
472
|
return {
|
|
@@ -455,6 +479,43 @@ class SampleConfigurationSource(ConfigurationSource):
|
|
|
455
479
|
}
|
|
456
480
|
```
|
|
457
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
|
+
|
|
458
519
|
|
|
459
520
|
|
|
460
521
|
|
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "aspyx"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.1"
|
|
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]
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides dependency injection and aop capabilities for Python applications.
|
|
3
|
+
"""
|
|
1
4
|
from .di import InjectorException, CallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, environment, inject, create, on_init, on_destroy, inject_environment, Factory, PostProcessor
|
|
2
5
|
|
|
3
6
|
# import something from the subpackages, so that teh decorators are executed
|
|
@@ -26,4 +29,4 @@ __all__ = [
|
|
|
26
29
|
"LifecycleCallable",
|
|
27
30
|
"InjectorException",
|
|
28
31
|
"Lifecycle"
|
|
29
|
-
]
|
|
32
|
+
]
|