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
aspyx/di/__init__.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
This module provides dependency injection and aop capabilities for Python applications.
|
|
3
|
+
"""
|
|
4
|
+
from .di import InjectorException, AbstractCallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, environment, inject, create, on_init, on_running, on_destroy, inject_environment, Factory, PostProcessor
|
|
2
5
|
|
|
3
6
|
# import something from the subpackages, so that teh decorators are executed
|
|
4
7
|
|
|
5
|
-
from
|
|
6
|
-
from
|
|
8
|
+
from .configuration import ConfigurationManager
|
|
9
|
+
from .aop import before
|
|
7
10
|
|
|
8
11
|
imports = [ConfigurationManager, before]
|
|
9
12
|
|
|
@@ -18,12 +21,13 @@ __all__ = [
|
|
|
18
21
|
"create",
|
|
19
22
|
|
|
20
23
|
"on_init",
|
|
24
|
+
"on_running",
|
|
21
25
|
"on_destroy",
|
|
22
26
|
"inject_environment",
|
|
23
27
|
"Factory",
|
|
24
28
|
"PostProcessor",
|
|
25
|
-
"
|
|
29
|
+
"AbstractCallableProcessor",
|
|
26
30
|
"LifecycleCallable",
|
|
27
31
|
"InjectorException",
|
|
28
32
|
"Lifecycle"
|
|
29
|
-
]
|
|
33
|
+
]
|
aspyx/di/aop/__init__.py
CHANGED
aspyx/di/aop/aop.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides aspect-oriented programming (AOP) capabilities for Python applications.
|
|
3
|
+
"""
|
|
1
4
|
from __future__ import annotations
|
|
2
5
|
|
|
3
6
|
from abc import ABC, abstractmethod
|
|
@@ -16,8 +19,8 @@ from aspyx.di import injectable, Providers, ClassInstanceProvider, Environment,
|
|
|
16
19
|
|
|
17
20
|
class AOPException(Exception):
|
|
18
21
|
"""
|
|
19
|
-
Exception raised for errors in the aop logic.
|
|
20
|
-
|
|
22
|
+
Exception raised for errors in the aop logic.
|
|
23
|
+
"""
|
|
21
24
|
|
|
22
25
|
class AspectType(Enum):
|
|
23
26
|
"""
|
|
@@ -62,6 +65,8 @@ class AspectTarget(ABC):
|
|
|
62
65
|
def __init__(self):
|
|
63
66
|
self._clazz = None
|
|
64
67
|
self._instance = None
|
|
68
|
+
self._function = None
|
|
69
|
+
self._type = None
|
|
65
70
|
|
|
66
71
|
self.patterns = []
|
|
67
72
|
self.names = []
|
|
@@ -70,12 +75,10 @@ class AspectTarget(ABC):
|
|
|
70
75
|
|
|
71
76
|
self.other : list[AspectTarget] = []
|
|
72
77
|
|
|
73
|
-
pass
|
|
74
|
-
|
|
75
78
|
# abstract
|
|
76
79
|
|
|
77
80
|
def _matches(self, clazz : Type, func):
|
|
78
|
-
if not self.
|
|
81
|
+
if not self._matches_self(clazz, func):
|
|
79
82
|
for target in self.other:
|
|
80
83
|
if target._matches(clazz, func):
|
|
81
84
|
return True
|
|
@@ -85,7 +88,7 @@ class AspectTarget(ABC):
|
|
|
85
88
|
return True
|
|
86
89
|
|
|
87
90
|
@abstractmethod
|
|
88
|
-
def
|
|
91
|
+
def _matches_self(self, clazz: Type, func):
|
|
89
92
|
pass
|
|
90
93
|
|
|
91
94
|
# protected
|
|
@@ -112,7 +115,7 @@ class AspectTarget(ABC):
|
|
|
112
115
|
def decorated_with(self, decorator):
|
|
113
116
|
self.decorators.append(decorator)
|
|
114
117
|
return self
|
|
115
|
-
|
|
118
|
+
|
|
116
119
|
def matches(self, pattern: str):
|
|
117
120
|
"""
|
|
118
121
|
Matches the target against a pattern.
|
|
@@ -130,17 +133,10 @@ class ClassAspectTarget(AspectTarget):
|
|
|
130
133
|
__slots__ = [
|
|
131
134
|
]
|
|
132
135
|
|
|
133
|
-
# constructor
|
|
134
|
-
|
|
135
|
-
def __init__(self):
|
|
136
|
-
super().__init__()
|
|
137
|
-
|
|
138
|
-
pass
|
|
139
|
-
|
|
140
136
|
# public
|
|
141
137
|
|
|
142
|
-
def
|
|
143
|
-
|
|
138
|
+
def _matches_self(self, clazz : Type, func):
|
|
139
|
+
class_descriptor = TypeDescriptor.for_type(clazz)
|
|
144
140
|
#descriptor = TypeDescriptor.for_type(func)
|
|
145
141
|
# type
|
|
146
142
|
|
|
@@ -151,7 +147,7 @@ class ClassAspectTarget(AspectTarget):
|
|
|
151
147
|
# decorators
|
|
152
148
|
|
|
153
149
|
if len(self.decorators) > 0:
|
|
154
|
-
if next((decorator for decorator in self.decorators if
|
|
150
|
+
if next((decorator for decorator in self.decorators if class_descriptor.has_decorator(decorator)), None) is None:
|
|
155
151
|
return False
|
|
156
152
|
|
|
157
153
|
# names
|
|
@@ -159,7 +155,7 @@ class ClassAspectTarget(AspectTarget):
|
|
|
159
155
|
if len(self.names) > 0:
|
|
160
156
|
if next((name for name in self.names if name == clazz.__name__), None) is None:
|
|
161
157
|
return False
|
|
162
|
-
|
|
158
|
+
|
|
163
159
|
# patterns
|
|
164
160
|
|
|
165
161
|
if len(self.patterns) > 0:
|
|
@@ -167,36 +163,20 @@ class ClassAspectTarget(AspectTarget):
|
|
|
167
163
|
return False
|
|
168
164
|
|
|
169
165
|
return True
|
|
170
|
-
|
|
171
|
-
# fluent
|
|
172
166
|
|
|
167
|
+
# fluent
|
|
173
168
|
|
|
174
|
-
|
|
175
169
|
class MethodAspectTarget(AspectTarget):
|
|
176
170
|
# properties
|
|
177
171
|
|
|
178
|
-
__slots__ = [
|
|
179
|
-
"_clazz",
|
|
180
|
-
"_instance",
|
|
181
|
-
"_type",
|
|
182
|
-
"_function",
|
|
183
|
-
"names",
|
|
184
|
-
"patterns",
|
|
185
|
-
"types",
|
|
186
|
-
"decorators",
|
|
187
|
-
]
|
|
188
|
-
|
|
189
|
-
# constructor
|
|
190
|
-
|
|
191
|
-
def __init__(self):
|
|
192
|
-
super().__init__()
|
|
172
|
+
__slots__ = [ ]
|
|
193
173
|
|
|
194
174
|
# public
|
|
195
175
|
|
|
196
|
-
def
|
|
176
|
+
def _matches_self(self, clazz : Type, func):
|
|
197
177
|
descriptor = TypeDescriptor.for_type(clazz)
|
|
198
178
|
|
|
199
|
-
|
|
179
|
+
method_descriptor = descriptor.get_method(func.__name__)
|
|
200
180
|
|
|
201
181
|
# type
|
|
202
182
|
|
|
@@ -207,7 +187,7 @@ class MethodAspectTarget(AspectTarget):
|
|
|
207
187
|
# decorators
|
|
208
188
|
|
|
209
189
|
if len(self.decorators) > 0:
|
|
210
|
-
if next((decorator for decorator in self.decorators if
|
|
190
|
+
if next((decorator for decorator in self.decorators if method_descriptor.has_decorator(decorator)), None) is None:
|
|
211
191
|
return False
|
|
212
192
|
|
|
213
193
|
# names
|
|
@@ -215,13 +195,13 @@ class MethodAspectTarget(AspectTarget):
|
|
|
215
195
|
if len(self.names) > 0:
|
|
216
196
|
if next((name for name in self.names if name == func.__name__), None) is None:
|
|
217
197
|
return False
|
|
218
|
-
|
|
198
|
+
|
|
219
199
|
# patterns
|
|
220
200
|
|
|
221
201
|
if len(self.patterns) > 0:
|
|
222
202
|
if next((pattern for pattern in self.patterns if re.fullmatch(pattern, func.__name__) is not None), None) is None:
|
|
223
203
|
return False
|
|
224
|
-
|
|
204
|
+
|
|
225
205
|
# yipee
|
|
226
206
|
|
|
227
207
|
return True
|
|
@@ -267,7 +247,7 @@ class FunctionJoinPoint(JoinPoint):
|
|
|
267
247
|
self.func = func
|
|
268
248
|
|
|
269
249
|
def call(self, invocation: 'Invocation'):
|
|
270
|
-
invocation.
|
|
250
|
+
invocation.current_join_point = self
|
|
271
251
|
|
|
272
252
|
return self.func(self.instance, invocation)
|
|
273
253
|
|
|
@@ -278,7 +258,7 @@ class MethodJoinPoint(FunctionJoinPoint):
|
|
|
278
258
|
super().__init__(instance, func, None)
|
|
279
259
|
|
|
280
260
|
def call(self, invocation: 'Invocation'):
|
|
281
|
-
invocation.
|
|
261
|
+
invocation.current_join_point = self
|
|
282
262
|
|
|
283
263
|
return self.func(*invocation.args, **invocation.kwargs)
|
|
284
264
|
|
|
@@ -302,20 +282,20 @@ class Invocation:
|
|
|
302
282
|
"kwargs",
|
|
303
283
|
"result",
|
|
304
284
|
"exception",
|
|
305
|
-
"
|
|
306
|
-
"
|
|
285
|
+
"join_points",
|
|
286
|
+
"current_join_point",
|
|
307
287
|
]
|
|
308
288
|
|
|
309
289
|
# constructor
|
|
310
290
|
|
|
311
|
-
def __init__(self,
|
|
291
|
+
def __init__(self, func, join_points: JoinPoints):
|
|
312
292
|
self.func = func
|
|
313
293
|
self.args : list[object] = []
|
|
314
294
|
self.kwargs = None
|
|
315
295
|
self.result = None
|
|
316
296
|
self.exception = None
|
|
317
|
-
self.
|
|
318
|
-
self.
|
|
297
|
+
self.join_points = join_points
|
|
298
|
+
self.current_join_point = None
|
|
319
299
|
|
|
320
300
|
def call(self, *args, **kwargs):
|
|
321
301
|
# remember args
|
|
@@ -325,28 +305,28 @@ class Invocation:
|
|
|
325
305
|
|
|
326
306
|
# run all before
|
|
327
307
|
|
|
328
|
-
for
|
|
329
|
-
|
|
308
|
+
for join_point in self.join_points.before:
|
|
309
|
+
join_point.call(self)
|
|
330
310
|
|
|
331
311
|
# run around's with the method being the last aspect!
|
|
332
312
|
|
|
333
313
|
try:
|
|
334
|
-
self.result = self.
|
|
314
|
+
self.result = self.join_points.around[0].call(self) # will follow the proceed chain
|
|
335
315
|
|
|
336
316
|
except Exception as e:
|
|
337
317
|
self.exception = e
|
|
338
|
-
for
|
|
339
|
-
|
|
318
|
+
for join_point in self.join_points.error:
|
|
319
|
+
join_point.call(self)
|
|
340
320
|
|
|
341
321
|
# run all before
|
|
342
322
|
|
|
343
|
-
for
|
|
344
|
-
|
|
323
|
+
for join_point in self.join_points.after:
|
|
324
|
+
join_point.call(self)
|
|
345
325
|
|
|
346
326
|
if self.exception is not None:
|
|
347
327
|
raise self.exception # rethrow the error
|
|
348
|
-
|
|
349
|
-
|
|
328
|
+
|
|
329
|
+
return self.result
|
|
350
330
|
|
|
351
331
|
def proceed(self, *args, **kwargs):
|
|
352
332
|
"""
|
|
@@ -358,7 +338,7 @@ class Invocation:
|
|
|
358
338
|
|
|
359
339
|
# next one please...
|
|
360
340
|
|
|
361
|
-
return self.
|
|
341
|
+
return self.current_join_point.next.call(self)
|
|
362
342
|
|
|
363
343
|
@injectable()
|
|
364
344
|
class Advice:
|
|
@@ -374,7 +354,7 @@ class Advice:
|
|
|
374
354
|
# constructor
|
|
375
355
|
|
|
376
356
|
def __init__(self):
|
|
377
|
-
self.cache : Dict[Type, Dict[Callable,JoinPoints]] =
|
|
357
|
+
self.cache : Dict[Type, Dict[Callable,JoinPoints]] = {}
|
|
378
358
|
self.lock = threading.RLock()
|
|
379
359
|
|
|
380
360
|
# methods
|
|
@@ -391,7 +371,7 @@ class Advice:
|
|
|
391
371
|
|
|
392
372
|
return aspects
|
|
393
373
|
|
|
394
|
-
def
|
|
374
|
+
def join_points4(self, instance, environment: Environment) -> Dict[Callable,JoinPoints]:
|
|
395
375
|
clazz = type(instance)
|
|
396
376
|
|
|
397
377
|
result = self.cache.get(clazz, None)
|
|
@@ -400,18 +380,18 @@ class Advice:
|
|
|
400
380
|
result = self.cache.get(clazz, None)
|
|
401
381
|
|
|
402
382
|
if result is None:
|
|
403
|
-
result =
|
|
383
|
+
result = {}
|
|
404
384
|
|
|
405
|
-
for
|
|
406
|
-
|
|
407
|
-
if
|
|
408
|
-
result[member] =
|
|
385
|
+
for _, member in inspect.getmembers(clazz, predicate=inspect.isfunction):
|
|
386
|
+
join_points = self.compute_join_points(clazz, member, environment)
|
|
387
|
+
if join_points is not None:
|
|
388
|
+
result[member] = join_points
|
|
409
389
|
|
|
410
390
|
self.cache[clazz] = result
|
|
411
391
|
|
|
412
392
|
# add around methods
|
|
413
393
|
|
|
414
|
-
value =
|
|
394
|
+
value = {}
|
|
415
395
|
|
|
416
396
|
for key, cjp in result.items():
|
|
417
397
|
jp = JoinPoints(
|
|
@@ -432,7 +412,7 @@ class Advice:
|
|
|
432
412
|
|
|
433
413
|
return value
|
|
434
414
|
|
|
435
|
-
def
|
|
415
|
+
def compute_join_points(self, clazz, member, environment: Environment) -> Optional[JoinPoints]:
|
|
436
416
|
befores = self.collect(clazz, member, AspectType.BEFORE, environment)
|
|
437
417
|
arounds = self.collect(clazz, member, AspectType.AROUND, environment)
|
|
438
418
|
afters = self.collect(clazz, member, AspectType.AFTER, environment)
|
|
@@ -448,9 +428,9 @@ class Advice:
|
|
|
448
428
|
else:
|
|
449
429
|
return None
|
|
450
430
|
|
|
451
|
-
def
|
|
431
|
+
def sanity_check(clazz: Type, name: str):
|
|
452
432
|
m = TypeDescriptor.for_type(clazz).get_method(name)
|
|
453
|
-
if len(m.
|
|
433
|
+
if len(m.param_types) != 1 or m.param_types[0] != Invocation:
|
|
454
434
|
raise AOPException(f"Method {clazz.__name__}.{name} expected to have one parameter of type Invocation")
|
|
455
435
|
|
|
456
436
|
# decorators
|
|
@@ -469,7 +449,7 @@ def advice(cls):
|
|
|
469
449
|
if decorator is not None:
|
|
470
450
|
target = decorator.args[0]
|
|
471
451
|
target._clazz = cls
|
|
472
|
-
|
|
452
|
+
sanity_check(cls, name)
|
|
473
453
|
Advice.targets.append(target)
|
|
474
454
|
|
|
475
455
|
return cls
|
|
@@ -477,13 +457,13 @@ def advice(cls):
|
|
|
477
457
|
|
|
478
458
|
# decorators
|
|
479
459
|
|
|
480
|
-
def _register(decorator, targets: list[AspectTarget], func,
|
|
460
|
+
def _register(decorator, targets: list[AspectTarget], func, aspect_type: AspectType):
|
|
481
461
|
target = targets[0]
|
|
482
462
|
|
|
483
463
|
for i in range(1, len(targets)):
|
|
484
464
|
target._add(targets[i])
|
|
485
465
|
|
|
486
|
-
target.function(func).type(
|
|
466
|
+
target.function(func).type(aspect_type)
|
|
487
467
|
|
|
488
468
|
Decorators.add(func, decorator, target)
|
|
489
469
|
|
|
@@ -551,12 +531,12 @@ class AdviceProcessor(PostProcessor):
|
|
|
551
531
|
# implement
|
|
552
532
|
|
|
553
533
|
def process(self, instance: object, environment: Environment):
|
|
554
|
-
|
|
534
|
+
join_point_dict = self.advice.join_points4(instance, environment)
|
|
555
535
|
|
|
556
|
-
for member,
|
|
557
|
-
Environment.logger.debug(
|
|
536
|
+
for member, join_points in join_point_dict.items():
|
|
537
|
+
Environment.logger.debug("add aspects for %s:%s", type(instance), member.__name__)
|
|
558
538
|
|
|
559
539
|
def wrap(jp):
|
|
560
540
|
return lambda *args, **kwargs: Invocation(member, jp).call(*args, **kwargs)
|
|
561
541
|
|
|
562
|
-
setattr(instance, member.__name__, types.MethodType(wrap(
|
|
542
|
+
setattr(instance, member.__name__, types.MethodType(wrap(join_points), instance))
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration handling module.
|
|
3
|
+
"""
|
|
1
4
|
from __future__ import annotations
|
|
2
5
|
|
|
3
6
|
from abc import ABC, abstractmethod
|
|
@@ -5,8 +8,8 @@ import os
|
|
|
5
8
|
from typing import Optional, Type, TypeVar
|
|
6
9
|
from dotenv import load_dotenv
|
|
7
10
|
|
|
8
|
-
from aspyx.di import injectable, Environment,
|
|
9
|
-
from aspyx.di.di import order
|
|
11
|
+
from aspyx.di import injectable, Environment, LifecycleCallable, Lifecycle
|
|
12
|
+
from aspyx.di.di import order, inject
|
|
10
13
|
from aspyx.reflection import Decorators, DecoratorDescriptor, TypeDescriptor
|
|
11
14
|
|
|
12
15
|
T = TypeVar("T")
|
|
@@ -15,7 +18,6 @@ class ConfigurationException(Exception):
|
|
|
15
18
|
"""
|
|
16
19
|
Exception raised for errors in the configuration logic.
|
|
17
20
|
"""
|
|
18
|
-
pass
|
|
19
21
|
|
|
20
22
|
@injectable()
|
|
21
23
|
class ConfigurationManager:
|
|
@@ -30,7 +32,7 @@ class ConfigurationManager:
|
|
|
30
32
|
|
|
31
33
|
def __init__(self):
|
|
32
34
|
self.sources = []
|
|
33
|
-
self._data =
|
|
35
|
+
self._data = {}
|
|
34
36
|
self.coercions = {
|
|
35
37
|
int: int,
|
|
36
38
|
float: float,
|
|
@@ -82,17 +84,17 @@ class ConfigurationManager:
|
|
|
82
84
|
current = current[key]
|
|
83
85
|
|
|
84
86
|
return current
|
|
85
|
-
|
|
87
|
+
|
|
86
88
|
v = resolve_value(path, default)
|
|
87
89
|
|
|
88
90
|
if isinstance(v, type):
|
|
89
91
|
return v
|
|
90
|
-
|
|
92
|
+
|
|
91
93
|
if type in self.coercions:
|
|
92
94
|
try:
|
|
93
95
|
return self.coercions[type](v)
|
|
94
|
-
except Exception:
|
|
95
|
-
raise ConfigurationException(f"error during coercion to {type}")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
raise ConfigurationException(f"error during coercion to {type}") from e
|
|
96
98
|
else:
|
|
97
99
|
raise ConfigurationException(f"unknown coercion to {type}")
|
|
98
100
|
|
|
@@ -104,15 +106,18 @@ class ConfigurationSource(ABC):
|
|
|
104
106
|
|
|
105
107
|
__slots__ = []
|
|
106
108
|
|
|
107
|
-
def __init__(self
|
|
108
|
-
manager._register(self)
|
|
109
|
+
def __init__(self):
|
|
109
110
|
pass
|
|
110
111
|
|
|
112
|
+
@inject()
|
|
113
|
+
def set_manager(self, manager: ConfigurationManager):
|
|
114
|
+
manager._register(self)
|
|
115
|
+
|
|
111
116
|
@abstractmethod
|
|
112
117
|
def load(self) -> dict:
|
|
113
118
|
"""
|
|
114
|
-
return the configuration values of this source as a dictionary.
|
|
115
|
-
|
|
119
|
+
return the configuration values of this source as a dictionary.
|
|
120
|
+
"""
|
|
116
121
|
|
|
117
122
|
@injectable()
|
|
118
123
|
class EnvConfigurationSource(ConfigurationSource):
|
|
@@ -124,8 +129,8 @@ class EnvConfigurationSource(ConfigurationSource):
|
|
|
124
129
|
|
|
125
130
|
# constructor
|
|
126
131
|
|
|
127
|
-
def __init__(self
|
|
128
|
-
super().__init__(
|
|
132
|
+
def __init__(self):
|
|
133
|
+
super().__init__()
|
|
129
134
|
|
|
130
135
|
load_dotenv()
|
|
131
136
|
|
|
@@ -183,10 +188,10 @@ def value(key: str, default=None):
|
|
|
183
188
|
@injectable()
|
|
184
189
|
@order(9)
|
|
185
190
|
class ConfigurationLifecycleCallable(LifecycleCallable):
|
|
186
|
-
def __init__(self,
|
|
187
|
-
super().__init__(value,
|
|
191
|
+
def __init__(self, manager: ConfigurationManager):
|
|
192
|
+
super().__init__(value, Lifecycle.ON_INJECT)
|
|
188
193
|
|
|
189
194
|
self.manager = manager
|
|
190
195
|
|
|
191
196
|
def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
|
|
192
|
-
return [self.manager.get(decorator.args[0], method.
|
|
197
|
+
return [self.manager.get(decorator.args[0], method.param_types[0], decorator.args[1])]
|