aspyx 0.1.0__py3-none-any.whl → 1.0.1__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 CHANGED
@@ -1,3 +1,6 @@
1
+ """
2
+ This module provides dependency injection and aop capabilities for Python applications.
3
+ """
1
4
  from .di import InjectorException, CallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, environment, inject, create, on_init, on_destroy, inject_environment, Factory, PostProcessor
2
5
 
3
6
  # import something from the subpackages, so that teh decorators are executed
@@ -26,4 +29,4 @@ __all__ = [
26
29
  "LifecycleCallable",
27
30
  "InjectorException",
28
31
  "Lifecycle"
29
- ]
32
+ ]
aspyx/di/aop/__init__.py CHANGED
@@ -1,3 +1,6 @@
1
+ """
2
+ AOP module
3
+ """
1
4
  from .aop import before, after, classes, around, error, advice, methods, Invocation
2
5
  __all__ = [
3
6
  "before",
@@ -8,4 +11,4 @@ __all__ = [
8
11
  "classes",
9
12
  "methods",
10
13
  "Invocation",
11
- ]
14
+ ]
aspyx/di/aop/aop.py CHANGED
@@ -1,21 +1,26 @@
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
4
7
  import inspect
5
8
  import re
9
+ import threading
6
10
  import types
7
11
  from dataclasses import dataclass
8
12
  from enum import auto, Enum
9
13
  from typing import Optional, Dict, Type, Callable
10
14
 
15
+ from aspyx.di.di import order
11
16
  from aspyx.reflection import Decorators, TypeDescriptor
12
17
  from aspyx.di import injectable, Providers, ClassInstanceProvider, Environment, PostProcessor
13
18
 
14
19
 
15
20
  class AOPException(Exception):
16
21
  """
17
- Exception raised for errors in the aop logic."""
18
- pass
22
+ Exception raised for errors in the aop logic.
23
+ """
19
24
 
20
25
  class AspectType(Enum):
21
26
  """
@@ -51,6 +56,7 @@ class AspectTarget(ABC):
51
56
  "names",
52
57
  "patterns",
53
58
  "types",
59
+ "other",
54
60
  "decorators",
55
61
  ]
56
62
 
@@ -59,20 +65,38 @@ class AspectTarget(ABC):
59
65
  def __init__(self):
60
66
  self._clazz = None
61
67
  self._instance = None
68
+ self._function = None
69
+ self._type = None
62
70
 
63
71
  self.patterns = []
64
72
  self.names = []
65
73
  self.types = []
66
74
  self.decorators = []
67
75
 
68
- pass
76
+ self.other : list[AspectTarget] = []
69
77
 
70
78
  # abstract
71
79
 
72
- @abstractmethod
73
80
  def _matches(self, clazz : Type, func):
81
+ if not self._matches_self(clazz, func):
82
+ for target in self.other:
83
+ if target._matches(clazz, func):
84
+ return True
85
+
86
+ return False
87
+
88
+ return True
89
+
90
+ @abstractmethod
91
+ def _matches_self(self, clazz: Type, func):
74
92
  pass
75
93
 
94
+ # protected
95
+
96
+ def _add(self, target: AspectTarget):
97
+ self.other.append(target)
98
+ return self
99
+
76
100
  # fluent
77
101
 
78
102
  def function(self, func):
@@ -83,7 +107,7 @@ class AspectTarget(ABC):
83
107
  self._type = type
84
108
 
85
109
  return self
86
-
110
+
87
111
  def of_type(self, type: Type):
88
112
  self.types.append(type)
89
113
  return self
@@ -91,7 +115,7 @@ class AspectTarget(ABC):
91
115
  def decorated_with(self, decorator):
92
116
  self.decorators.append(decorator)
93
117
  return self
94
-
118
+
95
119
  def matches(self, pattern: str):
