aspyx 1.0.0__tar.gz → 1.1.0__tar.gz

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

Potentially problematic release.


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

Files changed (27) hide show
  1. {aspyx-1.0.0/src/aspyx.egg-info → aspyx-1.1.0}/PKG-INFO +126 -38
  2. {aspyx-1.0.0 → aspyx-1.1.0}/README.md +124 -36
  3. {aspyx-1.0.0 → aspyx-1.1.0}/pyproject.toml +2 -2
  4. aspyx-1.1.0/src/aspyx/di/__init__.py +33 -0
  5. {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/di/aop/__init__.py +4 -1
  6. {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/di/aop/aop.py +56 -76
  7. {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/di/configuration/__init__.py +4 -1
  8. {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/di/configuration/configuration.py +22 -17
  9. {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/di/di.py +280 -171
  10. aspyx-1.1.0/src/aspyx/di/util/__init__.py +8 -0
  11. aspyx-1.1.0/src/aspyx/di/util/stringbuilder.py +31 -0
  12. {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/reflection/__init__.py +4 -1
  13. {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/reflection/proxy.py +10 -7
  14. {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx/reflection/reflection.py +38 -25
  15. {aspyx-1.0.0 → aspyx-1.1.0/src/aspyx.egg-info}/PKG-INFO +126 -38
  16. {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx.egg-info/SOURCES.txt +2 -0
  17. {aspyx-1.0.0 → aspyx-1.1.0}/tests/test_aop.py +14 -12
  18. {aspyx-1.0.0 → aspyx-1.1.0}/tests/test_configuration.py +12 -12
  19. {aspyx-1.0.0 → aspyx-1.1.0}/tests/test_di.py +42 -28
  20. {aspyx-1.0.0 → aspyx-1.1.0}/tests/test_di_cycle.py +7 -5
  21. {aspyx-1.0.0 → aspyx-1.1.0}/tests/test_proxy.py +4 -1
  22. {aspyx-1.0.0 → aspyx-1.1.0}/tests/test_reflection.py +10 -14
  23. aspyx-1.0.0/src/aspyx/di/__init__.py +0 -29
  24. {aspyx-1.0.0 → aspyx-1.1.0}/LICENSE +0 -0
  25. {aspyx-1.0.0 → aspyx-1.1.0}/setup.cfg +0 -0
  26. {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx.egg-info/dependency_links.txt +0 -0
  27. {aspyx-1.0.0 → aspyx-1.1.0}/src/aspyx.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspyx
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: A DI and AOP library for Python
5
5
  Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
6
6
  License: MIT License
@@ -25,7 +25,7 @@ License: MIT License
25
25
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
26
  SOFTWARE.
27
27
 
28
- Requires-Python: >=3.8
28
+ Requires-Python: >=3.9
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Dynamic: license-file
@@ -34,11 +34,14 @@ Dynamic: license-file
34
34
 
35
35
  ![Pylint](https://github.com/coolsamson7/aspyx/actions/workflows/pylint.yml/badge.svg)
36
36
  ![Build Status](https://github.com/coolsamson7/aspyx/actions/workflows/ci.yml/badge.svg)
37
-
37
+ ![Python Versions](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12-blue)
38
+ ![License](https://img.shields.io/github/license/coolsamson7/aspyx)
39
+ ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
38
40
 
39
41
  ## Table of Contents
40
42
 
41
43
  - [Introduction](#aspyx)
44
+ - [Installation](#installation)
42
45
  - [Registration](#registration)
43
46
  - [Class](#class)
44
47
  - [Class Factory](#class-factory)
@@ -53,30 +56,32 @@ Dynamic: license-file
53
56
  - [Custom scopes](#custom-scopes)
54
57
  - [AOP](#aop)
55
58
  - [Configuration](#configuration)
59
+ - [Reflection](#reflection)
60
+ - [Version History](#version-history)
56
61
 
57
62
  # Introduction
58
63
 
59
64
  Aspyx is a small python libary, that adds support for both dependency injection and aop.
60
65
 
61
66
  The following features are supported
62
- - constructor injection
63
- - method injection
67
+ - constructor and setter injection
64
68
  - post processors
65
69
  - factory classes and methods
66
70
  - support for eager construction
67
- - support for singleton and reuqest scopes
71
+ - support for singleton and request scopes
68
72
  - possibilty to add custom scopes
69
73
  - lifecycle events methods
70
74
  - bundling of injectable object sets by environment classes including recursive imports and inheritance
71
75
  - container instances that relate to environment classes and manage the lifecylce of related objects
72
76
  - hierarchical environments
73
77
 
78
+ The library is thread-safe!
79
+
74
80
  Let's look at a simple example
75
81
 
76
82
  ```python
77
83
  from aspyx.di import injectable, on_init, on_destroy, environment, Environment
78
84
 
79
-
80
85
  @injectable()
81
86
  class Foo:
82
87
  def __init__(self):
@@ -85,13 +90,12 @@ class Foo:
85
90
  def hello(msg: str):
86
91
  print(f"hello {msg}")
87
92
 
88
-
89
93
  @injectable() # eager and singleton by default
90
94
  class Bar:
91
95
  def __init__(self, foo: Foo): # will inject the Foo dependency
92
96
  self.foo = foo
93
97
 
94
- @on_init() # a lifecycle callback called after the constructor
98
+ @on_init() # a lifecycle callback called after the constructor and all possible injections
95
99
  def init(self):
96
100
  ...
97
101
 
@@ -101,41 +105,40 @@ class Bar:
101
105
 
102
106
  @environment()
103
107
  class SampleEnvironment:
104
- # constructor
105
-
106
108
  def __init__(self):
107
109
  pass
108
110
 
109
-
110
111
  # go, forrest
111
112
 
112
- environment = SampleEnvironment(Configuration)
113
+ environment = Environment(SampleEnvironment)
113
114
 
114
115
  bar = env.get(Bar)
115
- bar.foo.hello("Andi")
116
+
117
+ bar.foo.hello("world")
116
118
  ```
117
119
 
118
- The concepts should be pretty familiar , as well as the names which are a combination of Spring and Angular names :-)
120
+ The concepts should be pretty familiar as well as the names which are a combination of Spring and Angular names :-)
119
121
 
120
122
  Let's add some aspects...
121
123
 
122
124
  ```python
125
+
123
126
  @advice
124
127
  class SampleAdvice:
125
- def __init__(self):
128
+ def __init__(self): # could inject additional stuff
126
129
  pass
127
130
 
128
131
  @before(methods().named("hello").of_type(Foo))
129
- def callBefore(self, invocation: Invocation):
132
+ def call_before(self, invocation: Invocation):
130
133
  print("before Foo.hello(...)")
131
134
 
132
135
  @error(methods().named("hello").of_type(Foo))
133
- def callError(self, invocation: Invocation):
136
+ def call_error(self, invocation: Invocation):
134
137
  print("error Foo.hello(...)")
135
138
  print(invocation.exception)
136
139
 
137
140
  @around(methods().named("hello"))
138
- def callAround(self, invocation: Invocation):
141
+ def call_around(self, invocation: Invocation):
139
142
  print("around Foo.hello()")
140
143
 
141
144
  return invocation.proceed()
@@ -150,6 +153,14 @@ The invocation parameter stores the complete context of the current execution, w
150
153
 
151
154
  Let's look at the details
152
155
 
156
+ # Installation
157
+
158
+ `pip install aspyx`
159
+
160
+ The library is tested with all Python version > 3.9
161
+
162
+ Ready to go...
163
+
153
164
  # Registration
154
165
 
155
166
  Different mechanisms are available that make classes eligible for injection
@@ -171,12 +182,21 @@ All referenced types will be injected by the environemnt.
171
182
 
172
183
  Only eligible types are allowed, of course!
173
184
 
185
+ The decorator accepts the keyword arguments
186
+ - `eager : boolean`
187
+ if `True`, the container will create the instances automatically while booting the environment. This is the default.
188
+ - `scope: str`
189
+ the name of a - registered - scope which will determine how often instances will be created.
174
190
 
175
- The decorator accepts the keyword arguments
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
191
+ The following scopes are implemented out of the box:
192
+ - `singleton`
193
+ objects are created once inside an environment and cached. This is the default.
194
+ - `request`
195
+ obejcts are created on every injection request
196
+ - `thread`
197
+ objects are cerated and cached with respect to the current thread.
178
198
 
179
- Other scopes can be defined. Please check the corresponding chapter.
199
+ Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
180
200
 
181
201
  ## Class Factory
182
202
 
@@ -292,7 +312,7 @@ Different decorators are implemented, that call methods with computed values
292
312
  the method is called with all resolved parameter types ( same as the constructor call)
293
313
  - `@inject_environment`
294
314
  the method is called with the creating environment as a single parameter
295
- - `@value()`
315
+ - `@inject_value()`
296
316
  the method is called with a resolved configuration value. Check the corresponding chapter
297
317
 
298
318
  **Example**:
@@ -313,9 +333,11 @@ class Foo:
313
333
 
314
334
  ## Lifecycle methods
315
335
 
316
- It is possible to mark specific lifecle methods.
336
+ It is possible to mark specific lifecyle methods.
317
337
  - `@on_init()`
318
338
  called after the constructor and all other injections.
339
+ - `@on_running()`
340
+ called an environment has initialized all eager objects.
319
341
  - `@on_destroy()`
320
342
  called during shutdown of the environment
321
343
 
@@ -386,17 +408,17 @@ class SampleAdvice:
386
408
  pass
387
409
 
388
410
  @before(methods().named("hello").of_type(Foo))
389
- def callBefore(self, invocation: Invocation):
411
+ def call_before(self, invocation: Invocation):
390
412
  # arguments: invocation.args
391
413
  print("before Foo.hello(...)")
392
414
 
393
415
  @error(methods().named("hello").of_type(Foo))
394
- def callError(self, invocation: Invocation):
416
+ def call_error(self, invocation: Invocation):
395
417
  print("error Foo.hello(...)")
396
418
  print(invocation.exception)
397
419
 
398
420
  @around(methods().named("hello"))
399
- def callAround(self, invocation: Invocation):
421
+ def call_around(self, invocation: Invocation):
400
422
  print("around Foo.hello()")
401
423
 
402
424
  return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
@@ -417,7 +439,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
417
439
  It is essential for `around` methods to call `proceed()` on the invocation, which will call the next around method in the chain and finally the original method.
418
440
  If the `proceed` is called with parameters, they will replace the original parameters!
419
441
 
420
- The arguments to the corresponding decorators control, how aspects are associated with which methods.
442
+ The argument list to the corresponding decorators control, how aspects are associated with which methods.
421
443
  A fluent interface is used describe the mapping.
422
444
  The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
423
445
 
@@ -431,11 +453,24 @@ Both add the fluent methods:
431
453
  - `decorated_with(type: Type)`
432
454
  defines decorators on methods or classes
433
455
 
434
- The fluent methods `named`, `matches` and `of_type` can be called multiple timess!
456
+ The fluent methods `named`, `matches` and `of_type` can be called multiple times!
457
+
458
+ **Example**:
459
+
460
+ ```python
461
+ @injectable()
462
+ class TransactionAdvice:
463
+ def __init__(self):
464
+ pass
465
+
466
+ @around(methods().decorated_with(transactional), classes().decorated_with(transactional))
467
+ def establish_transaction(self, invocation: Invocation):
468
+ ...
469
+ ```
435
470
 
436
471
  # Configuration
437
472
 
438
- It is possible to inject configuration values, by decorating methods with `@value(<name>)` given a configuration key.
473
+ It is possible to inject configuration values, by decorating methods with `@inject-value(<name>)` given a configuration key.
439
474
 
440
475
  ```python
441
476
  @injectable()
@@ -452,10 +487,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
452
487
 
453
488
  ```python
454
489
  class ConfigurationSource(ABC):
455
- def __init__(self, manager: ConfigurationManager):
456
- manager._register(self)
490
+ def __init__(self):
457
491
  pass
458
492
 
493
+ ...
494
+
459
495
  @abstractmethod
460
496
  def load(self) -> dict:
461
497
  pass
@@ -467,14 +503,12 @@ As a default environment variables are already supported.
467
503
 
468
504
  Other sources can be added dynamically by just registering them.
469
505
 
506
+ **Example**:
470
507
  ```python
471
508
  @injectable()
472
509
  class SampleConfigurationSource(ConfigurationSource):
473
- # constructor
474
-
475
- def __init__(self, manager: ConfigurationManager):
476
- super().__init__(manager)
477
-
510
+ def __init__(self):
511
+ super().__init__()
478
512
 
479
513
  def load(self) -> dict:
480
514
  return {
@@ -487,7 +521,61 @@ class SampleConfigurationSource(ConfigurationSource):
487
521
  }
488
522
  ```
489
523
 
524
+ # Reflection
525
+
526
+ As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
527
+
528
+ After beeing instatiated with
529
+
530
+ ```python
531
+ TypeDescriptor.for_type(<type>)
532
+ ```
533
+
534
+ it offers the methods
535
+ - `get_methods(local=False)`
536
+ return a list of either local or overall methods
537
+ - `get_method(name: str, local=False)`
538
+ return a single either local or overall method
539
+ - `has_decorator(decorator: Callable) -> bool`
540
+ return `True`, if the class is decorated with the specified decrator
541
+ - `get_decorator(decorator) -> Optional[DecoratorDescriptor]`
542
+ return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
543
+
544
+ The returned method descriptors offer:
545
+ - `param_types`
546
+ list of arg types
547
+ - `return_type`
548
+ the retur type
549
+ - `has_decorator(decorator: Callable) -> bool`
550
+ return `True`, if the method is decorated with the specified decrator
551
+ - `get_decorator(decorator: Callable) -> Optional[DecoratorDescriptor]`
552
+ return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
553
+
554
+ The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
555
+
556
+ Whenver you define a custom decorator, you will need to register it accordingly.
557
+
558
+ **Example**:
559
+ ```python
560
+ def transactional():
561
+ def decorator(func):
562
+ Decorators.add(func, transactional)
563
+ return func
564
+
565
+ return decorator
566
+ ```
567
+
568
+
569
+ # Version History
570
+
571
+ **1.0.1**
572
+
573
+ - some internal refactorings
574
+
575
+ **1.1.0**
490
576
 
577
+ - added `@on_running()` callback
578
+ - added `thread` scope
491
579
 
492
580
 
493
581
 
@@ -2,11 +2,14 @@
2
2
 
3
3
  ![Pylint](https://github.com/coolsamson7/aspyx/actions/workflows/pylint.yml/badge.svg)
4
4
  ![Build Status](https://github.com/coolsamson7/aspyx/actions/workflows/ci.yml/badge.svg)
5
-
5
+ ![Python Versions](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12-blue)
6
+ ![License](https://img.shields.io/github/license/coolsamson7/aspyx)
7
+ ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
6
8
 
7
9
  ## Table of Contents
8
10
 
9
11
  - [Introduction](#aspyx)
12
+ - [Installation](#installation)
10
13
  - [Registration](#registration)
11
14
  - [Class](#class)
12
15
  - [Class Factory](#class-factory)
@@ -21,30 +24,32 @@
21
24
  - [Custom scopes](#custom-scopes)
22
25
  - [AOP](#aop)
23
26
  - [Configuration](#configuration)
27
+ - [Reflection](#reflection)
28
+ - [Version History](#version-history)
24
29
 
25
30
  # Introduction
26
31
 
27
32
  Aspyx is a small python libary, that adds support for both dependency injection and aop.
28
33
 
29
34
  The following features are supported
30
- - constructor injection
31
- - method injection
35
+ - constructor and setter injection
32
36
  - post processors
33
37
  - factory classes and methods
34
38
  - support for eager construction
35
- - support for singleton and reuqest scopes
39
+ - support for singleton and request scopes
36
40
  - possibilty to add custom scopes
37
41
  - lifecycle events methods
38
42
  - bundling of injectable object sets by environment classes including recursive imports and inheritance
39
43
  - container instances that relate to environment classes and manage the lifecylce of related objects
40
44
  - hierarchical environments
41
45
 
46
+ The library is thread-safe!
47
+
42
48
  Let's look at a simple example
43
49
 
44
50
  ```python
45
51
  from aspyx.di import injectable, on_init, on_destroy, environment, Environment
46
52
 
47
-
48
53
  @injectable()
49
54
  class Foo:
50
55
  def __init__(self):
@@ -53,13 +58,12 @@ class Foo:
53
58
  def hello(msg: str):
54
59
  print(f"hello {msg}")
55
60
 
56
-
57
61
  @injectable() # eager and singleton by default
58
62
  class Bar:
59
63
  def __init__(self, foo: Foo): # will inject the Foo dependency
60
64
  self.foo = foo
61
65
 
62
- @on_init() # a lifecycle callback called after the constructor
66
+ @on_init() # a lifecycle callback called after the constructor and all possible injections
63
67
  def init(self):
64
68
  ...
65
69
 
@@ -69,41 +73,40 @@ class Bar:
69
73
 
70
74
  @environment()
71
75
  class SampleEnvironment:
72
- # constructor
73
-
74
76
  def __init__(self):
75
77
  pass
76
78
 
77
-
78
79
  # go, forrest
79
80
 
80
- environment = SampleEnvironment(Configuration)
81
+ environment = Environment(SampleEnvironment)
81
82
 
82
83
  bar = env.get(Bar)
83
- bar.foo.hello("Andi")
84
+
85
+ bar.foo.hello("world")
84
86
  ```
85
87
 
86
- The concepts should be pretty familiar , as well as the names which are a combination of Spring and Angular names :-)
88
+ The concepts should be pretty familiar as well as the names which are a combination of Spring and Angular names :-)
87
89
 
88
90
  Let's add some aspects...
89
91
 
90
92
  ```python
93
+
91
94
  @advice
92
95
  class SampleAdvice:
93
- def __init__(self):
96
+ def __init__(self): # could inject additional stuff
94
97
  pass
95
98
 
96
99
  @before(methods().named("hello").of_type(Foo))
97
- def callBefore(self, invocation: Invocation):
100
+ def call_before(self, invocation: Invocation):
98
101
  print("before Foo.hello(...)")
99
102
 
100
103
  @error(methods().named("hello").of_type(Foo))
101
- def callError(self, invocation: Invocation):
104
+ def call_error(self, invocation: Invocation):
102
105
  print("error Foo.hello(...)")
103
106
  print(invocation.exception)
104
107
 
105
108
  @around(methods().named("hello"))
106
- def callAround(self, invocation: Invocation):
109
+ def call_around(self, invocation: Invocation):
107
110
  print("around Foo.hello()")
108
111
 
109
112
  return invocation.proceed()
@@ -118,6 +121,14 @@ The invocation parameter stores the complete context of the current execution, w
118
121
 
119
122
  Let's look at the details
120
123
 
124
+ # Installation
125
+
126
+ `pip install aspyx`
127
+
128
+ The library is tested with all Python version > 3.9
129
+
130
+ Ready to go...
131
+
121
132
  # Registration
122
133
 
123
134
  Different mechanisms are available that make classes eligible for injection
@@ -139,12 +150,21 @@ All referenced types will be injected by the environemnt.
139
150
 
140
151
  Only eligible types are allowed, of course!
141
152
 
153
+ The decorator accepts the keyword arguments
154
+ - `eager : boolean`
155
+ if `True`, the container will create the instances automatically while booting the environment. This is the default.
156
+ - `scope: str`
157
+ the name of a - registered - scope which will determine how often instances will be created.
142
158
 
143
- The decorator accepts the keyword arguments
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
159
+ The following scopes are implemented out of the box:
160
+ - `singleton`
161
+ objects are created once inside an environment and cached. This is the default.
162
+ - `request`
163
+ obejcts are created on every injection request
164
+ - `thread`
165
+ objects are cerated and cached with respect to the current thread.
146
166
 
147
- Other scopes can be defined. Please check the corresponding chapter.
167
+ Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
148
168
 
149
169
  ## Class Factory
150
170
 
@@ -260,7 +280,7 @@ Different decorators are implemented, that call methods with computed values
260
280
  the method is called with all resolved parameter types ( same as the constructor call)
261
281
  - `@inject_environment`
262
282
  the method is called with the creating environment as a single parameter
263
- - `@value()`
283
+ - `@inject_value()`
264
284
  the method is called with a resolved configuration value. Check the corresponding chapter
265
285
 
266
286
  **Example**:
@@ -281,9 +301,11 @@ class Foo:
281
301
 
282
302
  ## Lifecycle methods
283
303
 
284
- It is possible to mark specific lifecle methods.
304
+ It is possible to mark specific lifecyle methods.
285
305
  - `@on_init()`
286
306
  called after the constructor and all other injections.
307
+ - `@on_running()`
308
+ called an environment has initialized all eager objects.
287
309
  - `@on_destroy()`
288
310
  called during shutdown of the environment
289
311
 
@@ -354,17 +376,17 @@ class SampleAdvice:
354
376
  pass
355
377
 
356
378
  @before(methods().named("hello").of_type(Foo))
357
- def callBefore(self, invocation: Invocation):
379
+ def call_before(self, invocation: Invocation):
358
380
  # arguments: invocation.args
359
381
  print("before Foo.hello(...)")
360
382
 
361
383
  @error(methods().named("hello").of_type(Foo))
362
- def callError(self, invocation: Invocation):
384
+ def call_error(self, invocation: Invocation):
363
385
  print("error Foo.hello(...)")
364
386
  print(invocation.exception)
365
387
 
366
388
  @around(methods().named("hello"))
367
- def callAround(self, invocation: Invocation):
389
+ def call_around(self, invocation: Invocation):
368
390
  print("around Foo.hello()")
369
391
 
370
392
  return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
@@ -385,7 +407,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
385
407
  It is essential for `around` methods to call `proceed()` on the invocation, which will call the next around method in the chain and finally the original method.
386
408
  If the `proceed` is called with parameters, they will replace the original parameters!
387
409
 
388
- The arguments to the corresponding decorators control, how aspects are associated with which methods.
410
+ The argument list to the corresponding decorators control, how aspects are associated with which methods.
389
411
  A fluent interface is used describe the mapping.
390
412
  The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
391
413
 
@@ -399,11 +421,24 @@ Both add the fluent methods:
399
421
  - `decorated_with(type: Type)`
400
422
  defines decorators on methods or classes
401
423
 
402
- The fluent methods `named`, `matches` and `of_type` can be called multiple timess!
424
+ The fluent methods `named`, `matches` and `of_type` can be called multiple times!
425
+
426
+ **Example**:
427
+
428
+ ```python
429
+ @injectable()
430
+ class TransactionAdvice:
431
+ def __init__(self):
432
+ pass
433
+
434
+ @around(methods().decorated_with(transactional), classes().decorated_with(transactional))
435
+ def establish_transaction(self, invocation: Invocation):
436
+ ...
437
+ ```
403
438
 
404
439
  # Configuration
405
440
 
406
- It is possible to inject configuration values, by decorating methods with `@value(<name>)` given a configuration key.
441
+ It is possible to inject configuration values, by decorating methods with `@inject-value(<name>)` given a configuration key.
407
442
 
408
443
  ```python
409
444
  @injectable()
@@ -420,10 +455,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
420
455
 
421
456
  ```python
422
457
  class ConfigurationSource(ABC):
423
- def __init__(self, manager: ConfigurationManager):
424
- manager._register(self)
458
+ def __init__(self):
425
459
  pass
426
460
 
461
+ ...
462
+
427
463
  @abstractmethod
428
464
  def load(self) -> dict:
429
465
  pass
@@ -435,14 +471,12 @@ As a default environment variables are already supported.
435
471
 
436
472
  Other sources can be added dynamically by just registering them.
437
473
 
474
+ **Example**:
438
475
  ```python
439
476
  @injectable()
440
477
  class SampleConfigurationSource(ConfigurationSource):
441
- # constructor
442
-
443
- def __init__(self, manager: ConfigurationManager):
444
- super().__init__(manager)
445
-
478
+ def __init__(self):
479
+ super().__init__()
446
480
 
447
481
  def load(self) -> dict:
448
482
  return {
@@ -455,7 +489,61 @@ class SampleConfigurationSource(ConfigurationSource):
455
489
  }
456
490
  ```
457
491
 
492
+ # Reflection
493
+
494
+ As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
495
+
496
+ After beeing instatiated with
497
+
498
+ ```python
499
+ TypeDescriptor.for_type(<type>)
500
+ ```
501
+
502
+ it offers the methods
503
+ - `get_methods(local=False)`
504
+ return a list of either local or overall methods
505
+ - `get_method(name: str, local=False)`
506
+ return a single either local or overall method
507
+ - `has_decorator(decorator: Callable) -> bool`
508
+ return `True`, if the class is decorated with the specified decrator
509
+ - `get_decorator(decorator) -> Optional[DecoratorDescriptor]`
510
+ return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
511
+
512
+ The returned method descriptors offer:
513
+ - `param_types`
514
+ list of arg types
515
+ - `return_type`
516
+ the retur type
517
+ - `has_decorator(decorator: Callable) -> bool`
518
+ return `True`, if the method is decorated with the specified decrator
519
+ - `get_decorator(decorator: Callable) -> Optional[DecoratorDescriptor]`
520
+ return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
521
+
522
+ The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
523
+
524
+ Whenver you define a custom decorator, you will need to register it accordingly.
525
+
526
+ **Example**:
527
+ ```python
528
+ def transactional():
529
+ def decorator(func):
530
+ Decorators.add(func, transactional)
531
+ return func
532
+
533
+ return decorator
534
+ ```
535
+
536
+
537
+ # Version History
538
+
539
+ **1.0.1**
540
+
541
+ - some internal refactorings
542
+
543
+ **1.1.0**
458
544
 
545
+ - added `@on_running()` callback
546
+ - added `thread` scope
459
547
 
460
548
 
461
549
 
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aspyx"
7
- version = "1.0.0"
7
+ version = "1.1.0"
8
8
  description = "A DI and AOP library for Python"
9
9
  authors = [{ name = "Andreas Ernst", email = "andreas.ernst7@gmail.com" }]
10
10
  readme = "README.md"
11
11
  license = { file = "LICENSE" }
12
- requires-python = ">=3.8"
12
+ requires-python = ">=3.9"
13
13
  dependencies = []
14
14
 
15
15
  [tool.setuptools]