aspyx 1.0.0__py3-none-any.whl → 1.1.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 +9 -5
- aspyx/di/aop/__init__.py +4 -1
- aspyx/di/aop/aop.py +56 -76
- aspyx/di/configuration/__init__.py +4 -1
- aspyx/di/configuration/configuration.py +22 -17
- aspyx/di/di.py +280 -171
- aspyx/di/util/__init__.py +8 -0
- aspyx/di/util/stringbuilder.py +31 -0
- aspyx/reflection/__init__.py +4 -1
- aspyx/reflection/proxy.py +10 -7
- aspyx/reflection/reflection.py +38 -25
- {aspyx-1.0.0.dist-info → aspyx-1.1.0.dist-info}/METADATA +126 -38
- aspyx-1.1.0.dist-info/RECORD +16 -0
- aspyx-1.0.0.dist-info/RECORD +0 -14
- {aspyx-1.0.0.dist-info → aspyx-1.1.0.dist-info}/WHEEL +0 -0
- {aspyx-1.0.0.dist-info → aspyx-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {aspyx-1.0.0.dist-info → aspyx-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility class for Java lovers
|
|
3
|
+
"""
|
|
4
|
+
class StringBuilder:
|
|
5
|
+
___slots__ = ("_parts",)
|
|
6
|
+
|
|
7
|
+
# constructor
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self._parts = []
|
|
11
|
+
|
|
12
|
+
# public
|
|
13
|
+
|
|
14
|
+
def append(self, s: str) -> "StringBuilder":
|
|
15
|
+
self._parts.append(str(s))
|
|
16
|
+
|
|
17
|
+
return self
|
|
18
|
+
|
|
19
|
+
def extend(self, iterable) -> "StringBuilder":
|
|
20
|
+
for s in iterable:
|
|
21
|
+
self._parts.append(str(s))
|
|
22
|
+
|
|
23
|
+
return self
|
|
24
|
+
|
|
25
|
+
def clear(self):
|
|
26
|
+
self._parts.clear()
|
|
27
|
+
|
|
28
|
+
# object
|
|
29
|
+
|
|
30
|
+
def __str__(self):
|
|
31
|
+
return ''.join(self._parts)
|
aspyx/reflection/__init__.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides tools for dynamic proxy creation and reflection
|
|
3
|
+
"""
|
|
1
4
|
from .proxy import DynamicProxy
|
|
2
5
|
from .reflection import Decorators, TypeDescriptor, DecoratorDescriptor
|
|
3
6
|
|
|
@@ -6,4 +9,4 @@ __all__ = [
|
|
|
6
9
|
"Decorators",
|
|
7
10
|
"DecoratorDescriptor",
|
|
8
11
|
"TypeDescriptor"
|
|
9
|
-
]
|
|
12
|
+
]
|
aspyx/reflection/proxy.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dynamic proxies for method interception and delegation.
|
|
3
|
+
"""
|
|
1
4
|
from typing import Generic, TypeVar, Type
|
|
2
5
|
|
|
3
6
|
T = TypeVar("T")
|
|
@@ -22,7 +25,7 @@ class DynamicProxy(Generic[T]):
|
|
|
22
25
|
|
|
23
26
|
Attributes:
|
|
24
27
|
type: The proxied class type.
|
|
25
|
-
|
|
28
|
+
invocation_handler: The handler that processes intercepted method calls.
|
|
26
29
|
"""
|
|
27
30
|
# inner class
|
|
28
31
|
|
|
@@ -40,19 +43,19 @@ class DynamicProxy(Generic[T]):
|
|
|
40
43
|
# class methods
|
|
41
44
|
|
|
42
45
|
@classmethod
|
|
43
|
-
def create(cls, type: Type[T],
|
|
44
|
-
return DynamicProxy(type,
|
|
46
|
+
def create(cls, type: Type[T], invocation_handler: 'DynamicProxy.InvocationHandler') -> T:
|
|
47
|
+
return DynamicProxy(type, invocation_handler)
|
|
45
48
|
|
|
46
49
|
# constructor
|
|
47
50
|
|
|
48
|
-
def __init__(self, type: Type[T],
|
|
51
|
+
def __init__(self, type: Type[T], invocation_handler: 'DynamicProxy.InvocationHandler'):
|
|
49
52
|
self.type = type
|
|
50
|
-
self.
|
|
53
|
+
self.invocation_handler = invocation_handler
|
|
51
54
|
|
|
52
55
|
# public
|
|
53
56
|
|
|
54
57
|
def __getattr__(self, name):
|
|
55
58
|
def wrapper(*args, **kwargs):
|
|
56
|
-
return self.
|
|
59
|
+
return self.invocation_handler.invoke(DynamicProxy.Invocation(self.type, name, *args, **kwargs))
|
|
57
60
|
|
|
58
|
-
return wrapper
|
|
61
|
+
return wrapper
|
aspyx/reflection/reflection.py
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides a TypeDescriptor class that allows introspection of Python classes,
|
|
3
|
+
including their methods, decorators, and type hints. It supports caching for performance
|
|
4
|
+
"""
|
|
1
5
|
from __future__ import annotations
|
|
2
6
|
|
|
3
7
|
import inspect
|
|
4
8
|
from inspect import signature, getmembers
|
|
9
|
+
import threading
|
|
5
10
|
from typing import Callable, get_type_hints, Type, Dict, Optional
|
|
6
11
|
from weakref import WeakKeyDictionary
|
|
7
12
|
|
|
13
|
+
|
|
8
14
|
class DecoratorDescriptor:
|
|
9
15
|
__slots__ = [
|
|
10
16
|
"decorator",
|
|
@@ -39,25 +45,25 @@ class TypeDescriptor:
|
|
|
39
45
|
self.clazz = cls
|
|
40
46
|
self.method = method
|
|
41
47
|
self.decorators: list[DecoratorDescriptor] = Decorators.get(method)
|
|
42
|
-
self.
|
|
48
|
+
self.param_types : list[Type] = []
|
|
43
49
|
|
|
44
50
|
type_hints = get_type_hints(method)
|
|
45
51
|
sig = signature(method)
|
|
46
52
|
|
|
47
|
-
for name,
|
|
53
|
+
for name, _ in sig.parameters.items():
|
|
48
54
|
if name != 'self':
|
|
49
|
-
self.
|
|
55
|
+
self.param_types.append(type_hints.get(name, object))
|
|
50
56
|
|
|
51
|
-
self.
|
|
57
|
+
self.return_type = type_hints.get('return', None)
|
|
52
58
|
|
|
53
|
-
def get_decorator(self, decorator):
|
|
59
|
+
def get_decorator(self, decorator: Callable) -> Optional[DecoratorDescriptor]:
|
|
54
60
|
for dec in self.decorators:
|
|
55
61
|
if dec.decorator is decorator:
|
|
56
62
|
return dec
|
|
57
63
|
|
|
58
64
|
return None
|
|
59
65
|
|
|
60
|
-
def has_decorator(self, decorator):
|
|
66
|
+
def has_decorator(self, decorator: Callable) -> bool:
|
|
61
67
|
for dec in self.decorators:
|
|
62
68
|
if dec.decorator is decorator:
|
|
63
69
|
return True
|
|
@@ -67,11 +73,10 @@ class TypeDescriptor:
|
|
|
67
73
|
def __str__(self):
|
|
68
74
|
return f"Method({self.method.__name__})"
|
|
69
75
|
|
|
70
|
-
# class methods
|
|
71
|
-
|
|
72
76
|
# class properties
|
|
73
77
|
|
|
74
78
|
_cache = WeakKeyDictionary()
|
|
79
|
+
_lock = threading.RLock()
|
|
75
80
|
|
|
76
81
|
# class methods
|
|
77
82
|
|
|
@@ -79,8 +84,12 @@ class TypeDescriptor:
|
|
|
79
84
|
def for_type(cls, clazz: Type) -> TypeDescriptor:
|
|
80
85
|
descriptor = cls._cache.get(clazz)
|
|
81
86
|
if descriptor is None:
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
with cls._lock:
|
|
88
|
+
descriptor = cls._cache.get(clazz)
|
|
89
|
+
if descriptor is None:
|
|
90
|
+
descriptor = TypeDescriptor(clazz)
|
|
91
|
+
cls._cache[clazz] = descriptor
|
|
92
|
+
|
|
84
93
|
return descriptor
|
|
85
94
|
|
|
86
95
|
# constructor
|
|
@@ -88,27 +97,25 @@ class TypeDescriptor:
|
|
|
88
97
|
def __init__(self, cls):
|
|
89
98
|
self.cls = cls
|
|
90
99
|
self.decorators = Decorators.get(cls)
|
|
91
|
-
self.methods: Dict[str, TypeDescriptor.MethodDescriptor] =
|
|
92
|
-
self.
|
|
100
|
+
self.methods: Dict[str, TypeDescriptor.MethodDescriptor] = {}
|
|
101
|
+
self.local_methods: Dict[str, TypeDescriptor.MethodDescriptor] = {}
|
|
93
102
|
|
|
94
103
|
# check superclasses
|
|
95
104
|
|
|
96
|
-
self.
|
|
105
|
+
self.super_types = [TypeDescriptor.for_type(x) for x in cls.__bases__ if not x is object]
|
|
97
106
|
|
|
98
|
-
for
|
|
99
|
-
self.methods = self.methods |
|
|
107
|
+
for super_type in self.super_types:
|
|
108
|
+
self.methods = self.methods | super_type.methods
|
|
100
109
|
|
|
101
110
|
# methods
|
|
102
111
|
|
|
103
112
|
for name, member in self._get_local_members(cls):
|
|
104
113
|
method = TypeDescriptor.MethodDescriptor(cls, member)
|
|
105
|
-
self.
|
|
114
|
+
self.local_methods[name] = method
|
|
106
115
|
self.methods[name] = method
|
|
107
116
|
|
|
108
117
|
# internal
|
|
109
118
|
|
|
110
|
-
#isinstance(attr, classmethod)
|
|
111
|
-
|
|
112
119
|
def _get_local_members(self, cls):
|
|
113
120
|
return [
|
|
114
121
|
(name, value)
|
|
@@ -118,22 +125,28 @@ class TypeDescriptor:
|
|
|
118
125
|
|
|
119
126
|
# public
|
|
120
127
|
|
|
121
|
-
def get_decorator(self, decorator) -> Optional[DecoratorDescriptor]:
|
|
128
|
+
def get_decorator(self, decorator: Callable) -> Optional[DecoratorDescriptor]:
|
|
122
129
|
for dec in self.decorators:
|
|
123
130
|
if dec.decorator is decorator:
|
|
124
131
|
return dec
|
|
125
132
|
|
|
126
133
|
return None
|
|
127
134
|
|
|
128
|
-
def has_decorator(self, decorator) -> bool:
|
|
135
|
+
def has_decorator(self, decorator: Callable) -> bool:
|
|
129
136
|
for dec in self.decorators:
|
|
130
|
-
if dec.decorator
|
|
137
|
+
if dec.decorator is decorator:
|
|
131
138
|
return True
|
|
132
139
|
|
|
133
140
|
return False
|
|
134
141
|
|
|
135
|
-
def
|
|
136
|
-
|
|
142
|
+
def get_methods(self, local = False) -> list[TypeDescriptor.MethodDescriptor]:
|
|
143
|
+
if local:
|
|
144
|
+
return list(self.local_methods.values())
|
|
145
|
+
else:
|
|
146
|
+
return list(self.methods.values())
|
|
137
147
|
|
|
138
|
-
def get_method(self, name) -> Optional[MethodDescriptor]:
|
|
139
|
-
|
|
148
|
+
def get_method(self, name: str, local = False) -> Optional[TypeDescriptor.MethodDescriptor]:
|
|
149
|
+
if local:
|
|
150
|
+
return self.local_methods.get(name, None)
|
|
151
|
+
else:
|
|
152
|
+
return self.methods.get(name, None)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: A DI and AOP library for Python
|
|
5
5
|
Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -25,7 +25,7 @@ License: MIT License
|
|
|
25
25
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
|
|
28
|
-
Requires-Python: >=3.
|
|
28
|
+
Requires-Python: >=3.9
|
|
29
29
|
Description-Content-Type: text/markdown
|
|
30
30
|
License-File: LICENSE
|
|
31
31
|
Dynamic: license-file
|
|
@@ -34,11 +34,14 @@ Dynamic: license-file
|
|
|
34
34
|
|
|
35
35
|

|
|
36
36
|

|
|
37
|
-
|
|
37
|
+

|
|
38
|
+

|
|
39
|
+

|
|
38
40
|
|
|
39
41
|
## Table of Contents
|
|
40
42
|
|
|
41
43
|
- [Introduction](#aspyx)
|
|
44
|
+
- [Installation](#installation)
|
|
42
45
|
- [Registration](#registration)
|
|
43
46
|
- [Class](#class)
|
|
44
47
|
- [Class Factory](#class-factory)
|
|
@@ -53,30 +56,32 @@ Dynamic: license-file
|
|
|
53
56
|
- [Custom scopes](#custom-scopes)
|
|
54
57
|
- [AOP](#aop)
|
|
55
58
|
- [Configuration](#configuration)
|
|
59
|
+
- [Reflection](#reflection)
|
|
60
|
+
- [Version History](#version-history)
|
|
56
61
|
|
|
57
62
|
# Introduction
|
|
58
63
|
|
|
59
64
|
Aspyx is a small python libary, that adds support for both dependency injection and aop.
|
|
60
65
|
|
|
61
66
|
The following features are supported
|
|
62
|
-
- constructor injection
|
|
63
|
-
- method injection
|
|
67
|
+
- constructor and setter injection
|
|
64
68
|
- post processors
|
|
65
69
|
- factory classes and methods
|
|
66
70
|
- support for eager construction
|
|
67
|
-
- support for singleton and
|
|
71
|
+
- support for singleton and request scopes
|
|
68
72
|
- possibilty to add custom scopes
|
|
69
73
|
- lifecycle events methods
|
|
70
74
|
- bundling of injectable object sets by environment classes including recursive imports and inheritance
|
|
71
75
|
- container instances that relate to environment classes and manage the lifecylce of related objects
|
|
72
76
|
- hierarchical environments
|
|
73
77
|
|
|
78
|
+
The library is thread-safe!
|
|
79
|
+
|
|
74
80
|
Let's look at a simple example
|
|
75
81
|
|
|
76
82
|
```python
|
|
77
83
|
from aspyx.di import injectable, on_init, on_destroy, environment, Environment
|
|
78
84
|
|
|
79
|
-
|
|
80
85
|
@injectable()
|
|
81
86
|
class Foo:
|
|
82
87
|
def __init__(self):
|
|
@@ -85,13 +90,12 @@ class Foo:
|
|
|
85
90
|
def hello(msg: str):
|
|
86
91
|
print(f"hello {msg}")
|
|
87
92
|
|
|
88
|
-
|
|
89
93
|
@injectable() # eager and singleton by default
|
|
90
94
|
class Bar:
|
|
91
95
|
def __init__(self, foo: Foo): # will inject the Foo dependency
|
|
92
96
|
self.foo = foo
|
|
93
97
|
|
|
94
|
-
@on_init() # a lifecycle callback called
|
|
98
|
+
@on_init() # a lifecycle callback called after the constructor and all possible injections
|
|
95
99
|
def init(self):
|
|
96
100
|
...
|
|
97
101
|
|
|
@@ -101,41 +105,40 @@ class Bar:
|
|
|
101
105
|
|
|
102
106
|
@environment()
|
|
103
107
|
class SampleEnvironment:
|
|
104
|
-
# constructor
|
|
105
|
-
|
|
106
108
|
def __init__(self):
|
|
107
109
|
pass
|
|
108
110
|
|
|
109
|
-
|
|
110
111
|
# go, forrest
|
|
111
112
|
|
|
112
|
-
environment = SampleEnvironment
|
|
113
|
+
environment = Environment(SampleEnvironment)
|
|
113
114
|
|
|
114
115
|
bar = env.get(Bar)
|
|
115
|
-
|
|
116
|
+
|
|
117
|
+
bar.foo.hello("world")
|
|
116
118
|
```
|
|
117
119
|
|
|
118
|
-
The concepts should be pretty familiar
|
|
120
|
+
The concepts should be pretty familiar as well as the names which are a combination of Spring and Angular names :-)
|
|
119
121
|
|
|
120
122
|
Let's add some aspects...
|
|
121
123
|
|
|
122
124
|
```python
|
|
125
|
+
|
|
123
126
|
@advice
|
|
124
127
|
class SampleAdvice:
|
|
125
|
-
def __init__(self):
|
|
128
|
+
def __init__(self): # could inject additional stuff
|
|
126
129
|
pass
|
|
127
130
|
|
|
128
131
|
@before(methods().named("hello").of_type(Foo))
|
|
129
|
-
def
|
|
132
|
+
def call_before(self, invocation: Invocation):
|
|
130
133
|
print("before Foo.hello(...)")
|
|
131
134
|
|
|
132
135
|
@error(methods().named("hello").of_type(Foo))
|
|
133
|
-
def
|
|
136
|
+
def call_error(self, invocation: Invocation):
|
|
134
137
|
print("error Foo.hello(...)")
|
|
135
138
|
print(invocation.exception)
|
|
136
139
|
|
|
137
140
|
@around(methods().named("hello"))
|
|
138
|
-
def
|
|
141
|
+
def call_around(self, invocation: Invocation):
|
|
139
142
|
print("around Foo.hello()")
|
|
140
143
|
|
|
141
144
|
return invocation.proceed()
|
|
@@ -150,6 +153,14 @@ The invocation parameter stores the complete context of the current execution, w
|
|
|
150
153
|
|
|
151
154
|
Let's look at the details
|
|
152
155
|
|
|
156
|
+
# Installation
|
|
157
|
+
|
|
158
|
+
`pip install aspyx`
|
|
159
|
+
|
|
160
|
+
The library is tested with all Python version > 3.9
|
|
161
|
+
|
|
162
|
+
Ready to go...
|
|
163
|
+
|
|
153
164
|
# Registration
|
|
154
165
|
|
|
155
166
|
Different mechanisms are available that make classes eligible for injection
|
|
@@ -171,12 +182,21 @@ All referenced types will be injected by the environemnt.
|
|
|
171
182
|
|
|
172
183
|
Only eligible types are allowed, of course!
|
|
173
184
|
|
|
185
|
+
The decorator accepts the keyword arguments
|
|
186
|
+
- `eager : boolean`
|
|
187
|
+
if `True`, the container will create the instances automatically while booting the environment. This is the default.
|
|
188
|
+
- `scope: str`
|
|
189
|
+
the name of a - registered - scope which will determine how often instances will be created.
|
|
174
190
|
|
|
175
|
-
The
|
|
176
|
-
- `
|
|
177
|
-
|
|
191
|
+
The following scopes are implemented out of the box:
|
|
192
|
+
- `singleton`
|
|
193
|
+
objects are created once inside an environment and cached. This is the default.
|
|
194
|
+
- `request`
|
|
195
|
+
obejcts are created on every injection request
|
|
196
|
+
- `thread`
|
|
197
|
+
objects are cerated and cached with respect to the current thread.
|
|
178
198
|
|
|
179
|
-
Other scopes can be defined. Please check the corresponding chapter.
|
|
199
|
+
Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
|
|
180
200
|
|
|
181
201
|
## Class Factory
|
|
182
202
|
|
|
@@ -292,7 +312,7 @@ Different decorators are implemented, that call methods with computed values
|
|
|
292
312
|
the method is called with all resolved parameter types ( same as the constructor call)
|
|
293
313
|
- `@inject_environment`
|
|
294
314
|
the method is called with the creating environment as a single parameter
|
|
295
|
-
- `@
|
|
315
|
+
- `@inject_value()`
|
|
296
316
|
the method is called with a resolved configuration value. Check the corresponding chapter
|
|
297
317
|
|
|
298
318
|
**Example**:
|
|
@@ -313,9 +333,11 @@ class Foo:
|
|
|
313
333
|
|
|
314
334
|
## Lifecycle methods
|
|
315
335
|
|
|
316
|
-
It is possible to mark specific
|
|
336
|
+
It is possible to mark specific lifecyle methods.
|
|
317
337
|
- `@on_init()`
|
|
318
338
|
called after the constructor and all other injections.
|
|
339
|
+
- `@on_running()`
|
|
340
|
+
called an environment has initialized all eager objects.
|
|
319
341
|
- `@on_destroy()`
|
|
320
342
|
called during shutdown of the environment
|
|
321
343
|
|
|
@@ -386,17 +408,17 @@ class SampleAdvice:
|
|
|
386
408
|
pass
|
|
387
409
|
|
|
388
410
|
@before(methods().named("hello").of_type(Foo))
|
|
389
|
-
def
|
|
411
|
+
def call_before(self, invocation: Invocation):
|
|
390
412
|
# arguments: invocation.args
|
|
391
413
|
print("before Foo.hello(...)")
|
|
392
414
|
|
|
393
415
|
@error(methods().named("hello").of_type(Foo))
|
|
394
|
-
def
|
|
416
|
+
def call_error(self, invocation: Invocation):
|
|
395
417
|
print("error Foo.hello(...)")
|
|
396
418
|
print(invocation.exception)
|
|
397
419
|
|
|
398
420
|
@around(methods().named("hello"))
|
|
399
|
-
def
|
|
421
|
+
def call_around(self, invocation: Invocation):
|
|
400
422
|
print("around Foo.hello()")
|
|
401
423
|
|
|
402
424
|
return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
|
|
@@ -417,7 +439,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
|
|
|
417
439
|
It is essential for `around` methods to call `proceed()` on the invocation, which will call the next around method in the chain and finally the original method.
|
|
418
440
|
If the `proceed` is called with parameters, they will replace the original parameters!
|
|
419
441
|
|
|
420
|
-
The
|
|
442
|
+
The argument list to the corresponding decorators control, how aspects are associated with which methods.
|
|
421
443
|
A fluent interface is used describe the mapping.
|
|
422
444
|
The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
|
|
423
445
|
|
|
@@ -431,11 +453,24 @@ Both add the fluent methods:
|
|
|
431
453
|
- `decorated_with(type: Type)`
|
|
432
454
|
defines decorators on methods or classes
|
|
433
455
|
|
|
434
|
-
The fluent methods `named`, `matches` and `of_type` can be called multiple
|
|
456
|
+
The fluent methods `named`, `matches` and `of_type` can be called multiple times!
|
|
457
|
+
|
|
458
|
+
**Example**:
|
|
459
|
+
|
|
460
|
+
```python
|
|
461
|
+
@injectable()
|
|
462
|
+
class TransactionAdvice:
|
|
463
|
+
def __init__(self):
|
|
464
|
+
pass
|
|
465
|
+
|
|
466
|
+
@around(methods().decorated_with(transactional), classes().decorated_with(transactional))
|
|
467
|
+
def establish_transaction(self, invocation: Invocation):
|
|
468
|
+
...
|
|
469
|
+
```
|
|
435
470
|
|
|
436
471
|
# Configuration
|
|
437
472
|
|
|
438
|
-
It is possible to inject configuration values, by decorating methods with `@value(<name>)` given a configuration key.
|
|
473
|
+
It is possible to inject configuration values, by decorating methods with `@inject-value(<name>)` given a configuration key.
|
|
439
474
|
|
|
440
475
|
```python
|
|
441
476
|
@injectable()
|
|
@@ -452,10 +487,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
|
|
|
452
487
|
|
|
453
488
|
```python
|
|
454
489
|
class ConfigurationSource(ABC):
|
|
455
|
-
def __init__(self
|
|
456
|
-
manager._register(self)
|
|
490
|
+
def __init__(self):
|
|
457
491
|
pass
|
|
458
492
|
|
|
493
|
+
...
|
|
494
|
+
|
|
459
495
|
@abstractmethod
|
|
460
496
|
def load(self) -> dict:
|
|
461
497
|
pass
|
|
@@ -467,14 +503,12 @@ As a default environment variables are already supported.
|
|
|
467
503
|
|
|
468
504
|
Other sources can be added dynamically by just registering them.
|
|
469
505
|
|
|
506
|
+
**Example**:
|
|
470
507
|
```python
|
|
471
508
|
@injectable()
|
|
472
509
|
class SampleConfigurationSource(ConfigurationSource):
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
def __init__(self, manager: ConfigurationManager):
|
|
476
|
-
super().__init__(manager)
|
|
477
|
-
|
|
510
|
+
def __init__(self):
|
|
511
|
+
super().__init__()
|
|
478
512
|
|
|
479
513
|
def load(self) -> dict:
|
|
480
514
|
return {
|
|
@@ -487,7 +521,61 @@ class SampleConfigurationSource(ConfigurationSource):
|
|
|
487
521
|
}
|
|
488
522
|
```
|
|
489
523
|
|
|
524
|
+
# Reflection
|
|
525
|
+
|
|
526
|
+
As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
|
|
527
|
+
|
|
528
|
+
After beeing instatiated with
|
|
529
|
+
|
|
530
|
+
```python
|
|
531
|
+
TypeDescriptor.for_type(<type>)
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
it offers the methods
|
|
535
|
+
- `get_methods(local=False)`
|
|
536
|
+
return a list of either local or overall methods
|
|
537
|
+
- `get_method(name: str, local=False)`
|
|
538
|
+
return a single either local or overall method
|
|
539
|
+
- `has_decorator(decorator: Callable) -> bool`
|
|
540
|
+
return `True`, if the class is decorated with the specified decrator
|
|
541
|
+
- `get_decorator(decorator) -> Optional[DecoratorDescriptor]`
|
|
542
|
+
return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
|
|
543
|
+
|
|
544
|
+
The returned method descriptors offer:
|
|
545
|
+
- `param_types`
|
|
546
|
+
list of arg types
|
|
547
|
+
- `return_type`
|
|
548
|
+
the retur type
|
|
549
|
+
- `has_decorator(decorator: Callable) -> bool`
|
|
550
|
+
return `True`, if the method is decorated with the specified decrator
|
|
551
|
+
- `get_decorator(decorator: Callable) -> Optional[DecoratorDescriptor]`
|
|
552
|
+
return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
|
|
553
|
+
|
|
554
|
+
The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
|
|
555
|
+
|
|
556
|
+
Whenver you define a custom decorator, you will need to register it accordingly.
|
|
557
|
+
|
|
558
|
+
**Example**:
|
|
559
|
+
```python
|
|
560
|
+
def transactional():
|
|
561
|
+
def decorator(func):
|
|
562
|
+
Decorators.add(func, transactional)
|
|
563
|
+
return func
|
|
564
|
+
|
|
565
|
+
return decorator
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
# Version History
|
|
570
|
+
|
|
571
|
+
**1.0.1**
|
|
572
|
+
|
|
573
|
+
- some internal refactorings
|
|
574
|
+
|
|
575
|
+
**1.1.0**
|
|
490
576
|
|
|
577
|
+
- added `@on_running()` callback
|
|
578
|
+
- added `thread` scope
|
|
491
579
|
|
|
492
580
|
|
|
493
581
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
aspyx/di/__init__.py,sha256=U2-4JnCmSO_IXCnN1jeig9nuWfJN__z9yRzl1WexQJk,927
|
|
2
|
+
aspyx/di/di.py,sha256=VVLOXhTKJer2cV0rNEOqMLyMigsNMEQ7xoc-6M7a8wU,34296
|
|
3
|
+
aspyx/di/aop/__init__.py,sha256=nOABex49zSyMZ2w1ezwX3Q3yrOcQRSDjDtSj0DwKVbQ,233
|
|
4
|
+
aspyx/di/aop/aop.py,sha256=1RUgijk8RsiXWTizfNQX1IfHF7b96kCPdUgahX_J4Io,14457
|
|
5
|
+
aspyx/di/configuration/__init__.py,sha256=Zw7h-OlbJD7LyJvzkgyF0EmVqra6oN_Pt0HuUdjTPTA,249
|
|
6
|
+
aspyx/di/configuration/configuration.py,sha256=jJJSHRG2wOIvle23Xc8CQ4WM7lp_xCL8-ENVZO603uQ,5682
|
|
7
|
+
aspyx/di/util/__init__.py,sha256=8H2yKkXu3nkRGeTerb8ialzKGfvzUx44XUWFUYcYuQM,125
|
|
8
|
+
aspyx/di/util/stringbuilder.py,sha256=JywkLxZfaQUUWjSB5wvqA6a6Cfs3sW1jbaZ1z4U0-CQ,540
|
|
9
|
+
aspyx/reflection/__init__.py,sha256=r2sNJrfHDpuqaIYu4fTYsoo046gpgn4VTd7bsS3mQJY,282
|
|
10
|
+
aspyx/reflection/proxy.py,sha256=zJ6Psd6zWfFABdrKOf4cULt3gibyqCRdcR6z8WKIkzE,1982
|
|
11
|
+
aspyx/reflection/reflection.py,sha256=HfPFd_FehTGmgF6NA-IJ_aHePE6GXM1ag7K3h8YIXjI,4600
|
|
12
|
+
aspyx-1.1.0.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
|
|
13
|
+
aspyx-1.1.0.dist-info/METADATA,sha256=rdeICMYyQSJmSZDJZrsdw6l5ZVrQrxvAPS_z47ObM-Y,17989
|
|
14
|
+
aspyx-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
+
aspyx-1.1.0.dist-info/top_level.txt,sha256=A_ZwhBY_ybIgjZlztd44eaOrWqkJAndiqjGlbJ3tR_I,6
|
|
16
|
+
aspyx-1.1.0.dist-info/RECORD,,
|
aspyx-1.0.0.dist-info/RECORD
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
aspyx/di/__init__.py,sha256=US6i6s94TIpk4eiRFKYm9JB0NnU9F_3RAoIoXa-OnTI,800
|
|
2
|
-
aspyx/di/di.py,sha256=IdMNkPmMaVhRNr8heE8msGO3KYEc69pf2pjcJ8_rp4E,31169
|
|
3
|
-
aspyx/di/aop/__init__.py,sha256=2mv6o38HHoBzF_nx8rsrvuOlUlNgYITmNRcQJVT2Bk8,213
|
|
4
|
-
aspyx/di/aop/aop.py,sha256=stgSLSBpOd9XV_qo7KEazinq8Ul_csWbCx1w30dioWw,14647
|
|
5
|
-
aspyx/di/configuration/__init__.py,sha256=YgUk1bHVYq1EJ9W4D_CJ9ZM-3kje-vzPNMxBdUm0Vlg,211
|
|
6
|
-
aspyx/di/configuration/configuration.py,sha256=6EG2kf2v5pk9fTeCQUD5j6Jo5v00CFd5Nz7qXyYmxKw,5725
|
|
7
|
-
aspyx/reflection/__init__.py,sha256=jZAjw5NDrtXENmlBsQ0u9MobcNkfEuT0Oey8IWNYh34,204
|
|
8
|
-
aspyx/reflection/proxy.py,sha256=Re643BJAgbQjmDXO5JAvj4mu3RTpe_HF3TcFQ1euArg,1910
|
|
9
|
-
aspyx/reflection/reflection.py,sha256=LIUu-SxA_GJ1JurbQKTrvMUrE05OOcXxXOu65zRWubw,4004
|
|
10
|
-
aspyx-1.0.0.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
|
|
11
|
-
aspyx-1.0.0.dist-info/METADATA,sha256=3AuH9LBUHpPfxu9BcmawTaaJIHTBWwHuFpP_gBasyh0,15227
|
|
12
|
-
aspyx-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
aspyx-1.0.0.dist-info/top_level.txt,sha256=A_ZwhBY_ybIgjZlztd44eaOrWqkJAndiqjGlbJ3tR_I,6
|
|
14
|
-
aspyx-1.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|