aspyx 0.1.0__tar.gz → 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aspyx might be problematic. Click here for more details.

Files changed (24) hide show
  1. {aspyx-0.1.0/src/aspyx.egg-info → aspyx-1.0.1}/PKG-INFO +147 -43
  2. {aspyx-0.1.0 → aspyx-1.0.1}/README.md +145 -41
  3. {aspyx-0.1.0 → aspyx-1.0.1}/pyproject.toml +2 -2
  4. {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/di/__init__.py +4 -1
  5. {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/di/aop/__init__.py +4 -1
  6. {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/di/aop/aop.py +98 -88
  7. {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/di/configuration/__init__.py +4 -1
  8. {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/di/configuration/configuration.py +21 -14
  9. {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/di/di.py +137 -109
  10. {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/reflection/__init__.py +4 -1
  11. {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/reflection/proxy.py +10 -7
  12. {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx/reflection/reflection.py +35 -22
  13. {aspyx-0.1.0 → aspyx-1.0.1/src/aspyx.egg-info}/PKG-INFO +147 -43
  14. {aspyx-0.1.0 → aspyx-1.0.1}/tests/test_aop.py +5 -4
  15. {aspyx-0.1.0 → aspyx-1.0.1}/tests/test_configuration.py +5 -7
  16. {aspyx-0.1.0 → aspyx-1.0.1}/tests/test_di.py +3 -1
  17. {aspyx-0.1.0 → aspyx-1.0.1}/tests/test_reflection.py +20 -3
  18. {aspyx-0.1.0 → aspyx-1.0.1}/LICENSE +0 -0
  19. {aspyx-0.1.0 → aspyx-1.0.1}/setup.cfg +0 -0
  20. {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx.egg-info/SOURCES.txt +0 -0
  21. {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx.egg-info/dependency_links.txt +0 -0
  22. {aspyx-0.1.0 → aspyx-1.0.1}/src/aspyx.egg-info/top_level.txt +0 -0
  23. {aspyx-0.1.0 → aspyx-1.0.1}/tests/test_di_cycle.py +0 -0
  24. {aspyx-0.1.0 → aspyx-1.0.1}/tests/test_proxy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspyx
3
- Version: 0.1.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,20 +25,22 @@ License: MIT License
25
25
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
26
  SOFTWARE.
27
27
 
28
- Requires-Python: >=3.8
28
+ Requires-Python: >=3.9
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Dynamic: license-file
32
32
 
33
33
  # aspyx
34
34
 
35
- ![Pylint](https://github.com/andreasernst/aspyx/actions/workflows/pylint.yml/badge.svg)
36
- ![Build Status](https://github.com/andreasernst/aspyx/actions/workflows/ci.yml/badge.svg)
37
-
35
+ ![Pylint](https://github.com/coolsamson7/aspyx/actions/workflows/pylint.yml/badge.svg)
36
+ ![Build Status](https://github.com/coolsamson7/aspyx/actions/workflows/ci.yml/badge.svg)
37
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/aspyx)
38
+ ![License](https://img.shields.io/github/license/coolsamson7/aspyx)
38
39
 
39
40
  ## Table of Contents
40
41
 
41
42
  - [Introduction](#aspyx)
43
+ - [Installation](#installation)
42
44
  - [Registration](#registration)
43
45
  - [Class](#class)
44
46
  - [Class Factory](#class-factory)
@@ -46,11 +48,14 @@ Dynamic: license-file
46
48
  - [Environment](#environment)
47
49
  - [Definition](#definition)
48
50
  - [Retrieval](#retrieval)
49
- - [Lifecycle methods](#lifecycle-methods)
50
- - [Post Processors](#post-processors)
51
+ - [Instantiation logic](#instantiation-logic)
52
+ - [Injection Methods](#injection-methods)
53
+ - [Lifecycle Methods](#lifecycle-methods)
54
+ - [Post Processors](#post-processors)
51
55
  - [Custom scopes](#custom-scopes)
52
56
  - [AOP](#aop)
53
57
  - [Configuration](#configuration)
58
+ - [Reflection](#reflection)
54
59
 
55
60
  # Introduction
56
61
 
@@ -69,12 +74,13 @@ The following features are supported
69
74
  - container instances that relate to environment classes and manage the lifecylce of related objects
70
75
  - hierarchical environments
71
76
 
77
+ The library is thread-safe!
78
+
72
79
  Let's look at a simple example
73
80
 
74
81
  ```python
75
82
  from aspyx.di import injectable, on_init, on_destroy, environment, Environment
76
83
 
77
-
78
84
  @injectable()
79
85
  class Foo:
80
86
  def __init__(self):
@@ -83,13 +89,12 @@ class Foo:
83
89
  def hello(msg: str):
84
90
  print(f"hello {msg}")
85
91
 
86
-
87
92
  @injectable() # eager and singleton by default
88
93
  class Bar:
89
94
  def __init__(self, foo: Foo): # will inject the Foo dependency
90
95
  self.foo = foo
91
96
 
92
- @on_init() # a lifecycle callback called after the constructor
97
+ @on_init() # a lifecycle callback called after the constructor and all possible injections
93
98
  def init(self):
94
99
  ...
95
100
 
@@ -99,41 +104,40 @@ class Bar:
99
104
 
100
105
  @environment()
101
106
  class SampleEnvironment:
102
- # constructor
103
-
104
107
  def __init__(self):
105
108
  pass
106
109
 
107
-
108
110
  # go, forrest
109
111
 
110
- environment = SampleEnvironment(Configuration)
112
+ environment = Environment(SampleEnvironment)
111
113
 
112
114
  bar = env.get(Bar)
113
- bar.foo.hello("Andi")
115
+
116
+ bar.foo.hello("world")
114
117
  ```
115
118
 
116
- The concepts should be pretty familiar , as well as the names which are a combination of Spring and Angular names :-)
119
+ The concepts should be pretty familiar as well as the names which are a combination of Spring and Angular names :-)
117
120
 
118
121
  Let's add some aspects...
119
122
 
120
123
  ```python
124
+
121
125
  @advice
122
126
  class SampleAdvice:
123
- def __init__(self):
127
+ def __init__(self): # could inject additional stuff
124
128
  pass
125
129
 
126
130
  @before(methods().named("hello").of_type(Foo))
127
- def callBefore(self, invocation: Invocation):
131
+ def call_before(self, invocation: Invocation):
128
132
  print("before Foo.hello(...)")
129
133
 
130
134
  @error(methods().named("hello").of_type(Foo))
131
- def callError(self, invocation: Invocation):
135
+ def call_error(self, invocation: Invocation):
132
136
  print("error Foo.hello(...)")
133
137
  print(invocation.exception)
134
138
 
135
139
  @around(methods().named("hello"))
136
- def callAround(self, invocation: Invocation):
140
+ def call_around(self, invocation: Invocation):
137
141
  print("around Foo.hello()")
138
142
 
139
143
  return invocation.proceed()
@@ -148,6 +152,14 @@ The invocation parameter stores the complete context of the current execution, w
148
152
 
149
153
  Let's look at the details
150
154
 
155
+ # Installation
156
+
157
+ `pip install aspyx`
158
+
159
+ The library is tested with Python version > 3.8
160
+
161
+ Ready to go...
162
+
151
163
  # Registration
152
164
 
153
165
  Different mechanisms are available that make classes eligible for injection
@@ -164,16 +176,19 @@ class Foo:
164
176
  def __init__(self):
165
177
  pass
166
178
  ```
167
- Please make sure, that the class defines a constructor, as this is required to determine injected instances.
168
-
169
- The constructor can only define parameter types that are known as well to the container!
179
+ Please make sure, that the class defines a local constructor, as this is required to determine injected instances.
180
+ All referenced types will be injected by the environemnt.
170
181
 
182
+ Only eligible types are allowed, of course!
171
183
 
172
- The decorator accepts the keyword arguments
173
- - `eager=True` if `True`, the container will create the instances automatically while booting the environment
174
- - `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
184
+ The decorator accepts the keyword arguments
185
+ - `eager : boolean`
186
+ if `True`, the container will create the instances automatically while booting the environment. This is the default.
187
+ - `scope: str`
188
+ the name of a scope which will determine how often instances will be created.
189
+ `singleton` will create it only once - per environment -, while `request` will recreate it on every injection request. The default is `singleton`
175
190
 
176
- Other scopes can be defined. Please check the corresponding chapter.
191
+ Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
177
192
 
178
193
  ## Class Factory
179
194
 
@@ -268,15 +283,55 @@ In case of ambiguities, it will throw an exception.
268
283
 
269
284
  Please be aware, that a base class are not _required_ to be annotated with `@injectable`, as this would mean, that it could be created on its own as well. ( Which is possible as well, btw. )
270
285
 
271
- # Lifecycle methods
286
+ # Instantiation logic
287
+
288
+ Constructing a new instance involves a number of steps executed in this order
289
+ - Constructor call
290
+ the constructor is called with the resolved parameters
291
+ - Advice injection
292
+ All methods involving aspects are updated
293
+ - Lifecycle methods
294
+ different decorators can mark methods that should be called during the lifecycle ( here the construction ) of an instance.
295
+ These are various injection possibilities as well as an optional final `on_init` call
296
+ - PostProcessors
297
+ Any custom post processors, that can add isde effects or modify the instances
298
+
299
+ ## Injection methods
300
+
301
+ Different decorators are implemented, that call methods with computed values
302
+
303
+ - `@inject`
304
+ the method is called with all resolved parameter types ( same as the constructor call)
305
+ - `@inject_environment`
306
+ the method is called with the creating environment as a single parameter
307
+ - `@value()`
308
+ the method is called with a resolved configuration value. Check the corresponding chapter
309
+
310
+ **Example**:
311
+ ```python
312
+ @injectable()
313
+ class Foo:
314
+ def __init__(self):
315
+ pass
316
+
317
+ @inject_environment()
318
+ def initEnvironment(self, env: Environment):
319
+ ...
320
+
321
+ @inject()
322
+ def set(self, baz: Baz) -> None:
323
+ ...
324
+ ```
325
+
326
+ ## Lifecycle methods
272
327
 
273
- It is possible to declare methods that will be called from the container
328
+ It is possible to mark specific lifecle methods.
274
329
  - `@on_init()`
275
330
  called after the constructor and all other injections.
276
331
  - `@on_destroy()`
277
- called after the container has been shut down
332
+ called during shutdown of the environment
278
333
 
279
- # Post Processors
334
+ ## Post Processors
280
335
 
281
336
  As part of the instantiation logic it is possible to define post processors that execute any side effect on newly created instances.
282
337
 
@@ -343,17 +398,17 @@ class SampleAdvice:
343
398
  pass
344
399
 
345
400
  @before(methods().named("hello").of_type(Foo))
346
- def callBefore(self, invocation: Invocation):
401
+ def call_before(self, invocation: Invocation):
347
402
  # arguments: invocation.args
348
403
  print("before Foo.hello(...)")
349
404
 
350
405
  @error(methods().named("hello").of_type(Foo))
351
- def callError(self, invocation: Invocation):
406
+ def call_error(self, invocation: Invocation):
352
407
  print("error Foo.hello(...)")
353
408
  print(invocation.exception)
354
409
 
355
410
  @around(methods().named("hello"))
356
- def callAround(self, invocation: Invocation):
411
+ def call_around(self, invocation: Invocation):
357
412
  print("around Foo.hello()")
358
413
 
359
414
  return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
@@ -374,7 +429,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
374
429
  It is essential for `around` methods to call `proceed()` on the invocation, which will call the next around method in the chain and finally the original method.
375
430
  If the `proceed` is called with parameters, they will replace the original parameters!
376
431
 
377
- The arguments to the corresponding decorators control, how aspects are associated with which methods.
432
+ The argument list to the corresponding decorators control, how aspects are associated with which methods.
378
433
  A fluent interface is used describe the mapping.
379
434
  The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
380
435
 
@@ -388,7 +443,20 @@ Both add the fluent methods:
388
443
  - `decorated_with(type: Type)`
389
444
  defines decorators on methods or classes
390
445
 
391
- The fluent methods `named`, `matches` and `of_type` can be called multiple timess!
446
+ The fluent methods `named`, `matches` and `of_type` can be called multiple times!
447
+
448
+ **Example**:
449
+
450
+ ```python
451
+ @injectable()
452
+ class TransactionAdvice:
453
+ def __init__(self):
454
+ pass
455
+
456
+ @around(methods().decorated_with(transactional), classes().decorated_with(transactional))
457
+ def establish_transaction(self, invocation: Invocation):
458
+ ...
459
+ ```
392
460
 
393
461
  # Configuration
394
462
 
@@ -409,10 +477,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
409
477
 
410
478
  ```python
411
479
  class ConfigurationSource(ABC):
412
- def __init__(self, manager: ConfigurationManager):
413
- manager._register(self)
480
+ def __init__(self):
414
481
  pass
415
482
 
483
+ ...
484
+
416
485
  @abstractmethod
417
486
  def load(self) -> dict:
418
487
  pass
@@ -424,14 +493,12 @@ As a default environment variables are already supported.
424
493
 
425
494
  Other sources can be added dynamically by just registering them.
426
495
 
496
+ **Example**:
427
497
  ```python
428
498
  @injectable()
429
499
  class SampleConfigurationSource(ConfigurationSource):
430
- # constructor
431
-
432
- def __init__(self, manager: ConfigurationManager):
433
- super().__init__(manager)
434
-
500
+ def __init__(self):
501
+ super().__init__()
435
502
 
436
503
  def load(self) -> dict:
437
504
  return {
@@ -444,6 +511,43 @@ class SampleConfigurationSource(ConfigurationSource):
444
511
  }
445
512
  ```
446
513
 
514
+ # Reflection
515
+
516
+ As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
517
+
518
+ After beeing instatiated with
519
+
520
+ ```python
521
+ TypeDescriptor.for_type(<type>)
522
+ ```
523
+
524
+ it offers the methods
525
+ - `get_methods(local=False)`
526
+ - `get_method(name: str, local=False)`
527
+ - `has_decorator(decorator) -> bool`
528
+ - `get_decorator(decorator)`
529
+
530
+ The returned method descriptors offer:
531
+ - `param_types`
532
+ - `return_type`
533
+ - `has_decorator(decorator)`
534
+ - `get_decorator(decorator)`
535
+
536
+ The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
537
+
538
+ Whenver you define a custom decorator, you will need to register it accordingly.
539
+
540
+ **Example**:
541
+ ```python
542
+ def transactional():
543
+ def decorator(func):
544
+ Decorators.add(func, transactional)
545
+ return func
546
+
547
+ return decorator
548
+ ```
549
+
550
+
447
551
 
448
552
 
449
553
 
@@ -1,12 +1,14 @@
1
1
  # aspyx
2
2
 
3
- ![Pylint](https://github.com/andreasernst/aspyx/actions/workflows/pylint.yml/badge.svg)
4
- ![Build Status](https://github.com/andreasernst/aspyx/actions/workflows/ci.yml/badge.svg)
5
-
3
+ ![Pylint](https://github.com/coolsamson7/aspyx/actions/workflows/pylint.yml/badge.svg)
4
+ ![Build Status](https://github.com/coolsamson7/aspyx/actions/workflows/ci.yml/badge.svg)
5
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/aspyx)
6
+ ![License](https://img.shields.io/github/license/coolsamson7/aspyx)
6
7
 
7
8
  ## Table of Contents
8
9
 
9
10
  - [Introduction](#aspyx)
11
+ - [Installation](#installation)
10
12
  - [Registration](#registration)
11
13
  - [Class](#class)
12
14
  - [Class Factory](#class-factory)
@@ -14,11 +16,14 @@
14
16
  - [Environment](#environment)
15
17
  - [Definition](#definition)
16
18
  - [Retrieval](#retrieval)
17
- - [Lifecycle methods](#lifecycle-methods)
18
- - [Post Processors](#post-processors)
19
+ - [Instantiation logic](#instantiation-logic)
20
+ - [Injection Methods](#injection-methods)
21
+ - [Lifecycle Methods](#lifecycle-methods)
22
+ - [Post Processors](#post-processors)
19
23
  - [Custom scopes](#custom-scopes)
20
24
  - [AOP](#aop)
21
25
  - [Configuration](#configuration)
26
+ - [Reflection](#reflection)
22
27
 
23
28
  # Introduction
24
29
 
@@ -37,12 +42,13 @@ The following features are supported
37
42
  - container instances that relate to environment classes and manage the lifecylce of related objects
38
43
  - hierarchical environments
39
44
 
45
+ The library is thread-safe!
46
+
40
47
  Let's look at a simple example
41
48
 
42
49
  ```python
43
50
  from aspyx.di import injectable, on_init, on_destroy, environment, Environment
44
51
 
45
-
46
52
  @injectable()
47
53
  class Foo:
48
54
  def __init__(self):
@@ -51,13 +57,12 @@ class Foo:
51
57
  def hello(msg: str):
52
58
  print(f"hello {msg}")
53
59
 
54
-
55
60
  @injectable() # eager and singleton by default
56
61
  class Bar:
57
62
  def __init__(self, foo: Foo): # will inject the Foo dependency
58
63
  self.foo = foo
59
64
 
60
- @on_init() # a lifecycle callback called after the constructor
65
+ @on_init() # a lifecycle callback called after the constructor and all possible injections
61
66
  def init(self):
62
67
  ...
63
68
 
@@ -67,41 +72,40 @@ class Bar:
67
72
 
68
73
  @environment()
69
74
  class SampleEnvironment:
70
- # constructor
71
-
72
75
  def __init__(self):
73
76
  pass
74
77
 
75
-
76
78
  # go, forrest
77
79
 
78
- environment = SampleEnvironment(Configuration)
80
+ environment = Environment(SampleEnvironment)
79
81
 
80
82
  bar = env.get(Bar)
81
- bar.foo.hello("Andi")
83
+
84
+ bar.foo.hello("world")
82
85
  ```
83
86
 
84
- The concepts should be pretty familiar , as well as the names which are a combination of Spring and Angular names :-)
87
+ The concepts should be pretty familiar as well as the names which are a combination of Spring and Angular names :-)
85
88
 
86
89
  Let's add some aspects...
87
90
 
88
91
  ```python
92
+
89
93
  @advice
90
94
  class SampleAdvice:
91
- def __init__(self):
95
+ def __init__(self): # could inject additional stuff
92
96
  pass
93
97
 
94
98
  @before(methods().named("hello").of_type(Foo))
95
- def callBefore(self, invocation: Invocation):
99
+ def call_before(self, invocation: Invocation):
96
100
  print("before Foo.hello(...)")
97
101
 
98
102
  @error(methods().named("hello").of_type(Foo))
99
- def callError(self, invocation: Invocation):
103
+ def call_error(self, invocation: Invocation):
100
104
  print("error Foo.hello(...)")
101
105
  print(invocation.exception)
102
106
 
103
107
  @around(methods().named("hello"))
104
- def callAround(self, invocation: Invocation):
108
+ def call_around(self, invocation: Invocation):
105
109
  print("around Foo.hello()")
106
110
 
107
111
  return invocation.proceed()
@@ -116,6 +120,14 @@ The invocation parameter stores the complete context of the current execution, w
116
120
 
117
121
  Let's look at the details
118
122
 
123
+ # Installation
124
+
125
+ `pip install aspyx`
126
+
127
+ The library is tested with Python version > 3.8
128
+
129
+ Ready to go...
130
+
119
131
  # Registration
120
132
 
121
133
  Different mechanisms are available that make classes eligible for injection
@@ -132,16 +144,19 @@ class Foo:
132
144
  def __init__(self):
133
145
  pass
134
146
  ```
135
- Please make sure, that the class defines a constructor, as this is required to determine injected instances.
136
-
137
- The constructor can only define parameter types that are known as well to the container!
147
+ Please make sure, that the class defines a local constructor, as this is required to determine injected instances.
148
+ All referenced types will be injected by the environemnt.
138
149
 
150
+ Only eligible types are allowed, of course!
139
151
 
140
- The decorator accepts the keyword arguments
141
- - `eager=True` if `True`, the container will create the instances automatically while booting the environment
142
- - `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
152
+ The decorator accepts the keyword arguments
153
+ - `eager : boolean`
154
+ if `True`, the container will create the instances automatically while booting the environment. This is the default.
155
+ - `scope: str`
156
+ the name of a scope which will determine how often instances will be created.
157
+ `singleton` will create it only once - per environment -, while `request` will recreate it on every injection request. The default is `singleton`
143
158
 
144
- Other scopes can be defined. Please check the corresponding chapter.
159
+ Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
145
160
 
146
161
  ## Class Factory
147
162
 
@@ -236,15 +251,55 @@ In case of ambiguities, it will throw an exception.
236
251
 
237
252
  Please be aware, that a base class are not _required_ to be annotated with `@injectable`, as this would mean, that it could be created on its own as well. ( Which is possible as well, btw. )
238
253
 
239
- # Lifecycle methods
254
+ # Instantiation logic
255
+
256
+ Constructing a new instance involves a number of steps executed in this order
257
+ - Constructor call
258
+ the constructor is called with the resolved parameters
259
+ - Advice injection
260
+ All methods involving aspects are updated
261
+ - Lifecycle methods
262
+ different decorators can mark methods that should be called during the lifecycle ( here the construction ) of an instance.
263
+ These are various injection possibilities as well as an optional final `on_init` call
264
+ - PostProcessors
265
+ Any custom post processors, that can add isde effects or modify the instances
266
+
267
+ ## Injection methods
268
+
269
+ Different decorators are implemented, that call methods with computed values
270
+
271
+ - `@inject`
272
+ the method is called with all resolved parameter types ( same as the constructor call)
273
+ - `@inject_environment`
274
+ the method is called with the creating environment as a single parameter
275
+ - `@value()`
276
+ the method is called with a resolved configuration value. Check the corresponding chapter
277
+
278
+ **Example**:
279
+ ```python
280
+ @injectable()
281
+ class Foo:
282
+ def __init__(self):
283
+ pass
284
+
285
+ @inject_environment()
286
+ def initEnvironment(self, env: Environment):
287
+ ...
288
+
289
+ @inject()
290
+ def set(self, baz: Baz) -> None:
291
+ ...
292
+ ```
293
+
294
+ ## Lifecycle methods
240
295
 
241
- It is possible to declare methods that will be called from the container
296
+ It is possible to mark specific lifecle methods.
242
297
  - `@on_init()`
243
298
  called after the constructor and all other injections.
244
299
  - `@on_destroy()`
245
- called after the container has been shut down
300
+ called during shutdown of the environment
246
301
 
247
- # Post Processors
302
+ ## Post Processors
248
303
 
249
304
  As part of the instantiation logic it is possible to define post processors that execute any side effect on newly created instances.
250
305
 
@@ -311,17 +366,17 @@ class SampleAdvice:
311
366
  pass
312
367
 
313
368
  @before(methods().named("hello").of_type(Foo))
314
- def callBefore(self, invocation: Invocation):
369
+ def call_before(self, invocation: Invocation):
315
370
  # arguments: invocation.args
316
371
  print("before Foo.hello(...)")
317
372
 
318
373
  @error(methods().named("hello").of_type(Foo))
319
- def callError(self, invocation: Invocation):
374
+ def call_error(self, invocation: Invocation):
320
375
  print("error Foo.hello(...)")
321
376
  print(invocation.exception)
322
377
 
323
378
  @around(methods().named("hello"))
324
- def callAround(self, invocation: Invocation):
379
+ def call_around(self, invocation: Invocation):
325
380
  print("around Foo.hello()")
326
381
 
327
382
  return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
@@ -342,7 +397,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
342
397
  It is essential for `around` methods to call `proceed()` on the invocation, which will call the next around method in the chain and finally the original method.
343
398
  If the `proceed` is called with parameters, they will replace the original parameters!
344
399
 
345
- The arguments to the corresponding decorators control, how aspects are associated with which methods.
400
+ The argument list to the corresponding decorators control, how aspects are associated with which methods.
346
401
  A fluent interface is used describe the mapping.
347
402
  The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
348
403
 
@@ -356,7 +411,20 @@ Both add the fluent methods:
356
411
  - `decorated_with(type: Type)`
357
412
  defines decorators on methods or classes
358
413
 
359
- The fluent methods `named`, `matches` and `of_type` can be called multiple timess!
414
+ The fluent methods `named`, `matches` and `of_type` can be called multiple times!
415
+
416
+ **Example**:
417
+
418
+ ```python
419
+ @injectable()
420
+ class TransactionAdvice:
421
+ def __init__(self):
422
+ pass
423
+
424
+ @around(methods().decorated_with(transactional), classes().decorated_with(transactional))
425
+ def establish_transaction(self, invocation: Invocation):
426
+ ...
427
+ ```
360
428
 
361
429
  # Configuration
362
430
 
@@ -377,10 +445,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
377
445
 
378
446
  ```python
379
447
  class ConfigurationSource(ABC):
380
- def __init__(self, manager: ConfigurationManager):
381
- manager._register(self)
448
+ def __init__(self):
382
449
  pass
383
450
 
451
+ ...
452
+
384
453
  @abstractmethod
385
454
  def load(self) -> dict:
386
455
  pass
@@ -392,14 +461,12 @@ As a default environment variables are already supported.
392
461
 
393
462
  Other sources can be added dynamically by just registering them.
394
463
 
464
+ **Example**:
395
465
  ```python
396
466
  @injectable()
397
467
  class SampleConfigurationSource(ConfigurationSource):
398
- # constructor
399
-
400
- def __init__(self, manager: ConfigurationManager):
401
- super().__init__(manager)
402
-
468
+ def __init__(self):
469
+ super().__init__()
403
470
 
404
471
  def load(self) -> dict:
405
472
  return {
@@ -412,6 +479,43 @@ class SampleConfigurationSource(ConfigurationSource):
412
479
  }
413
480
  ```
414
481
 
482
+ # Reflection
483
+
484
+ As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
485
+
486
+ After beeing instatiated with
487
+
488
+ ```python
489
+ TypeDescriptor.for_type(<type>)
490
+ ```
491
+
492
+ it offers the methods
493
+ - `get_methods(local=False)`
494
+ - `get_method(name: str, local=False)`
495
+ - `has_decorator(decorator) -> bool`
496
+ - `get_decorator(decorator)`
497
+
498
+ The returned method descriptors offer:
499
+ - `param_types`
500
+ - `return_type`
501
+ - `has_decorator(decorator)`
502
+ - `get_decorator(decorator)`
503
+
504
+ The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
505
+
506
+ Whenver you define a custom decorator, you will need to register it accordingly.
507
+
508
+ **Example**:
509
+ ```python
510
+ def transactional():
511
+ def decorator(func):
512
+ Decorators.add(func, transactional)
513
+ return func
514
+
515
+ return decorator
516
+ ```
517
+
518
+
415
519
 
416
520
 
417
521