aspyx 1.3.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/aop/aop.py +112 -87
- aspyx/di/di.py +125 -24
- aspyx/di/threading/synchronized.py +8 -5
- aspyx/exception/__init__.py +10 -0
- aspyx/exception/exception_manager.py +168 -0
- aspyx/reflection/reflection.py +37 -1
- aspyx/threading/__init__.py +10 -0
- aspyx/threading/thread_local.py +47 -0
- aspyx/{di/util → util}/stringbuilder.py +11 -0
- {aspyx-1.3.0.dist-info → aspyx-1.4.0.dist-info}/METADATA +180 -58
- aspyx-1.4.0.dist-info/RECORD +25 -0
- aspyx-1.3.0.dist-info/RECORD +0 -20
- /aspyx/{di/util → util}/__init__.py +0 -0
- {aspyx-1.3.0.dist-info → aspyx-1.4.0.dist-info}/WHEEL +0 -0
- {aspyx-1.3.0.dist-info → aspyx-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {aspyx-1.3.0.dist-info → aspyx-1.4.0.dist-info}/top_level.txt +0 -0
aspyx/__init__.py
ADDED
|
File without changes
|
aspyx/di/aop/aop.py
CHANGED
|
@@ -12,10 +12,8 @@ from dataclasses import dataclass
|
|
|
12
12
|
from enum import auto, Enum
|
|
13
13
|
from typing import Optional, Dict, Type, Callable
|
|
14
14
|
|
|
15
|
-
from aspyx.di.di import order
|
|
16
15
|
from aspyx.reflection import Decorators, TypeDescriptor
|
|
17
|
-
from aspyx.di import injectable, Providers, ClassInstanceProvider, Environment, PostProcessor
|
|
18
|
-
|
|
16
|
+
from aspyx.di import injectable, order, Providers, ClassInstanceProvider, Environment, PostProcessor
|
|
19
17
|
|
|
20
18
|
class AOPException(Exception):
|
|
21
19
|
"""
|
|
@@ -110,14 +108,27 @@ class AspectTarget(ABC):
|
|
|
110
108
|
return self
|
|
111
109
|
|
|
112
110
|
def that_are_async(self):
|
|
111
|
+
"""
|
|
112
|
+
matches methods that are async
|
|
113
|
+
:return: self
|
|
114
|
+
"""
|
|
113
115
|
self._async = True
|
|
114
116
|
return self
|
|
115
117
|
|
|
116
118
|
def of_type(self, type: Type):
|
|
119
|
+
"""
|
|
120
|
+
matches methods belonging to a class or classes that are subclasses of the specified type
|
|
121
|
+
:return: self
|
|
122
|
+
"""
|
|
117
123
|
self.types.append(type)
|
|
118
124
|
return self
|
|
119
125
|
|
|
120
126
|
def decorated_with(self, decorator):
|
|
127
|
+
"""
|
|
128
|
+
matches methods or classes that are decorated with the specified decorator
|
|
129
|
+
:param decorator: the decorator callable
|
|
130
|
+
:return:
|
|
131
|
+
"""
|
|
121
132
|
self.decorators.append(decorator)
|
|
122
133
|
return self
|
|
123
134
|
|
|
@@ -133,6 +144,9 @@ class AspectTarget(ABC):
|
|
|
133
144
|
return self
|
|
134
145
|
|
|
135
146
|
class ClassAspectTarget(AspectTarget):
|
|
147
|
+
"""
|
|
148
|
+
An AspectTarget matching classes
|
|
149
|
+
"""
|
|
136
150
|
# properties
|
|
137
151
|
|
|
138
152
|
__slots__ = [
|
|
@@ -172,6 +186,10 @@ class ClassAspectTarget(AspectTarget):
|
|
|
172
186
|
# fluent
|
|
173
187
|
|
|
174
188
|
class MethodAspectTarget(AspectTarget):
|
|
189
|
+
"""
|
|
190
|
+
An AspectTarget matching methods
|
|
191
|
+
"""
|
|
192
|
+
|
|
175
193
|
# properties
|
|
176
194
|
|
|
177
195
|
__slots__ = [ ]
|
|
@@ -185,9 +203,6 @@ class MethodAspectTarget(AspectTarget):
|
|
|
185
203
|
|
|
186
204
|
# async
|
|
187
205
|
|
|
188
|
-
name = method_descriptor.method.__name__
|
|
189
|
-
is_async = method_descriptor.is_async()
|
|
190
|
-
|
|
191
206
|
if self._async is not method_descriptor.is_async():
|
|
192
207
|
return False
|
|
193
208
|
|
|
@@ -232,14 +247,14 @@ def classes():
|
|
|
232
247
|
return ClassAspectTarget()
|
|
233
248
|
|
|
234
249
|
|
|
235
|
-
class
|
|
250
|
+
class Aspect:
|
|
236
251
|
__slots__ = [
|
|
237
252
|
"next",
|
|
238
253
|
]
|
|
239
254
|
|
|
240
255
|
# constructor
|
|
241
256
|
|
|
242
|
-
def __init__(self, next: '
|
|
257
|
+
def __init__(self, next: 'Aspect'):
|
|
243
258
|
self.next = next
|
|
244
259
|
|
|
245
260
|
# public
|
|
@@ -250,55 +265,58 @@ class JoinPoint:
|
|
|
250
265
|
async def call_async(self, invocation: 'Invocation'):
|
|
251
266
|
pass
|
|
252
267
|
|
|
253
|
-
class
|
|
268
|
+
class FunctionAspect(Aspect):
|
|
254
269
|
__slots__ = [
|
|
255
270
|
"instance",
|
|
256
271
|
"func",
|
|
272
|
+
"order",
|
|
257
273
|
]
|
|
258
274
|
|
|
259
|
-
def __init__(self, instance, func,
|
|
260
|
-
super().__init__(
|
|
275
|
+
def __init__(self, instance, func, next_aspect: Optional['Aspect']):
|
|
276
|
+
super().__init__(next_aspect)
|
|
261
277
|
|
|
262
278
|
self.instance = instance
|
|
263
279
|
self.func = func
|
|
264
280
|
|
|
281
|
+
self.order = next((decorator.args[0] for decorator in Decorators.get(func) if decorator.decorator is order), 0)
|
|
282
|
+
|
|
265
283
|
def call(self, invocation: 'Invocation'):
|
|
266
|
-
invocation.
|
|
284
|
+
invocation.current_aspect = self
|
|
267
285
|
|
|
268
286
|
return self.func(self.instance, invocation)
|
|
269
287
|
|
|
270
288
|
async def call_async(self, invocation: 'Invocation'):
|
|
271
|
-
invocation.
|
|
289
|
+
invocation.current_aspect = self
|
|
272
290
|
|
|
273
291
|
return await self.func(self.instance, invocation)
|
|
274
292
|
|
|
275
|
-
class
|
|
293
|
+
class MethodAspect(FunctionAspect):
|
|
276
294
|
__slots__ = []
|
|
277
295
|
|
|
278
296
|
def __init__(self, instance, func):
|
|
279
297
|
super().__init__(instance, func, None)
|
|
280
298
|
|
|
281
299
|
def call(self, invocation: 'Invocation'):
|
|
282
|
-
invocation.
|
|
300
|
+
invocation.current_aspect = self
|
|
283
301
|
|
|
284
302
|
return self.func(*invocation.args, **invocation.kwargs)
|
|
285
303
|
|
|
286
304
|
async def call_async(self, invocation: 'Invocation'):
|
|
287
|
-
invocation.
|
|
305
|
+
invocation.current_aspect = self
|
|
288
306
|
|
|
289
307
|
return await self.func(*invocation.args, **invocation.kwargs)
|
|
290
308
|
|
|
291
309
|
@dataclass
|
|
292
|
-
class
|
|
293
|
-
before: list[
|
|
294
|
-
around: list[
|
|
295
|
-
error: list[
|
|
296
|
-
after: list[
|
|
310
|
+
class Aspects:
|
|
311
|
+
before: list[Aspect]
|
|
312
|
+
around: list[Aspect]
|
|
313
|
+
error: list[Aspect]
|
|
314
|
+
after: list[Aspect]
|
|
297
315
|
|
|
298
316
|
class Invocation:
|
|
299
317
|
"""
|
|
300
318
|
Invocation stores the relevant data of a single method invocation.
|
|
301
|
-
It holds the arguments, keyword arguments, result, error, and the
|
|
319
|
+
It holds the arguments, keyword arguments, result, error, and the aspects that define the aspect behavior.
|
|
302
320
|
"""
|
|
303
321
|
# properties
|
|
304
322
|
|
|
@@ -308,20 +326,20 @@ class Invocation:
|
|
|
308
326
|
"kwargs",
|
|
309
327
|
"result",
|
|
310
328
|
"exception",
|
|
311
|
-
"
|
|
312
|
-
"
|
|
329
|
+
"aspects",
|
|
330
|
+
"current_aspect",
|
|
313
331
|
]
|
|
314
332
|
|
|
315
333
|
# constructor
|
|
316
334
|
|
|
317
|
-
def __init__(self, func,
|
|
335
|
+
def __init__(self, func, aspects: Aspects):
|
|
318
336
|
self.func = func
|
|
319
337
|
self.args : list[object] = []
|
|
320
338
|
self.kwargs = None
|
|
321
339
|
self.result = None
|
|
322
340
|
self.exception = None
|
|
323
|
-
self.
|
|
324
|
-
self.
|
|
341
|
+
self.aspects = aspects
|
|
342
|
+
self.current_aspect = None
|
|
325
343
|
|
|
326
344
|
def call(self, *args, **kwargs):
|
|
327
345
|
# remember args
|
|
@@ -331,23 +349,23 @@ class Invocation:
|
|
|
331
349
|
|
|
332
350
|
# run all before
|
|
333
351
|
|
|
334
|
-
for
|
|
335
|
-
|
|
352
|
+
for aspect in self.aspects.before:
|
|
353
|
+
aspect.call(self)
|
|
336
354
|
|
|
337
355
|
# run around's with the method being the last aspect!
|
|
338
356
|
|
|
339
357
|
try:
|
|
340
|
-
self.result = self.
|
|
358
|
+
self.result = self.aspects.around[0].call(self) # will follow the proceed chain
|
|
341
359
|
|
|
342
360
|
except Exception as e:
|
|
343
361
|
self.exception = e
|
|
344
|
-
for
|
|
345
|
-
|
|
362
|
+
for aspect in self.aspects.error:
|
|
363
|
+
aspect.call(self)
|
|
346
364
|
|
|
347
365
|
# run all before
|
|
348
366
|
|
|
349
|
-
for
|
|
350
|
-
|
|
367
|
+
for aspect in self.aspects.after:
|
|
368
|
+
aspect.call(self)
|
|
351
369
|
|
|
352
370
|
if self.exception is not None:
|
|
353
371
|
raise self.exception # rethrow the error
|
|
@@ -362,23 +380,23 @@ class Invocation:
|
|
|
362
380
|
|
|
363
381
|
# run all before
|
|
364
382
|
|
|
365
|
-
for
|
|
366
|
-
|
|
383
|
+
for aspect in self.aspects.before:
|
|
384
|
+
aspect.call(self)
|
|
367
385
|
|
|
368
386
|
# run around's with the method being the last aspect!
|
|
369
387
|
|
|
370
388
|
try:
|
|
371
|
-
self.result = await self.
|
|
389
|
+
self.result = await self.aspects.around[0].call_async(self) # will follow the proceed chain
|
|
372
390
|
|
|
373
391
|
except Exception as e:
|
|
374
392
|
self.exception = e
|
|
375
|
-
for
|
|
376
|
-
|
|
393
|
+
for aspect in self.aspects.error:
|
|
394
|
+
aspect.call(self)
|
|
377
395
|
|
|
378
396
|
# run all before
|
|
379
397
|
|
|
380
|
-
for
|
|
381
|
-
|
|
398
|
+
for aspect in self.aspects.after:
|
|
399
|
+
aspect.call(self)
|
|
382
400
|
|
|
383
401
|
if self.exception is not None:
|
|
384
402
|
raise self.exception # rethrow the error
|
|
@@ -395,7 +413,7 @@ class Invocation:
|
|
|
395
413
|
|
|
396
414
|
# next one please...
|
|
397
415
|
|
|
398
|
-
return self.
|
|
416
|
+
return self.current_aspect.next.call(self)
|
|
399
417
|
|
|
400
418
|
async def proceed_async(self, *args, **kwargs):
|
|
401
419
|
"""
|
|
@@ -407,29 +425,29 @@ class Invocation:
|
|
|
407
425
|
|
|
408
426
|
# next one please...
|
|
409
427
|
|
|
410
|
-
return await self.
|
|
428
|
+
return await self.current_aspect.next.call_async(self)
|
|
411
429
|
|
|
412
|
-
|
|
413
|
-
class Advice:
|
|
430
|
+
class Advices:
|
|
414
431
|
# static data
|
|
415
432
|
|
|
416
433
|
targets: list[AspectTarget] = []
|
|
417
434
|
|
|
418
|
-
__slots__ = [
|
|
419
|
-
"cache",
|
|
420
|
-
"lock"
|
|
421
|
-
]
|
|
435
|
+
__slots__ = []
|
|
422
436
|
|
|
423
437
|
# constructor
|
|
424
438
|
|
|
425
439
|
def __init__(self):
|
|
426
|
-
|
|
427
|
-
self.lock = threading.RLock()
|
|
440
|
+
pass
|
|
428
441
|
|
|
429
442
|
# methods
|
|
430
443
|
|
|
431
|
-
|
|
432
|
-
|
|
444
|
+
@classmethod
|
|
445
|
+
def collect(cls, clazz, member, type: AspectType, environment: Environment):
|
|
446
|
+
aspects = [FunctionAspect(environment.get(target._clazz), target._function, None) for target in Advices.targets if target._type == type and target._clazz is not clazz and environment.providers.get(target._clazz) is not None and target._matches(clazz, member)]
|
|
447
|
+
|
|
448
|
+
# sort according to order
|
|
449
|
+
|
|
450
|
+
aspects = sorted(aspects, key=lambda aspect: aspect.order)
|
|
433
451
|
|
|
434
452
|
# link
|
|
435
453
|
|
|
@@ -440,31 +458,23 @@ class Advice:
|
|
|
440
458
|
|
|
441
459
|
return aspects
|
|
442
460
|
|
|
443
|
-
|
|
461
|
+
@classmethod
|
|
462
|
+
def aspects_for(cls, instance, environment: Environment) -> Dict[Callable,Aspects]:
|
|
444
463
|
clazz = type(instance)
|
|
445
464
|
|
|
446
|
-
result =
|
|
447
|
-
if result is None:
|
|
448
|
-
with self.lock:
|
|
449
|
-
result = self.cache.get(clazz, None)
|
|
450
|
-
|
|
451
|
-
if result is None:
|
|
452
|
-
result = {}
|
|
453
|
-
self.cache[clazz] = result
|
|
454
|
-
|
|
455
|
-
for _, member in inspect.getmembers(clazz, predicate=inspect.isfunction):
|
|
456
|
-
join_points = self.compute_join_points(clazz, member, environment)
|
|
457
|
-
if join_points is not None:
|
|
458
|
-
result[member] = join_points
|
|
459
|
-
|
|
465
|
+
result = {}
|
|
460
466
|
|
|
467
|
+
for _, member in inspect.getmembers(clazz, predicate=inspect.isfunction):
|
|
468
|
+
aspects = cls.compute_aspects(clazz, member, environment)
|
|
469
|
+
if aspects is not None:
|
|
470
|
+
result[member] = aspects
|
|
461
471
|
|
|
462
472
|
# add around methods
|
|
463
473
|
|
|
464
474
|
value = {}
|
|
465
475
|
|
|
466
476
|
for key, cjp in result.items():
|
|
467
|
-
jp =
|
|
477
|
+
jp = Aspects(
|
|
468
478
|
before=cjp.before,
|
|
469
479
|
around=cjp.around,
|
|
470
480
|
error=cjp.error,
|
|
@@ -472,7 +482,7 @@ class Advice:
|
|
|
472
482
|
|
|
473
483
|
# add method to around
|
|
474
484
|
|
|
475
|
-
jp.around.append(
|
|
485
|
+
jp.around.append(MethodAspect(instance, key))
|
|
476
486
|
if len(jp.around) > 1:
|
|
477
487
|
jp.around[len(jp.around) - 2].next = jp.around[len(jp.around) - 1]
|
|
478
488
|
|
|
@@ -482,14 +492,15 @@ class Advice:
|
|
|
482
492
|
|
|
483
493
|
return value
|
|
484
494
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
495
|
+
@classmethod
|
|
496
|
+
def compute_aspects(cls, clazz, member, environment: Environment) -> Optional[Aspects]:
|
|
497
|
+
befores = cls.collect(clazz, member, AspectType.BEFORE, environment)
|
|
498
|
+
arounds = cls.collect(clazz, member, AspectType.AROUND, environment)
|
|
499
|
+
afters = cls.collect(clazz, member, AspectType.AFTER, environment)
|
|
500
|
+
errors = cls.collect(clazz, member, AspectType.ERROR, environment)
|
|
490
501
|
|
|
491
502
|
if len(befores) > 0 or len(arounds) > 0 or len(afters) > 0 or len(errors) > 0:
|
|
492
|
-
return
|
|
503
|
+
return Aspects(
|
|
493
504
|
before=befores,
|
|
494
505
|
around=arounds,
|
|
495
506
|
error=errors,
|
|
@@ -507,7 +518,7 @@ def sanity_check(clazz: Type, name: str):
|
|
|
507
518
|
|
|
508
519
|
def advice(cls):
|
|
509
520
|
"""
|
|
510
|
-
Classes decorated with @advice are treated as
|
|
521
|
+
Classes decorated with @advice are treated as advice classes.
|
|
511
522
|
They can contain methods decorated with @before, @after, @around, or @error to define aspects.
|
|
512
523
|
"""
|
|
513
524
|
Providers.register(ClassInstanceProvider(cls, True))
|
|
@@ -517,10 +528,10 @@ def advice(cls):
|
|
|
517
528
|
for name, member in TypeDescriptor.for_type(cls).methods.items():
|
|
518
529
|
decorator = next((decorator for decorator in member.decorators if decorator.decorator in [before, after, around, error]), None)
|
|
519
530
|
if decorator is not None:
|
|
520
|
-
target = decorator.args[0]
|
|
531
|
+
target = decorator.args[0] # ? ...?? TODO, can be multiple
|
|
521
532
|
target._clazz = cls
|
|
522
533
|
sanity_check(cls, name)
|
|
523
|
-
|
|
534
|
+
Advices.targets.append(target) #??
|
|
524
535
|
|
|
525
536
|
return cls
|
|
526
537
|
|
|
@@ -582,28 +593,42 @@ def around(*targets: AspectTarget):
|
|
|
582
593
|
|
|
583
594
|
return decorator
|
|
584
595
|
|
|
585
|
-
@injectable()
|
|
596
|
+
@injectable(scope="environment")
|
|
586
597
|
@order(0)
|
|
587
598
|
class AdviceProcessor(PostProcessor):
|
|
588
599
|
# properties
|
|
589
600
|
|
|
590
601
|
__slots__ = [
|
|
591
|
-
"
|
|
602
|
+
"lock",
|
|
603
|
+
"cache"
|
|
592
604
|
]
|
|
593
605
|
|
|
594
606
|
# constructor
|
|
595
607
|
|
|
596
|
-
def __init__(self
|
|
608
|
+
def __init__(self):
|
|
597
609
|
super().__init__()
|
|
598
610
|
|
|
599
|
-
self.
|
|
611
|
+
self.cache : Dict[Type, Dict[Callable,Aspects]] = {}
|
|
612
|
+
self.lock = threading.RLock()
|
|
613
|
+
|
|
614
|
+
# local
|
|
615
|
+
|
|
616
|
+
def aspects_for(self, instance, environment: Environment) -> Dict[Callable,Aspects]:
|
|
617
|
+
clazz = type(instance)
|
|
618
|
+
result = self.cache.get(clazz, None)
|
|
619
|
+
if result is None:
|
|
620
|
+
with self.lock:
|
|
621
|
+
result = Advices.aspects_for(instance, environment)
|
|
622
|
+
self.cache[clazz] = result # TOID der cache ist zu dick?????
|
|
623
|
+
|
|
624
|
+
return result
|
|
600
625
|
|
|
601
626
|
# implement
|
|
602
627
|
|
|
603
628
|
def process(self, instance: object, environment: Environment):
|
|
604
|
-
|
|
629
|
+
aspect_dict = self.aspects_for(instance, environment)
|
|
605
630
|
|
|
606
|
-
for member,
|
|
631
|
+
for member, aspects in aspect_dict.items():
|
|
607
632
|
Environment.logger.debug("add aspects for %s:%s", type(instance), member.__name__)
|
|
608
633
|
|
|
609
634
|
def wrap(jp):
|
|
@@ -619,6 +644,6 @@ class AdviceProcessor(PostProcessor):
|
|
|
619
644
|
return async_wrapper
|
|
620
645
|
|
|
621
646
|
if inspect.iscoroutinefunction(member):
|
|
622
|
-
setattr(instance, member.__name__, types.MethodType(wrap_async(
|
|
647
|
+
setattr(instance, member.__name__, types.MethodType(wrap_async(aspects), instance))
|
|
623
648
|
else:
|
|
624
|
-
setattr(instance, member.__name__, types.MethodType(wrap(
|
|
649
|
+
setattr(instance, member.__name__, types.MethodType(wrap(aspects), instance))
|