aspyx 1.5.1__py3-none-any.whl → 1.5.2__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/aop/aop.py CHANGED
@@ -3,6 +3,7 @@ This module provides aspect-oriented programming (AOP) capabilities for Python a
3
3
  """
4
4
  from __future__ import annotations
5
5
 
6
+ import functools
6
7
  from abc import ABC, abstractmethod
7
8
  import inspect
8
9
  import re
@@ -64,7 +65,7 @@ class AspectTarget(ABC):
64
65
  def __init__(self):
65
66
  self._clazz = None
66
67
  self._instance = None
67
- self._async = False
68
+ self._async : Optional[bool] = None
68
69
  self._function = None
69
70
  self._type = None
70
71
 
@@ -118,6 +119,16 @@ class AspectTarget(ABC):
118
119
  self._async = True
119
120
  return self
120
121
 
122
+ def that_are_sync(self) -> AspectTarget:
123
+ """
124
+ matches methods that are sync
125
+
126
+ Returns:
127
+ AspectTarget: self
128
+ """
129
+ self._async = False
130
+ return self
131
+
121
132
  def of_type(self, type: Type) -> AspectTarget:
122
133
  """
123
134
  matches methods belonging to a class or classes that are subclasses of the specified type
@@ -217,10 +228,14 @@ class MethodAspectTarget(AspectTarget):
217
228
  An AspectTarget matching methods
218
229
  """
219
230
 
220
- # properties
231
+ __slots__ = ["belonging_to"]
221
232
 
222
- __slots__ = [ ]
233
+ # constructor
223
234
 
235
+ def __init__(self):
236
+ super().__init__()
237
+
238
+ self.belonging_to : list[ClassAspectTarget] = []
224
239
  # public
225
240
 
226
241
  def _matches_self(self, clazz : Type, func):
@@ -228,9 +243,21 @@ class MethodAspectTarget(AspectTarget):
228
243
 
229
244
  method_descriptor = descriptor.get_method(func.__name__)
230
245
 
246
+ # classes
247
+
248
+ if len(self.belonging_to) > 0:
249
+ match = False
250
+ for classes in self.belonging_to:
251
+ if classes._matches(clazz, func):
252
+ match = True
253
+ break
254
+
255
+ if not match:
256
+ return False
257
+
231
258
  # async
232
259
 
233
- if self._async is not method_descriptor.is_async():
260
+ if self._async is not None and self._async is not method_descriptor.is_async():
234
261
  return False
235
262
 
236
263
  # type
@@ -261,7 +288,14 @@ class MethodAspectTarget(AspectTarget):
261
288
 
262
289
  return True
263
290
 
264
- def methods() -> AspectTarget:
291
+ # fluent
292
+
293
+ def declared_by(self, classes: ClassAspectTarget) -> MethodAspectTarget:
294
+ self.belonging_to.append(classes)
295
+
296
+ return self
297
+
298
+ def methods() -> MethodAspectTarget:
265
299
  """
266
300
  Create a new AspectTarget instance to define method aspect targets.
267
301
 
@@ -270,7 +304,7 @@ def methods() -> AspectTarget:
270
304
  """
271
305
  return MethodAspectTarget()
272
306
 
273
- def classes() -> AspectTarget:
307
+ def classes() -> ClassAspectTarget:
274
308
  """
275
309
  Create a new AspectTarget instance to define class aspect targets.
276
310
 
@@ -563,7 +597,7 @@ def advice(cls):
563
597
  Classes decorated with `@advice` are treated as advice classes.
564
598
  They can contain methods decorated with `@before`, `@after`, `@around`, or `@error` to define aspects.
565
599
  """
566
- Providers.register(ClassInstanceProvider(cls, True))
600
+ #Providers.register(ClassInstanceProvider(cls, True))
567
601
 
568
602
  Decorators.add(cls, advice)
569
603
 
@@ -673,13 +707,15 @@ class AdviceProcessor(PostProcessor):
673
707
  for member, aspects in aspect_dict.items():
674
708
  Environment.logger.debug("add aspects for %s:%s", type(instance), member.__name__)
675
709
 
676
- def wrap(jp):
710
+ def wrap(jp, member=member):
711
+ @functools.wraps(member)
677
712
  def sync_wrapper(*args, **kwargs):
678
713
  return Invocation(member, jp).call(*args, **kwargs)
679
714
 
680
715
  return sync_wrapper
681
716
 
682
- def wrap_async(jp):
717
+ def wrap_async(jp, member=member):
718
+ @functools.wraps(member)
683
719
  async def async_wrapper(*args, **kwargs):
684
720
  return await Invocation(member, jp).call_async(*args, **kwargs)
685
721
 
aspyx/di/di.py CHANGED
@@ -552,7 +552,7 @@ class PostProcessor(LifecycleProcessor):
552
552
  """