96
120
  """
97
121
  Matches the target against a pattern.
@@ -109,18 +133,11 @@ class ClassAspectTarget(AspectTarget):
109
133
  __slots__ = [
110
134
  ]
111
135
 
112
- # constructor
113
-
114
- def __init__(self):
115
- super().__init__()
116
-
117
- pass
118
-
119
136
  # public
120
137
 
121
- def _matches(self, clazz : Type, func):
122
- descriptor = TypeDescriptor.for_type(clazz)
123
-
138
+ def _matches_self(self, clazz : Type, func):
139
+ class_descriptor = TypeDescriptor.for_type(clazz)
140
+ #descriptor = TypeDescriptor.for_type(func)
124
141
  # type
125
142
 
126
143
  if len(self.types) > 0:
@@ -130,7 +147,7 @@ class ClassAspectTarget(AspectTarget):
130
147
  # decorators
131
148
 
132
149
  if len(self.decorators) > 0:
133
- if next((decorator for decorator in self.decorators if descriptor.has_decorator(decorator)), None) is None:
150
+ if next((decorator for decorator in self.decorators if class_descriptor.has_decorator(decorator)), None) is None:
134
151
  return False
135
152
 
136
153
  # names
@@ -138,46 +155,28 @@ class ClassAspectTarget(AspectTarget):
138
155
  if len(self.names) > 0:
139
156
  if next((name for name in self.names if name == clazz.__name__), None) is None:
140
157
  return False
141
-
158
+
142
159
  # patterns
143
160
 
144
161
  if len(self.patterns) > 0:
145
162
  if next((pattern for pattern in self.patterns if re.fullmatch(pattern, clazz.__name__) is not None), None) is None:
146
163
  return False
147
-
148
- # yipee
149
164
 
150
165
  return True
151
-
152
- # fluent
153
166
 
167
+ # fluent
154
168
 
155
-
156
169
  class MethodAspectTarget(AspectTarget):
157
170
  # properties
158
171
 
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__()
172
+ __slots__ = [ ]
174
173
 
175
174
  # public
176
175
 
177
- def _matches(self, clazz : Type, func):
176
+ def _matches_self(self, clazz : Type, func):
178
177
  descriptor = TypeDescriptor.for_type(clazz)
179
178
 
180
- methodDescriptor = descriptor.get_method(func.__name__)
179
+ method_descriptor = descriptor.get_method(func.__name__)
181
180
 
182
181
  # type
183
182
 
@@ -188,7 +187,7 @@ class MethodAspectTarget(AspectTarget):
188
187
  # decorators
189
188
 
190
189
  if len(self.decorators) > 0:
191
- if next((decorator for decorator in self.decorators if methodDescriptor.has_decorator(decorator)), None) is None:
190
+ if next((decorator for decorator in self.decorators if method_descriptor.has_decorator(decorator)), None) is None:
192
191
  return False
193
192
 
194
193
  # names
@@ -196,13 +195,13 @@ class MethodAspectTarget(AspectTarget):
196
195
  if len(self.names) > 0:
197
196
  if next((name for name in self.names if name == func.__name__), None) is None:
198
197
  return False
199
-
198
+
200
199
  # patterns
201
200
 
202
201
  if len(self.patterns) > 0:
203
202
  if next((pattern for pattern in self.patterns if re.fullmatch(pattern, func.__name__) is not None), None) is None:
204
203
  return False
205
-
204
+
206
205
  # yipee
207
206
 
208
207
  return True
@@ -248,7 +247,7 @@ class FunctionJoinPoint(JoinPoint):
248
247
  self.func = func
249
248
 
250
249
  def call(self, invocation: 'Invocation'):
251
- invocation.currentJoinPoint = self
250
+ invocation.current_join_point = self
252
251
 
253
252
  return self.func(self.instance, invocation)
254
253
 
@@ -259,7 +258,7 @@ class MethodJoinPoint(FunctionJoinPoint):
259
258
  super().__init__(instance, func, None)
260
259
 
261
260
  def call(self, invocation: 'Invocation'):
262
- invocation.currentJoinPoint = self
261
+ invocation.current_join_point = self
263
262
 
264
263
  return self.func(*invocation.args, **invocation.kwargs)
265
264
 
@@ -283,20 +282,20 @@ class Invocation:
283
282
  "kwargs",
284
283
  "result",
285
284
  "exception",
286
- "joinPoints",
287
- "currentJoinPoint",
285
+ "join_points",
286
+ "current_join_point",
288
287
  ]
289
288
 
290
289
  # constructor
291
290
 
292
- def __init__(self, func, joinPoints: JoinPoints):
291
+ def __init__(self, func, join_points: JoinPoints):
293
292
  self.func = func
294
293
  self.args : list[object] = []
295
294
  self.kwargs = None
296
295
  self.result = None
297
296
  self.exception = None
298
- self.joinPoints = joinPoints
299
- self.currentJoinPoint = None
297
+ self.join_points = join_points
298
+ self.current_join_point = None
300
299
 
301
300
  def call(self, *args, **kwargs):
302
301
  # remember args
@@ -306,28 +305,28 @@ class Invocation:
306
305
 
307
306
  # run all before
308
307
 
309
- for joinPoint in self.joinPoints.before:
310
- joinPoint.call(self)
308
+ for join_point in self.join_points.before:
309
+ join_point.call(self)
311
310
 
312
311
  # run around's with the method being the last aspect!
313
312
 
314
313
  try:
315
- self.result = self.joinPoints.around[0].call(self) # will follow the proceed chain
314
+ self.result = self.join_points.around[0].call(self) # will follow the proceed chain
316
315
 
317
316
  except Exception as e:
318
317
  self.exception = e
319
- for joinPoint in self.joinPoints.error:
320
- joinPoint.call(self)
318
+ for join_point in self.join_points.error:
319
+ join_point.call(self)
321
320
 
322
321
  # run all before
323
322
 
324
- for joinPoint in self.joinPoints.after:
325
- joinPoint.call(self)
323
+ for join_point in self.join_points.after:
324
+ join_point.call(self)
326
325
 
327
326
  if self.exception is not None:
328
327
  raise self.exception # rethrow the error
329
- else:
330
- return self.result
328
+
329
+ return self.result
331
330
 
332
331
  def proceed(self, *args, **kwargs):
333
332
  """
