aspyx 1.4.0__tar.gz → 1.4.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.4.0/src/aspyx.egg-info → aspyx-1.4.1}/PKG-INFO +67 -47
- {aspyx-1.4.0 → aspyx-1.4.1}/README.md +66 -46
- {aspyx-1.4.0 → aspyx-1.4.1}/pyproject.toml +1 -1
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/__init__.py +2 -2
- aspyx-1.4.1/src/aspyx/di/aop/__init__.py +28 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/aop/aop.py +64 -22
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/configuration/__init__.py +3 -3
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/configuration/configuration.py +7 -5
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/di.py +100 -23
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/exception/__init__.py +1 -1
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/exception/exception_manager.py +34 -17
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/reflection/proxy.py +2 -4
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/reflection/reflection.py +55 -15
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/threading/__init__.py +1 -1
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/threading/thread_local.py +6 -2
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/util/stringbuilder.py +6 -2
- {aspyx-1.4.0 → aspyx-1.4.1/src/aspyx.egg-info}/PKG-INFO +67 -47
- {aspyx-1.4.0 → aspyx-1.4.1}/tests/test_aop.py +31 -5
- {aspyx-1.4.0 → aspyx-1.4.1}/tests/test_configuration.py +8 -8
- {aspyx-1.4.0 → aspyx-1.4.1}/tests/test_cycle.py +4 -4
- {aspyx-1.4.0 → aspyx-1.4.1}/tests/test_di.py +51 -18
- {aspyx-1.4.0 → aspyx-1.4.1}/tests/test_exception_manager.py +12 -10
- aspyx-1.4.0/src/aspyx/di/aop/__init__.py +0 -14
- {aspyx-1.4.0 → aspyx-1.4.1}/LICENSE +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/setup.cfg +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/__init__.py +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/configuration/env_configuration_source.py +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/configuration/yaml_configuration_source.py +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/threading/__init__.py +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/threading/synchronized.py +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/reflection/__init__.py +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/util/__init__.py +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx.egg-info/SOURCES.txt +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx.egg-info/dependency_links.txt +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx.egg-info/requires.txt +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx.egg-info/top_level.txt +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/tests/test_proxy.py +0 -0
- {aspyx-1.4.0 → aspyx-1.4.1}/tests/test_reflection.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.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
|
|
@@ -40,12 +40,14 @@ Dynamic: license-file
|
|
|
40
40
|

|
|
41
41
|

