aspyx 1.0.0__tar.gz → 1.0.1__tar.gz

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

Potentially problematic release.


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

Files changed (24) hide show
  1. {aspyx-1.0.0/src/aspyx.egg-info → aspyx-1.0.1}/PKG-INFO +94 -33
  2. {aspyx-1.0.0 → aspyx-1.0.1}/README.md +92 -31
  3. {aspyx-1.0.0 → aspyx-1.0.1}/pyproject.toml +2 -2
  4. {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/di/__init__.py +4 -1
  5. {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/di/aop/__init__.py +4 -1
  6. {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/di/aop/aop.py +56 -76
  7. {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/di/configuration/__init__.py +4 -1
  8. {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/di/configuration/configuration.py +20 -15
  9. {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/di/di.py +98 -113
  10. {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/reflection/__init__.py +4 -1
  11. {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/reflection/proxy.py +10 -7
  12. {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx/reflection/reflection.py +29 -21
  13. {aspyx-1.0.0 → aspyx-1.0.1/src/aspyx.egg-info}/PKG-INFO +94 -33
  14. {aspyx-1.0.0 → aspyx-1.0.1}/tests/test_configuration.py +5 -7
  15. {aspyx-1.0.0 → aspyx-1.0.1}/tests/test_reflection.py +1 -1
  16. {aspyx-1.0.0 → aspyx-1.0.1}/LICENSE +0 -0
  17. {aspyx-1.0.0 → aspyx-1.0.1}/setup.cfg +0 -0
  18. {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx.egg-info/SOURCES.txt +0 -0
  19. {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx.egg-info/dependency_links.txt +0 -0
  20. {aspyx-1.0.0 → aspyx-1.0.1}/src/aspyx.egg-info/top_level.txt +0 -0
  21. {aspyx-1.0.0 → aspyx-1.0.1}/tests/test_aop.py +0 -0
  22. {aspyx-1.0.0 → aspyx-1.0.1}/tests/test_di.py +0 -0
  23. {aspyx-1.0.0 → aspyx-1.0.1}/tests/test_di_cycle.py +0 -0
  24. {aspyx-1.0.0 → aspyx-1.0.1}/tests/test_proxy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspyx
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: A DI and AOP library for Python
5
5
  Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
6
6
  License: MIT License
@@ -25,7 +25,7 @@ License: MIT License
25
25
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
26
  SOFTWARE.
27
27
 
28
- Requires-Python: >=3.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,13 @@ 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
+ ![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)
@@ -53,6 +55,7 @@ Dynamic: license-file
53
55
  - [Custom scopes](#custom-scopes)
54
56
  - [AOP](#aop)
55
57
  - [Configuration](#configuration)
58
+ - [Reflection](#reflection)
56
59
 
57
60
  # Introduction
58
61
 
@@ -71,12 +74,13 @@ The following features are supported
71
74
  - container instances that relate to environment classes and manage the lifecylce of related objects
72
75
  - hierarchical environments
73
76
 
77
+ The library is thread-safe!
78
+
74
79
  Let's look at a simple example
75
80
 
76
81
  ```python
77
82
  from aspyx.di import injectable, on_init, on_destroy, environment, Environment
78
83
 
79
-
80
84
  @injectable()
81
85
  class Foo:
82
86
  def __init__(self):
@@ -85,13 +89,12 @@ class Foo:
85
89
  def hello(msg: str):
86
90
  print(f"hello {msg}")
87
91
 
88
-
89
92
  @injectable() # eager and singleton by default
90
93
  class Bar:
91
94
  def __init__(self, foo: Foo): # will inject the Foo dependency
92
95
  self.foo = foo
93
96
 
94
- @on_init() # a lifecycle callback called after the constructor
97
+ @on_init() # a lifecycle callback called after the constructor and all possible injections
95
98
  def init(self):
96
99
  ...
97
100
 
@@ -101,41 +104,40 @@ class Bar:
101
104
 
102
105
  @environment()
103
106
  class SampleEnvironment:
104
- # constructor
105
-
106
107
  def __init__(self):
107
108
  pass
108
109
 
109
-
110
110
  # go, forrest
111
111
 
112
- environment = SampleEnvironment(Configuration)
112
+ environment = Environment(SampleEnvironment)
113
113
 
114
114
  bar = env.get(Bar)
115
- bar.foo.hello("Andi")
115
+
116
+ bar.foo.hello("world")
116
117
  ```
117
118
 
118
- 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 :-)
119
120
 
120
121
  Let's add some aspects...
121
122
 
122
123
  ```python
124
+
123
125
  @advice
124
126
  class SampleAdvice:
125
- def __init__(self):
127
+ def __init__(self): # could inject additional stuff
126
128
  pass
127
129
 
128
130
  @before(methods().named("hello").of_type(Foo))
129
- def callBefore(self, invocation: Invocation):
131
+ def call_before(self, invocation: Invocation):
130
132
  print("before Foo.hello(...)")
131
133
 
132
134
  @error(methods().named("hello").of_type(Foo))
133
- def callError(self, invocation: Invocation):
135
+ def call_error(self, invocation: Invocation):
134
136
  print("error Foo.hello(...)")
135
137
  print(invocation.exception)
136
138
 
137
139
  @around(methods().named("hello"))
138
- def callAround(self, invocation: Invocation):
140
+ def call_around(self, invocation: Invocation):
139
141
  print("around Foo.hello()")
140
142
 
141
143
  return invocation.proceed()
@@ -150,6 +152,14 @@ The invocation parameter stores the complete context of the current execution, w
150
152
 
151
153
  Let's look at the details
152
154
 
155
+ # Installation
156
+
157
+ `pip install aspyx`
158
+
159
+ The library is tested with Python version > 3.8
160
+
161
+ Ready to go...
162
+
153
163
  # Registration
154
164
 
155
165
  Different mechanisms are available that make classes eligible for injection
@@ -171,12 +181,14 @@ All referenced types will be injected by the environemnt.
171
181
 
172
182
  Only eligible types are allowed, of course!
173
183
 
184
+ The decorator accepts the keyword arguments
185
+ - `eager : boolean`
186
+ if `True`, the container will create the instances automatically while booting the environment. This is the default.
187
+ - `scope: str`
188
+ the name of a scope which will determine how often instances will be created.
189
+ `singleton` will create it only once - per environment -, while `request` will recreate it on every injection request. The default is `singleton`
174
190
 
175
- 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
178
-
179
- Other scopes can be defined. Please check the corresponding chapter.
191
+ Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
180
192
 
181
193
  ## Class Factory
182
194
 
@@ -386,17 +398,17 @@ class SampleAdvice:
386
398
  pass
387
399
 
388
400
  @before(methods().named("hello").of_type(Foo))
389
- def callBefore(self, invocation: Invocation):
401
+ def call_before(self, invocation: Invocation):
390
402
  # arguments: invocation.args
391
403
  print("before Foo.hello(...)")
392
404
 
393
405
  @error(methods().named("hello").of_type(Foo))
394
- def callError(self, invocation: Invocation):
406
+ def call_error(self, invocation: Invocation):
395
407
  print("error Foo.hello(...)")
396
408
  print(invocation.exception)
397
409
 
398
410
  @around(methods().named("hello"))
399
- def callAround(self, invocation: Invocation):
411
+ def call_around(self, invocation: Invocation):
400
412
  print("around Foo.hello()")
401
413
 
402
414
  return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
@@ -417,7 +429,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
417
429
  It is essential for `around` methods to call `proceed()` on the invocation, which will call the next around method in the chain and finally the original method.
418
430
  If the `proceed` is called with parameters, they will replace the original parameters!
419
431
 
420
- The 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.
421
433
  A fluent interface is used describe the mapping.
422
434
  The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
423
435
 
@@ -431,7 +443,20 @@ Both add the fluent methods:
431
443
  - `decorated_with(type: Type)`
432
444
  defines decorators on methods or classes
433
445
 
434
- The fluent methods `named`, `matches` and `of_type` can be called multiple 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
+ ```
435
460
 
436
461
  # Configuration
437
462
 
@@ -452,10 +477,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
452
477
 
453
478
  ```python
454
479
  class ConfigurationSource(ABC):
455
- def __init__(self, manager: ConfigurationManager):
456
- manager._register(self)
480
+ def __init__(self):
457
481
  pass
458
482
 
483
+ ...
484
+
459
485
  @abstractmethod
460
486
  def load(self) -> dict:
461
487
  pass
@@ -467,14 +493,12 @@ As a default environment variables are already supported.
467
493
 
468
494
  Other sources can be added dynamically by just registering them.
469
495
 
496
+ **Example**:
470
497
  ```python
471
498
  @injectable()
472
499
  class SampleConfigurationSource(ConfigurationSource):
473
- # constructor
474
-
475
- def __init__(self, manager: ConfigurationManager):
476
- super().__init__(manager)
477
-
500
+ def __init__(self):
501
+ super().__init__()
478
502
 
479
503
  def load(self) -> dict:
480
504
  return {
@@ -487,6 +511,43 @@ class SampleConfigurationSource(ConfigurationSource):
487
511
  }
488
512
  ```
489
513
 
514
+ # Reflection
515
+
516
+ As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
517
+
518
+ After beeing instatiated with
519
+
520
+ ```python
521
+ TypeDescriptor.for_type(<type>)
522
+ ```
523
+
524
+ it offers the methods
525
+ - `get_methods(local=False)`
526
+ - `get_method(name: str, local=False)`
527
+ - `has_decorator(decorator) -> bool`
528
+ - `get_decorator(decorator)`
529
+
530
+ The returned method descriptors offer:
531
+ - `param_types`
532
+ - `return_type`
533
+ - `has_decorator(decorator)`
534
+ - `get_decorator(decorator)`
535
+
536
+ The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
537
+
538
+ Whenver you define a custom decorator, you will need to register it accordingly.
539
+
540
+ **Example**:
541
+ ```python
542
+ def transactional():
543
+ def decorator(func):
544
+ Decorators.add(func, transactional)
545
+ return func
546
+
547
+ return decorator
548
+ ```
549
+
550
+
490
551
 
491
552
 
492
553
 
@@ -2,11 +2,13 @@
2
2
 
3
3
  ![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
+ ![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)
@@ -21,6 +23,7 @@
21
23
  - [Custom scopes](#custom-scopes)
22
24
  - [AOP](#aop)
23
25
  - [Configuration](#configuration)
26
+ - [Reflection](#reflection)
24
27
 
25
28
  # Introduction
26
29
 
@@ -39,12 +42,13 @@ The following features are supported
39
42
  - container instances that relate to environment classes and manage the lifecylce of related objects
40
43
  - hierarchical environments
41
44
 
45
+ The library is thread-safe!
46
+
42
47
  Let's look at a simple example
43
48
 
44
49
  ```python
45
50
  from aspyx.di import injectable, on_init, on_destroy, environment, Environment
46
51
 
47
-
48
52
  @injectable()
49
53
  class Foo:
50
54
  def __init__(self):
@@ -53,13 +57,12 @@ class Foo:
53
57
  def hello(msg: str):
54
58
  print(f"hello {msg}")
55
59
 
56
-
57
60
  @injectable() # eager and singleton by default
58
61
  class Bar:
59
62
  def __init__(self, foo: Foo): # will inject the Foo dependency
60
63
  self.foo = foo
61
64
 
62
- @on_init() # a lifecycle callback called after the constructor
65
+ @on_init() # a lifecycle callback called after the constructor and all possible injections
63
66
  def init(self):
64
67
  ...
65
68
 
@@ -69,41 +72,40 @@ class Bar:
69
72
 
70
73
  @environment()
71
74
  class SampleEnvironment:
72
- # constructor
73
-
74
75
  def __init__(self):
75
76
  pass
76
77
 
77
-
78
78
  # go, forrest
79
79
 
80
- environment = SampleEnvironment(Configuration)
80
+ environment = Environment(SampleEnvironment)
81
81
 
82
82
  bar = env.get(Bar)
83
- bar.foo.hello("Andi")
83
+
84
+ bar.foo.hello("world")
84
85
  ```
85
86
 
86
- 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 :-)
87
88
 
88
89
  Let's add some aspects...
89
90
 
90
91
  ```python
92
+
91
93
  @advice
92
94
  class SampleAdvice:
93
- def __init__(self):
95
+ def __init__(self): # could inject additional stuff
94
96
  pass
95
97
 
96
98
  @before(methods().named("hello").of_type(Foo))
97
- def callBefore(self, invocation: Invocation):
99
+ def call_before(self, invocation: Invocation):
98
100
  print("before Foo.hello(...)")
99
101
 
100
102
  @error(methods().named("hello").of_type(Foo))
101
- def callError(self, invocation: Invocation):
103
+ def call_error(self, invocation: Invocation):
102
104
  print("error Foo.hello(...)")
103
105
  print(invocation.exception)
104
106
 
105
107
  @around(methods().named("hello"))
106
- def callAround(self, invocation: Invocation):
108
+ def call_around(self, invocation: Invocation):
107
109
  print("around Foo.hello()")
108
110
 
109
111
  return invocation.proceed()
@@ -118,6 +120,14 @@ The invocation parameter stores the complete context of the current execution, w
118
120
 
119
121
  Let's look at the details
120
122
 
123
+ # Installation
124
+
125
+ `pip install aspyx`
126
+
127
+ The library is tested with Python version > 3.8
128
+
129
+ Ready to go...
130
+
121
131
  # Registration
122
132
 
123
133
  Different mechanisms are available that make classes eligible for injection
@@ -139,12 +149,14 @@ All referenced types will be injected by the environemnt.
139
149
 
140
150
  Only eligible types are allowed, of course!
141
151
 
152
+ The decorator accepts the keyword arguments
153
+ - `eager : boolean`
154
+ if `True`, the container will create the instances automatically while booting the environment. This is the default.
155
+ - `scope: str`
156
+ the name of a scope which will determine how often instances will be created.
157
+ `singleton` will create it only once - per environment -, while `request` will recreate it on every injection request. The default is `singleton`
142
158
 
143
- 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
146
-
147
- Other scopes can be defined. Please check the corresponding chapter.
159
+ Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
148
160
 
149
161
  ## Class Factory
150
162
 
@@ -354,17 +366,17 @@ class SampleAdvice:
354
366
  pass
355
367
 
356
368
  @before(methods().named("hello").of_type(Foo))
357
- def callBefore(self, invocation: Invocation):
369
+ def call_before(self, invocation: Invocation):
358
370
  # arguments: invocation.args
359
371
  print("before Foo.hello(...)")
360
372
 
361
373
  @error(methods().named("hello").of_type(Foo))
362
- def callError(self, invocation: Invocation):
374
+ def call_error(self, invocation: Invocation):
363
375
  print("error Foo.hello(...)")
364
376
  print(invocation.exception)
365
377
 
366
378
  @around(methods().named("hello"))
367
- def callAround(self, invocation: Invocation):
379
+ def call_around(self, invocation: Invocation):
368
380
  print("around Foo.hello()")
369
381
 
370
382
  return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
@@ -385,7 +397,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
385
397
  It is essential for `around` methods to call `proceed()` on the invocation, which will call the next around method in the chain and finally the original method.
386
398
  If the `proceed` is called with parameters, they will replace the original parameters!
387
399
 
388
- The 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.
389
401
  A fluent interface is used describe the mapping.
390
402
  The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
391
403
 
@@ -399,7 +411,20 @@ Both add the fluent methods:
399
411
  - `decorated_with(type: Type)`
400
412
  defines decorators on methods or classes
401
413
 
402
- The fluent methods `named`, `matches` and `of_type` can be called multiple 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
+ ```
403
428
 
404
429
  # Configuration
405
430
 
@@ -420,10 +445,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
420
445
 
421
446
  ```python
422
447
  class ConfigurationSource(ABC):
423
- def __init__(self, manager: ConfigurationManager):
424
- manager._register(self)
448
+ def __init__(self):
425
449
  pass
426
450
 
451
+ ...
452
+
427
453
  @abstractmethod
428
454
  def load(self) -> dict:
429
455
  pass
@@ -435,14 +461,12 @@ As a default environment variables are already supported.
435
461
 
436
462
  Other sources can be added dynamically by just registering them.
437
463
 
464
+ **Example**:
438
465
  ```python
439
466
  @injectable()
440
467
  class SampleConfigurationSource(ConfigurationSource):
441
- # constructor
442
-
443
- def __init__(self, manager: ConfigurationManager):
444
- super().__init__(manager)
445
-
468
+ def __init__(self):
469
+ super().__init__()
446
470
 
447
471
  def load(self) -> dict:
448
472
  return {
@@ -455,6 +479,43 @@ class SampleConfigurationSource(ConfigurationSource):
455
479
  }
456
480
  ```
457
481
 
482
+ # Reflection
483
+
484
+ As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
485
+
486
+ After beeing instatiated with
487
+
488
+ ```python
489
+ TypeDescriptor.for_type(<type>)
490
+ ```
491
+
492
+ it offers the methods
493
+ - `get_methods(local=False)`
494
+ - `get_method(name: str, local=False)`
495
+ - `has_decorator(decorator) -> bool`
496
+ - `get_decorator(decorator)`
497
+
498
+ The returned method descriptors offer:
499
+ - `param_types`
500
+ - `return_type`
501
+ - `has_decorator(decorator)`
502
+ - `get_decorator(decorator)`
503
+
504
+ The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
505
+
506
+ Whenver you define a custom decorator, you will need to register it accordingly.
507
+
508
+ **Example**:
509
+ ```python
510
+ def transactional():
511
+ def decorator(func):
512
+ Decorators.add(func, transactional)
513
+ return func
514
+
515
+ return decorator
516
+ ```
517
+
518
+
458
519
 
459
520
 
460
521
 
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aspyx"
7
- version = "1.0.0"
7
+ version = "1.0.1"
8
8
  description = "A DI and AOP library for Python"
9
9
  authors = [{ name = "Andreas Ernst", email = "andreas.ernst7@gmail.com" }]
10
10
  readme = "README.md"
11
11
  license = { file = "LICENSE" }
12
- requires-python = ">=3.8"
12
+ requires-python = ">=3.9"
13
13
  dependencies = []
14
14
 
15
15
  [tool.setuptools]
@@ -1,3 +1,6 @@
1
+ """
2
+ This module provides dependency injection and aop capabilities for Python applications.
3
+ """
1
4
  from .di import InjectorException, CallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, environment, inject, create, on_init, on_destroy, inject_environment, Factory, PostProcessor
2
5
 
3
6
  # import something from the subpackages, so that teh decorators are executed
@@ -26,4 +29,4 @@ __all__ = [
26
29
  "LifecycleCallable",
27
30
  "InjectorException",
28
31
  "Lifecycle"
29
- ]
32
+ ]
@@ -1,3 +1,6 @@
1
+ """
2
+ AOP module
3
+ """
1
4
  from .aop import before, after, classes, around, error, advice, methods, Invocation
2
5
  __all__ = [
3
6
  "before",
@@ -8,4 +11,4 @@ __all__ = [
8
11
  "classes",
9
12
  "methods",
10
13
  "Invocation",
11
- ]
14
+ ]