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 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 JoinPoint:
250
+ class Aspect:
236
251
  __slots__ = [
237
252
  "next",
238
253
  ]
239
254
 
240
255
  # constructor
241
256
 
242
- def __init__(self, next: 'JoinPoint'):
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 FunctionJoinPoint(JoinPoint):
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, next: Optional['JoinPoint']):
260
- super().__init__(next)
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.current_join_point = self
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.current_join_point = self
289
+ invocation.current_aspect = self
272
290
 
273
291
  return await self.func(self.instance, invocation)
274
292
 
275
- class MethodJoinPoint(FunctionJoinPoint):
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.current_join_point = self
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.current_join_point = self
305
+ invocation.current_aspect = self
288
306
 
289
307
  return await self.func(*invocation.args, **invocation.kwargs)
290
308
 
291
309
  @dataclass
292
- class JoinPoints:
293
- before: list[JoinPoint]
294
- around: list[JoinPoint]
295
- error: list[JoinPoint]
296
- after: list[JoinPoint]
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 join points that define the aspect behavior.
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
- "join_points",
312
- "current_join_point",
329
+ "aspects",
330
+ "current_aspect",
313
331
  ]
314
332
 
315
333
  # constructor
316
334
 
317
- def __init__(self, func, join_points: JoinPoints):
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.join_points = join_points
324
- self.current_join_point = None
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 join_point in self.join_points.before:
335
- join_point.call(self)
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.join_points.around[0].call(self) # will follow the proceed chain
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 join_point in self.join_points.error:
345
- join_point.call(self)
362
+ for aspect in self.aspects.error:
363
+ aspect.call(self)
346
364
 
347
365
  # run all before
348
366
 
349
- for join_point in self.join_points.after:
350
- join_point.call(self)
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 join_point in self.join_points.before:
366
- join_point.call(self)
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.join_points.around[0].call_async(self) # will follow the proceed chain
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 join_point in self.join_points.error:
376
- join_point.call(self)
393
+ for aspect in self.aspects.error:
394
+ aspect.call(self)
377
395
 
378
396
  # run all before
379
397
 
380
- for join_point in self.join_points.after:
381
- join_point.call(self)
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.current_join_point.next.call(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.current_join_point.next.call_async(self)
428
+ return await self.current_aspect.next.call_async(self)
411
429
 
412
- @injectable()
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
- self.cache : Dict[Type, Dict[Callable,JoinPoints]] = {}
427
- self.lock = threading.RLock()
440
+ pass
428
441
 
429
442
  # methods
430
443
 
431
- def collect(self, clazz, member, type: AspectType, environment: Environment):
432
- aspects = [FunctionJoinPoint(environment.get(target._clazz), target._function, None) for target in Advice.targets if target._type == type and target._matches(clazz, member)]
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
- def join_points4(self, instance, environment: Environment) -> Dict[Callable,JoinPoints]:
461
+ @classmethod
462
+ def aspects_for(cls, instance, environment: Environment) -> Dict[Callable,Aspects]:
444
463
  clazz = type(instance)
445
464
 
446
- result = self.cache.get(clazz, None)
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 = JoinPoints(
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(MethodJoinPoint(instance, key))
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
- def compute_join_points(self, clazz, member, environment: Environment) -> Optional[JoinPoints]:
486
- befores = self.collect(clazz, member, AspectType.BEFORE, environment)
487
- arounds = self.collect(clazz, member, AspectType.AROUND, environment)
488
- afters = self.collect(clazz, member, AspectType.AFTER, environment)
489
- errors = self.collect(clazz, member, AspectType.ERROR, environment)
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 JoinPoints(
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 aspect classes.
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
- Advice.targets.append(target)
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
- "advice",
602
+ "lock",
603
+ "cache"
592
604
  ]
593
605
 
594
606
  # constructor
595
607
 
596
- def __init__(self, advice: Advice):
608
+ def __init__(self):
597
609
  super().__init__()
598
610
 
599
- self.advice = advice
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
- join_point_dict = self.advice.join_points4(instance, environment)
629
+ aspect_dict = self.aspects_for(instance, environment)
605
630
 
606
- for member, join_points in join_point_dict.items():
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(join_points), instance))
647
+ setattr(instance, member.__name__, types.MethodType(wrap_async(aspects), instance))
623
648
  else:
624
- setattr(instance, member.__name__, types.MethodType(wrap(join_points), instance))
649
+ setattr(instance, member.__name__, types.MethodType(wrap(aspects), instance))