aspyx 1.5.0__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
@@ -316,7 +316,7 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
316
316
 
317
317
  self.environment = environment
318
318
  self.provider = provider
319
- self.dependencies : list[AbstractInstanceProvider] = []
319
+ self.dependencies : Optional[list[AbstractInstanceProvider]] = None # FOO
320
320
  self.scope_instance = Scopes.get(provider.get_scope(), environment)
321
321
 
322
322
  # public
@@ -340,27 +340,20 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
340
340
  # implement
341
341
 
342
342
  def resolve(self, context: Providers.ResolveContext):
343
- context.add(self)
344
- context.push(self)
343
+ if self.dependencies is None:
344
+ self.dependencies = []
345
+ context.push(self)
346
+ try:
347
+ type_and_params = self.provider.get_dependencies()
348
+ #params = type_and_params[1]
349
+ for type in type_and_params[0]:
350
+ provider = context.require_provider(type)
345
351
 
346
- if not context.is_resolved(self):
347
- context.provider_dependencies[self] = [] #?
352
+ self.dependencies.append(provider)
348
353
 
349
- type_and_params = self.provider.get_dependencies()
350
- params = type_and_params[1]
351
- for type in type_and_params[0]:
352
- if params > 0:
353
- params -= 1
354
- self.dependencies.append(context.get_provider(type)) # try/catch TODO
355
-
356
- provider = context.add_provider_dependency(self, type)
357
- if provider is not None:
358
354
  provider.resolve(context)
359
-
360
- else:
361
- context.add(*context.get_provider_dependencies(self))
362
-
363
- context.pop()
355
+ finally:
356
+ context.pop()
364
357
 
365
358
  def get_module(self) -> str:
366
359
  return self.provider.get_module()
@@ -559,7 +552,7 @@ class PostProcessor(LifecycleProcessor):
559
552
  """
560
553
  __slots__ = []
561
554
 
562
-
555
+ @abstractmethod
563
556
  def process(self, instance: object, environment: Environment):
564
557
  pass
565
558
 
@@ -570,72 +563,39 @@ class PostProcessor(LifecycleProcessor):
570
563
 
571
564
  class Providers:
572
565
  """
573
- The Providers class is a static class that manages the registration and resolution of InstanceProviders.
566
+ The Providers class is a static class used in the context of the registration and resolution of InstanceProviders.
574
567
  """
575
568
  # local class
576
569
 
577
570
  class ResolveContext:
578
571
  __slots__ = [
579
- "dependencies",
580
572
  "providers",
581
- "provider_dependencies",
582
573
  "path"
583
574
  ]
584
575
 
585
576
  # constructor
586
577
 
587
578
  def __init__(self, providers: Dict[Type, EnvironmentInstanceProvider]):
588
- self.dependencies : list[EnvironmentInstanceProvider] = []
589
579
  self.providers = providers
590
580
  self.path = []
591
- self.provider_dependencies : dict[EnvironmentInstanceProvider, list[EnvironmentInstanceProvider]] = {}
592
581
 
593
582
  # public
594
583
 
595
- def is_resolved(self, provider: EnvironmentInstanceProvider) -> bool:
596
- return self.provider_dependencies.get(provider, None) is not None
597
-
598
- def get_provider_dependencies(self, provider: EnvironmentInstanceProvider) -> list[EnvironmentInstanceProvider]:
599
- return self.provider_dependencies[provider]
600
-
601
- def add_provider_dependency(self, provider: EnvironmentInstanceProvider, type: Type) -> Optional[EnvironmentInstanceProvider]:
602
- provider_dependencies = self.provider_dependencies.get(provider, None)
603
- if provider_dependencies is None:
604
- provider_dependencies = []
605
- self.provider_dependencies[provider] = provider_dependencies
606
-
607
- provider = self.get_provider(type)
608
-
609
- if any(issubclass(provider.get_type(), dependency.get_type()) for dependency in provider_dependencies):
610
- return None
611
-
612
- provider_dependencies.append(provider)
613
-
614
- return provider
615
-
616
584
  def push(self, provider):
617
585
  self.path.append(provider)
618
586
 
619
587
  def pop(self):
620
588
  self.path.pop()
621
589
 
622
- def next(self):
623
- self.path.clear()
624
- self.dependencies.clear()
625
-
626
- def get_provider(self, type: Type) -> EnvironmentInstanceProvider:
590
+ def require_provider(self, type: Type) -> EnvironmentInstanceProvider:
627
591
  provider = self.providers.get(type, None)
628
592
  if provider is None:
629
593
  raise DIRegistrationException(f"Provider for {type} is not defined")
630
594
 
631
- return provider
632
-
633
- def add(self, *providers: EnvironmentInstanceProvider):
634
- for provider in providers:
635
- if next((p for p in self.dependencies if p.get_type() is provider.get_type()), None) is not None:
636
- raise DIRegistrationException(self.cycle_report(provider))
595
+ if provider in self.path:
596
+ raise DIRegistrationException(self.cycle_report(provider))
637
597
 
638
- self.dependencies.append(provider)
598
+ return provider
639
599
 
640
600
  def cycle_report(self, provider: AbstractInstanceProvider):
641
601
  cycle = ""
@@ -729,7 +689,7 @@ class Providers:
729
689
  return True
730
690
 
731
691
  def is_injectable(type: Type) -> bool:
732
- if type is object:
692
+ if type in [object, ABC]:
733
693
  return False
734
694
 
735
695
  if inspect.isabstract(type):
@@ -788,7 +748,6 @@ class Providers:
788
748
  provider_context = Providers.ResolveContext(providers)
789
749
  for provider in mapped.values():
790
750
  provider.resolve(provider_context)
791
- provider_context.next() # clear dependencies
792
751
 
793
752
  # done
794
753
 
@@ -1016,6 +975,11 @@ class Environment:
1016
975
  parent (Optional[Environment]): Optional parent environment, whose objects are inherited.
1017
976
  """
