aspyx 1.2.0__py3-none-any.whl → 1.3.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/di/__init__.py +8 -4
- aspyx/di/aop/aop.py +87 -5
- aspyx/di/configuration/env_configuration_source.py +1 -1
- aspyx/di/configuration/yaml_configuration_source.py +1 -1
- aspyx/di/di.py +290 -200
- aspyx/di/threading/__init__.py +11 -0
- aspyx/di/threading/synchronized.py +46 -0
- aspyx/reflection/reflection.py +4 -1
- {aspyx-1.2.0.dist-info → aspyx-1.3.0.dist-info}/METADATA +96 -16
- aspyx-1.3.0.dist-info/RECORD +20 -0
- aspyx-1.2.0.dist-info/RECORD +0 -18
- {aspyx-1.2.0.dist-info → aspyx-1.3.0.dist-info}/WHEEL +0 -0
- {aspyx-1.2.0.dist-info → aspyx-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {aspyx-1.2.0.dist-info → aspyx-1.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Synchronized decorator and advice
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import threading
|
|
7
|
+
from weakref import WeakKeyDictionary
|
|
8
|
+
|
|
9
|
+
from aspyx.reflection import Decorators
|
|
10
|
+
from aspyx.di.aop import advice, around, methods, Invocation
|
|
11
|
+
|
|
12
|
+
def synchronized():
|
|
13
|
+
"""
|
|
14
|
+
decorate methods to synchronize them based on an instance related `RLock`
|
|
15
|
+
"""
|
|
16
|
+
def decorator(func):
|
|
17
|
+
Decorators.add(func, synchronized)
|
|
18
|
+
return func #
|
|
19
|
+
|
|
20
|
+
return decorator
|
|
21
|
+
|
|
22
|
+
@advice
|
|
23
|
+
class SynchronizeAdvice():
|
|
24
|
+
__slots__ = ("locks")
|
|
25
|
+
|
|
26
|
+
# constructor
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.locks = WeakKeyDictionary()
|
|
30
|
+
|
|
31
|
+
# internal
|
|
32
|
+
|
|
33
|
+
def get_lock(self, instance) -> threading.RLock:
|
|
34
|
+
lock = self.locks.get(instance, None)
|
|
35
|
+
if lock is None:
|
|
36
|
+
lock = threading.RLock()
|
|
37
|
+
self.locks[instance] = lock
|
|
38
|
+
|
|
39
|
+
return lock
|
|
40
|
+
|
|
41
|
+
# around
|
|
42
|
+
|
|
43
|
+
@around(methods().decorated_with(synchronized))
|
|
44
|
+
def synchronize(self, invocation: Invocation):
|
|
45
|
+
with self.get_lock(invocation.args[0]):
|
|
46
|
+
return invocation.proceed()
|
aspyx/reflection/reflection.py
CHANGED
|
@@ -17,7 +17,7 @@ class DecoratorDescriptor:
|
|
|
17
17
|
"args"
|
|
18
18
|
]
|
|
19
19
|
|
|
20
|
-
def __init__(self, decorator, *args):
|
|
20
|
+
def __init__(self, decorator: Callable, *args):
|
|
21
21
|
self.decorator = decorator
|
|
22
22
|
self.args = args
|
|
23
23
|
|
|
@@ -69,6 +69,9 @@ class TypeDescriptor:
|
|
|
69
69
|
|
|
70
70
|
# public
|
|
71
71
|
|
|
72
|
+
def is_async(self) -> bool:
|
|
73
|
+
return inspect.iscoroutinefunction(self.method)
|
|
74
|
+
|
|
72
75
|
def get_decorator(self, decorator: Callable) -> Optional[DecoratorDescriptor]:
|
|
73
76
|
for dec in self.decorators:
|
|
74
77
|
if dec.decorator is decorator:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -37,15 +37,18 @@ Dynamic: license-file
|
|
|
37
37
|

|
|
38
38
|

|
|
39
39
|

|
|
40
|
+
[](https://pypi.org/project/aspyx/)
|
|
40
41
|
|
|
41
42
|
## Table of Contents
|
|
42
43
|
|
|
43
|
-
- [
|
|
44
|
+
- [Motivation](#motivation)
|
|
45
|
+
- [Introduction](#introduction)
|
|
44
46
|
- [Installation](#installation)
|
|
45
47
|
- [Registration](#registration)
|
|
46
48
|
- [Class](#class)
|
|
47
49
|
- [Class Factory](#class-factory)
|
|
48
50
|
- [Method](#method)
|
|
51
|
+
- [Conditional](#conditional)
|
|
49
52
|
- [Environment](#environment)
|
|
50
53
|
- [Definition](#definition)
|
|
51
54
|
- [Retrieval](#retrieval)
|
|
@@ -59,22 +62,38 @@ Dynamic: license-file
|
|
|
59
62
|
- [Reflection](#reflection)
|
|
60
63
|
- [Version History](#version-history)
|
|
61
64
|
|
|
65
|
+
# Motivation
|
|
66
|
+
|
|
67
|
+
While working on some AI related topics in Python, i required a simple DI framework.
|
|
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".
|
|
70
|
+
|
|
71
|
+
So, why not develop one on my own...Having done that in other languages previously, the task was not that hard, and last but not least...it is fun :-)
|
|
72
|
+
|
|
62
73
|
# Introduction
|
|
63
74
|
|
|
64
75
|
Aspyx is a small python libary, that adds support for both dependency injection and aop.
|
|
65
76
|
|
|
66
|
-
The following features are supported
|
|
77
|
+
The following di features are supported
|
|
67
78
|
- constructor and setter injection
|
|
79
|
+
- possibility to define custom injections
|
|
68
80
|
- post processors
|
|
69
|
-
- factory classes and methods
|
|
81
|
+
- support for factory classes and methods
|
|
70
82
|
- support for eager construction
|
|
71
|
-
- support for singleton and
|
|
83
|
+
- support for scopes singleton, request and thread
|
|
72
84
|
- possibilty to add custom scopes
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
85
|
+
- conditional registration of classes and factories
|
|
86
|
+
- lifecycle events methods `on_init`, `on_destroy`
|
|
87
|
+
- bundling of injectable objects according to their module location including recursive imports and inheritance
|
|
88
|
+
- instatiation of - possibly multiple - container instances - so called environments - that manage the lifecylce of related objects
|
|
89
|
+
- filtering of classes by associating "features" sets to environment ( similar to spring profiles )
|
|
76
90
|
- hierarchical environments
|
|
77
91
|
|
|
92
|
+
With respect to aop:
|
|
93
|
+
- support for before, around, after and error aspects
|
|
94
|
+
- sync and async method support
|
|
95
|
+
- `synchronized` decorator that adds locking to methods
|
|
96
|
+
|
|
78
97
|
The library is thread-safe!
|
|
79
98
|
|
|
80
99
|
Let's look at a simple example
|
|
@@ -155,11 +174,11 @@ Let's look at the details
|
|
|
155
174
|
|
|
156
175
|
# Installation
|
|
157
176
|
|
|
158
|
-
|
|
177
|
+
Just install from PyPI with
|
|
159
178
|
|
|
160
|
-
|
|
179
|
+
`pip install aspyx`
|
|
161
180
|
|
|
162
|
-
|
|
181
|
+
The library is tested with all Python version >= 3.9
|
|
163
182
|
|
|
164
183
|
# Registration
|
|
165
184
|
|
|
@@ -233,6 +252,22 @@ class Foo:
|
|
|
233
252
|
|
|
234
253
|
The same arguments as in `@injectable` are possible.
|
|
235
254
|
|
|
255
|
+
## Conditional
|
|
256
|
+
|
|
257
|
+
All `@injectable` declarations can be supplemented with
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
@conditional(<condition>, ..., <condition>)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
decorators that act as filters in the context of an environment.
|
|
264
|
+
|
|
265
|
+
Valid conditions are created by:
|
|
266
|
+
- `requires_class(clazz: Type)`
|
|
267
|
+
the injectable is valid, if the specified class is registered as well.
|
|
268
|
+
- `requires_feature(feature: str)`
|
|
269
|
+
the injectable is valid, if the environment defines the specified feature.
|
|
270
|
+
|
|
236
271
|
# Environment
|
|
237
272
|
|
|
238
273
|
## Definition
|
|
@@ -251,6 +286,26 @@ environment = Environment(SampleEnvironment)
|
|
|
251
286
|
|
|
252
287
|
The default is that all eligible classes, that are implemented in the containing module or in any submodule will be managed.
|
|
253
288
|
|
|
289
|
+
By adding the parameter `features: list[str]`, it is possible to filter injectables by evaluating the corresponding `@conditional` decorators.
|
|
290
|
+
|
|
291
|
+
**Example**:
|
|
292
|
+
```python
|
|
293
|
+
|
|
294
|
+
@injectable()
|
|
295
|
+
@conditional(requires_feature("dev"))
|
|
296
|
+
class DevOnly:
|
|
297
|
+
def __init__(self):
|
|
298
|
+
pass
|
|
299
|
+
|
|
300
|
+
@environment()
|
|
301
|
+
class SampleEnvironmen()):
|
|
302
|
+
def __init__(self):
|
|
303
|
+
pass
|
|
304
|
+
|
|
305
|
+
environment = Environment(SampleEnvironment, features=["dev"])
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
|
|
254
309
|
By adding an `imports: list[Type]` parameter, specifying other environment types, it will register the appropriate classes recursively.
|
|
255
310
|
|
|
256
311
|
**Example**:
|
|
@@ -428,18 +483,35 @@ Different aspects - with the appropriate decorator - are possible:
|
|
|
428
483
|
- `before`
|
|
429
484
|
methods that will be executed _prior_ to the original method
|
|
430
485
|
- `around`
|
|
431
|
-
methods that will be executed _around_ to the original method giving it the possibility add side effects or even change the parameters.
|
|
486
|
+
methods that will be executed _around_ to the original method giving it the possibility to add side effects or even change the parameters.
|
|
432
487
|
- `after`
|
|
433
|
-
|
|
488
|
+
methods that will be executed _after_ to the original method
|
|
434
489
|
- `error`
|
|
435
|
-
methods that will be executed in case of a caught exception
|
|
490
|
+
methods that will be executed in case of a caught exception
|
|
491
|
+
|
|
492
|
+
All methods are expected to have single `Invocation` parameter, that stores
|
|
436
493
|
|
|
437
|
-
|
|
494
|
+
- `func` the target function
|
|
495
|
+
- `args` the suppliued args
|
|
496
|
+
- `kwargs` the keywords args
|
|
497
|
+
- `result` the result ( initially `None`)
|
|
498
|
+
- `exception` a possible caught excpetion ( initially `None`)
|
|
438
499
|
|
|
439
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.
|
|
440
501
|
If the `proceed` is called with parameters, they will replace the original parameters!
|
|
441
502
|
|
|
442
|
-
|
|
503
|
+
**Example**: Parameter modifications
|
|
504
|
+
|
|
505
|
+
```python
|
|
506
|
+
@around(methods().named("say"))
|
|
507
|
+
def call_around(self, invocation: Invocation):
|
|
508
|
+
args = [invocation.args[0],invocation.args[1] + "!"] # 0 is self!
|
|
509
|
+
|
|
510
|
+
return invocation.proceed(*args)
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
The argument list to the corresponding decorators control which methods are targeted by the advice.
|
|
514
|
+
|
|
443
515
|
A fluent interface is used describe the mapping.
|
|
444
516
|
The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
|
|
445
517
|
|
|
@@ -448,6 +520,8 @@ Both add the fluent methods:
|
|
|
448
520
|
defines the matching classes
|
|
449
521
|
- `named(name: str)`
|
|
450
522
|
defines method or class names
|
|
523
|
+
- `that_are_async()`
|
|
524
|
+
defines async methods
|
|
451
525
|
- `matches(re: str)`
|
|
452
526
|
defines regular expressions for methods or classes
|
|
453
527
|
- `decorated_with(type: Type)`
|
|
@@ -468,6 +542,10 @@ class TransactionAdvice:
|
|
|
468
542
|
...
|
|
469
543
|
```
|
|
470
544
|
|
|
545
|
+
With respect to async methods, you need to make sure, to replace a `proceeed()` with a `await proceed_async()` to have the overall chain async!
|
|
546
|
+
|
|
547
|
+
A handy decorator `@synchronized` is implemented that automatically synchronizes methods with a `RLock` associated with the instance.
|
|
548
|
+
|
|
471
549
|
# Configuration
|
|
472
550
|
|
|
473
551
|
It is possible to inject configuration values, by decorating methods with `@inject-value(<name>)` given a configuration key.
|
|
@@ -618,6 +696,8 @@ def transactional(scope):
|
|
|
618
696
|
|
|
619
697
|
- added `YamlConfigurationSource`
|
|
620
698
|
|
|
699
|
+
**1.2.1**
|
|
700
|
+
|
|
621
701
|
|
|
622
702
|
|
|
623
703
|
|
|
@@ -0,0 +1,20 @@
|
|
|
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,,
|
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
|