553
553
  __slots__ = []
554
554
 
555
-
555
+ @abstractmethod
556
556
  def process(self, instance: object, environment: Environment):
557
557
  pass
558
558
 
@@ -689,7 +689,7 @@ class Providers:
689
689
  return True
690
690
 
691
691
  def is_injectable(type: Type) -> bool:
692
- if type is object:
692
+ if type in [object, ABC]:
693
693
  return False
694
694
 
695
695
  if inspect.isabstract(type):
@@ -998,13 +998,13 @@ class Environment:
998
998
  # inherit providers from parent
999
999
 
1000
1000
  for provider_type, inherited_provider in self.parent.providers.items():
1001
+ provider = inherited_provider
1001
1002
  if inherited_provider.get_scope() == "environment":
1002
1003
  # replace with own environment instance provider
1003
1004
  provider = EnvironmentInstanceProvider(self, cast(EnvironmentInstanceProvider, inherited_provider).provider)
1004
1005
  provider.dependencies = [] # ??
1005
- add_provider(provider_type, provider)
1006
- else:
1007
- add_provider(provider_type, inherited_provider)
1006
+
1007
+ add_provider(provider_type, provider)
1008
1008
 
1009
1009
  # inherit processors as is unless they have an environment scope
1010
1010
 
@@ -1012,8 +1012,7 @@ class Environment:
1012
1012
  if self.providers[type(processor)].get_scope() != "environment":
1013
1013
  self.lifecycle_processors.append(processor)
1014
1014
  else:
1015
- # create and remember
1016
- self.lifecycle_processors.append(self.get(type(processor)))
1015
+ self.get(type(processor)) # will automatically be appended
1017
1016
  else:
1018
1017
  self.providers[SingletonScope] = SingletonScopeInstanceProvider()
1019
1018
  self.providers[RequestScope] = RequestScopeInstanceProvider()
@@ -6,6 +6,7 @@ from __future__ import annotations
6
6
  import threading
7
7
  from weakref import WeakKeyDictionary
8
8
 
9
+ from aspyx.di import injectable
9
10
  from aspyx.reflection import Decorators
10
11
  from aspyx.di.aop import advice, around, methods, Invocation
11
12
 
@@ -20,6 +21,7 @@ def synchronized():
20
21
  return decorator
21
22
 
22
23
  @advice
24
+ @injectable()
23
25
  class SynchronizeAdvice:
24
26
  # constructor
25
27
 
@@ -38,7 +40,7 @@ class SynchronizeAdvice:
38
40
 
39
41
  # around
40
42
 
41
- @around(methods().decorated_with(synchronized))
43
+ @around(methods().decorated_with(synchronized).that_are_sync())
42
44
  def synchronize_sync(self, invocation: Invocation):
43
45
  with self.get_lock(invocation.args[0]):
44
46
  return invocation.proceed()
@@ -107,6 +107,24 @@ class ExceptionManager:
107
107
 
108
108
  # internal
109
109
 
110
+ def collect_handlers(self, instance: Any):
111
+ type_descriptor = TypeDescriptor.for_type(type(instance))
112
+
113
+ # analyze methods
114
+
115
+ for method in type_descriptor.get_methods():
116
+ if method.has_decorator(handle):
117
+ if len(method.param_types) == 1:
118
+ exception_type = method.param_types[0]
119
+
120
+ self.handler.append(Handler(
121
+ exception_type,
122
+ instance,
123
+ method.method,
124
+ ))
125
+ else:
126
+ print(f"handler {method.method} expected to have one parameter")
127
+
110
128
  @inject_environment()
111
129
  def set_environment(self, environment: Environment):
112
130
  self.environment = environment
@@ -114,23 +132,7 @@ class ExceptionManager:
114
132
  @on_running()
115
133
  def setup(self):
116
134
  for handler_class in self.exception_handler_classes:
117
- type_descriptor = TypeDescriptor.for_type(handler_class)
118
- instance = self.environment.get(handler_class)
119
-
120
- # analyze methods
121
-
122
- for method in type_descriptor.get_methods():
123
- if method.has_decorator(handle):
124
- if len(method.param_types) == 1:
125
- exception_type = method.param_types[0]
126
-
127
- self.handler.append(Handler(
128
- exception_type,
129
- instance,
130
- method.method,
131
- ))
132
- else:
133
- print(f"handler {method.method} expected to have one parameter")
135
+ self.collect_handlers(self.environment.get(handler_class))
134
136
 
