aspyx 1.3.0__py3-none-any.whl → 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aspyx might be problematic. Click here for more details.
- aspyx/__init__.py +0 -0
- aspyx/di/aop/aop.py +112 -87
- aspyx/di/di.py +125 -24
- aspyx/di/threading/synchronized.py +8 -5
- aspyx/exception/__init__.py +10 -0
- aspyx/exception/exception_manager.py +168 -0
- aspyx/reflection/reflection.py +37 -1
- aspyx/threading/__init__.py +10 -0
- aspyx/threading/thread_local.py +47 -0
- aspyx/{di/util → util}/stringbuilder.py +11 -0
- {aspyx-1.3.0.dist-info → aspyx-1.4.0.dist-info}/METADATA +180 -58
- aspyx-1.4.0.dist-info/RECORD +25 -0
- aspyx-1.3.0.dist-info/RECORD +0 -20
- /aspyx/{di/util → util}/__init__.py +0 -0
- {aspyx-1.3.0.dist-info → aspyx-1.4.0.dist-info}/WHEEL +0 -0
- {aspyx-1.3.0.dist-info → aspyx-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {aspyx-1.3.0.dist-info → aspyx-1.4.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.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
|
|
@@ -28,6 +28,8 @@ License: MIT License
|
|
|
28
28
|
Requires-Python: >=3.9
|
|
29
29
|
Description-Content-Type: text/markdown
|
|
30
30
|
License-File: LICENSE
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: mkdocstrings-python; extra == "dev"
|
|
31
33
|
Dynamic: license-file
|
|
32
34
|
|
|
33
35
|
# aspyx
|
|
@@ -38,8 +40,9 @@ Dynamic: license-file
|
|
|
38
40
|

|
|
39
41
|

|
|
40
42
|
[](https://pypi.org/project/aspyx/)
|
|
43
|
+
[](https://coolsamson7.github.io/aspyx/)
|
|
41
44
|
|
|
42
|
-
## Table of Contents
|
|
45
|
+
## Table of Contents
|
|
43
46
|
|
|
44
47
|
- [Motivation](#motivation)
|
|
45
48
|
- [Introduction](#introduction)
|
|
@@ -58,43 +61,48 @@ Dynamic: license-file
|
|
|
58
61
|
- [Post Processors](#post-processors)
|
|
59
62
|
- [Custom scopes](#custom-scopes)
|
|
60
63
|
- [AOP](#aop)
|
|
64
|
+
- [Threading](#threading)
|
|
61
65
|
- [Configuration](#configuration)
|
|
62
66
|
- [Reflection](#reflection)
|
|
67
|
+
- [Exceptions](#exceptions)
|
|
63
68
|
- [Version History](#version-history)
|
|
64
69
|
|
|
65
70
|
# Motivation
|
|
66
71
|
|
|
67
|
-
While working on
|
|
68
|
-
Looking at the existing solutions - there are quite a number of - i was not quite happy. Either the solutions
|
|
69
|
-
lacked functionality - starting with that usually aop and di are separate libraries -, that i am accustomed to from other languages, or the API was in my mind too clumsy and "technical".
|
|
72
|
+
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
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
- bring both di and AOP features together in a lightweight library,
|
|
75
|
+
- be as minimal invasive as possible,
|
|
76
|
+
- 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
|
|
78
|
+
|
|
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.
|
|
72
80
|
|
|
73
81
|
# Introduction
|
|
74
82
|
|
|
75
|
-
Aspyx is a
|
|
83
|
+
Aspyx is a lightweight Python library that provides both Dependency Injection (DI) and Aspect-Oriented Programming (AOP) support.
|
|
76
84
|
|
|
77
|
-
The following
|
|
85
|
+
The following DI features are supported
|
|
78
86
|
- constructor and setter injection
|
|
87
|
+
- injection of configuration variables
|
|
79
88
|
- possibility to define custom injections
|
|
80
89
|
- post processors
|
|
81
90
|
- support for factory classes and methods
|
|
82
|
-
- support for eager construction
|
|
91
|
+
- support for eager and lazy construction
|
|
83
92
|
- support for scopes singleton, request and thread
|
|
84
|
-
-
|
|
85
|
-
- conditional registration of classes and factories
|
|
86
|
-
- lifecycle events methods `on_init`, `on_destroy`
|
|
93
|
+
- possibility to add custom scopes
|
|
94
|
+
- conditional registration of classes and factories ( aka profiles in spring )
|
|
95
|
+
- lifecycle events methods `on_init`, `on_destroy`, `on_running`
|
|
87
96
|
- bundling of injectable objects according to their module location including recursive imports and inheritance
|
|
88
|
-
-
|
|
89
|
-
- filtering of classes by associating "features" sets to environment ( similar to spring profiles )
|
|
97
|
+
- instantiation of - possibly multiple - container instances - so called environments - that manage the lifecycle of related objects
|
|
90
98
|
- hierarchical environments
|
|
91
99
|
|
|
92
|
-
With respect to
|
|
100
|
+
With respect to AOP:
|
|
93
101
|
- support for before, around, after and error aspects
|
|
102
|
+
- simple fluent interface to specify which methods are targeted by an aspect
|
|
94
103
|
- sync and async method support
|
|
95
|
-
- `synchronized` decorator that adds locking to methods
|
|
96
104
|
|
|
97
|
-
The library is thread-safe!
|
|
105
|
+
The library is thread-safe and heavily performance optimized as most of the runtime information is precomputed and cached!
|
|
98
106
|
|
|
99
107
|
Let's look at a simple example
|
|
100
108
|
|
|
@@ -106,7 +114,7 @@ class Foo:
|
|
|
106
114
|
def __init__(self):
|
|
107
115
|
pass
|
|
108
116
|
|
|
109
|
-
def hello(msg: str):
|
|
117
|
+
def hello(self, msg: str):
|
|
110
118
|
print(f"hello {msg}")
|
|
111
119
|
|
|
112
120
|
@injectable() # eager and singleton by default
|
|
@@ -127,16 +135,18 @@ class SampleEnvironment:
|
|
|
127
135
|
def __init__(self):
|
|
128
136
|
pass
|
|
129
137
|
|
|
130
|
-
#
|
|
138
|
+
# create environment
|
|
131
139
|
|
|
132
140
|
environment = Environment(SampleEnvironment)
|
|
133
141
|
|
|
142
|
+
# fetch an instance
|
|
143
|
+
|
|
134
144
|
bar = env.get(Bar)
|
|
135
145
|
|
|
136
146
|
bar.foo.hello("world")
|
|
137
147
|
```
|
|
138
148
|
|
|
139
|
-
The concepts should be pretty familiar as well as the names
|
|
149
|
+
The concepts should be pretty familiar as well as the names as they are inspired by both Spring and Angular.
|
|
140
150
|
|
|
141
151
|
Let's add some aspects...
|
|
142
152
|
|
|
@@ -149,17 +159,15 @@ class SampleAdvice:
|
|
|
149
159
|
|
|
150
160
|
@before(methods().named("hello").of_type(Foo))
|
|
151
161
|
def call_before(self, invocation: Invocation):
|
|
152
|
-
|
|
162
|
+
...
|
|
153
163
|
|
|
154
164
|
@error(methods().named("hello").of_type(Foo))
|
|
155
165
|
def call_error(self, invocation: Invocation):
|
|
156
|
-
|
|
157
|
-
print(invocation.exception)
|
|
166
|
+
... # exception accessible in invocation.exception
|
|
158
167
|
|
|
159
168
|
@around(methods().named("hello"))
|
|
160
169
|
def call_around(self, invocation: Invocation):
|
|
161
|
-
|
|
162
|
-
|
|
170
|
+
...
|
|
163
171
|
return invocation.proceed()
|
|
164
172
|
```
|
|
165
173
|
|
|
@@ -196,8 +204,8 @@ class Foo:
|
|
|
196
204
|
def __init__(self):
|
|
197
205
|
pass
|
|
198
206
|
```
|
|
199
|
-
Please make sure, that the class defines a local constructor, as this is
|
|
200
|
-
All referenced types will be injected by the
|
|
207
|
+
⚠️ **Attention:** Please make sure, that the class defines a local constructor, as this is _required_ to determine injected instances.
|
|
208
|
+
All referenced types will be injected by the environment.
|
|
201
209
|
|
|
202
210
|
Only eligible types are allowed, of course!
|
|
203
211
|
|
|
@@ -285,6 +293,8 @@ environment = Environment(SampleEnvironment)
|
|
|
285
293
|
```
|
|
286
294
|
|
|
287
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
|
+
|
|
288
298
|
|
|
289
299
|
By adding the parameter `features: list[str]`, it is possible to filter injectables by evaluating the corresponding `@conditional` decorators.
|
|
290
300
|
|
|
@@ -298,7 +308,7 @@ class DevOnly:
|
|
|
298
308
|
pass
|
|
299
309
|
|
|
300
310
|
@environment()
|
|
301
|
-
class SampleEnvironmen()
|
|
311
|
+
class SampleEnvironmen():
|
|
302
312
|
def __init__(self):
|
|
303
313
|
pass
|
|
304
314
|
|
|
@@ -311,7 +321,7 @@ By adding an `imports: list[Type]` parameter, specifying other environment types
|
|
|
311
321
|
**Example**:
|
|
312
322
|
```python
|
|
313
323
|
@environment()
|
|
314
|
-
class SampleEnvironmen(imports=[OtherEnvironment])
|
|
324
|
+
class SampleEnvironmen(imports=[OtherEnvironment]):
|
|
315
325
|
def __init__(self):
|
|
316
326
|
pass
|
|
317
327
|
```
|
|
@@ -344,7 +354,7 @@ The container knows about class hierarchies and is able to `get` base classes, a
|
|
|
344
354
|
|
|
345
355
|
In case of ambiguities, it will throw an exception.
|
|
346
356
|
|
|
347
|
-
|
|
357
|
+
Note 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. )
|
|
348
358
|
|
|
349
359
|
# Instantiation logic
|
|
350
360
|
|
|
@@ -357,7 +367,7 @@ Constructing a new instance involves a number of steps executed in this order
|
|
|
357
367
|
different decorators can mark methods that should be called during the lifecycle ( here the construction ) of an instance.
|
|
358
368
|
These are various injection possibilities as well as an optional final `on_init` call
|
|
359
369
|
- PostProcessors
|
|
360
|
-
Any custom post processors, that can add
|
|
370
|
+
Any custom post processors, that can add side effects or modify the instances
|
|
361
371
|
|
|
362
372
|
## Injection methods
|
|
363
373
|
|
|
@@ -392,7 +402,7 @@ It is possible to mark specific lifecyle methods.
|
|
|
392
402
|
- `@on_init()`
|
|
393
403
|
called after the constructor and all other injections.
|
|
394
404
|
- `@on_running()`
|
|
395
|
-
called an environment has initialized all eager objects.
|
|
405
|
+
called after an environment has initialized completely ( e.g. created all eager objects ).
|
|
396
406
|
- `@on_destroy()`
|
|
397
407
|
called during shutdown of the environment
|
|
398
408
|
|
|
@@ -410,7 +420,7 @@ class SamplePostProcessor(PostProcessor):
|
|
|
410
420
|
|
|
411
421
|
Any implementing class of `PostProcessor` that is eligible for injection will be called by passing the new instance.
|
|
412
422
|
|
|
413
|
-
|
|
423
|
+
Note that a post processor will only handle instances _after_ its _own_ registration.
|
|
414
424
|
|
|
415
425
|
As injectables within a single file will be handled in the order as they are declared, a post processor will only take effect for all classes after its declaration!
|
|
416
426
|
|
|
@@ -427,7 +437,7 @@ def get(self, provider: AbstractInstanceProvider, environment: Environment, argP
|
|
|
427
437
|
Arguments are:
|
|
428
438
|
- `provider` the actual provider that will create an instance
|
|
429
439
|
- `environment`the requesting environment
|
|
430
|
-
- `
|
|
440
|
+
- `argProvider` a function that can be called to compute the required arguments recursively
|
|
431
441
|
|
|
432
442
|
**Example**: The simplified code of the singleton provider ( disregarding locking logic )
|
|
433
443
|
|
|
@@ -452,52 +462,62 @@ class SingletonScope(Scope):
|
|
|
452
462
|
|
|
453
463
|
# AOP
|
|
454
464
|
|
|
455
|
-
It is possible to define different
|
|
465
|
+
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.
|
|
456
466
|
|
|
457
467
|
Advice classes need to be part of classes that add a `@advice()` decorator and can define methods that add aspects.
|
|
458
468
|
|
|
459
469
|
```python
|
|
460
|
-
@advice
|
|
470
|
+
@advice
|
|
461
471
|
class SampleAdvice:
|
|
462
472
|
def __init__(self): # could inject dependencies
|
|
463
473
|
pass
|
|
464
474
|
|
|
465
475
|
@before(methods().named("hello").of_type(Foo))
|
|
466
476
|
def call_before(self, invocation: Invocation):
|
|
467
|
-
# arguments: invocation.args
|
|
468
|
-
|
|
477
|
+
# arguments: invocation.args and invocation.kwargs
|
|
478
|
+
...
|
|
479
|
+
|
|
480
|
+
@after(methods().named("hello").of_type(Foo))
|
|
481
|
+
def call_after(self, invocation: Invocation):
|
|
482
|
+
# arguments: invocation.args and invocation.kwargs
|
|
483
|
+
...
|
|
469
484
|
|
|
470
485
|
@error(methods().named("hello").of_type(Foo))
|
|
471
486
|
def call_error(self, invocation: Invocation):
|
|
472
|
-
|
|
473
|
-
|
|
487
|
+
# error: invocation.exception
|
|
488
|
+
...
|
|
474
489
|
|
|
475
490
|
@around(methods().named("hello"))
|
|
476
491
|
def call_around(self, invocation: Invocation):
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
492
|
+
try:
|
|
493
|
+
...
|
|
494
|
+
return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
|
|
495
|
+
finally:
|
|
496
|
+
...
|
|
480
497
|
```
|
|
481
498
|
|
|
482
499
|
Different aspects - with the appropriate decorator - are possible:
|
|
483
500
|
- `before`
|
|
484
501
|
methods that will be executed _prior_ to the original method
|
|
485
502
|
- `around`
|
|
486
|
-
methods that will be executed _around_ to the original method
|
|
503
|
+
methods that will be executed _around_ to the original method allowing you to add side effects or even modify parameters.
|
|
487
504
|
- `after`
|
|
488
505
|
methods that will be executed _after_ to the original method
|
|
489
506
|
- `error`
|
|
490
507
|
methods that will be executed in case of a caught exception
|
|
491
508
|
|
|
509
|
+
The different aspects can be supplemented with an `@order(<prio>)` decorator that controls the execution order based on the passed number. Smaller values get executed first.
|
|
510
|
+
|
|
492
511
|
All methods are expected to have single `Invocation` parameter, that stores
|
|
493
512
|
|
|
494
513
|
- `func` the target function
|
|
495
|
-
- `args` the
|
|
514
|
+
- `args` the supplied args ( including the `self` instance as the first element)
|
|
496
515
|
- `kwargs` the keywords args
|
|
497
516
|
- `result` the result ( initially `None`)
|
|
498
|
-
- `exception` a possible caught
|
|
517
|
+
- `exception` a possible caught exception ( initially `None`)
|
|
518
|
+
|
|
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.
|
|
499
520
|
|
|
500
|
-
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.
|
|
501
521
|
If the `proceed` is called with parameters, they will replace the original parameters!
|
|
502
522
|
|
|
503
523
|
**Example**: Parameter modifications
|
|
@@ -505,9 +525,7 @@ If the `proceed` is called with parameters, they will replace the original param
|
|
|
505
525
|
```python
|
|
506
526
|
@around(methods().named("say"))
|
|
507
527
|
def call_around(self, invocation: Invocation):
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
return invocation.proceed(*args)
|
|
528
|
+
return invocation.proceed(invocation.args[0], invocation.args[1] + "!") # 0 is self!
|
|
511
529
|
```
|
|
512
530
|
|
|
513
531
|
The argument list to the corresponding decorators control which methods are targeted by the advice.
|
|
@@ -532,7 +550,7 @@ The fluent methods `named`, `matches` and `of_type` can be called multiple times
|
|
|
532
550
|
**Example**: react on both `transactional` decorators on methods or classes
|
|
533
551
|
|
|
534
552
|
```python
|
|
535
|
-
@
|
|
553
|
+
@advice
|
|
536
554
|
class TransactionAdvice:
|
|
537
555
|
def __init__(self):
|
|
538
556
|
pass
|
|
@@ -542,9 +560,23 @@ class TransactionAdvice:
|
|
|
542
560
|
...
|
|
543
561
|
```
|
|
544
562
|
|
|
545
|
-
With respect to async methods, you need to make sure, to replace a `
|
|
563
|
+
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
|
+
|
|
565
|
+
# Threading
|
|
546
566
|
|
|
547
|
-
A handy decorator `@synchronized` is implemented that automatically synchronizes methods with a `RLock` associated with the instance.
|
|
567
|
+
A handy decorator `@synchronized` in combination with the respective advice is implemented that automatically synchronizes methods with a `RLock` associated with the instance.
|
|
568
|
+
|
|
569
|
+
**Example**:
|
|
570
|
+
```python
|
|
571
|
+
@injectable()
|
|
572
|
+
class Foo:
|
|
573
|
+
def __init__(self):
|
|
574
|
+
pass
|
|
575
|
+
|
|
576
|
+
@synchronized()
|
|
577
|
+
def execute_synchronized(self):
|
|
578
|
+
...
|
|
579
|
+
```
|
|
548
580
|
|
|
549
581
|
# Configuration
|
|
550
582
|
|
|
@@ -561,9 +593,9 @@ class Foo:
|
|
|
561
593
|
...
|
|
562
594
|
```
|
|
563
595
|
|
|
564
|
-
If required
|
|
596
|
+
If required type coercion will be applied.
|
|
565
597
|
|
|
566
|
-
|
|
598
|
+
Configuration values are managed centrally using a `ConfigurationManager`, which aggregates values from various configuration sources that are defined as follows.
|
|
567
599
|
|
|
568
600
|
```python
|
|
569
601
|
class ConfigurationSource(ABC):
|
|
@@ -640,7 +672,7 @@ class SampleEnvironment:
|
|
|
640
672
|
|
|
641
673
|
As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
|
|
642
674
|
|
|
643
|
-
After
|
|
675
|
+
After being instantiated with
|
|
644
676
|
|
|
645
677
|
```python
|
|
646
678
|
TypeDescriptor.for_type(<type>)
|
|
@@ -656,7 +688,7 @@ it offers the methods
|
|
|
656
688
|
- `get_decorator(decorator) -> Optional[DecoratorDescriptor]`
|
|
657
689
|
return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
|
|
658
690
|
|
|
659
|
-
The returned method descriptors
|
|
691
|
+
The returned method descriptors provide:
|
|
660
692
|
- `param_types`
|
|
661
693
|
list of arg types
|
|
662
694
|
- `return_type`
|
|
@@ -680,6 +712,87 @@ def transactional(scope):
|
|
|
680
712
|
return decorator
|
|
681
713
|
```
|
|
682
714
|
|
|
715
|
+
# Exceptions
|
|
716
|
+
|
|
717
|
+
The class `ExceptionManager` is used to collect dynamic handlers for specific exceptions and is able to dispatch to the concrete functions
|
|
718
|
+
given a specific exception.
|
|
719
|
+
|
|
720
|
+
The handlers are declared by annoting a class with `@exception_handler` and decorating specific methods with `@handle`
|
|
721
|
+
|
|
722
|
+
**Example**:
|
|
723
|
+
```python
|
|
724
|
+
class DerivedException(Exception):
|
|
725
|
+
def __init__(self):
|
|
726
|
+
pass
|
|
727
|
+
|
|
728
|
+
@environment()
|
|
729
|
+
class SampleEnvironment:
|
|
730
|
+
# constructor
|
|
731
|
+
|
|
732
|
+
def __init__(self):
|
|
733
|
+
pass
|
|
734
|
+
|
|
735
|
+
@create()
|
|
736
|
+
def create_exception_manager(self) -> ExceptionManager:
|
|
737
|
+
return ExceptionManager()
|
|
738
|
+
|
|
739
|
+
@injectable()
|
|
740
|
+
@exception_handler()
|
|
741
|
+
class TestExceptionHandler:
|
|
742
|
+
def __init__(self):
|
|
743
|
+
pass
|
|
744
|
+
|
|
745
|
+
@handle()
|
|
746
|
+
def handle_derived_exception(self, exception: DerivedException):
|
|
747
|
+
ExceptionManager.proceed()
|
|
748
|
+
|
|
749
|
+
@handle()
|
|
750
|
+
def handle_exception(self, exception: Exception):
|
|
751
|
+
pass
|
|
752
|
+
|
|
753
|
+
@handle()
|
|
754
|
+
def handle_base_exception(self, exception: BaseException):
|
|
755
|
+
pass
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
@advice
|
|
759
|
+
class ExceptionAdvice:
|
|
760
|
+
def __init__(self, exceptionManager: ExceptionManager):
|
|
761
|
+
self.exceptionManager = exceptionManager
|
|
762
|
+
|
|
763
|
+
@error(methods().of_type(Service))
|
|
764
|
+
def handle_error(self, invocation: Invocation):
|
|
765
|
+
self.exceptionManager.handle(invocation.exception)
|
|
766
|
+
|
|
767
|
+
environment = Environment(SampleEnvironment)
|
|
768
|
+
|
|
769
|
+
environment.get(ExceptionManager).handle(DerivedException())
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
The exception maanger will first call the most appropriate method.
|
|
773
|
+
Any `ExceptionManager.proceed()` will in turn call the next most applicable method ( if available).
|
|
774
|
+
|
|
775
|
+
Together with a simple around advice we can now add exception handling to any method:
|
|
776
|
+
|
|
777
|
+
**Example**:
|
|
778
|
+
```python
|
|
779
|
+
@injectable()
|
|
780
|
+
class Service:
|
|
781
|
+
def __init__(self):
|
|
782
|
+
pass
|
|
783
|
+
|
|
784
|
+
def throw(self):
|
|
785
|
+
raise DerivedException()
|
|
786
|
+
|
|
787
|
+
@advice
|
|
788
|
+
class ExceptionAdvice:
|
|
789
|
+
def __init__(self, exceptionManager: ExceptionManager):
|
|
790
|
+
self.exceptionManager = exceptionManager
|
|
791
|
+
|
|
792
|
+
@error(methods().of_type(Service))
|
|
793
|
+
def handle_error(self, invocation: Invocation):
|
|
794
|
+
self.exceptionManager.handle(invocation.exception)
|
|
795
|
+
```
|
|
683
796
|
|
|
684
797
|
# Version History
|
|
685
798
|
|
|
@@ -696,7 +809,16 @@ def transactional(scope):
|
|
|
696
809
|
|
|
697
810
|
- added `YamlConfigurationSource`
|
|
698
811
|
|
|
699
|
-
**1.
|
|
812
|
+
**1.3.0**
|
|
813
|
+
|
|
814
|
+
- added `@conditional`
|
|
815
|
+
- added support for `async` advices
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
**1.4.0**
|
|
819
|
+
|
|
820
|
+
- bugfixes
|
|
821
|
+
- added `@ExceptionManager`
|
|
700
822
|
|
|
701
823
|
|
|
702
824
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
aspyx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
aspyx/di/__init__.py,sha256=OfETLGaquTbFHhhDRzzGtnSu-KkMj68aDdEaVU19KoI,1107
|
|
3
|
+
aspyx/di/di.py,sha256=qoOChsiUBla52UaclRlX6o5ZxRylaFF4IneanvwISos,42095
|
|
4
|
+
aspyx/di/aop/__init__.py,sha256=nOABex49zSyMZ2w1ezwX3Q3yrOcQRSDjDtSj0DwKVbQ,233
|
|
5
|
+
aspyx/di/aop/aop.py,sha256=36p3jCnNtrDL11jTi7NNpW1eGO_BqKL0s5rErsPdxps,17520
|
|
6
|
+
aspyx/di/configuration/__init__.py,sha256=mweJ3tZX1YJfY1d4ra-i0TWEcF3EwXBpGbHrKg1Kc6E,380
|
|
7
|
+
aspyx/di/configuration/configuration.py,sha256=KfPjrlUhhmEOUxdJiXePt5RGxKc8JczkWqlEBjpWQTg,4362
|
|
8
|
+
aspyx/di/configuration/env_configuration_source.py,sha256=FXPvREzq2ZER6_GG5xdpx154TQQDxZVf7LW7cvaylAk,1446
|
|
9
|
+
aspyx/di/configuration/yaml_configuration_source.py,sha256=NDl3SeoLMNVlzHgfP-Ysvhco1tRew_zFnBL5gGy2WRk,550
|
|
10
|
+
aspyx/di/threading/__init__.py,sha256=qrWdaq7MewQ2UmZy4J0Dn6BhY-ahfiG3xsv-EHqoqSE,191
|
|
11
|
+
aspyx/di/threading/synchronized.py,sha256=BQ9PjMQUJsF5r-qWaDgvqg3AvFm_R9QZdKB49EkoelQ,1263
|
|
12
|
+
aspyx/exception/__init__.py,sha256=2Jo0a_fZK8_U9SpPZ0j4aeAXJZ28uw6g-20TH_85JqY,200
|
|
13
|
+
aspyx/exception/exception_manager.py,sha256=8H5fbbcpzLxiK7OI-EZaXyX5Db4uZt9-VrAx5LMiSm8,4692
|
|
14
|
+
aspyx/reflection/__init__.py,sha256=r2sNJrfHDpuqaIYu4fTYsoo046gpgn4VTd7bsS3mQJY,282
|
|
15
|
+
aspyx/reflection/proxy.py,sha256=zJ6Psd6zWfFABdrKOf4cULt3gibyqCRdcR6z8WKIkzE,1982
|
|
16
|
+
aspyx/reflection/reflection.py,sha256=bzH5KVJ5X5ycQu5SG7BFZtioWN7sa1w1Y-xR_Y-oN6w,7113
|
|
17
|
+
aspyx/threading/__init__.py,sha256=_j_AQ4t1ecRaKIb9KCTT_EV0b7hivNML-2wV2XF7G6Y,125
|
|
18
|
+
aspyx/threading/thread_local.py,sha256=nOSS2DM1rIHmzdU9_fjxaUF3oXCaRTBHwe76IdwMqC8,1158
|
|
19
|
+
aspyx/util/__init__.py,sha256=8H2yKkXu3nkRGeTerb8ialzKGfvzUx44XUWFUYcYuQM,125
|
|
20
|
+
aspyx/util/stringbuilder.py,sha256=L3MkHAo4CJrBXuWmaRQASIa9EAs8O_ea7EjZoLsvp08,811
|
|
21
|
+
aspyx-1.4.0.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
|
|
22
|
+
aspyx-1.4.0.dist-info/METADATA,sha256=S9Qcqc9v6VHJSU76dGT_iEo1Z4ghvpp8nE7uxoc2NkI,25213
|
|
23
|
+
aspyx-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
+
aspyx-1.4.0.dist-info/top_level.txt,sha256=A_ZwhBY_ybIgjZlztd44eaOrWqkJAndiqjGlbJ3tR_I,6
|
|
25
|
+
aspyx-1.4.0.dist-info/RECORD,,
|
aspyx-1.3.0.dist-info/RECORD
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
aspyx/di/__init__.py,sha256=OfETLGaquTbFHhhDRzzGtnSu-KkMj68aDdEaVU19KoI,1107
|
|
2
|
-
aspyx/di/di.py,sha256=OmvcEd2Arsyo0n9Vh40LM_-jLFdOg-bKwahcEINJ7GA,38363
|
|
3
|
-
aspyx/di/aop/__init__.py,sha256=nOABex49zSyMZ2w1ezwX3Q3yrOcQRSDjDtSj0DwKVbQ,233
|
|
4
|
-
aspyx/di/aop/aop.py,sha256=GPkNE6CgQZUPeMlDdmE5TiksNLSgPb2A8D2IXRZmJ_M,16887
|
|
5
|
-
aspyx/di/configuration/__init__.py,sha256=mweJ3tZX1YJfY1d4ra-i0TWEcF3EwXBpGbHrKg1Kc6E,380
|
|
6
|
-
aspyx/di/configuration/configuration.py,sha256=KfPjrlUhhmEOUxdJiXePt5RGxKc8JczkWqlEBjpWQTg,4362
|
|
7
|
-
aspyx/di/configuration/env_configuration_source.py,sha256=FXPvREzq2ZER6_GG5xdpx154TQQDxZVf7LW7cvaylAk,1446
|
|
8
|
-
aspyx/di/configuration/yaml_configuration_source.py,sha256=NDl3SeoLMNVlzHgfP-Ysvhco1tRew_zFnBL5gGy2WRk,550
|
|
9
|
-
aspyx/di/threading/__init__.py,sha256=qrWdaq7MewQ2UmZy4J0Dn6BhY-ahfiG3xsv-EHqoqSE,191
|
|
10
|
-
aspyx/di/threading/synchronized.py,sha256=awFm8T5_lPw0An-H4-jdTUumPmX348WiYw1x8xyz4qs,1053
|
|
11
|
-
aspyx/di/util/__init__.py,sha256=8H2yKkXu3nkRGeTerb8ialzKGfvzUx44XUWFUYcYuQM,125
|
|
12
|
-
aspyx/di/util/stringbuilder.py,sha256=JywkLxZfaQUUWjSB5wvqA6a6Cfs3sW1jbaZ1z4U0-CQ,540
|
|
13
|
-
aspyx/reflection/__init__.py,sha256=r2sNJrfHDpuqaIYu4fTYsoo046gpgn4VTd7bsS3mQJY,282
|
|
14
|
-
aspyx/reflection/proxy.py,sha256=zJ6Psd6zWfFABdrKOf4cULt3gibyqCRdcR6z8WKIkzE,1982
|
|
15
|
-
aspyx/reflection/reflection.py,sha256=0kQXQcyOCj29vy5Ls4dbAlvOKsY7FRyHndiygZzg7HY,5826
|
|
16
|
-
aspyx-1.3.0.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
|
|
17
|
-
aspyx-1.3.0.dist-info/METADATA,sha256=lxqBe-dsW1oe6HgBOfoRIUk_q-qFObZSNpO4e8Xi40U,21691
|
|
18
|
-
aspyx-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
-
aspyx-1.3.0.dist-info/top_level.txt,sha256=A_ZwhBY_ybIgjZlztd44eaOrWqkJAndiqjGlbJ3tR_I,6
|
|
20
|
-
aspyx-1.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|