aspyx 1.4.1__py3-none-any.whl → 1.5.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/__init__.py +1 -0
- aspyx/di/__init__.py +2 -2
- aspyx/di/di.py +105 -80
- aspyx/exception/exception_manager.py +1 -1
- aspyx/reflection/proxy.py +33 -6
- aspyx/reflection/reflection.py +31 -7
- {aspyx-1.4.1.dist-info → aspyx-1.5.1.dist-info}/METADATA +7 -13
- {aspyx-1.4.1.dist-info → aspyx-1.5.1.dist-info}/RECORD +10 -11
- {aspyx-1.4.1.dist-info → aspyx-1.5.1.dist-info}/WHEEL +1 -2
- aspyx-1.4.1.dist-info/top_level.txt +0 -1
- {aspyx-1.4.1.dist-info → aspyx-1.5.1.dist-info}/licenses/LICENSE +0 -0
aspyx/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#
|
aspyx/di/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module provides dependency injection and aop capabilities for Python applications.
|
|
3
3
|
"""
|
|
4
|
-
from .di import conditional, requires_class, requires_feature, DIException, AbstractCallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, module, inject, order, create, on_init, on_running, on_destroy, inject_environment, Factory, PostProcessor
|
|
4
|
+
from .di import InstanceProvider, conditional, requires_class, requires_feature, DIException, AbstractCallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, module, inject, order, create, on_init, on_running, on_destroy, inject_environment, Factory, PostProcessor
|
|
5
5
|
|
|
6
6
|
# import something from the subpackages, so that the decorators are executed
|
|
7
7
|
|
|
@@ -21,7 +21,7 @@ __all__ = [
|
|
|
21
21
|
"inject",
|
|
22
22
|
"create",
|
|
23
23
|
"order",
|
|
24
|
-
|
|
24
|
+
"InstanceProvider",
|
|
25
25
|
"on_init",
|
|
26
26
|
"on_running",
|
|
27
27
|
"on_destroy",
|
aspyx/di/di.py
CHANGED
|
@@ -316,30 +316,44 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
316
316
|
|
|
317
317
|
self.environment = environment
|
|
318
318
|
self.provider = provider
|
|
319
|
-
self.dependencies
|
|
319
|
+
self.dependencies : Optional[list[AbstractInstanceProvider]] = None # FOO
|
|
320
320
|
self.scope_instance = Scopes.get(provider.get_scope(), environment)
|
|
321
321
|
|
|
322
|
+
# public
|
|
323
|
+
|
|
324
|
+
def print_tree(self, prefix=""):
|
|
325
|
+
children = self.dependencies
|
|
326
|
+
last_index = len(children) - 1
|
|
327
|
+
print(prefix + "+- " + self.report())
|
|
328
|
+
|
|
329
|
+
for i, child in enumerate(children):
|
|
330
|
+
if i == last_index:
|
|
331
|
+
# Last child
|
|
332
|
+
child_prefix = prefix + " "
|
|
333
|
+
else:
|
|
334
|
+
# Not last child
|
|
335
|
+
child_prefix = prefix + "| "
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
cast(EnvironmentInstanceProvider, child).print_tree(child_prefix)
|
|
339
|
+
|
|
322
340
|
# implement
|
|
323
341
|
|
|
324
342
|
def resolve(self, context: Providers.ResolveContext):
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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)
|
|
329
351
|
|
|
330
|
-
|
|
331
|
-
params = type_and_params[1]
|
|
332
|
-
for type in type_and_params[0]:
|
|
333
|
-
if params > 0:
|
|
334
|
-
params -= 1
|
|
335
|
-
self.dependencies.append(context.get_provider(type))
|
|
352
|
+
self.dependencies.append(provider)
|
|
336
353
|
|
|
337
|
-
provider = context.add_provider_dependency(self, type)
|
|
338
|
-
if provider is not None:
|
|
339
354
|
provider.resolve(context)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
context.add(*context.get_provider_dependencies(self))
|
|
355
|
+
finally:
|
|
356
|
+
context.pop()
|
|
343
357
|
|
|
344
358
|
def get_module(self) -> str:
|
|
345
359
|
return self.provider.get_module()
|
|
@@ -403,7 +417,11 @@ class ClassInstanceProvider(InstanceProvider):
|
|
|
403
417
|
for method in TypeDescriptor.for_type(self.type).get_methods():
|
|
404
418
|
if method.has_decorator(inject):
|
|
405
419
|
for param in method.param_types:
|
|
420
|
+
if not Providers.is_registered(param):
|
|
421
|
+
raise DIRegistrationException(f"{self.type.__name__}.{method.method.__name__} declares an unknown parameter type {param.__name__}")
|
|
422
|
+
|
|
406
423
|
types.append(param)
|
|
424
|
+
# done
|
|
407
425
|
|
|
408
426
|
return types, self.params
|
|
409
427
|
|
|
@@ -431,28 +449,28 @@ class FunctionInstanceProvider(InstanceProvider):
|
|
|
431
449
|
|
|
432
450
|
# constructor
|
|
433
451
|
|
|
434
|
-
def __init__(self, clazz : Type, method
|
|
435
|
-
super().__init__(clazz, return_type, eager, scope)
|
|
452
|
+
def __init__(self, clazz : Type, method: TypeDescriptor.MethodDescriptor, eager = True, scope = "singleton"):
|
|
453
|
+
super().__init__(clazz, method.return_type, eager, scope)
|
|
436
454
|
|
|
437
|
-
self.method = method
|
|
455
|
+
self.method : TypeDescriptor.MethodDescriptor = method
|
|
438
456
|
|
|
439
457
|
# implement
|
|
440
458
|
|
|
441
459
|
def get_dependencies(self) -> (list[Type],int):
|
|
442
|
-
return [self.host], 1
|
|
460
|
+
return [self.host, *self.method.param_types], 1 + len(self.method.param_types)
|
|
443
461
|
|
|
444
462
|
def create(self, environment: Environment, *args):
|
|
445
463
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
446
464
|
|
|
447
|
-
instance = self.method(*args) # args[0]=self
|
|
465
|
+
instance = self.method.method(*args) # args[0]=self
|
|
448
466
|
|
|
449
467
|
return environment.created(instance)
|
|
450
468
|
|
|
451
469
|
def report(self) -> str:
|
|
452
|
-
return f"{self.host.__name__}.{self.method.__name__}"
|
|
470
|
+
return f"{self.host.__name__}.{self.method.get_name()}({', '.join(t.__name__ for t in self.method.param_types)}) -> {self.type.__qualname__}"
|
|
453
471
|
|
|
454
472
|
def __str__(self):
|
|
455
|
-
return f"FunctionInstanceProvider({self.host.__name__}.{self.method.__name__} -> {self.type.__name__})"
|
|
473
|
+
return f"FunctionInstanceProvider({self.host.__name__}.{self.method.get_name()}({', '.join(t.__name__ for t in self.method.param_types)}) -> {self.type.__name__})"
|
|
456
474
|
|
|
457
475
|
class FactoryInstanceProvider(InstanceProvider):
|
|
458
476
|
"""
|
|
@@ -483,7 +501,7 @@ class FactoryInstanceProvider(InstanceProvider):
|
|
|
483
501
|
return environment.created(args[0].create())
|
|
484
502
|
|
|
485
503
|
def report(self) -> str:
|
|
486
|
-
return f"{self.host.__name__}.create"
|
|
504
|
+
return f"{self.host.__name__}.create() -> {self.type.__name__} "
|
|
487
505
|
|
|
488
506
|
def __str__(self):
|
|
489
507
|
return f"FactoryInstanceProvider({self.host.__name__} -> {self.type.__name__})"
|
|
@@ -545,69 +563,45 @@ class PostProcessor(LifecycleProcessor):
|
|
|
545
563
|
|
|
546
564
|
class Providers:
|
|
547
565
|
"""
|
|
548
|
-
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.
|
|
549
567
|
"""
|
|
550
568
|
# local class
|
|
551
569
|
|
|
552
570
|
class ResolveContext:
|
|
553
571
|
__slots__ = [
|
|
554
|
-
"dependencies",
|
|
555
572
|
"providers",
|
|
556
|
-
"
|
|
573
|
+
"path"
|
|
557
574
|
]
|
|
558
575
|
|
|
559
576
|
# constructor
|
|
560
577
|
|
|
561
578
|
def __init__(self, providers: Dict[Type, EnvironmentInstanceProvider]):
|
|
562
|
-
self.dependencies : list[EnvironmentInstanceProvider] = []
|
|
563
579
|
self.providers = providers
|
|
564
|
-
self.
|
|
580
|
+
self.path = []
|
|
565
581
|
|
|
566
582
|
# public
|
|
567
583
|
|
|
568
|
-
def
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
def get_provider_dependencies(self, provider: EnvironmentInstanceProvider) -> list[EnvironmentInstanceProvider]:
|
|
572
|
-
return self.provider_dependencies[provider]
|
|
573
|
-
|
|
574
|
-
def add_provider_dependency(self, provider: EnvironmentInstanceProvider, type: Type) -> Optional[EnvironmentInstanceProvider]:
|
|
575
|
-
provider_dependencies = self.provider_dependencies.get(provider, None)
|
|
576
|
-
if provider_dependencies is None:
|
|
577
|
-
provider_dependencies = []
|
|
578
|
-
self.provider_dependencies[provider] = provider_dependencies
|
|
584
|
+
def push(self, provider):
|
|
585
|
+
self.path.append(provider)
|
|
579
586
|
|
|
580
|
-
|
|
587
|
+
def pop(self):
|
|
588
|
+
self.path.pop()
|
|
581
589
|
|
|
582
|
-
|
|
583
|
-
return None
|
|
584
|
-
|
|
585
|
-
provider_dependencies.append(provider)
|
|
586
|
-
|
|
587
|
-
return provider
|
|
588
|
-
|
|
589
|
-
def next(self):
|
|
590
|
-
self.dependencies.clear()
|
|
591
|
-
|
|
592
|
-
def get_provider(self, type: Type) -> EnvironmentInstanceProvider:
|
|
590
|
+
def require_provider(self, type: Type) -> EnvironmentInstanceProvider:
|
|
593
591
|
provider = self.providers.get(type, None)
|
|
594
592
|
if provider is None:
|
|
595
593
|
raise DIRegistrationException(f"Provider for {type} is not defined")
|
|
596
594
|
|
|
597
|
-
|
|
595
|
+
if provider in self.path:
|
|
596
|
+
raise DIRegistrationException(self.cycle_report(provider))
|
|
598
597
|
|
|
599
|
-
|
|
600
|
-
for provider in providers:
|
|
601
|
-
if next((p for p in self.dependencies if p.get_type() is provider.get_type()), None) is not None:
|
|
602
|
-
raise DIRegistrationException(self.cycle_report(provider))
|
|
603
|
-
|
|
604
|
-
self.dependencies.append(provider)
|
|
598
|
+
return provider
|
|
605
599
|
|
|
606
600
|
def cycle_report(self, provider: AbstractInstanceProvider):
|
|
607
601
|
cycle = ""
|
|
608
602
|
|
|
609
603
|
first = True
|
|
610
|
-
for p in self.
|
|
604
|
+
for p in self.path:
|
|
611
605
|
if not first:
|
|
612
606
|
cycle += " -> "
|
|
613
607
|
|
|
@@ -638,6 +632,10 @@ class Providers:
|
|
|
638
632
|
else:
|
|
639
633
|
candidates.append(provider)
|
|
640
634
|
|
|
635
|
+
@classmethod
|
|
636
|
+
def is_registered(cls,type: Type) -> bool:
|
|
637
|
+
return Providers.providers.get(type, None) is not None
|
|
638
|
+
|
|
641
639
|
# add factories lazily
|
|
642
640
|
|
|
643
641
|
@classmethod
|
|
@@ -652,7 +650,7 @@ class Providers:
|
|
|
652
650
|
cache: Dict[Type,AbstractInstanceProvider] = {}
|
|
653
651
|
|
|
654
652
|
context: ConditionContext = {
|
|
655
|
-
"requires_feature":
|
|
653
|
+
"requires_feature": environment.has_feature,
|
|
656
654
|
"requires_class": lambda clazz : cache.get(clazz, None) is not None # ? only works if the class is in the cache already?
|
|
657
655
|
}
|
|
658
656
|
|
|
@@ -708,10 +706,13 @@ class Providers:
|
|
|
708
706
|
if type is provider.get_type():
|
|
709
707
|
raise ProviderCollisionException(f"type {type.__name__} already registered", existing_provider, provider)
|
|
710
708
|
|
|
711
|
-
if
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
709
|
+
if existing_provider.get_type() is not type:
|
|
710
|
+
# only overwrite if the existing provider is not specific
|
|
711
|
+
|
|
712
|
+
if isinstance(existing_provider, AmbiguousProvider):
|
|
713
|
+
cast(AmbiguousProvider, existing_provider).add_provider(provider)
|
|
714
|
+
else:
|
|
715
|
+
cache[type] = AmbiguousProvider(type, existing_provider, provider)
|
|
715
716
|
|
|
716
717
|
# recursion
|
|
717
718
|
|
|
@@ -747,7 +748,6 @@ class Providers:
|
|
|
747
748
|
provider_context = Providers.ResolveContext(providers)
|
|
748
749
|
for provider in mapped.values():
|
|
749
750
|
provider.resolve(provider_context)
|
|
750
|
-
provider_context.next() # clear dependencies
|
|
751
751
|
|
|
752
752
|
# done
|
|
753
753
|
|
|
@@ -763,8 +763,7 @@ def register_factories(cls: Type):
|
|
|
763
763
|
if return_type is None:
|
|
764
764
|
raise DIRegistrationException(f"{cls.__name__}.{method.method.__name__} expected to have a return type")
|
|
765
765
|
|
|
766
|
-
Providers.register(FunctionInstanceProvider(cls, method
|
|
767
|
-
create_decorator.args[1]))
|
|
766
|
+
Providers.register(FunctionInstanceProvider(cls, method, create_decorator.args[0], create_decorator.args[1]))
|
|
768
767
|
def order(prio = 0):
|
|
769
768
|
def decorator(cls):
|
|
770
769
|
Decorators.add(cls, order, prio)
|
|
@@ -939,12 +938,12 @@ class Environment:
|
|
|
939
938
|
class Foo:
|
|
940
939
|
def __init__(self):
|
|
941
940
|
|
|
942
|
-
@
|
|
943
|
-
class
|
|
941
|
+
@module()
|
|
942
|
+
class Module:
|
|
944
943
|
def __init__(self):
|
|
945
944
|
pass
|
|
946
945
|
|
|
947
|
-
environment = Environment(
|
|
946
|
+
environment = Environment(Module)
|
|
948
947
|
|
|
949
948
|
foo = environment.get(Foo) # will create an instance of Foo
|
|
950
949
|
```
|
|
@@ -976,6 +975,11 @@ class Environment:
|
|
|
976
975
|
parent (Optional[Environment]): Optional parent environment, whose objects are inherited.
|
|
977
976
|
"""
|
|
978
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
|
+
|
|
979
983
|
Environment.logger.debug("create environment for class %s", env.__qualname__)
|
|
980
984
|
|
|
981
985
|
# initialize
|
|
@@ -996,9 +1000,11 @@ class Environment:
|
|
|
996
1000
|
for provider_type, inherited_provider in self.parent.providers.items():
|
|
997
1001
|
if inherited_provider.get_scope() == "environment":
|
|
998
1002
|
# replace with own environment instance provider
|
|
999
|
-
|
|
1003
|
+
provider = EnvironmentInstanceProvider(self, cast(EnvironmentInstanceProvider, inherited_provider).provider)
|
|
1004
|
+
provider.dependencies = [] # ??
|
|
1005
|
+
add_provider(provider_type, provider)
|
|
1000
1006
|
else:
|
|
1001
|
-
|
|
1007
|
+
add_provider(provider_type, inherited_provider)
|
|
1002
1008
|
|
|
1003
1009
|
# inherit processors as is unless they have an environment scope
|
|
1004
1010
|
|
|
@@ -1019,16 +1025,35 @@ class Environment:
|
|
|
1019
1025
|
|
|
1020
1026
|
loaded = set()
|
|
1021
1027
|
|
|
1022
|
-
def add_provider(type: Type, provider: AbstractInstanceProvider):
|
|
1023
|
-
Environment.logger.debug("\tadd provider %s for %s", provider, type)
|
|
1024
|
-
|
|
1025
|
-
self.providers[type] = provider
|
|
1026
|
-
|
|
1027
1028
|
def get_type_package(type: Type):
|
|
1028
1029
|
module_name = type.__module__
|
|
1029
|
-
module = sys.modules
|
|
1030
|
+
module = sys.modules.get(module_name)
|
|
1031
|
+
|
|
1032
|
+
if not module:
|
|
1033
|
+
raise ImportError(f"Module {module_name} not found")
|
|
1034
|
+
|
|
1035
|
+
# Try to get the package
|
|
1036
|
+
|
|
1037
|
+
package = getattr(module, '__package__', None)
|
|
1038
|
+
|
|
1039
|
+
# Fallback: if module is __main__, try to infer from the module name if possible
|
|
1040
|
+
|
|
1041
|
+
if not package:
|
|
1042
|
+
if module_name == '__main__':
|
|
1043
|
+
# Try to resolve real name via __file__
|
|
1044
|
+
path = getattr(module, '__file__', None)
|
|
1045
|
+
if path:
|
|
1046
|
+
Environment.logger.warning(
|
|
1047
|
+
"Module is __main__; consider running via -m to preserve package context")
|
|
1048
|
+
return ''
|
|
1049
|
+
|
|
1050
|
+
# Try to infer package name from module name
|
|
1051
|
+
|
|
1052
|
+
parts = module_name.split('.')
|
|
1053
|
+
if len(parts) > 1:
|
|
1054
|
+
return '.'.join(parts[:-1])
|
|
1030
1055
|
|
|
1031
|
-
return
|
|
1056
|
+
return package or ''
|
|
1032
1057
|
|
|
1033
1058
|
def import_package(name: str):
|
|
1034
1059
|
"""Import a package and all its submodules recursively."""
|
aspyx/reflection/proxy.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Dynamic proxies for method interception and delegation.
|
|
3
3
|
"""
|
|
4
|
-
|
|
4
|
+
import inspect
|
|
5
|
+
from typing import Generic, TypeVar, Type, Callable
|
|
5
6
|
|
|
6
7
|
T = TypeVar("T")
|
|
7
8
|
|
|
@@ -28,9 +29,18 @@ class DynamicProxy(Generic[T]):
|
|
|
28
29
|
# inner class
|
|
29
30
|
|
|
30
31
|
class Invocation:
|
|
31
|
-
|
|
32
|
+
__slots__ = [
|
|
33
|
+
"type",
|
|
34
|
+
"method",
|
|
35
|
+
"args",
|
|
36
|
+
"kwargs",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
# constructor
|
|
40
|
+
|
|
41
|
+
def __init__(self, type: Type[T], method: Callable, *args, **kwargs):
|
|
32
42
|
self.type = type
|
|
33
|
-
self.
|
|
43
|
+
self.method = method
|
|
34
44
|
self.args = args
|
|
35
45
|
self.kwargs = kwargs
|
|
36
46
|
|
|
@@ -38,12 +48,20 @@ class DynamicProxy(Generic[T]):
|
|
|
38
48
|
def invoke(self, invocation: 'DynamicProxy.Invocation'):
|
|
39
49
|
pass
|
|
40
50
|
|
|
51
|
+
async def invoke_async(self, invocation: 'DynamicProxy.Invocation'):
|
|
52
|
+
return self.invoke(invocation)
|
|
53
|
+
|
|
41
54
|
# class methods
|
|
42
55
|
|
|
43
56
|
@classmethod
|
|
44
57
|
def create(cls, type: Type[T], invocation_handler: 'DynamicProxy.InvocationHandler') -> T:
|
|
45
58
|
return DynamicProxy(type, invocation_handler)
|
|
46
59
|
|
|
60
|
+
__slots__ = [
|
|
61
|
+
"type",
|
|
62
|
+
"invocation_handler"
|
|
63
|
+
]
|
|
64
|
+
|
|
47
65
|
# constructor
|
|
48
66
|
|
|
49
67
|
def __init__(self, type: Type[T], invocation_handler: 'DynamicProxy.InvocationHandler'):
|
|
@@ -53,7 +71,16 @@ class DynamicProxy(Generic[T]):
|
|
|
53
71
|
# public
|
|
54
72
|
|
|
55
73
|
def __getattr__(self, name):
|
|
56
|
-
|
|
57
|
-
|
|
74
|
+
method = getattr(self.type, name)
|
|
75
|
+
|
|
76
|
+
if inspect.iscoroutinefunction(method):
|
|
77
|
+
async def async_wrapper(*args, **kwargs):
|
|
78
|
+
return await self.invocation_handler.invoke_async(DynamicProxy.Invocation(self.type, method, *args, **kwargs))
|
|
79
|
+
|
|
80
|
+
return async_wrapper
|
|
81
|
+
|
|
82
|
+
else:
|
|
83
|
+
def sync_wrapper(*args, **kwargs):
|
|
84
|
+
return self.invocation_handler.invoke(DynamicProxy.Invocation(self.type, method, *args, **kwargs))
|
|
58
85
|
|
|
59
|
-
|
|
86
|
+
return sync_wrapper
|
aspyx/reflection/reflection.py
CHANGED
|
@@ -5,8 +5,10 @@ including their methods, decorators, and type hints. It supports caching for per
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import inspect
|
|
8
|
-
from inspect import signature
|
|
8
|
+
from inspect import signature
|
|
9
9
|
import threading
|
|
10
|
+
from types import FunctionType
|
|
11
|
+
|
|
10
12
|
from typing import Callable, get_type_hints, Type, Dict, Optional
|
|
11
13
|
from weakref import WeakKeyDictionary
|
|
12
14
|
|
|
@@ -25,7 +27,7 @@ class DecoratorDescriptor:
|
|
|
25
27
|
self.args = args
|
|
26
28
|
|
|
27
29
|
def __str__(self):
|
|
28
|
-
return f"@{self.decorator.__name__}({','.join(self.args)})"
|
|
30
|
+
return f"@{self.decorator.__name__}({', '.join(map(str, self.args))})"
|
|
29
31
|
|
|
30
32
|
class Decorators:
|
|
31
33
|
"""
|
|
@@ -59,6 +61,14 @@ class Decorators:
|
|
|
59
61
|
"""
|
|
60
62
|
return any(decorator.decorator is callable for decorator in Decorators.get(func_or_class))
|
|
61
63
|
|
|
64
|
+
@classmethod
|
|
65
|
+
def get_decorator(cls, func_or_class, callable: Callable) -> DecoratorDescriptor:
|
|
66
|
+
return next((decorator for decorator in Decorators.get_all(func_or_class) if decorator.decorator is callable), None)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def get_all(cls, func_or_class) -> list[DecoratorDescriptor]:
|
|
70
|
+
return getattr(func_or_class, '__decorators__', [])
|
|
71
|
+
|
|
62
72
|
@classmethod
|
|
63
73
|
def get(cls, func_or_class) -> list[DecoratorDescriptor]:
|
|
64
74
|
"""
|
|
@@ -67,9 +77,14 @@ class Decorators:
|
|
|
67
77
|
func_or_class: the function or class
|
|
68
78
|
|
|
69
79
|
Returns:
|
|
70
|
-
list[DecoratorDescriptor]:
|
|
80
|
+
list[DecoratorDescriptor]: the list
|
|
71
81
|
"""
|
|
72
|
-
|
|
82
|
+
if inspect.ismethod(func_or_class):
|
|
83
|
+
func_or_class = func_or_class.__func__ # unwrap bound method
|
|
84
|
+
|
|
85
|
+
#return getattr(func_or_class, '__decorators__', []) will return inherited as well
|
|
86
|
+
return func_or_class.__dict__.get('__decorators__', [])
|
|
87
|
+
|
|
73
88
|
|
|
74
89
|
class TypeDescriptor:
|
|
75
90
|
"""
|
|
@@ -130,6 +145,9 @@ class TypeDescriptor:
|
|
|
130
145
|
"""
|
|
131
146
|
return inspect.iscoroutinefunction(self.method)
|
|
132
147
|
|
|
148
|
+
def get_decorators(self) -> list[DecoratorDescriptor]:
|
|
149
|
+
return self.decorators
|
|
150
|
+
|
|
133
151
|
def get_decorator(self, decorator: Callable) -> Optional[DecoratorDescriptor]:
|
|
134
152
|
"""
|
|
135
153
|
return the DecoratorDescriptor - if any - associated with the passed Callable
|
|
@@ -212,10 +230,16 @@ class TypeDescriptor:
|
|
|
212
230
|
# internal
|
|
213
231
|
|
|
214
232
|
def _get_local_members(self, cls):
|
|
233
|
+
#return [
|
|
234
|
+
# (name, value)
|
|
235
|
+
# for name, value in getmembers(cls, predicate=inspect.isfunction)
|
|
236
|
+
# if name in cls.__dict__
|
|
237
|
+
#]
|
|
238
|
+
|
|
215
239
|
return [
|
|
216
|
-
(name,
|
|
217
|
-
for name,
|
|
218
|
-
if
|
|
240
|
+
(name, attr)
|
|
241
|
+
for name, attr in cls.__dict__.items()
|
|
242
|
+
if isinstance(attr, FunctionType)
|
|
219
243
|
]
|
|
220
244
|
|
|
221
245
|
# public
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.1
|
|
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
|
|
@@ -24,13 +24,11 @@ License: MIT License
|
|
|
24
24
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
25
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
26
|
SOFTWARE.
|
|
27
|
-
|
|
27
|
+
License-File: LICENSE
|
|
28
28
|
Requires-Python: >=3.9
|
|
29
|
+
Requires-Dist: python-dotenv~=1.1.0
|
|
30
|
+
Requires-Dist: pyyaml~=6.0.2
|
|
29
31
|
Description-Content-Type: text/markdown
|
|
30
|
-
License-File: LICENSE
|
|
31
|
-
Provides-Extra: dev
|
|
32
|
-
Requires-Dist: mkdocstrings-python; extra == "dev"
|
|
33
|
-
Dynamic: license-file
|
|
34
32
|
|
|
35
33
|
# aspyx
|
|
36
34
|
|
|
@@ -73,7 +71,7 @@ Dynamic: license-file
|
|
|
73
71
|
|
|
74
72
|
While working on AI-related projects in Python, I was looking for a dependency injection (DI) framework. After evaluating existing options, my impression was that the most either lacked key features — such as integrated AOP — or had APIs that felt overly technical and complex, which made me develop a library on my own with the following goals
|
|
75
73
|
|
|
76
|
-
- bring both di and AOP features together in a lightweight library
|
|
74
|
+
- bring both di and AOP features together in a lightweight library,
|
|
77
75
|
- be as minimal invasive as possible,
|
|
78
76
|
- offering mechanisms to easily extend and customize features without touching the core,
|
|
79
77
|
- while still offering a _simple_ and _readable_ api that doesnt overwhelm developers and only requires a minimum initial learning curve
|
|
@@ -85,7 +83,7 @@ The AOP integration, in particular, makes a lot of sense because:
|
|
|
85
83
|
|
|
86
84
|
# Overview
|
|
87
85
|
|
|
88
|
-
Aspyx is a lightweight - still only about
|
|
86
|
+
Aspyx is a lightweight - still only about 2K LOC - Python library that provides both Dependency Injection (DI) and Aspect-Oriented Programming (AOP) support.
|
|
89
87
|
|
|
90
88
|
The following DI features are supported
|
|
91
89
|
- constructor and setter injection
|
|
@@ -100,7 +98,7 @@ The following DI features are supported
|
|
|
100
98
|
- lifecycle events methods `on_init`, `on_destroy`, `on_running`
|
|
101
99
|
- Automatic discovery and bundling of injectable objects based on their module location, including support for recursive imports
|
|
102
100
|
- Instantiation of one or possible more isolated container instances — called environments — each managing the lifecycle of a related set of objects,
|
|
103
|
-
|
|
101
|
+
- Support for hierarchical environments, enabling structured scoping and layered object management.
|
|
104
102
|
|
|
105
103
|
With respect to AOP:
|
|
106
104
|
- support for before, around, after and error aspects
|
|
@@ -839,7 +837,3 @@ class ExceptionAdvice:
|
|
|
839
837
|
**1.4.1**
|
|
840
838
|
|
|
841
839
|
- mkdocs
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
aspyx/__init__.py,sha256=
|
|
2
|
-
aspyx/di/__init__.py,sha256=
|
|
3
|
-
aspyx/di/di.py,sha256=
|
|
1
|
+
aspyx/__init__.py,sha256=MsSFjiLMLJZ7QhUPpVBWKiyDnCzryquRyr329NoCACI,2
|
|
2
|
+
aspyx/di/__init__.py,sha256=AGVU2VBWQyBxSssvbk_GOKrYWIYtcmSoIlupz-Oqxi4,1138
|
|
3
|
+
aspyx/di/di.py,sha256=O8FUZm68aimUbt7GnIc43XzRa47iFV8Qh696JGOwdEE,44703
|
|
4
4
|
aspyx/di/aop/__init__.py,sha256=rn6LSpzFtUOlgaBATyhLRWBzFmZ6XoVKA9B8SgQzYEI,746
|
|
5
5
|
aspyx/di/aop/aop.py,sha256=Cn-fqFW6PznVDM38fPX7mqlSpjGKMsgpJRBSYBv59xY,18403
|
|
6
6
|
aspyx/di/configuration/__init__.py,sha256=flM9A79J2wfA5I8goQbxs4tTqYustR9tn_9s0YO2WJQ,484
|
|
@@ -10,16 +10,15 @@ aspyx/di/configuration/yaml_configuration_source.py,sha256=NDl3SeoLMNVlzHgfP-Ysv
|
|
|
10
10
|
aspyx/di/threading/__init__.py,sha256=qrWdaq7MewQ2UmZy4J0Dn6BhY-ahfiG3xsv-EHqoqSE,191
|
|
11
11
|
aspyx/di/threading/synchronized.py,sha256=BQ9PjMQUJsF5r-qWaDgvqg3AvFm_R9QZdKB49EkoelQ,1263
|
|
12
12
|
aspyx/exception/__init__.py,sha256=OZwv-C3ZHD0Eg1rohCQMj575WLJ7lfYuk6PZD6sh1MA,211
|
|
13
|
-
aspyx/exception/exception_manager.py,sha256=
|
|
13
|
+
aspyx/exception/exception_manager.py,sha256=tv0nb0b2CFPiYWK6wwH9yI8hSc--9Xz9_xQVBwU42wc,5228
|
|
14
14
|
aspyx/reflection/__init__.py,sha256=r2sNJrfHDpuqaIYu4fTYsoo046gpgn4VTd7bsS3mQJY,282
|
|
15
|
-
aspyx/reflection/proxy.py,sha256=
|
|
16
|
-
aspyx/reflection/reflection.py,sha256=
|
|
15
|
+
aspyx/reflection/proxy.py,sha256=9zqzmK2HGGx7LxdiBw8MfKRNT8H03h_0I6Y972eKFH8,2582
|
|
16
|
+
aspyx/reflection/reflection.py,sha256=HYzbzExG7jzdHG_AggrRxy9yte6Cl192dPsJBRl785Y,8939
|
|
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.
|
|
22
|
-
aspyx-1.
|
|
23
|
-
aspyx-1.
|
|
24
|
-
aspyx-1.
|
|
25
|
-
aspyx-1.4.1.dist-info/RECORD,,
|
|
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,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
aspyx
|
|
File without changes
|