135
137
  def get_handlers(self, clazz: Type) -> Optional[Chain]:
136
138
  chain = self.cache.get(clazz, None)
aspyx/reflection/proxy.py CHANGED
@@ -1,7 +1,9 @@
1
1
  """
2
2
  Dynamic proxies for method interception and delegation.
3
3
  """
4
+ import functools
4
5
  import inspect
6
+ from abc import ABC, abstractmethod
5
7
  from typing import Generic, TypeVar, Type, Callable
6
8
 
7
9
  T = TypeVar("T")
@@ -44,10 +46,12 @@ class DynamicProxy(Generic[T]):
44
46
  self.args = args
45
47
  self.kwargs = kwargs
46
48
 
47
- class InvocationHandler:
49
+ class InvocationHandler(ABC):
50
+ @abstractmethod
48
51
  def invoke(self, invocation: 'DynamicProxy.Invocation'):
49
52
  pass
50
53
 
54
+ @abstractmethod
51
55
  async def invoke_async(self, invocation: 'DynamicProxy.Invocation'):
52
56
  return self.invoke(invocation)
53
57
 
@@ -73,13 +77,16 @@ class DynamicProxy(Generic[T]):
73
77
  def __getattr__(self, name):
74
78
  method = getattr(self.type, name)
75
79
 
76
- if inspect.iscoroutinefunction(method):
80
+ if inspect.iscoroutinefunction(method):
81
+
82
+ @functools.wraps(method)
77
83
  async def async_wrapper(*args, **kwargs):
78
- return await self.invocation_handler.invoke_async(DynamicProxy.Invocation(self.type, method, *args, **kwargs))
84
+ return await self.invocation_handler.invoke_async(DynamicProxy.Invocation(self.type, method, *args, **kwargs))
79
85
 
80
86
  return async_wrapper
81
87
 
82
88
  else:
89
+ @functools.wraps(method)
83
90
  def sync_wrapper(*args, **kwargs):
84
91
  return self.invocation_handler.invoke(DynamicProxy.Invocation(self.type, method, *args, **kwargs))
85
92
 
@@ -42,11 +42,15 @@ class Decorators:
42
42
  decorator: the decorator
43
43
  *args: any arguments supplied to the decorator
