aspyx 0.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 +29 -0
- aspyx/di/aop/__init__.py +11 -0
- aspyx/di/aop/aop.py +532 -0
- aspyx/di/configuration/__init__.py +8 -0
- aspyx/di/configuration/configuration.py +190 -0
- aspyx/di/di.py +1055 -0
- aspyx/reflection/__init__.py +9 -0
- aspyx/reflection/proxy.py +58 -0
- aspyx/reflection/reflection.py +134 -0
- aspyx-0.1.0.dist-info/METADATA +451 -0
- aspyx-0.1.0.dist-info/RECORD +14 -0
- aspyx-0.1.0.dist-info/WHEEL +5 -0
- aspyx-0.1.0.dist-info/licenses/LICENSE +21 -0
- aspyx-0.1.0.dist-info/top_level.txt +1 -0
aspyx/di/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .di import InjectorException, CallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, environment, inject, create, on_init, on_destroy, inject_environment, Factory, PostProcessor
|
|
2
|
+
|
|
3
|
+
# import something from the subpackages, so that teh decorators are executed
|
|
4
|
+
|
|
5
|
+
from aspyx.di.configuration import ConfigurationManager
|
|
6
|
+
from aspyx.di.aop import before
|
|
7
|
+
|
|
8
|
+
imports = [ConfigurationManager, before]
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ClassInstanceProvider",
|
|
12
|
+
"Providers",
|
|
13
|
+
"Environment",
|
|
14
|
+
"injectable",
|
|
15
|
+
"factory",
|
|
16
|
+
"environment",
|
|
17
|
+
"inject",
|
|
18
|
+
"create",
|
|
19
|
+
|
|
20
|
+
"on_init",
|
|
21
|
+
"on_destroy",
|
|
22
|
+
"inject_environment",
|
|
23
|
+
"Factory",
|
|
24
|
+
"PostProcessor",
|
|
25
|
+
"CallableProcessor",
|
|
26
|
+
"LifecycleCallable",
|
|
27
|
+
"InjectorException",
|
|
28
|
+
"Lifecycle"
|
|
29
|
+
]
|
aspyx/di/aop/__init__.py
ADDED
aspyx/di/aop/aop.py
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
import inspect
|
|
5
|
+
import re
|
|
6
|
+
import types
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from enum import auto, Enum
|
|
9
|
+
from typing import Optional, Dict, Type, Callable
|
|
10
|
+
|
|
11
|
+
from aspyx.reflection import Decorators, TypeDescriptor
|
|
12
|
+
from aspyx.di import injectable, Providers, ClassInstanceProvider, Environment, PostProcessor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AOPException(Exception):
|
|
16
|
+
"""
|
|
17
|
+
Exception raised for errors in the aop logic."""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
class AspectType(Enum):
|
|
21
|
+
"""
|
|
22
|
+
AspectType defines the types of aspect-oriented advice that can be applied to methods.
|
|
23
|
+
|
|
24
|
+
The available types are:
|
|
25
|
+
- BEFORE: Advice to be executed before the method invocation.
|
|
26
|
+
- AROUND: Advice that intercepts the method invocation.
|
|
27
|
+
- AFTER: Advice to be executed after the method invocation, regardless of its outcome.
|
|
28
|
+
- ERROR: Advice to be executed if the method invocation raises an exception.
|
|
29
|
+
|
|
30
|
+
These types are used to categorize and apply aspect logic at different points in a method's execution lifecycle.
|
|
31
|
+
"""
|
|
32
|
+
BEFORE = auto()
|
|
33
|
+
AROUND = auto()
|
|
34
|
+
AFTER = auto()
|
|
35
|
+
ERROR = auto()
|
|
36
|
+
|
|
37
|
+
class AspectTarget(ABC):
|
|
38
|
+
"""
|
|
39
|
+
AspectTarget defines the target for an aspect. It can be used to specify the class, method, and conditions under which the aspect should be applied.
|
|
40
|
+
It supports matching by class type, method name, patterns, decorators, and more.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# properties
|
|
44
|
+
|
|
45
|
+
__slots__ = [
|
|
46
|
+
"_function",
|
|
47
|
+
"_type",
|
|
48
|
+
|
|
49
|
+
"_clazz",
|
|
50
|
+
"_instance",
|
|
51
|
+
"names",
|
|
52
|
+
"patterns",
|
|
53
|
+
"types",
|
|
54
|
+
"decorators",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
# constructor
|
|
58
|
+
|
|
59
|
+
def __init__(self):
|
|
60
|
+
self._clazz = None
|
|
61
|
+
self._instance = None
|
|
62
|
+
|
|
63
|
+
self.patterns = []
|
|
64
|
+
self.names = []
|
|
65
|
+
self.types = []
|
|
66
|
+
self.decorators = []
|
|
67
|
+
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
# abstract
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def _matches(self, clazz : Type, func):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
# fluent
|
|
77
|
+
|
|
78
|
+
def function(self, func):
|
|
79
|
+
self._function = func
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
def type(self, type: AspectType):
|
|
83
|
+
self._type = type
|
|
84
|
+
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
def of_type(self, type: Type):
|
|
88
|
+
self.types.append(type)
|
|
89
|
+
return self
|
|
90
|
+
|
|
91
|
+
def decorated_with(self, decorator):
|
|
92
|
+
self.decorators.append(decorator)
|
|
93
|
+
return self
|
|
94
|
+
|
|
95
|
+
def matches(self, pattern: str):
|
|
96
|
+
"""
|
|
97
|
+
Matches the target against a pattern.
|
|
98
|
+
"""
|
|
99
|
+
self.patterns.append(re.compile(pattern))
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
def named(self, name: str):
|
|
103
|
+
self.names.append(name)
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
class ClassAspectTarget(AspectTarget):
|
|
107
|
+
# properties
|
|
108
|
+
|
|
109
|
+
__slots__ = [
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
# constructor
|
|
113
|
+
|
|
114
|
+
def __init__(self):
|
|
115
|
+
super().__init__()
|
|
116
|
+
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
# public
|
|
120
|
+
|
|
121
|
+
def _matches(self, clazz : Type, func):
|
|
122
|
+
descriptor = TypeDescriptor.for_type(clazz)
|
|
123
|
+
|
|
124
|
+
# type
|
|
125
|
+
|
|
126
|
+
if len(self.types) > 0:
|
|
127
|
+
if next((type for type in self.types if issubclass(clazz, type)), None) is None:
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
# decorators
|
|
131
|
+
|
|
132
|
+
if len(self.decorators) > 0:
|
|
133
|
+
if next((decorator for decorator in self.decorators if descriptor.has_decorator(decorator)), None) is None:
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
# names
|
|
137
|
+
|
|
138
|
+
if len(self.names) > 0:
|
|
139
|
+
if next((name for name in self.names if name == clazz.__name__), None) is None:
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
# patterns
|
|
143
|
+
|
|
144
|
+
if len(self.patterns) > 0:
|
|
145
|
+
if next((pattern for pattern in self.patterns if re.fullmatch(pattern, clazz.__name__) is not None), None) is None:
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
# yipee
|
|
149
|
+
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
# fluent
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class MethodAspectTarget(AspectTarget):
|
|
157
|
+
# properties
|
|
158
|
+
|
|
159
|
+
__slots__ = [
|
|
160
|
+
"_clazz",
|
|
161
|
+
"_instance",
|
|
162
|
+
"_type",
|
|
163
|
+
"_function",
|
|
164
|
+
"names",
|
|
165
|
+
"patterns",
|
|
166
|
+
"types",
|
|
167
|
+
"decorators",
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
# constructor
|
|
171
|
+
|
|
172
|
+
def __init__(self):
|
|
173
|
+
super().__init__()
|
|
174
|
+
|
|
175
|
+
# public
|
|
176
|
+
|
|
177
|
+
def _matches(self, clazz : Type, func):
|
|
178
|
+
descriptor = TypeDescriptor.for_type(clazz)
|
|
179
|
+
|
|
180
|
+
methodDescriptor = descriptor.get_method(func.__name__)
|
|
181
|
+
|
|
182
|
+
# type
|
|
183
|
+
|
|
184
|
+
if len(self.types) > 0:
|
|
185
|
+
if next((type for type in self.types if issubclass(clazz, type)), None) is None:
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
# decorators
|
|
189
|
+
|
|
190
|
+
if len(self.decorators) > 0:
|
|
191
|
+
if next((decorator for decorator in self.decorators if methodDescriptor.has_decorator(decorator)), None) is None:
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
# names
|
|
195
|
+
|
|
196
|
+
if len(self.names) > 0:
|
|
197
|
+
if next((name for name in self.names if name == func.__name__), None) is None:
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
# patterns
|
|
201
|
+
|
|
202
|
+
if len(self.patterns) > 0:
|
|
203
|
+
if next((pattern for pattern in self.patterns if re.fullmatch(pattern, func.__name__) is not None), None) is None:
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
# yipee
|
|
207
|
+
|
|
208
|
+
return True
|
|
209
|
+
|
|
210
|
+
def methods():
|
|
211
|
+
"""
|
|
212
|
+
Create a new AspectTarget instance to define method aspect targets.
|
|
213
|
+
"""
|
|
214
|
+
return MethodAspectTarget()
|
|
215
|
+
|
|
216
|
+
def classes():
|
|
217
|
+
"""
|
|
218
|
+
Create a new AspectTarget instance to define class aspect targets.
|
|
219
|
+
"""
|
|
220
|
+
return ClassAspectTarget()
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class JoinPoint:
|
|
224
|
+
__slots__ = [
|
|
225
|
+
"next",
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
# constructor
|
|
229
|
+
|
|
230
|
+
def __init__(self, next: 'JoinPoint'):
|
|
231
|
+
self.next = next
|
|
232
|
+
|
|
233
|
+
# public
|
|
234
|
+
|
|
235
|
+
def call(self, invocation: 'Invocation'):
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
class FunctionJoinPoint(JoinPoint):
|
|
239
|
+
__slots__ = [
|
|
240
|
+
"instance",
|
|
241
|
+
"func",
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
def __init__(self, instance, func, next: Optional['JoinPoint']):
|
|
245
|
+
super().__init__(next)
|
|
246
|
+
|
|
247
|
+
self.instance = instance
|
|
248
|
+
self.func = func
|
|
249
|
+
|
|
250
|
+
def call(self, invocation: 'Invocation'):
|
|
251
|
+
invocation.currentJoinPoint = self
|
|
252
|
+
|
|
253
|
+
return self.func(self.instance, invocation)
|
|
254
|
+
|
|
255
|
+
class MethodJoinPoint(FunctionJoinPoint):
|
|
256
|
+
__slots__ = []
|
|
257
|
+
|
|
258
|
+
def __init__(self, instance, func):
|
|
259
|
+
super().__init__(instance, func, None)
|
|
260
|
+
|
|
261
|
+
def call(self, invocation: 'Invocation'):
|
|
262
|
+
invocation.currentJoinPoint = self
|
|
263
|
+
|
|
264
|
+
return self.func(*invocation.args, **invocation.kwargs)
|
|
265
|
+
|
|
266
|
+
@dataclass
|
|
267
|
+
class JoinPoints:
|
|
268
|
+
before: list[JoinPoint]
|
|
269
|
+
around: list[JoinPoint]
|
|
270
|
+
error: list[JoinPoint]
|
|
271
|
+
after: list[JoinPoint]
|
|
272
|
+
|
|
273
|
+
class Invocation:
|
|
274
|
+
"""
|
|
275
|
+
Invocation stores the relevant data of a single method invocation.
|
|
276
|
+
It holds the arguments, keyword arguments, result, error, and the join points that define the aspect behavior.
|
|
277
|
+
"""
|
|
278
|
+
# properties
|
|
279
|
+
|
|
280
|
+
__slots__ = [
|
|
281
|
+
"func",
|
|
282
|
+
"args",
|
|
283
|
+
"kwargs",
|
|
284
|
+
"result",
|
|
285
|
+
"exception",
|
|
286
|
+
"joinPoints",
|
|
287
|
+
"currentJoinPoint",
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
# constructor
|
|
291
|
+
|
|
292
|
+
def __init__(self, func, joinPoints: JoinPoints):
|
|
293
|
+
self.func = func
|
|
294
|
+
self.args : list[object] = []
|
|
295
|
+
self.kwargs = None
|
|
296
|
+
self.result = None
|
|
297
|
+
self.exception = None
|
|
298
|
+
self.joinPoints = joinPoints
|
|
299
|
+
self.currentJoinPoint = None
|
|
300
|
+
|
|
301
|
+
def call(self, *args, **kwargs):
|
|
302
|
+
# remember args
|
|
303
|
+
|
|
304
|
+
self.args = args
|
|
305
|
+
self.kwargs = kwargs
|
|
306
|
+
|
|
307
|
+
# run all before
|
|
308
|
+
|
|
309
|
+
for joinPoint in self.joinPoints.before:
|
|
310
|
+
joinPoint.call(self)
|
|
311
|
+
|
|
312
|
+
# run around's with the method being the last aspect!
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
self.result = self.joinPoints.around[0].call(self) # will follow the proceed chain
|
|
316
|
+
|
|
317
|
+
except Exception as e:
|
|
318
|
+
self.exception = e
|
|
319
|
+
for joinPoint in self.joinPoints.error:
|
|
320
|
+
joinPoint.call(self)
|
|
321
|
+
|
|
322
|
+
# run all before
|
|
323
|
+
|
|
324
|
+
for joinPoint in self.joinPoints.after:
|
|
325
|
+
joinPoint.call(self)
|
|
326
|
+
|
|
327
|
+
if self.exception is not None:
|
|
328
|
+
raise self.exception # rethrow the error
|
|
329
|
+
else:
|
|
330
|
+
return self.result
|
|
331
|
+
|
|
332
|
+
def proceed(self, *args, **kwargs):
|
|
333
|
+
"""
|
|
334
|
+
Proceed to the next join point in the around chain up to the original method.
|
|
335
|
+
"""
|
|
336
|
+
if len(args) > 0 or len(kwargs) > 0: # as soon as we have args, we replace the current ones
|
|
337
|
+
self.args = args
|
|
338
|
+
self.kwargs = kwargs
|
|
339
|
+
|
|
340
|
+
# next one please...
|
|
341
|
+
|
|
342
|
+
return self.currentJoinPoint.next.call(self)
|
|
343
|
+
|
|
344
|
+
@injectable()
|
|
345
|
+
class Advice:
|
|
346
|
+
# static data
|
|
347
|
+
|
|
348
|
+
targets: list[AspectTarget] = []
|
|
349
|
+
|
|
350
|
+
__slots__ = [
|
|
351
|
+
"cache",
|
|
352
|
+
]
|
|
353
|
+
|
|
354
|
+
# constructor
|
|
355
|
+
|
|
356
|
+
def __init__(self):
|
|
357
|
+
self.cache : Dict[Type, Dict[Callable,JoinPoints]] = dict()
|
|
358
|
+
|
|
359
|
+
# methods
|
|
360
|
+
|
|
361
|
+
def collect(self, clazz, member, type: AspectType, environment: Environment):
|
|
362
|
+
aspects = [FunctionJoinPoint(environment.get(target._clazz), target._function, None) for target in Advice.targets if target._type == type and target._matches(clazz, member)]
|
|
363
|
+
|
|
364
|
+
# link
|
|
365
|
+
|
|
366
|
+
for i in range(0, len(aspects) - 1):
|
|
367
|
+
aspects[i].next = aspects[i + 1]
|
|
368
|
+
|
|
369
|
+
# done
|
|
370
|
+
|
|
371
|
+
return aspects
|
|
372
|
+
|
|
373
|
+
# TODO thread-safe
|
|
374
|
+
def joinPoints4(self, instance, environment: Environment) -> Dict[Callable,JoinPoints]:
|
|
375
|
+
clazz = type(instance)
|
|
376
|
+
|
|
377
|
+
result = self.cache.get(clazz, None)
|
|
378
|
+
if result is None:
|
|
379
|
+
result = dict()
|
|
380
|
+
|
|
381
|
+
for name, member in inspect.getmembers(clazz, predicate=inspect.isfunction):
|
|
382
|
+
joinPoints = self.computeJoinPoints(clazz, member, environment)
|
|
383
|
+
if joinPoints is not None:
|
|
384
|
+
result[member] = joinPoints
|
|
385
|
+
|
|
386
|
+
self.cache[clazz] = result
|
|
387
|
+
|
|
388
|
+
# add around methods
|
|
389
|
+
|
|
390
|
+
value = dict()
|
|
391
|
+
|
|
392
|
+
for key, cjp in result.items():
|
|
393
|
+
jp = JoinPoints(
|
|
394
|
+
before=cjp.before,
|
|
395
|
+
around=cjp.around,
|
|
396
|
+
error=cjp.error,
|
|
397
|
+
after=cjp.after)
|
|
398
|
+
|
|
399
|
+
# add method to around
|
|
400
|
+
|
|
401
|
+
jp.around.append(MethodJoinPoint(instance, key))
|
|
402
|
+
if len(jp.around) > 1:
|
|
403
|
+
jp.around[len(jp.around) - 2].next = jp.around[len(jp.around) - 1]
|
|
404
|
+
|
|
405
|
+
value[key] = jp
|
|
406
|
+
|
|
407
|
+
# done
|
|
408
|
+
|
|
409
|
+
return value
|
|
410
|
+
|
|
411
|
+
def computeJoinPoints(self, clazz, member, environment: Environment) -> Optional[JoinPoints]:
|
|
412
|
+
befores = self.collect(clazz, member, AspectType.BEFORE, environment)
|
|
413
|
+
arounds = self.collect(clazz, member, AspectType.AROUND, environment)
|
|
414
|
+
afters = self.collect(clazz, member, AspectType.AFTER, environment)
|
|
415
|
+
errors = self.collect(clazz, member, AspectType.ERROR, environment)
|
|
416
|
+
|
|
417
|
+
if len(befores) > 0 or len(arounds) > 0 or len(afters) > 0 or len(errors) > 0:
|
|
418
|
+
return JoinPoints(
|
|
419
|
+
before=befores,
|
|
420
|
+
around=arounds,
|
|
421
|
+
error=errors,
|
|
422
|
+
after=afters
|
|
423
|
+
)
|
|
424
|
+
else:
|
|
425
|
+
return None
|
|
426
|
+
|
|
427
|
+
def sanityCheck(clazz: Type, name: str):
|
|
428
|
+
m = TypeDescriptor.for_type(clazz).get_method(name)
|
|
429
|
+
if len(m.paramTypes) != 1 or m.paramTypes[0] != Invocation:
|
|
430
|
+
raise AOPException(f"Method {clazz.__name__}.{name} expected to have one parameter of type Invocation")
|
|
431
|
+
|
|
432
|
+
# decorators
|
|
433
|
+
|
|
434
|
+
def advice(cls):
|
|
435
|
+
"""
|
|
436
|
+
Classes decorated with @advice are treated as aspect classes.
|
|
437
|
+
They can contain methods decorated with @before, @after, @around, or @error to define aspects.
|
|
438
|
+
"""
|
|
439
|
+
Providers.register(ClassInstanceProvider(cls, True))
|
|
440
|
+
|
|
441
|
+
Decorators.add(cls, advice)
|
|
442
|
+
|
|
443
|
+
for name, member in TypeDescriptor.for_type(cls).methods.items():
|
|
444
|
+
decorator = next((decorator for decorator in member.decorators if decorator.decorator in [before, after, around, error]), None)
|
|
445
|
+
if decorator is not None:
|
|
446
|
+
target = decorator.args[0]
|
|
447
|
+
target._clazz = cls
|
|
448
|
+
sanityCheck(cls, name)
|
|
449
|
+
Advice.targets.append(target)
|
|
450
|
+
|
|
451
|
+
return cls
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
# decorators
|
|
455
|
+
|
|
456
|
+
def _register(decorator, target: AspectTarget, func, aspectType: AspectType):
|
|
457
|
+
target.function(func).type(aspectType)
|
|
458
|
+
|
|
459
|
+
Decorators.add(func, decorator, target)
|
|
460
|
+
|
|
461
|
+
def before(target: AspectTarget):
|
|
462
|
+
"""
|
|
463
|
+
Methods decorated with @before will be executed before the target method is invoked.
|
|
464
|
+
"""
|
|
465
|
+
def decorator(func):
|
|
466
|
+
_register(before, target, func, AspectType.BEFORE)
|
|
467
|
+
|
|
468
|
+
return func
|
|
469
|
+
|
|
470
|
+
return decorator
|
|
471
|
+
|
|
472
|
+
def error(target: AspectTarget):
|
|
473
|
+
"""
|
|
474
|
+
Methods decorated with @error will be executed if the target method raises an exception."""
|
|
475
|
+
def decorator(func):
|
|
476
|
+
_register(error, target, func, AspectType.ERROR)
|
|
477
|
+
|
|
478
|
+
return func
|
|
479
|
+
|
|
480
|
+
return decorator
|
|
481
|
+
|
|
482
|
+
def after(target: AspectTarget):
|
|
483
|
+
"""
|
|
484
|
+
Methods decorated with @after will be executed after the target method is invoked.
|
|
485
|
+
"""
|
|
486
|
+
def decorator(func):
|
|
487
|
+
_register(after, target, func, AspectType.AFTER)
|
|
488
|
+
|
|
489
|
+
return func
|
|
490
|
+
|
|
491
|
+
return decorator
|
|
492
|
+
|
|
493
|
+
def around(target: AspectTarget):
|
|
494
|
+
"""
|
|
495
|
+
Methods decorated with @around will be executed around the target method.
|
|
496
|
+
Every around method must accept a single parameter of type Invocation and needs to call proceed
|
|
497
|
+
on this parameter to proceed to the next around method.
|
|
498
|
+
"""
|
|
499
|
+
def decorator(func):
|
|
500
|
+
_register(around, target, func, AspectType.AROUND)
|
|
501
|
+
|
|
502
|
+
return func
|
|
503
|
+
|
|
504
|
+
return decorator
|
|
505
|
+
|
|
506
|
+
@injectable()
|
|
507
|
+
class AdviceProcessor(PostProcessor):
|
|
508
|
+
# properties
|
|
509
|
+
|
|
510
|
+
__slots__ = [
|
|
511
|
+
"advice",
|
|
512
|
+
]
|
|
513
|
+
|
|
514
|
+
# constructor
|
|
515
|
+
|
|
516
|
+
def __init__(self, advice: Advice):
|
|
517
|
+
super().__init__()
|
|
518
|
+
|
|
519
|
+
self.advice = advice
|
|
520
|
+
|
|
521
|
+
# implement
|
|
522
|
+
|
|
523
|
+
def process(self, instance: object, environment: Environment):
|
|
524
|
+
joinPointDict = self.advice.joinPoints4(instance, environment)
|
|
525
|
+
|
|
526
|
+
for member, joinPoints in joinPointDict.items():
|
|
527
|
+
Environment.logger.debug(f"add aspects for {type(instance)}:{member.__name__}")
|
|
528
|
+
|
|
529
|
+
def wrap(jp):
|
|
530
|
+
return lambda *args, **kwargs: Invocation(member, jp).call(*args, **kwargs)
|
|
531
|
+
|
|
532
|
+
setattr(instance, member.__name__, types.MethodType(wrap(joinPoints), instance))
|