@@ -339,7 +338,7 @@ class Invocation:
339
338
 
340
339
  # next one please...
341
340
 
342
- return self.currentJoinPoint.next.call(self)
341
+ return self.current_join_point.next.call(self)
343
342
 
344
343
  @injectable()
345
344
  class Advice:
@@ -349,12 +348,14 @@ class Advice:
349
348
 
350
349
  __slots__ = [
351
350
  "cache",
351
+ "lock"
352
352
  ]
353
353
 
354
354
  # constructor
355
355
 
356
356
  def __init__(self):
357
- self.cache : Dict[Type, Dict[Callable,JoinPoints]] = dict()
357
+ self.cache : Dict[Type, Dict[Callable,JoinPoints]] = {}
358
+ self.lock = threading.RLock()
358
359
 
359
360
  # methods
360
361
 
@@ -370,24 +371,27 @@ class Advice:
370
371
 
371
372
  return aspects
372
373
 
373
- # TODO thread-safe
374
- def joinPoints4(self, instance, environment: Environment) -> Dict[Callable,JoinPoints]:
374
+ def join_points4(self, instance, environment: Environment) -> Dict[Callable,JoinPoints]:
375
375
  clazz = type(instance)
376
376
 
377
377
  result = self.cache.get(clazz, None)
378
378
  if result is None:
379
- result = dict()
379
+ with self.lock:
380
+ result = self.cache.get(clazz, None)
380
381
 
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
382
+ if result is None:
383
+ result = {}
385
384
 
386
- self.cache[clazz] = result
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
389
+
390
+ self.cache[clazz] = result
387
391
 
388
392
  # add around methods
389
393
 
390
- value = dict()
394
+ value = {}
391
395
 
392
396
  for key, cjp in result.items():