44
44
  """
45
- decorators = getattr(func_or_class, '__decorators__', None)
46
- if decorators is None:
45
+ current = func_or_class.__dict__.get('__decorators__')
46
+ if current is None:
47
47
  setattr(func_or_class, '__decorators__', [DecoratorDescriptor(decorator, *args)])
48
48
  else:
49
- decorators.append(DecoratorDescriptor(decorator, *args))
49
+ # Avoid mutating inherited list
50
+ if '__decorators__' not in func_or_class.__dict__:
51
+ current = list(current)
52
+ setattr(func_or_class, '__decorators__', current)
53
+ current.append(DecoratorDescriptor(decorator, *args))
50
54
 
51
55
  @classmethod
52
56
  def has_decorator(cls, func_or_class, callable: Callable) -> bool:
@@ -82,7 +86,7 @@ class Decorators:
82
86
  if inspect.ismethod(func_or_class):
83
87
  func_or_class = func_or_class.__func__ # unwrap bound method
84
88
 
85
- #return getattr(func_or_class, '__decorators__', []) will return inherited as well
89
+ #return getattr(func_or_class, '__decorators__', []) #will return inherited as well
86
90
  return func_or_class.__dict__.get('__decorators__', [])
87
91
 
88
92
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspyx
3
- Version: 1.5.1
3
+ Version: 1.5.2
4
4
  Summary: A DI and AOP library for Python
5
5
  Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
6
6
  License: MIT License
@@ -1,24 +1,24 @@
1
1
  aspyx/__init__.py,sha256=MsSFjiLMLJZ7QhUPpVBWKiyDnCzryquRyr329NoCACI,2
2
2
  aspyx/di/__init__.py,sha256=AGVU2VBWQyBxSssvbk_GOKrYWIYtcmSoIlupz-Oqxi4,1138
3
- aspyx/di/di.py,sha256=O8FUZm68aimUbt7GnIc43XzRa47iFV8Qh696JGOwdEE,44703
3
+ aspyx/di/di.py,sha256=V_BAV6DmFCoepPqAXhBz2GW6NwYaKokHb03HMz6A5Sw,44639
4
4
  aspyx/di/aop/__init__.py,sha256=rn6LSpzFtUOlgaBATyhLRWBzFmZ6XoVKA9B8SgQzYEI,746
5
- aspyx/di/aop/aop.py,sha256=Cn-fqFW6PznVDM38fPX7mqlSpjGKMsgpJRBSYBv59xY,18403
5
+ aspyx/di/aop/aop.py,sha256=UqBpBI2nW6_8CLmY1j0F4cE2QVBQDgry9LWShVuxZKM,19367
6
6
  aspyx/di/configuration/__init__.py,sha256=flM9A79J2wfA5I8goQbxs4tTqYustR9tn_9s0YO2WJQ,484
7
7
  aspyx/di/configuration/configuration.py,sha256=cXW40bPXiUZ9hUtBoZkSATT3nLrDPWsSqxtgASIBQaM,4375
8
8
  aspyx/di/configuration/env_configuration_source.py,sha256=FXPvREzq2ZER6_GG5xdpx154TQQDxZVf7LW7cvaylAk,1446
9
9
  aspyx/di/configuration/yaml_configuration_source.py,sha256=NDl3SeoLMNVlzHgfP-Ysvhco1tRew_zFnBL5gGy2WRk,550
10
10
  aspyx/di/threading/__init__.py,sha256=qrWdaq7MewQ2UmZy4J0Dn6BhY-ahfiG3xsv-EHqoqSE,191
11
- aspyx/di/threading/synchronized.py,sha256=BQ9PjMQUJsF5r-qWaDgvqg3AvFm_R9QZdKB49EkoelQ,1263
11
+ aspyx/di/threading/synchronized.py,sha256=6JOg5BXWrRIS5nRPH9iWR7T-kUglO4qWBQpLwhy99pI,1325
12
12
  aspyx/exception/__init__.py,sha256=OZwv-C3ZHD0Eg1rohCQMj575WLJ7lfYuk6PZD6sh1MA,211
13
- aspyx/exception/exception_manager.py,sha256=tv0nb0b2CFPiYWK6wwH9yI8hSc--9Xz9_xQVBwU42wc,5228
13
+ aspyx/exception/exception_manager.py,sha256=_pK4yTS5FJYj2SW1ijdBVD_6qrPUM9elMgZki2pf-Tw,5237
14
14
  aspyx/reflection/__init__.py,sha256=r2sNJrfHDpuqaIYu4fTYsoo046gpgn4VTd7bsS3mQJY,282
15
- aspyx/reflection/proxy.py,sha256=9zqzmK2HGGx7LxdiBw8MfKRNT8H03h_0I6Y972eKFH8,2582
16
- aspyx/reflection/reflection.py,sha256=HYzbzExG7jzdHG_AggrRxy9yte6Cl192dPsJBRl785Y,8939
15
+ aspyx/reflection/proxy.py,sha256=1-pgw-TNORFXbV0gowFZqGd-bcWv1ny69bJhq8TLsKs,2761
16
+ aspyx/reflection/reflection.py,sha256=BcqXJcMO36Gzq71O4aBsMqPynmBhYLI8V6J7DOInPAM,9142
17
17
  aspyx/threading/__init__.py,sha256=3clmbCDP37GPan3dWtxTQvpg0Ti4aFzruAbUClkHGi0,147
18
18
  aspyx/threading/thread_local.py,sha256=86dNtbA4k2B-rNUUnZgn3_pU0DAojgLrRnh8RL6zf1E,1196
19
19
  aspyx/util/__init__.py,sha256=8H2yKkXu3nkRGeTerb8ialzKGfvzUx44XUWFUYcYuQM,125
20
20
  aspyx/util/stringbuilder.py,sha256=a-0T4YEXSJFUuQ3ztKN1ZPARkh8dIGMSkNEEJHRN7dc,856
21
- aspyx-1.5.1.dist-info/METADATA,sha256=U4sRH9owvFuCgQINKFlC84hL5BnQ-5kGavDNb-q8D88,26490
22
- aspyx-1.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- aspyx-1.5.1.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
24
- aspyx-1.5.1.dist-info/RECORD,,
21
+ aspyx-1.5.2.dist-info/METADATA,sha256=mRlR5o-1WlYVvThk483KJTv738uNq5y02VkquUUsYDk,26490
22
+ aspyx-1.5.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ aspyx-1.5.2.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
24
+ aspyx-1.5.2.dist-info/RECORD,,
File without changes