1018
977
 
978
+ def add_provider(type: Type, provider: AbstractInstanceProvider):
979
+ Environment.logger.debug("\tadd provider %s for %s", provider, type)
980
+
981
+ self.providers[type] = provider
982
+
1019
983
  Environment.logger.debug("create environment for class %s", env.__qualname__)
1020
984
 
1021
985
  # initialize
@@ -1034,11 +998,13 @@ class Environment:
1034
998
  # inherit providers from parent
1035
999
 
1036
1000
  for provider_type, inherited_provider in self.parent.providers.items():
1001
+ provider = inherited_provider
1037
1002
  if inherited_provider.get_scope() == "environment":
1038
1003
  # replace with own environment instance provider
1039
- self.providers[provider_type] = EnvironmentInstanceProvider(self, cast(EnvironmentInstanceProvider, inherited_provider).provider)
1040
- else:
1041
- self.providers[provider_type] = inherited_provider
1004
+ provider = EnvironmentInstanceProvider(self, cast(EnvironmentInstanceProvider, inherited_provider).provider)
1005
+ provider.dependencies = [] # ??
1006
+
1007
+ add_provider(provider_type, provider)
1042
1008
 
1043
1009
  # inherit processors as is unless they have an environment scope
1044
1010
 
@@ -1046,8 +1012,7 @@ class Environment:
1046
1012
  if self.providers[type(processor)].get_scope() != "environment":
1047
1013
  self.lifecycle_processors.append(processor)
1048
1014
  else:
1049
- # create and remember
1050
- self.lifecycle_processors.append(self.get(type(processor)))
1015
+ self.get(type(processor)) # will automatically be appended
1051
1016
  else:
1052
1017
  self.providers[SingletonScope] = SingletonScopeInstanceProvider()
1053
1018
  self.providers[RequestScope] = RequestScopeInstanceProvider()
@@ -1059,11 +1024,6 @@ class Environment:
1059
1024
 
1060
1025
  loaded = set()
1061
1026
 
1062
- def add_provider(type: Type, provider: AbstractInstanceProvider):
1063
- Environment.logger.debug("\tadd provider %s for %s", provider, type)
1064
-
1065
- self.providers[type] = provider
1066
-
1067
1027
  def get_type_package(type: Type):
1068
1028
  module_name = type.__module__
1069
1029
  module = sys.modules.get(module_name)
@@ -1170,9 +1130,6 @@ class Environment:
1170
1130
  # construct eager objects for local providers
1171
1131
 
1172
1132
  for provider in set(self.providers.values()):
1173
- if isinstance(provider, EnvironmentInstanceProvider):
1174
- provider.print_tree()
1175
-
1176
1133
  if provider.is_eager():
1177
1134
  provider.create(self)
1178
1135
 
@@ -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