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.

Files changed (38) hide show
  1. {aspyx-1.4.0/src/aspyx.egg-info → aspyx-1.4.1}/PKG-INFO +67 -47
  2. {aspyx-1.4.0 → aspyx-1.4.1}/README.md +66 -46
  3. {aspyx-1.4.0 → aspyx-1.4.1}/pyproject.toml +1 -1
  4. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/__init__.py +2 -2
  5. aspyx-1.4.1/src/aspyx/di/aop/__init__.py +28 -0
  6. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/aop/aop.py +64 -22
  7. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/configuration/__init__.py +3 -3
  8. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/configuration/configuration.py +7 -5
  9. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/di.py +100 -23
  10. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/exception/__init__.py +1 -1
  11. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/exception/exception_manager.py +34 -17
  12. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/reflection/proxy.py +2 -4
  13. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/reflection/reflection.py +55 -15
  14. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/threading/__init__.py +1 -1
  15. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/threading/thread_local.py +6 -2
  16. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/util/stringbuilder.py +6 -2
  17. {aspyx-1.4.0 → aspyx-1.4.1/src/aspyx.egg-info}/PKG-INFO +67 -47
  18. {aspyx-1.4.0 → aspyx-1.4.1}/tests/test_aop.py +31 -5
  19. {aspyx-1.4.0 → aspyx-1.4.1}/tests/test_configuration.py +8 -8
  20. {aspyx-1.4.0 → aspyx-1.4.1}/tests/test_cycle.py +4 -4
  21. {aspyx-1.4.0 → aspyx-1.4.1}/tests/test_di.py +51 -18
  22. {aspyx-1.4.0 → aspyx-1.4.1}/tests/test_exception_manager.py +12 -10
  23. aspyx-1.4.0/src/aspyx/di/aop/__init__.py +0 -14
  24. {aspyx-1.4.0 → aspyx-1.4.1}/LICENSE +0 -0
  25. {aspyx-1.4.0 → aspyx-1.4.1}/setup.cfg +0 -0
  26. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/__init__.py +0 -0
  27. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/configuration/env_configuration_source.py +0 -0
  28. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/configuration/yaml_configuration_source.py +0 -0
  29. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/threading/__init__.py +0 -0
  30. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/di/threading/synchronized.py +0 -0
  31. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/reflection/__init__.py +0 -0
  32. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx/util/__init__.py +0 -0
  33. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx.egg-info/SOURCES.txt +0 -0
  34. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx.egg-info/dependency_links.txt +0 -0
  35. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx.egg-info/requires.txt +0 -0
  36. {aspyx-1.4.0 → aspyx-1.4.1}/src/aspyx.egg-info/top_level.txt +0 -0
  37. {aspyx-1.4.0 → aspyx-1.4.1}/tests/test_proxy.py +0 -0
  38. {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.0
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
  ![License](https://img.shields.io/github/license/coolsamson7/aspyx)
41
41
  ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
42
42
  [![PyPI](https://img.shields.io/pypi/v/aspyx)](https://pypi.org/project/aspyx/)
43
- [![Docs](https://img.shields.io/badge/docs-online-blue?logo=github)](https://coolsamson7.github.io/aspyx/)
43
+ [![Docs](https://img.shields.io/badge/docs-online-blue?logo=github)](https://coolsamson7.github.io/aspyx/index/introduction)
44
+
45
+ ![image](https://github.com/user-attachments/assets/e808210a-b1a4-4fd0-93f1-b5f9845fa520)
44
46
 
45
47
  ## Table of Contents
46
48
 
47
49
  - [Motivation](#motivation)
48
- - [Introduction](#introduction)
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
- Especially the AOP integration definitely makes sense, as aspects on their own also usually require a context, which in a DI world is simply injected.
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
- # Introduction
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 according to their module location including recursive imports and inheritance
97
- - instantiation of - possibly multiple - container instances - so called environments - that manage the lifecycle of related objects
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, environment, Environment
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): # will inject the Foo dependency
129
+ def __init__(self, foo: Foo): # will inject the Foo dependency
123
130
  self.foo = foo
124
131
 
125
- @on_init() # a lifecycle callback called after the constructor and all possible injections
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 register all - specifically decorated - classes and factories in the own module
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
- @environment()
134
- class SampleEnvironment:
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(SampleEnvironment)
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
- The invocation parameter stores the complete context of the current execution, which are
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. The set of classes and instances is determined by a constructor argument that controls the class registry.
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
- @environment()
288
- class SampleEnvironment:
291
+ @module()
292
+ class SampleModule:
289
293
  def __init__(self):
290
294
  pass
291
-
292
- environment = Environment(SampleEnvironment)
293
295
  ```
294
296
 
295
- The default is that all eligible classes, that are implemented in the containing module or in any submodule will be managed.
296
- THe container will import the module and its children automatically. No need to add artificial import statements!
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
- @environment()
311
- class SampleEnvironmen():
317
+ @module()
318
+ class SampleModule():
312
319
  def __init__(self):
313
320
  pass
314
321
 
315
- environment = Environment(SampleEnvironment, features=["dev"])
322
+ environment = Environment(SampleModule, features=["dev"])
316
323
  ```
317
324
 
318
325
 
319
- By adding an `imports: list[Type]` parameter, specifying other environment types, it will register the appropriate classes recursively.
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
- @environment()
324
- class SampleEnvironmen(imports=[OtherEnvironment]):
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(RootEnvironment)
334
- environment = Environment(SampleEnvironment, parent=rootEnvironment)
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
- Advice classes need to be part of classes that add a `@advice()` decorator and can define methods that add aspects.
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
- ⚠️ **Attention:** 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.
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
- @environment()
656
- class SampleEnvironment:
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
- @environment()
729
- class SampleEnvironment:
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
  ![License](https://img.shields.io/github/license/coolsamson7/aspyx)
7
7
  ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
8
8
  [![PyPI](https://img.shields.io/pypi/v/aspyx)](https://pypi.org/project/aspyx/)
9
- [![Docs](https://img.shields.io/badge/docs-online-blue?logo=github)](https://coolsamson7.github.io/aspyx/)
9
+ [![Docs](https://img.shields.io/badge/docs-online-blue?logo=github)](https://coolsamson7.github.io/aspyx/index/introduction)
10
+
11
+ ![image](https://github.com/user-attachments/assets/e808210a-b1a4-4fd0-93f1-b5f9845fa520)
10
12
 
11
13
  ## Table of Contents
12
14
 
13
15
  - [Motivation](#motivation)
14
- - [Introduction](#introduction)
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
- Especially the AOP integration definitely makes sense, as aspects on their own also usually require a context, which in a DI world is simply injected.
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
- # Introduction
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 according to their module location including recursive imports and inheritance
63
- - instantiation of - possibly multiple - container instances - so called environments - that manage the lifecycle of related objects
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, environment, Environment
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): # will inject the Foo dependency
95
+ def __init__(self, foo: Foo): # will inject the Foo dependency
89
96
  self.foo = foo
90
97
 
91
- @on_init() # a lifecycle callback called after the constructor and all possible injections
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 register all - specifically decorated - classes and factories in the own module
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
- @environment()
100
- class SampleEnvironment:
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(SampleEnvironment)
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
- The invocation parameter stores the complete context of the current execution, which are
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. The set of classes and instances is determined by a constructor argument that controls the class registry.
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
- @environment()
254
- class SampleEnvironment:
257
+ @module()
258
+ class SampleModule:
255
259
  def __init__(self):
256
260
  pass
257
-
258
- environment = Environment(SampleEnvironment)
259
261
  ```
260
262
 
261
- The default is that all eligible classes, that are implemented in the containing module or in any submodule will be managed.
262
- THe container will import the module and its children automatically. No need to add artificial import statements!
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
- @environment()
277
- class SampleEnvironmen():
283
+ @module()
284
+ class SampleModule():
278
285
  def __init__(self):
279
286
  pass
280
287
 
281
- environment = Environment(SampleEnvironment, features=["dev"])
288
+ environment = Environment(SampleModule, features=["dev"])
282
289
  ```
283
290
 
284
291
 
285
- By adding an `imports: list[Type]` parameter, specifying other environment types, it will register the appropriate classes recursively.
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
- @environment()
290
- class SampleEnvironmen(imports=[OtherEnvironment]):
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(RootEnvironment)
300
- environment = Environment(SampleEnvironment, parent=rootEnvironment)
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
- Advice classes need to be part of classes that add a `@advice()` decorator and can define methods that add aspects.
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
- ⚠️ **Attention:** 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.
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
- @environment()
622
- class SampleEnvironment:
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
- @environment()
695
- class SampleEnvironment:
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
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aspyx"
7
- version = "1.4.0"
7
+ version = "1.4.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"
@@ -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, environment, inject, order, create, on_init, on_running, on_destroy, inject_environment, Factory, PostProcessor
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
- "environment",
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
+ ]