|
|
42
42
|
[](https://pypi.org/project/aspyx/)
|
|
43
|
-
[](https://coolsamson7.github.io/aspyx/)
|
|
43
|
+
[](https://coolsamson7.github.io/aspyx/index/introduction)
|
|
44
|
+
|
|
45
|
+

|
|
44
46
|
|
|
45
47
|
## Table of Contents
|
|
46
48
|
|
|
47
49
|
- [Motivation](#motivation)
|
|
48
|
-
- [
|
|
50
|
+
- [Overview](#overview)
|
|
49
51
|
- [Installation](#installation)
|
|
50
52
|
- [Registration](#registration)
|
|
51
53
|
- [Class](#class)
|
|
@@ -71,16 +73,19 @@ Dynamic: license-file
|
|
|
71
73
|
|
|
72
74
|
While working on AI-related projects in Python, I was looking for a dependency injection (DI) framework. After evaluating existing options, my impression was that the most either lacked key features — such as integrated AOP — or had APIs that felt overly technical and complex, which made me develop a library on my own with the following goals
|
|
73
75
|
|
|
74
|
-
- bring both di and AOP features together in a lightweight library,
|
|
76
|
+
- bring both di and AOP features together in a lightweight library ( still only about 2T loc),
|
|
75
77
|
- be as minimal invasive as possible,
|
|
76
78
|
- offering mechanisms to easily extend and customize features without touching the core,
|
|
77
|
-
- while still offering a _simple_ and _readable_ api that doesnt overwhelm developers
|
|
79
|
+
- while still offering a _simple_ and _readable_ api that doesnt overwhelm developers and only requires a minimum initial learning curve
|
|
80
|
+
|
|
81
|
+
The AOP integration, in particular, makes a lot of sense because:
|
|
78
82
|
|
|
79
|
-
|
|
83
|
+
- Aspects typically require context, which is naturally provided through DI,
|
|
84
|
+
- And they should only apply to objects managed by the container, rather than acting globally.
|
|
80
85
|
|
|
81
|
-
#
|
|
86
|
+
# Overview
|
|
82
87
|
|
|
83
|
-
Aspyx is a lightweight Python library that provides both Dependency Injection (DI) and Aspect-Oriented Programming (AOP) support.
|
|
88
|
+
Aspyx is a lightweight - still only about 2T LOC- Python library that provides both Dependency Injection (DI) and Aspect-Oriented Programming (AOP) support.
|
|
84
89
|
|
|
85
90
|
The following DI features are supported
|
|
86
91
|
- constructor and setter injection
|
|
@@ -89,13 +94,13 @@ The following DI features are supported
|
|
|
89
94
|
- post processors
|
|
90
95
|
- support for factory classes and methods
|
|
91
96
|
- support for eager and lazy construction
|
|
92
|
-
- support for scopes singleton, request and thread
|
|
97
|
+
- support for scopes "singleton", "request" and "thread"
|
|
93
98
|
- possibility to add custom scopes
|
|
94
99
|
- conditional registration of classes and factories ( aka profiles in spring )
|
|
95
100
|
- lifecycle events methods `on_init`, `on_destroy`, `on_running`
|
|
96
|
-
- bundling of injectable objects
|
|
97
|
-
-
|
|
98
|
-
- hierarchical environments
|
|
101
|
+
- Automatic discovery and bundling of injectable objects based on their module location, including support for recursive imports
|
|
102
|
+
- Instantiation of one or possible more isolated container instances — called environments — each managing the lifecycle of a related set of objects,
|
|
103
|
+
- Support for hierarchical environments, enabling structured scoping and layered object management.
|
|
99
104
|
|
|
100
105
|
With respect to AOP:
|
|
101
106
|
- support for before, around, after and error aspects
|
|
@@ -107,7 +112,8 @@ The library is thread-safe and heavily performance optimized as most of the runt
|
|
|
107
112
|
Let's look at a simple example
|
|
108
113
|
|
|
109
114
|
```python
|
|
110
|
-
from aspyx.di import injectable, on_init, on_destroy,
|
|
115
|
+
from aspyx.di import injectable, on_init, on_destroy, module, Environment
|
|
116
|
+
|
|
111
117
|
|
|
112
118
|
@injectable()
|
|
113
119
|
class Foo:
|
|
@@ -117,27 +123,28 @@ class Foo:
|
|
|
117
123
|
def hello(self, msg: str):
|
|
118
124
|
print(f"hello {msg}")
|
|
119
125
|
|
|
126
|
+
|
|
120
127
|
@injectable() # eager and singleton by default
|
|
121
128
|
class Bar:
|
|
122
|
-
def __init__(self, foo: Foo):
|
|
129
|
+
def __init__(self, foo: Foo): # will inject the Foo dependency
|
|
123
130
|
self.foo = foo
|
|
124
131
|
|
|
125
|
-
@on_init()
|
|
132
|
+
@on_init() # a lifecycle callback called after the constructor and all possible injections
|
|
126
133
|
def init(self):
|
|
127
134
|
...
|
|
128
135
|
|
|
129
136
|
|
|
130
|
-
# this class will
|
|
131
|
-
# In this case Foo and Bar
|
|
137
|
+
# this class will discover and manage all - specifically decorated - classes and factories that are part of the own module
|
|
132
138
|
|
|
133
|
-
@
|
|
134
|
-
class
|
|
139
|
+
@module()
|
|
140
|
+
class SampleModule:
|
|
135
141
|
def __init__(self):
|
|
136
142
|
pass
|
|
137
143
|
|
|
144
|
+
|
|
138
145
|
# create environment
|
|
139
146
|
|
|
140
|
-
environment = Environment(
|
|
147
|
+
environment = Environment(SampleModule)
|
|
141
148
|
|
|
142
149
|
# fetch an instance
|
|
143
150
|
|
|
@@ -171,12 +178,7 @@ class SampleAdvice:
|
|
|
171
178
|
return invocation.proceed()
|
|
172
179
|
```
|
|
173
180
|
|
|
174
|
-
|
|
175
|
-
- the method
|
|
176
|
-
- args
|
|
177
|
-
- kwargs
|
|
178
|
-
- the result
|
|
179
|
-
- the possible caught error
|
|
181
|
+
While features like DI and AOP are often associated with enterprise applcations, this example hopefully demonstrates that they work just as well in small- to medium-sized projects—without introducing significant overhead—while still providing powerful tools for achieving clean architecture, resulting in maintainable and easily testable code.
|
|
180
182
|
|
|
181
183
|
Let's look at the details
|
|
182
184
|
|
|
@@ -280,21 +282,26 @@ Valid conditions are created by:
|
|
|
280
282
|
|
|
281
283
|
## Definition
|
|
282
284
|
|
|
283
|
-
An `Environment` is the container that manages the lifecycle of objects.
|
|
285
|
+
An `Environment` is the container that manages the lifecycle of objects.
|
|
286
|
+
The set of classes and instances is determined by a
|
|
287
|
+
constructor type argument called `module`.
|
|
284
288
|
|
|
285
289
|
**Example**:
|
|
286
290
|
```python
|
|
287
|
-
@
|
|
288
|
-
class
|
|
291
|
+
@module()
|
|
292
|
+
class SampleModule:
|
|
289
293
|
def __init__(self):
|
|
290
294
|
pass
|
|
291
|
-
|
|
292
|
-
environment = Environment(SampleEnvironment)
|
|
293
295
|
```
|
|
294
296
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
+
A module is a regular injectable class decorated with `@module` that controls the discovery of injectable classes, by filtering classes according to their module location relative to this class.
|
|
298
|
+
All eligible classes, that are implemented in the containing module or in any submodule will be managed.
|
|
297
299
|
|
|
300
|
+
In a second step the real container - the environment - is created based on a module:
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
environment = Environment(SampleModule, features=["dev"])
|
|
304
|
+
```
|
|
298
305
|
|
|
299
306
|
By adding the parameter `features: list[str]`, it is possible to filter injectables by evaluating the corresponding `@conditional` decorators.
|
|
300
307
|
|
|
@@ -307,21 +314,21 @@ class DevOnly:
|
|
|
307
314
|
def __init__(self):
|
|
308
315
|
pass
|
|
309
316
|
|
|
310
|
-
@
|
|
311
|
-
class
|
|
317
|
+
@module()
|
|
318
|
+
class SampleModule():
|
|
312
319
|
def __init__(self):
|
|
313
320
|
pass
|
|
314
321
|
|
|
315
|
-
environment = Environment(
|
|
322
|
+
environment = Environment(SampleModule, features=["dev"])
|
|
316
323
|
```
|
|
317
324
|
|
|
318
325
|
|
|
319
|
-
By adding an `imports: list[Type]` parameter, specifying other
|
|
326
|
+
By adding an `imports: list[Type]` parameter, specifying other module types, it will register the appropriate classes recursively.
|
|
320
327
|
|
|
321
328
|
**Example**:
|
|
322
329
|
```python
|
|
323
|
-
@
|
|
324
|
-
class
|
|
330
|
+
@module()
|
|
331
|
+
class SampleModule(imports=[OtherModule]):
|
|
325
332
|
def __init__(self):
|
|
326
333
|
pass
|
|
327
334
|
```
|
|
@@ -330,8 +337,9 @@ Another possibility is to add a parent environment as an `Environment` construct
|
|
|
330
337
|
|
|
331
338
|
**Example**:
|
|
332
339
|
```python
|
|
333
|
-
rootEnvironment = Environment(
|
|
334
|
-
|
|
340
|
+
rootEnvironment = Environment(RootModule)
|
|
341
|
+
|
|
342
|
+
environment = Environment(SampleModule, parent=rootEnvironment)
|
|
335
343
|
```
|
|
336
344
|
|
|
337
345
|
The difference is, that in the first case, class instances of imported modules will be created in the scope of the _own_ environment, while in the second case, it will return instances managed by the parent.
|
|
@@ -464,7 +472,9 @@ class SingletonScope(Scope):
|
|
|
464
472
|
|
|
465
473
|
It is possible to define different aspects, that will be part of method calling flow. This logic fits nicely in the library, since the DI framework controls the instantiation logic and can handle aspects within a regular post processor.
|
|
466
474
|
|
|
467
|
-
|
|
475
|
+
On the other hand, advices are also regular DI objects, as they will usually require some kind of - injected - context.
|
|
476
|
+
|
|
477
|
+
Advices are regular classes decorated with `@advice` that define aspect methods.
|
|
468
478
|
|
|
469
479
|
```python
|
|
470
480
|
@advice
|
|
@@ -516,7 +526,7 @@ All methods are expected to have single `Invocation` parameter, that stores
|
|
|
516
526
|
- `result` the result ( initially `None`)
|
|
517
527
|
- `exception` a possible caught exception ( initially `None`)
|
|
518
528
|
|
|
519
|
-
⚠️ **
|
|
529
|
+
⚠️ **Note:** 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.
|
|
520
530
|
|
|
521
531
|
If the `proceed` is called with parameters, they will replace the original parameters!
|
|
522
532
|
|
|
@@ -562,6 +572,12 @@ class TransactionAdvice:
|
|
|
562
572
|
|
|
563
573
|
With respect to async methods, you need to make sure, to replace a `proceed()` with a `await proceed_async()` to have the overall chain async!
|
|
564
574
|
|
|
575
|
+
## Advice Lifecycle and visibility.
|
|
576
|
+
|
|
577
|
+
Advices are always part of a specific environment, and only modify methods of objects managed by exactly this environment.
|
|
578
|
+
|
|
579
|
+
An advice of a parent environment will for example not see classes of inherited environments. What is done instead, is to recreate the advice - more technically speaking, a processor that will collect and apply the advices - in every child environment, and let it operate on the local objects. With this approach different environments are completely isolated from each other with no side effects whatsoever.
|
|
580
|
+
|
|
565
581
|
# Threading
|
|
566
582
|
|
|
567
583
|
A handy decorator `@synchronized` in combination with the respective advice is implemented that automatically synchronizes methods with a `RLock` associated with the instance.
|
|
@@ -652,8 +668,8 @@ Two specific source are already implemented:
|
|
|
652
668
|
Typically you create the required configuration sources in an environment class, e.g.
|
|
653
669
|
|
|
654
670
|
```python
|
|
655
|
-
@
|
|
656
|
-
class
|
|
671
|
+
@module()
|
|
672
|
+
class SampleModule:
|
|
657
673
|
# constructor
|
|
658
674
|
|
|
659
675
|
def __init__(self):
|
|
@@ -725,8 +741,8 @@ class DerivedException(Exception):
|
|
|
725
741
|
def __init__(self):
|
|
726
742
|
pass
|
|
727
743
|
|
|
728
|
-
@
|
|
729
|
-
class
|
|
744
|
+
@module()
|
|
745
|
+
class SampleModule:
|
|
730
746
|
# constructor
|
|
731
747
|
|
|
732
748
|
def __init__(self):
|
|
@@ -820,6 +836,10 @@ class ExceptionAdvice:
|
|
|
820
836
|
- bugfixes
|
|
821
837
|
- added `@ExceptionManager`
|
|
822
838
|
|
|
839
|
+
**1.4.1**
|
|
840
|
+
|
|
841
|
+
- mkdocs
|
|
842
|
+
|
|
823
843
|
|
|
824
844
|
|
|
825
845
|
|
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
[](https://pypi.org/project/aspyx/)
|
|
9
|
-
[](https://coolsamson7.github.io/aspyx/)
|
|
9
|
+
[](https://coolsamson7.github.io/aspyx/index/introduction)
|
|
10
|
+
|
|
11
|
+

|
|
10
12
|
|
|
11
13
|
## Table of Contents
|
|
12
14
|
|
|
13
15
|
- [Motivation](#motivation)
|
|
14
|
-
- [
|
|
16
|
+
- [Overview](#overview)
|
|
15
17
|
- [Installation](#installation)
|
|
16
18
|
- [Registration](#registration)
|
|
17
19
|
- [Class](#class)
|
|
@@ -37,16 +39,19 @@
|
|
|
37
39
|
|
|
38
40
|
While working on AI-related projects in Python, I was looking for a dependency injection (DI) framework. After evaluating existing options, my impression was that the most either lacked key features — such as integrated AOP — or had APIs that felt overly technical and complex, which made me develop a library on my own with the following goals
|
|
39
41
|
|
|
40
|
-
- bring both di and AOP features together in a lightweight library,
|
|
42
|
+
- bring both di and AOP features together in a lightweight library ( still only about 2T loc),
|
|
41
43
|
- be as minimal invasive as possible,
|
|
42
44
|
- offering mechanisms to easily extend and customize features without touching the core,
|
|
43
|
-
- while still offering a _simple_ and _readable_ api that doesnt overwhelm developers
|
|
45
|
+
- while still offering a _simple_ and _readable_ api that doesnt overwhelm developers and only requires a minimum initial learning curve
|
|
46
|
+
|
|
47
|
+
The AOP integration, in particular, makes a lot of sense because:
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
- Aspects typically require context, which is naturally provided through DI,
|
|
50
|
+
- And they should only apply to objects managed by the container, rather than acting globally.
|
|
46
51
|
|
|
47
|
-
#
|
|
52
|
+
# Overview
|
|
48
53
|
|
|
49
|
-
Aspyx is a lightweight Python library that provides both Dependency Injection (DI) and Aspect-Oriented Programming (AOP) support.
|
|
54
|
+
Aspyx is a lightweight - still only about 2T LOC- Python library that provides both Dependency Injection (DI) and Aspect-Oriented Programming (AOP) support.
|
|
50
55
|
|
|
51
56
|
The following DI features are supported
|
|
52
57
|
- constructor and setter injection
|
|
@@ -55,13 +60,13 @@ The following DI features are supported
|
|
|
55
60
|
- post processors
|
|
56
61
|
- support for factory classes and methods
|
|
57
62
|
- support for eager and lazy construction
|
|
58
|
-
- support for scopes singleton, request and thread
|
|
63
|
+
- support for scopes "singleton", "request" and "thread"
|
|
59
64
|
- possibility to add custom scopes
|
|
60
65
|
- conditional registration of classes and factories ( aka profiles in spring )
|
|
61
66
|
- lifecycle events methods `on_init`, `on_destroy`, `on_running`
|
|
62
|
-
- bundling of injectable objects
|
|
63
|
-
-
|
|
64
|
-
- hierarchical environments
|
|
67
|
+
- Automatic discovery and bundling of injectable objects based on their module location, including support for recursive imports
|
|
68
|
+
- Instantiation of one or possible more isolated container instances — called environments — each managing the lifecycle of a related set of objects,
|
|
69
|
+
- Support for hierarchical environments, enabling structured scoping and layered object management.
|
|
65
70
|
|
|
66
71
|
With respect to AOP:
|
|
67
72
|
- support for before, around, after and error aspects
|
|
@@ -73,7 +78,8 @@ The library is thread-safe and heavily performance optimized as most of the runt
|
|
|
73
78
|
Let's look at a simple example
|
|
74
79
|
|
|
75
80
|
```python
|
|
76
|
-
from aspyx.di import injectable, on_init, on_destroy,
|
|
81
|
+
from aspyx.di import injectable, on_init, on_destroy, module, Environment
|
|
82
|
+
|
|
77
83
|
|
|
78
84
|
@injectable()
|
|
79
85
|
class Foo:
|
|
@@ -83,27 +89,28 @@ class Foo:
|
|
|
83
89
|
def hello(self, msg: str):
|
|
84
90
|
print(f"hello {msg}")
|
|
85
91
|
|
|
92
|
+
|
|
86
93
|
@injectable() # eager and singleton by default
|
|
87
94
|
class Bar:
|
|
88
|
-
def __init__(self, foo: Foo):
|
|
95
|
+
def __init__(self, foo: Foo): # will inject the Foo dependency
|
|
89
96
|
self.foo = foo
|
|
90
97
|
|
|
91
|
-
@on_init()
|
|
98
|
+
@on_init() # a lifecycle callback called after the constructor and all possible injections
|
|
92
99
|
def init(self):
|
|
93
100
|
...
|
|
94
101
|
|
|
95
102
|
|
|
96
|
-
# this class will
|
|
97
|
-
# In this case Foo and Bar
|
|
103
|
+
# this class will discover and manage all - specifically decorated - classes and factories that are part of the own module
|
|
98
104
|
|
|
99
|
-
@
|
|
100
|
-
class
|
|
105
|
+
@module()
|
|
106
|
+
class SampleModule:
|
|
101
107
|
def __init__(self):
|
|
102
108
|
pass
|
|
103
109
|
|
|
110
|
+
|
|
104
111
|
# create environment
|
|
105
112
|
|
|
106
|
-
environment = Environment(
|
|
113
|
+
environment = Environment(SampleModule)
|
|
107
114
|
|
|
108
115
|
# fetch an instance
|
|
109
116
|
|
|
@@ -137,12 +144,7 @@ class SampleAdvice:
|
|
|
137
144
|
return invocation.proceed()
|
|
138
145
|
```
|
|
139
146
|
|
|
140
|
-
|
|
141
|
-
- the method
|
|
142
|
-
- args
|
|
143
|
-
- kwargs
|
|
144
|
-
- the result
|
|
145
|
-
- the possible caught error
|
|
147
|
+
While features like DI and AOP are often associated with enterprise applcations, this example hopefully demonstrates that they work just as well in small- to medium-sized projects—without introducing significant overhead—while still providing powerful tools for achieving clean architecture, resulting in maintainable and easily testable code.
|
|
146
148
|
|
|
147
149
|
Let's look at the details
|
|
148
150
|
|
|
@@ -246,21 +248,26 @@ Valid conditions are created by:
|
|
|
246
248
|
|
|
247
249
|
## Definition
|
|
248
250
|
|
|
249
|
-
An `Environment` is the container that manages the lifecycle of objects.
|
|
251
|
+
An `Environment` is the container that manages the lifecycle of objects.
|
|
252
|
+
The set of classes and instances is determined by a
|
|
253
|
+
constructor type argument called `module`.
|
|
250
254
|
|
|
251
255
|
**Example**:
|
|
252
256
|
```python
|
|
253
|
-
@
|
|
254
|
-
class
|
|
257
|
+
@module()
|
|
258
|
+
class SampleModule:
|
|
255
259
|
def __init__(self):
|
|
256
260
|
pass
|
|
257
|
-
|
|
258
|
-
environment = Environment(SampleEnvironment)
|
|
259
261
|
```
|
|
260
262
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
+
A module is a regular injectable class decorated with `@module` that controls the discovery of injectable classes, by filtering classes according to their module location relative to this class.
|
|
264
|
+
All eligible classes, that are implemented in the containing module or in any submodule will be managed.
|
|
263
265
|
|
|
266
|
+
In a second step the real container - the environment - is created based on a module:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
environment = Environment(SampleModule, features=["dev"])
|
|
270
|
+
```
|
|
264
271
|
|
|
265
272
|
By adding the parameter `features: list[str]`, it is possible to filter injectables by evaluating the corresponding `@conditional` decorators.
|
|
266
273
|
|
|
@@ -273,21 +280,21 @@ class DevOnly:
|
|
|
273
280
|
def __init__(self):
|
|
274
281
|
pass
|
|
275
282
|
|
|
276
|
-
@
|
|
277
|
-
class
|
|
283
|
+
@module()
|
|
284
|
+
class SampleModule():
|
|
278
285
|
def __init__(self):
|
|
279
286
|
pass
|
|
280
287
|
|
|
281
|
-
environment = Environment(
|
|
288
|
+
environment = Environment(SampleModule, features=["dev"])
|
|
282
289
|
```
|
|
283
290
|
|
|
284
291
|
|
|
285
|
-
By adding an `imports: list[Type]` parameter, specifying other
|
|
292
|
+
By adding an `imports: list[Type]` parameter, specifying other module types, it will register the appropriate classes recursively.
|
|
286
293
|
|
|
287
294
|
**Example**:
|
|
288
295
|
```python
|
|
289
|
-
@
|
|
290
|
-
class
|
|
296
|
+
@module()
|
|
297
|
+
class SampleModule(imports=[OtherModule]):
|
|
291
298
|
def __init__(self):
|
|
292
299
|
pass
|
|
293
300
|
```
|
|
@@ -296,8 +303,9 @@ Another possibility is to add a parent environment as an `Environment` construct
|
|
|
296
303
|
|
|
297
304
|
**Example**:
|
|
298
305
|
```python
|
|
299
|
-
rootEnvironment = Environment(
|
|
300
|
-
|
|
306
|
+
rootEnvironment = Environment(RootModule)
|
|
307
|
+
|
|
308
|
+
environment = Environment(SampleModule, parent=rootEnvironment)
|
|
301
309
|
```
|
|
302
310
|
|
|
303
311
|
The difference is, that in the first case, class instances of imported modules will be created in the scope of the _own_ environment, while in the second case, it will return instances managed by the parent.
|
|
@@ -430,7 +438,9 @@ class SingletonScope(Scope):
|
|
|
430
438
|
|
|
431
439
|
It is possible to define different aspects, that will be part of method calling flow. This logic fits nicely in the library, since the DI framework controls the instantiation logic and can handle aspects within a regular post processor.
|
|
432
440
|
|
|
433
|
-
|
|
441
|
+
On the other hand, advices are also regular DI objects, as they will usually require some kind of - injected - context.
|
|
442
|
+
|
|
443
|
+
Advices are regular classes decorated with `@advice` that define aspect methods.
|
|
434
444
|
|
|
435
445
|
```python
|
|
436
446
|
@advice
|
|
@@ -482,7 +492,7 @@ All methods are expected to have single `Invocation` parameter, that stores
|
|
|
482
492
|
- `result` the result ( initially `None`)
|
|
483
493
|
- `exception` a possible caught exception ( initially `None`)
|
|
484
494
|
|
|
485
|
-
⚠️ **
|
|
495
|
+
⚠️ **Note:** 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.
|
|
486
496
|
|
|
487
497
|
If the `proceed` is called with parameters, they will replace the original parameters!
|
|
488
498
|
|
|
@@ -528,6 +538,12 @@ class TransactionAdvice:
|
|
|
528
538
|
|
|
529
539
|
With respect to async methods, you need to make sure, to replace a `proceed()` with a `await proceed_async()` to have the overall chain async!
|
|
530
540
|
|
|
541
|
+
## Advice Lifecycle and visibility.
|
|
542
|
+
|
|
543
|
+
Advices are always part of a specific environment, and only modify methods of objects managed by exactly this environment.
|
|
544
|
+
|
|
545
|
+
An advice of a parent environment will for example not see classes of inherited environments. What is done instead, is to recreate the advice - more technically speaking, a processor that will collect and apply the advices - in every child environment, and let it operate on the local objects. With this approach different environments are completely isolated from each other with no side effects whatsoever.
|
|
546
|
+
|
|
531
547
|
# Threading
|
|
532
548
|
|
|
533
549
|
A handy decorator `@synchronized` in combination with the respective advice is implemented that automatically synchronizes methods with a `RLock` associated with the instance.
|
|
@@ -618,8 +634,8 @@ Two specific source are already implemented:
|
|
|
618
634
|
Typically you create the required configuration sources in an environment class, e.g.
|
|
619
635
|
|
|
620
636
|
```python
|
|
621
|
-
@
|
|
622
|
-
class
|
|
637
|
+
@module()
|
|
638
|
+
class SampleModule:
|
|
623
639
|
# constructor
|
|
624
640
|
|
|
625
641
|
def __init__(self):
|
|
@@ -691,8 +707,8 @@ class DerivedException(Exception):
|
|
|
691
707
|
def __init__(self):
|
|
692
708
|
pass
|
|
693
709
|
|
|
694
|
-
@
|
|
695
|
-
class
|
|
710
|
+
@module()
|
|
711
|
+
class SampleModule:
|
|
696
712
|
# constructor
|
|
697
713
|
|
|
698
714
|
def __init__(self):
|
|
@@ -786,6 +802,10 @@ class ExceptionAdvice:
|
|
|
786
802
|
- bugfixes
|
|
787
803
|
- added `@ExceptionManager`
|
|
788
804
|
|
|
805
|
+
**1.4.1**
|
|
806
|
+
|
|
807
|
+
- mkdocs
|
|
808
|
+
|
|
789
809
|
|
|
790
810
|
|
|
791
811
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module provides dependency injection and aop capabilities for Python applications.
|
|
3
3
|
"""
|
|
4
|
-
from .di import conditional, requires_class, requires_feature, DIException, AbstractCallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory,
|
|
4
|
+
from .di import conditional, requires_class, requires_feature, DIException, AbstractCallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, module, inject, order, create, on_init, on_running, on_destroy, inject_environment, Factory, PostProcessor
|
|
5
5
|
|
|
6
6
|
# import something from the subpackages, so that the decorators are executed
|
|
7
7
|
|
|
@@ -17,7 +17,7 @@ __all__ = [
|
|
|
17
17
|
"Environment",
|
|
18
18
|
"injectable",
|
|
19
19
|
"factory",
|
|
20
|
-
"
|
|
20
|
+
"module",
|
|
21
21
|
"inject",
|
|
22
22
|
"create",
|
|
23
23
|
"order",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The AOP module gives you the possibility to define aspects that will participate in method execution flows.
|
|
3
|
+
|
|
4
|
+
**Example**: all method executions of methods named "foo" will include a `before` aspect, that will be executed before the original method
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
@advice
|
|
8
|
+
class Advice:
|
|
9
|
+
@before(methods().named("foo"))
|
|
10
|
+
def before_call(self, invocation: Invocation):
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Note, that this requires that both the advice and the targeted methods need to be managed by an environment.
|
|
16
|
+
"""
|
|
17
|
+
from .aop import before, after, classes, around, error, advice, methods, Invocation, AspectTarget
|
|
18
|
+
__all__ = [
|
|
19
|
+
"before",
|
|
20
|
+
"after",
|
|
21
|
+
"around",
|
|
22
|
+
"error",
|
|
23
|
+
"advice",
|
|
24
|
+
"classes",
|
|
25
|
+
"methods",
|
|
26
|
+
"Invocation",
|
|
27
|
+
"AspectTarget"
|
|
28
|
+
]
|