aspyx 1.2.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/__init__.py +8 -4
- aspyx/di/aop/aop.py +181 -74
- aspyx/di/configuration/env_configuration_source.py +1 -1
- aspyx/di/configuration/yaml_configuration_source.py +1 -1
- aspyx/di/di.py +389 -198
- aspyx/di/threading/__init__.py +11 -0
- aspyx/di/threading/synchronized.py +49 -0
- aspyx/exception/__init__.py +10 -0
- aspyx/exception/exception_manager.py +168 -0
- aspyx/reflection/reflection.py +41 -2
- aspyx/threading/__init__.py +10 -0
- aspyx/threading/thread_local.py +47 -0
- aspyx/{di/util → util}/stringbuilder.py +11 -0
- {aspyx-1.2.0.dist-info → aspyx-1.4.0.dist-info}/METADATA +254 -52
- aspyx-1.4.0.dist-info/RECORD +25 -0
- aspyx-1.2.0.dist-info/RECORD +0 -18
- /aspyx/{di/util → util}/__init__.py +0 -0
- {aspyx-1.2.0.dist-info → aspyx-1.4.0.dist-info}/WHEEL +0 -0
- {aspyx-1.2.0.dist-info → aspyx-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {aspyx-1.2.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
|
|
@@ -37,15 +39,19 @@ Dynamic: license-file
|
|
|
37
39
|

|
|
38
40
|

|
|
39
41
|

|
|
42
|
+
[](https://pypi.org/project/aspyx/)
|
|
43
|
+
[](https://coolsamson7.github.io/aspyx/)
|
|
40
44
|
|
|
41
|
-
## Table of Contents
|
|
45
|
+
## Table of Contents
|
|
42
46
|
|
|
43
|
-
- [
|
|
47
|
+
- [Motivation](#motivation)
|
|
48
|
+
- [Introduction](#introduction)
|
|
44
49
|
- [Installation](#installation)
|
|
45
50
|
- [Registration](#registration)
|
|
46
51
|
- [Class](#class)
|
|
47
52
|
- [Class Factory](#class-factory)
|
|
48
53
|
- [Method](#method)
|
|
54
|
+
- [Conditional](#conditional)
|
|
49
55
|
- [Environment](#environment)
|
|
50
56
|
- [Definition](#definition)
|
|
51
57
|
- [Retrieval](#retrieval)
|
|
@@ -55,27 +61,48 @@ Dynamic: license-file
|
|
|
55
61
|
- [Post Processors](#post-processors)
|
|
56
62
|
- [Custom scopes](#custom-scopes)
|
|
57
63
|
- [AOP](#aop)
|
|
64
|
+
- [Threading](#threading)
|
|
58
65
|
- [Configuration](#configuration)
|
|
59
66
|
- [Reflection](#reflection)
|
|
67
|
+
- [Exceptions](#exceptions)
|
|
60
68
|
- [Version History](#version-history)
|
|
61
69
|
|
|
70
|
+
# Motivation
|
|
71
|
+
|
|
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
|
|
73
|
+
|
|
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.
|
|
80
|
+
|
|
62
81
|
# Introduction
|
|
63
82
|
|
|
64
|
-
Aspyx is a
|
|
83
|
+
Aspyx is a lightweight Python library that provides both Dependency Injection (DI) and Aspect-Oriented Programming (AOP) support.
|
|
65
84
|
|
|
66
|
-
The following features are supported
|
|
85
|
+
The following DI features are supported
|
|
67
86
|
- constructor and setter injection
|
|
87
|
+
- injection of configuration variables
|
|
88
|
+
- possibility to define custom injections
|
|
68
89
|
- post processors
|
|
69
|
-
- factory classes and methods
|
|
70
|
-
- support for eager construction
|
|
71
|
-
- support for singleton and
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
90
|
+
- support for factory classes and methods
|
|
91
|
+
- support for eager and lazy construction
|
|
92
|
+
- support for scopes singleton, request and thread
|
|
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`
|
|
96
|
+
- bundling of injectable objects according to their module location including recursive imports and inheritance
|
|
97
|
+
- instantiation of - possibly multiple - container instances - so called environments - that manage the lifecycle of related objects
|
|
76
98
|
- hierarchical environments
|
|
77
99
|
|
|
78
|
-
|
|
100
|
+
With respect to AOP:
|
|
101
|
+
- support for before, around, after and error aspects
|
|
102
|
+
- simple fluent interface to specify which methods are targeted by an aspect
|
|
103
|
+
- sync and async method support
|
|
104
|
+
|
|
105
|
+
The library is thread-safe and heavily performance optimized as most of the runtime information is precomputed and cached!
|
|
79
106
|
|
|
80
107
|
Let's look at a simple example
|
|
81
108
|
|
|
@@ -87,7 +114,7 @@ class Foo:
|
|
|
87
114
|
def __init__(self):
|
|
88
115
|
pass
|
|
89
116
|
|
|
90
|
-
def hello(msg: str):
|
|
117
|
+
def hello(self, msg: str):
|
|
91
118
|
print(f"hello {msg}")
|
|
92
119
|
|
|
93
120
|
@injectable() # eager and singleton by default
|
|
@@ -108,16 +135,18 @@ class SampleEnvironment:
|
|
|
108
135
|
def __init__(self):
|
|
109
136
|
pass
|
|
110
137
|
|
|
111
|
-
#
|
|
138
|
+
# create environment
|
|
112
139
|
|
|
113
140
|
environment = Environment(SampleEnvironment)
|
|
114
141
|
|
|
142
|
+
# fetch an instance
|
|
143
|
+
|
|
115
144
|
bar = env.get(Bar)
|
|
116
145
|
|
|
117
146
|
bar.foo.hello("world")
|
|
118
147
|
```
|
|
119
148
|
|
|
120
|
-
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.
|
|
121
150
|
|
|
122
151
|
Let's add some aspects...
|
|
123
152
|
|
|
@@ -130,17 +159,15 @@ class SampleAdvice:
|
|
|
130
159
|
|
|
131
160
|
@before(methods().named("hello").of_type(Foo))
|
|
132
161
|
def call_before(self, invocation: Invocation):
|
|
133
|
-
|
|
162
|
+
...
|
|
134
163
|
|
|
135
164
|
@error(methods().named("hello").of_type(Foo))
|
|
136
165
|
def call_error(self, invocation: Invocation):
|
|
137
|
-
|
|
138
|
-
print(invocation.exception)
|
|
166
|
+
... # exception accessible in invocation.exception
|
|
139
167
|
|
|
140
168
|
@around(methods().named("hello"))
|
|
141
169
|
def call_around(self, invocation: Invocation):
|
|
142
|
-
|
|
143
|
-
|
|
170
|
+
...
|
|
144
171
|
return invocation.proceed()
|
|
145
172
|
```
|
|
146
173
|
|
|
@@ -155,11 +182,11 @@ Let's look at the details
|
|
|
155
182
|
|
|
156
183
|
# Installation
|
|
157
184
|
|
|
158
|
-
|
|
185
|
+
Just install from PyPI with
|
|
159
186
|
|
|
160
|
-
|
|
187
|
+
`pip install aspyx`
|
|
161
188
|
|
|
162
|
-
|
|
189
|
+
The library is tested with all Python version >= 3.9
|
|
163
190
|
|
|
164
191
|
# Registration
|
|
165
192
|
|
|
@@ -177,8 +204,8 @@ class Foo:
|
|
|
177
204
|
def __init__(self):
|
|
178
205
|
pass
|
|
179
206
|
```
|
|
180
|
-
Please make sure, that the class defines a local constructor, as this is
|
|
181
|
-
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.
|
|
182
209
|
|
|
183
210
|
Only eligible types are allowed, of course!
|
|
184
211
|
|
|
@@ -233,6 +260,22 @@ class Foo:
|
|
|
233
260
|
|
|
234
261
|
The same arguments as in `@injectable` are possible.
|
|
235
262
|
|
|
263
|
+
## Conditional
|
|
264
|
+
|
|
265
|
+
All `@injectable` declarations can be supplemented with
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
@conditional(<condition>, ..., <condition>)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
decorators that act as filters in the context of an environment.
|
|
272
|
+
|
|
273
|
+
Valid conditions are created by:
|
|
274
|
+
- `requires_class(clazz: Type)`
|
|
275
|
+
the injectable is valid, if the specified class is registered as well.
|
|
276
|
+
- `requires_feature(feature: str)`
|
|
277
|
+
the injectable is valid, if the environment defines the specified feature.
|
|
278
|
+
|
|
236
279
|
# Environment
|
|
237
280
|
|
|
238
281
|
## Definition
|
|
@@ -250,13 +293,35 @@ environment = Environment(SampleEnvironment)
|
|
|
250
293
|
```
|
|
251
294
|
|
|
252
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
|
+
|
|
298
|
+
|
|
299
|
+
By adding the parameter `features: list[str]`, it is possible to filter injectables by evaluating the corresponding `@conditional` decorators.
|
|
300
|
+
|
|
301
|
+
**Example**:
|
|
302
|
+
```python
|
|
303
|
+
|
|
304
|
+
@injectable()
|
|
305
|
+
@conditional(requires_feature("dev"))
|
|
306
|
+
class DevOnly:
|
|
307
|
+
def __init__(self):
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
@environment()
|
|
311
|
+
class SampleEnvironmen():
|
|
312
|
+
def __init__(self):
|
|
313
|
+
pass
|
|
314
|
+
|
|
315
|
+
environment = Environment(SampleEnvironment, features=["dev"])
|
|
316
|
+
```
|
|
317
|
+
|
|
253
318
|
|
|
254
319
|
By adding an `imports: list[Type]` parameter, specifying other environment types, it will register the appropriate classes recursively.
|
|
255
320
|
|
|
256
321
|
**Example**:
|
|
257
322
|
```python
|
|
258
323
|
@environment()
|
|
259
|
-
class SampleEnvironmen(imports=[OtherEnvironment])
|
|
324
|
+
class SampleEnvironmen(imports=[OtherEnvironment]):
|
|
260
325
|
def __init__(self):
|
|
261
326
|
pass
|
|
262
327
|
```
|
|
@@ -289,7 +354,7 @@ The container knows about class hierarchies and is able to `get` base classes, a
|
|
|
289
354
|
|
|
290
355
|
In case of ambiguities, it will throw an exception.
|
|
291
356
|
|
|
292
|
-
|
|
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. )
|
|
293
358
|
|
|
294
359
|
# Instantiation logic
|
|
295
360
|
|
|
@@ -302,7 +367,7 @@ Constructing a new instance involves a number of steps executed in this order
|
|
|
302
367
|
different decorators can mark methods that should be called during the lifecycle ( here the construction ) of an instance.
|
|
303
368
|
These are various injection possibilities as well as an optional final `on_init` call
|
|
304
369
|
- PostProcessors
|
|
305
|
-
Any custom post processors, that can add
|
|
370
|
+
Any custom post processors, that can add side effects or modify the instances
|
|
306
371
|
|
|
307
372
|
## Injection methods
|
|
308
373
|
|
|
@@ -337,7 +402,7 @@ It is possible to mark specific lifecyle methods.
|
|
|
337
402
|
- `@on_init()`
|
|
338
403
|
called after the constructor and all other injections.
|
|
339
404
|
- `@on_running()`
|
|
340
|
-
called an environment has initialized all eager objects.
|
|
405
|
+
called after an environment has initialized completely ( e.g. created all eager objects ).
|
|
341
406
|
- `@on_destroy()`
|
|
342
407
|
called during shutdown of the environment
|
|
343
408
|
|
|
@@ -355,7 +420,7 @@ class SamplePostProcessor(PostProcessor):
|
|
|
355
420
|
|
|
356
421
|
Any implementing class of `PostProcessor` that is eligible for injection will be called by passing the new instance.
|
|
357
422
|
|
|
358
|
-
|
|
423
|
+
Note that a post processor will only handle instances _after_ its _own_ registration.
|
|
359
424
|
|
|
360
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!
|
|
361
426
|
|
|
@@ -372,7 +437,7 @@ def get(self, provider: AbstractInstanceProvider, environment: Environment, argP
|
|
|
372
437
|
Arguments are:
|
|
373
438
|
- `provider` the actual provider that will create an instance
|
|
374
439
|
- `environment`the requesting environment
|
|
375
|
-
- `
|
|
440
|
+
- `argProvider` a function that can be called to compute the required arguments recursively
|
|
376
441
|
|
|
377
442
|
**Example**: The simplified code of the singleton provider ( disregarding locking logic )
|
|
378
443
|
|
|
@@ -397,49 +462,74 @@ class SingletonScope(Scope):
|
|
|
397
462
|
|
|
398
463
|
# AOP
|
|
399
464
|
|
|
400
|
-
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.
|
|
401
466
|
|
|
402
467
|
Advice classes need to be part of classes that add a `@advice()` decorator and can define methods that add aspects.
|
|
403
468
|
|
|
404
469
|
```python
|
|
405
|
-
@advice
|
|
470
|
+
@advice
|
|
406
471
|
class SampleAdvice:
|
|
407
472
|
def __init__(self): # could inject dependencies
|
|
408
473
|
pass
|
|
409
474
|
|
|
410
475
|
@before(methods().named("hello").of_type(Foo))
|
|
411
476
|
def call_before(self, invocation: Invocation):
|
|
412
|
-
# arguments: invocation.args
|
|
413
|
-
|
|
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
|
+
...
|
|
414
484
|
|
|
415
485
|
@error(methods().named("hello").of_type(Foo))
|
|
416
486
|
def call_error(self, invocation: Invocation):
|
|
417
|
-
|
|
418
|
-
|
|
487
|
+
# error: invocation.exception
|
|
488
|
+
...
|
|
419
489
|
|
|
420
490
|
@around(methods().named("hello"))
|
|
421
491
|
def call_around(self, invocation: Invocation):
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
+
...
|
|
425
497
|
```
|
|
426
498
|
|
|
427
499
|
Different aspects - with the appropriate decorator - are possible:
|
|
428
500
|
- `before`
|
|
429
501
|
methods that will be executed _prior_ to the original method
|
|
430
502
|
- `around`
|
|
431
|
-
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.
|
|
432
504
|
- `after`
|
|
433
|
-
|
|
505
|
+
methods that will be executed _after_ to the original method
|
|
434
506
|
- `error`
|
|
435
|
-
methods that will be executed in case of a caught exception
|
|
507
|
+
methods that will be executed in case of a caught exception
|
|
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
|
+
|
|
511
|
+
All methods are expected to have single `Invocation` parameter, that stores
|
|
512
|
+
|
|
513
|
+
- `func` the target function
|
|
514
|
+
- `args` the supplied args ( including the `self` instance as the first element)
|
|
515
|
+
- `kwargs` the keywords args
|
|
516
|
+
- `result` the result ( initially `None`)
|
|
517
|
+
- `exception` a possible caught exception ( initially `None`)
|
|
436
518
|
|
|
437
|
-
|
|
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.
|
|
438
520
|
|
|
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.
|
|
440
521
|
If the `proceed` is called with parameters, they will replace the original parameters!
|
|
441
522
|
|
|
442
|
-
|
|
523
|
+
**Example**: Parameter modifications
|
|
524
|
+
|
|
525
|
+
```python
|
|
526
|
+
@around(methods().named("say"))
|
|
527
|
+
def call_around(self, invocation: Invocation):
|
|
528
|
+
return invocation.proceed(invocation.args[0], invocation.args[1] + "!") # 0 is self!
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
The argument list to the corresponding decorators control which methods are targeted by the advice.
|
|
532
|
+
|
|
443
533
|
A fluent interface is used describe the mapping.
|
|
444
534
|
The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
|
|
445
535
|
|
|
@@ -448,6 +538,8 @@ Both add the fluent methods:
|
|
|
448
538
|
defines the matching classes
|
|
449
539
|
- `named(name: str)`
|
|
450
540
|
defines method or class names
|
|
541
|
+
- `that_are_async()`
|
|
542
|
+
defines async methods
|
|
451
543
|
- `matches(re: str)`
|
|
452
544
|
defines regular expressions for methods or classes
|
|
453
545
|
- `decorated_with(type: Type)`
|
|
@@ -458,7 +550,7 @@ The fluent methods `named`, `matches` and `of_type` can be called multiple times
|
|
|
458
550
|
**Example**: react on both `transactional` decorators on methods or classes
|
|
459
551
|
|
|
460
552
|
```python
|
|
461
|
-
@
|
|
553
|
+
@advice
|
|
462
554
|
class TransactionAdvice:
|
|
463
555
|
def __init__(self):
|
|
464
556
|
pass
|
|
@@ -468,6 +560,24 @@ class TransactionAdvice:
|
|
|
468
560
|
...
|
|
469
561
|
```
|
|
470
562
|
|
|
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
|
|
566
|
+
|
|
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
|
+
```
|
|
580
|
+
|
|
471
581
|
# Configuration
|
|
472
582
|
|
|
473
583
|
It is possible to inject configuration values, by decorating methods with `@inject-value(<name>)` given a configuration key.
|
|
@@ -483,9 +593,9 @@ class Foo:
|
|
|
483
593
|
...
|
|
484
594
|
```
|
|
485
595
|
|
|
486
|
-
If required
|
|
596
|
+
If required type coercion will be applied.
|
|
487
597
|
|
|
488
|
-
|
|
598
|
+
Configuration values are managed centrally using a `ConfigurationManager`, which aggregates values from various configuration sources that are defined as follows.
|
|
489
599
|
|
|
490
600
|
```python
|
|
491
601
|
class ConfigurationSource(ABC):
|
|
@@ -562,7 +672,7 @@ class SampleEnvironment:
|
|
|
562
672
|
|
|
563
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.
|
|
564
674
|
|
|
565
|
-
After
|
|
675
|
+
After being instantiated with
|
|
566
676
|
|
|
567
677
|
```python
|
|
568
678
|
TypeDescriptor.for_type(<type>)
|
|
@@ -578,7 +688,7 @@ it offers the methods
|
|
|
578
688
|
- `get_decorator(decorator) -> Optional[DecoratorDescriptor]`
|
|
579
689
|
return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
|
|
580
690
|
|
|
581
|
-
The returned method descriptors
|
|
691
|
+
The returned method descriptors provide:
|
|
582
692
|
- `param_types`
|
|
583
693
|
list of arg types
|
|
584
694
|
- `return_type`
|
|
@@ -602,6 +712,87 @@ def transactional(scope):
|
|
|
602
712
|
return decorator
|
|
603
713
|
```
|
|
604
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
|
+
```
|
|
605
796
|
|
|
606
797
|
# Version History
|
|
607
798
|
|
|
@@ -618,6 +809,17 @@ def transactional(scope):
|
|
|
618
809
|
|
|
619
810
|
- added `YamlConfigurationSource`
|
|
620
811
|
|
|
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`
|
|
822
|
+
|
|
621
823
|
|
|
622
824
|
|
|
623
825
|
|
|
@@ -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.2.0.dist-info/RECORD
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
aspyx/di/__init__.py,sha256=xdb2lsKh00uGMFCWYavhUEMGH15OSeAhUC-iSosqHqU,935
|
|
2
|
-
aspyx/di/di.py,sha256=evfhTznmPNRDjamthSPisMpDhGZJdNmEREUhdJ9nsTE,35571
|
|
3
|
-
aspyx/di/aop/__init__.py,sha256=nOABex49zSyMZ2w1ezwX3Q3yrOcQRSDjDtSj0DwKVbQ,233
|
|
4
|
-
aspyx/di/aop/aop.py,sha256=3GKN6sGlsZbJ7_WSxQvHZNFYouAfU4Eq6H5cBBB_e_4,14455
|
|
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=Xh8g3AuQdgk89nG6GKA4iKILXaqHecD0KqMW2w91hXs,1445
|
|
8
|
-
aspyx/di/configuration/yaml_configuration_source.py,sha256=LM-5J6IRxBBbBAjDeGIFsmiT-61WuGv1sU_zXWNzhkI,549
|
|
9
|
-
aspyx/di/util/__init__.py,sha256=8H2yKkXu3nkRGeTerb8ialzKGfvzUx44XUWFUYcYuQM,125
|
|
10
|
-
aspyx/di/util/stringbuilder.py,sha256=JywkLxZfaQUUWjSB5wvqA6a6Cfs3sW1jbaZ1z4U0-CQ,540
|
|
11
|
-
aspyx/reflection/__init__.py,sha256=r2sNJrfHDpuqaIYu4fTYsoo046gpgn4VTd7bsS3mQJY,282
|
|
12
|
-
aspyx/reflection/proxy.py,sha256=zJ6Psd6zWfFABdrKOf4cULt3gibyqCRdcR6z8WKIkzE,1982
|
|
13
|
-
aspyx/reflection/reflection.py,sha256=2oYJKYysH3ULmTzbXdjGBCB1iaLbWh3IPKSnorVLshU,5719
|
|
14
|
-
aspyx-1.2.0.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
|
|
15
|
-
aspyx-1.2.0.dist-info/METADATA,sha256=wXSu9Clxg4ZZCoiRAhiZzAR1Wcqtjv4NZie4XilJNSs,19009
|
|
16
|
-
aspyx-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
17
|
-
aspyx-1.2.0.dist-info/top_level.txt,sha256=A_ZwhBY_ybIgjZlztd44eaOrWqkJAndiqjGlbJ3tR_I,6
|
|
18
|
-
aspyx-1.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|