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 +45 -9
- aspyx/di/di.py +30 -73
- aspyx/di/threading/synchronized.py +3 -1
- aspyx/exception/exception_manager.py +19 -17
- aspyx/reflection/proxy.py +10 -3
- aspyx/reflection/reflection.py +8 -4
- aspyx-1.5.2.dist-info/METADATA +839 -0
- {aspyx-1.5.0.dist-info → aspyx-1.5.2.dist-info}/RECORD +10 -10
- aspyx-1.5.0.dist-info/METADATA +0 -33
- {aspyx-1.5.0.dist-info → aspyx-1.5.2.dist-info}/WHEEL +0 -0
- {aspyx-1.5.0.dist-info → aspyx-1.5.2.dist-info}/licenses/LICENSE +0 -0
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 =
|
|
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
|
-
|
|
231
|
+
__slots__ = ["belonging_to"]
|
|
221
232
|
|
|
222
|
-
|
|
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
|
-
|
|
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() ->
|
|
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
|
-
|
|
344
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
80
|
+
if inspect.iscoroutinefunction(method):
|
|
81
|
+
|
|
82
|
+
@functools.wraps(method)
|
|
77
83
|
async def async_wrapper(*args, **kwargs):
|
|
78
|
-
return
|
|
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
|
|
aspyx/reflection/reflection.py
CHANGED
|
@@ -42,11 +42,15 @@ class Decorators:
|
|
|
42
42
|
decorator: the decorator
|
|
43
43
|
*args: any arguments supplied to the decorator
|
|
44
44
|
"""
|
|
45
|
-
|
|
46
|
-
if
|
|
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
|
-
|
|
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
|
|