393
397
  jp = JoinPoints(
@@ -408,7 +412,7 @@ class Advice:
408
412
 
409
413
  return value
410
414
 
411
- def computeJoinPoints(self, clazz, member, environment: Environment) -> Optional[JoinPoints]:
415
+ def compute_join_points(self, clazz, member, environment: Environment) -> Optional[JoinPoints]:
412
416
  befores = self.collect(clazz, member, AspectType.BEFORE, environment)
413
417
  arounds = self.collect(clazz, member, AspectType.AROUND, environment)
414
418
  afters = self.collect(clazz, member, AspectType.AFTER, environment)
@@ -424,9 +428,9 @@ class Advice:
424
428
  else:
425
429
  return None
426
430
 
427
- def sanityCheck(clazz: Type, name: str):
431
+ def sanity_check(clazz: Type, name: str):
428
432
  m = TypeDescriptor.for_type(clazz).get_method(name)
429
- if len(m.paramTypes) != 1 or m.paramTypes[0] != Invocation:
433
+ if len(m.param_types) != 1 or m.param_types[0] != Invocation:
430
434
  raise AOPException(f"Method {clazz.__name__}.{name} expected to have one parameter of type Invocation")
431
435
 
432
436
  # decorators
@@ -445,7 +449,7 @@ def advice(cls):
445
449
  if decorator is not None:
446
450
  target = decorator.args[0]
447
451
  target._clazz = cls
448
- sanityCheck(cls, name)
452
+ sanity_check(cls, name)
449
453
  Advice.targets.append(target)
450
454
 
451
455
  return cls
@@ -453,57 +457,63 @@ def advice(cls):
453
457
 
454
458
  # decorators
455
459
 
456
- def _register(decorator, target: AspectTarget, func, aspectType: AspectType):
457
- target.function(func).type(aspectType)
460
+ def _register(decorator, targets: list[AspectTarget], func, aspect_type: AspectType):
461
+ target = targets[0]
462
+
463
+ for i in range(1, len(targets)):
464
+ target._add(targets[i])
465
+
466
+ target.function(func).type(aspect_type)
458
467
 
459
468
  Decorators.add(func, decorator, target)
460
469
 
461
- def before(target: AspectTarget):
470
+ def before(*targets: AspectTarget):
462
471
  """
463
472
  Methods decorated with @before will be executed before the target method is invoked.
464
473
  """
465
474
  def decorator(func):
466
- _register(before, target, func, AspectType.BEFORE)
475
+ _register(before, targets, func, AspectType.BEFORE)
467
476
 
468
477
  return func
469
478
 
470
479
  return decorator
471
480
 
472
- def error(target: AspectTarget):
481
+ def error(*targets: AspectTarget):
473
482
  """
474
483
  Methods decorated with @error will be executed if the target method raises an exception."""
475
484
  def decorator(func):
476
- _register(error, target, func, AspectType.ERROR)
485
+ _register(error, targets, func, AspectType.ERROR)
477
486
 
478
487
  return func
479
488
 
480
489
  return decorator
481
490
 
482
- def after(target: AspectTarget):
491
+ def after(*targets: AspectTarget):
483
492
  """
484
493
  Methods decorated with @after will be executed after the target method is invoked.
485
494
  """
486
495
  def decorator(func):
487
- _register(after, target, func, AspectType.AFTER)
496
+ _register(after, targets, func, AspectType.AFTER)
488
497
 
489
498
  return func
490
499
 
491
500
  return decorator
492
501
 
493
- def around(target: AspectTarget):
502
+ def around(*targets: AspectTarget):
494
503
  """
495
504
  Methods decorated with @around will be executed around the target method.
496
505
  Every around method must accept a single parameter of type Invocation and needs to call proceed
497
506
  on this parameter to proceed to the next around method.
498
507
  """
499
508
  def decorator(func):
500
- _register(around, target, func, AspectType.AROUND)
509
+ _register(around, targets, func, AspectType.AROUND)
501
510
 
502
511
  return func
503
512
 
504
513
  return decorator
505
514
 
506
515
  @injectable()
516
+ @order(0)
507
517
  class AdviceProcessor(PostProcessor):
508
518
  # properties
509
519
 
@@ -521,12 +531,12 @@ class AdviceProcessor(PostProcessor):
521
531
  # implement
522
532
 
523
533
  def process(self, instance: object, environment: Environment):
524
- joinPointDict = self.advice.joinPoints4(instance, environment)
534
+ join_point_dict = self.advice.join_points4(instance, environment)
525
535
 
526
- for member, joinPoints in joinPointDict.items():
527
- Environment.logger.debug(f"add aspects for {type(instance)}:{member.__name__}")
536
+ for member, join_points in join_point_dict.items():
537
+ Environment.logger.debug("add aspects for %s:%s", type(instance), member.__name__)
528
538
 
529
539
  def wrap(jp):
530
540
  return lambda *args, **kwargs: Invocation(member, jp).call(*args, **kwargs)
531
541
 
532
- setattr(instance, member.__name__, types.MethodType(wrap(joinPoints), instance))
542
+ setattr(instance, member.__name__, types.MethodType(wrap(join_points), instance))
@@ -1,3 +1,6 @@
1
+ """
2
+ Configuration value handling
3
+ """
1
4
  from .configuration import ConfigurationManager, ConfigurationSource, EnvConfigurationSource, value
2
5
 
3
6
  __all__ = [
@@ -5,4 +8,4 @@ __all__ = [
5
8
  "ConfigurationSource",
6
9
  "EnvConfigurationSource",
7
10
  "value"
8
- ]
11
+ ]
@@ -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,7 +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, CallableProcessor, LifecycleCallable, Lifecycle, environment
11
+ from aspyx.di import injectable, Environment, CallableProcessor, LifecycleCallable, Lifecycle
12
+ from aspyx.di.di import order, inject
9
13
  from aspyx.reflection import Decorators, DecoratorDescriptor, TypeDescriptor
10
14
 
11
15
  T = TypeVar("T")
@@ -14,7 +18,6 @@ class ConfigurationException(Exception):
14
18
  """
15
19
  Exception raised for errors in the configuration logic.
16
20
  """
17
- pass
18
21
 
19
22
  @injectable()
20
23
  class ConfigurationManager:
@@ -29,7 +32,7 @@ class ConfigurationManager:
29
32
 
30
33
  def __init__(self):
31
34
  self.sources = []
32
- self._data = dict()
35
+ self._data = {}
33
36
  self.coercions = {
34
37
  int: int,
35
38
  float: float,
@@ -81,17 +84,17 @@ class ConfigurationManager:
81
84
  current = current[key]
82
85
 
83
86
  return current
84
-
87
+
85
88
  v = resolve_value(path, default)
86
89
 
87
90
  if isinstance(v, type):
88
91
  return v
89
-
92
+
90
93
  if type in self.coercions:
91
94
  try:
92
95
  return self.coercions[type](v)
93
- except Exception:
94
- raise ConfigurationException(f"error during coercion to {type}")
96
+ except Exception as e:
97
+ raise ConfigurationException(f"error during coercion to {type}") from e
95
98
  else:
96
99
  raise ConfigurationException(f"unknown coercion to {type}")
97
100
 
@@ -103,15 +106,18 @@ class ConfigurationSource(ABC):
103
106
 
104
107
  __slots__ = []
105
108
 
106
- def __init__(self, manager: ConfigurationManager):
107
- manager._register(self)
109
+ def __init__(self):
108
110
  pass
109
111
 
112
+ @inject()
113
+ def set_manager(self, manager: ConfigurationManager):
114
+ manager._register(self)
115
+
110
116
  @abstractmethod
111
117
  def load(self) -> dict:
112
118
  """
113
- return the configuration values of this source as a dictionary."""
114
- pass
119
+ return the configuration values of this source as a dictionary.
120
+ """
115
121
 
116
122
  @injectable()
117
123
  class EnvConfigurationSource(ConfigurationSource):
@@ -123,8 +129,8 @@ class EnvConfigurationSource(ConfigurationSource):
123
129
 
124
130
  # constructor
125
131
 
126
- def __init__(self, manager: ConfigurationManager):
127
- super().__init__(manager)
132
+ def __init__(self):
133
+ super().__init__()
128
134
 
129
135
  load_dotenv()
130
136
 
@@ -180,6 +186,7 @@ def value(key: str, default=None):
180
186
  return decorator
181
187
 
182
188
  @injectable()
189
+ @order(9)
183
190
  class ConfigurationLifecycleCallable(LifecycleCallable):
184
191
  def __init__(self, processor: CallableProcessor, manager: ConfigurationManager):
185
192
  super().__init__(value, processor, Lifecycle.ON_INIT)
@@ -187,4 +194,4 @@ class ConfigurationLifecycleCallable(LifecycleCallable):
187
194
  self.manager = manager
188
195
 
189
196
  def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
190
- return [self.manager.get(decorator.args[0], method.paramTypes[0], decorator.args[1])]
197
+ return [self.manager.get(decorator.args[0], method.param_types[0], decorator.args[1])]