aspyx 1.4.0__py3-none-any.whl → 1.5.0__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 +3 -3
- aspyx/di/aop/__init__.py +16 -2
- aspyx/di/aop/aop.py +64 -22
- aspyx/di/configuration/__init__.py +3 -3
- aspyx/di/configuration/configuration.py +7 -5
- aspyx/di/di.py +188 -44
- aspyx/exception/__init__.py +1 -1
- aspyx/exception/exception_manager.py +35 -18
- aspyx/reflection/proxy.py +35 -10
- aspyx/reflection/reflection.py +84 -20
- aspyx/threading/__init__.py +1 -1
- aspyx/threading/thread_local.py +6 -2
- aspyx/util/stringbuilder.py +6 -2
- aspyx-1.5.0.dist-info/METADATA +33 -0
- aspyx-1.5.0.dist-info/RECORD +24 -0
- {aspyx-1.4.0.dist-info → aspyx-1.5.0.dist-info}/WHEEL +1 -2
- aspyx-1.4.0.dist-info/METADATA +0 -825
- aspyx-1.4.0.dist-info/RECORD +0 -25
- aspyx-1.4.0.dist-info/top_level.txt +0 -1
- {aspyx-1.4.0.dist-info → aspyx-1.5.0.dist-info}/licenses/LICENSE +0 -0
aspyx/di/di.py
CHANGED
|
@@ -62,33 +62,76 @@ class DIRuntimeException(DIException):
|
|
|
62
62
|
|
|
63
63
|
class AbstractInstanceProvider(ABC, Generic[T]):
|
|
64
64
|
"""
|
|
65
|
-
|
|
65
|
+
An AbstractInstanceProvider is responsible to create instances.
|
|
66
66
|
"""
|
|
67
67
|
@abstractmethod
|
|
68
68
|
def get_module(self) -> str:
|
|
69
|
-
|
|
69
|
+
"""
|
|
70
|
+
return the module name of the provider
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
str: the module name of the provider
|
|
74
|
+
"""
|
|
70
75
|
|
|
71
76
|
def get_host(self) -> Type[T]:
|
|
77
|
+
"""
|
|
78
|
+
return the class which is responsible for creation ( e.g. the injectable class )
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Type[T]: the class which is responsible for creation
|
|
82
|
+
"""
|
|
72
83
|
return type(self)
|
|
73
84
|
|
|
74
85
|
@abstractmethod
|
|
75
86
|
def get_type(self) -> Type[T]:
|
|
76
|
-
|
|
87
|
+
"""
|
|
88
|
+
return the type of the created instance
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Type[T: the type]
|
|
92
|
+
"""
|
|
77
93
|
|
|
78
94
|
@abstractmethod
|
|
79
95
|
def is_eager(self) -> bool:
|
|
80
|
-
|
|
96
|
+
"""
|
|
97
|
+
return True, if the provider will eagerly construct instances
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
bool: eager flag
|
|
101
|
+
"""
|
|
81
102
|
|
|
82
103
|
@abstractmethod
|
|
83
104
|
def get_scope(self) -> str:
|
|
84
|
-
|
|
105
|
+
"""
|
|
106
|
+
return the scope name
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
str: the scope name
|
|
110
|
+
"""
|
|
111
|
+
|
|
85
112
|
|
|
86
113
|
def get_dependencies(self) -> (list[Type],int):
|
|
114
|
+
"""
|
|
115
|
+
return the types that i depend on ( for constructor or setter injection ).
|
|
116
|
+
The second tuple element is the number of parameters that a construction injection will require
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
(list[Type],int): the type array and the number of parameters
|
|
120
|
+
"""
|
|
87
121
|
return [],1
|
|
88
122
|
|
|
89
123
|
@abstractmethod
|
|
90
|
-
def create(self, environment: Environment, *args):
|
|
91
|
-
|
|
124
|
+
def create(self, environment: Environment, *args) -> T:
|
|
125
|
+
"""
|
|
126
|
+
Create a new instance.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
environment: the Environment
|
|
130
|
+
*args: the required arguments
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
T: the instance
|
|
134
|
+
"""
|
|
92
135
|
|
|
93
136
|
def report(self) -> str:
|
|
94
137
|
return str(self)
|
|
@@ -163,7 +206,7 @@ class SingletonScopeInstanceProvider(InstanceProvider):
|
|
|
163
206
|
|
|
164
207
|
class EnvironmentScopeInstanceProvider(InstanceProvider):
|
|
165
208
|
def __init__(self):
|
|
166
|
-
super().__init__(SingletonScopeInstanceProvider, SingletonScope, False, "request")
|
|
209
|
+
super().__init__(SingletonScopeInstanceProvider, SingletonScope, False, "request")
|
|
167
210
|
|
|
168
211
|
def create(self, environment: Environment, *args):
|
|
169
212
|
return EnvironmentScope()
|
|
@@ -273,13 +316,32 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
273
316
|
|
|
274
317
|
self.environment = environment
|
|
275
318
|
self.provider = provider
|
|
276
|
-
self.dependencies = []
|
|
319
|
+
self.dependencies : list[AbstractInstanceProvider] = []
|
|
277
320
|
self.scope_instance = Scopes.get(provider.get_scope(), environment)
|
|
278
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
|
+
|
|
279
340
|
# implement
|
|
280
341
|
|
|
281
342
|
def resolve(self, context: Providers.ResolveContext):
|
|
282
343
|
context.add(self)
|
|
344
|
+
context.push(self)
|
|
283
345
|
|
|
284
346
|
if not context.is_resolved(self):
|
|
285
347
|
context.provider_dependencies[self] = [] #?
|
|
@@ -289,7 +351,7 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
289
351
|
for type in type_and_params[0]:
|
|
290
352
|
if params > 0:
|
|
291
353
|
params -= 1
|
|
292
|
-
self.dependencies.append(context.get_provider(type))
|
|
354
|
+
self.dependencies.append(context.get_provider(type)) # try/catch TODO
|
|
293
355
|
|
|
294
356
|
provider = context.add_provider_dependency(self, type)
|
|
295
357
|
if provider is not None:
|
|
@@ -298,6 +360,8 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
|
|
|
298
360
|
else:
|
|
299
361
|
context.add(*context.get_provider_dependencies(self))
|
|
300
362
|
|
|
363
|
+
context.pop()
|
|
364
|
+
|
|
301
365
|
def get_module(self) -> str:
|
|
302
366
|
return self.provider.get_module()
|
|
303
367
|
|
|
@@ -360,9 +424,13 @@ class ClassInstanceProvider(InstanceProvider):
|
|
|
360
424
|
for method in TypeDescriptor.for_type(self.type).get_methods():
|
|
361
425
|
if method.has_decorator(inject):
|
|
362
426
|
for param in method.param_types:
|
|
427
|
+
if not Providers.is_registered(param):
|
|
428
|
+
raise DIRegistrationException(f"{self.type.__name__}.{method.method.__name__} declares an unknown parameter type {param.__name__}")
|
|
429
|
+
|
|
363
430
|
types.append(param)
|
|
431
|
+
# done
|
|
364
432
|
|
|
365
|
-
return
|
|
433
|
+
return types, self.params
|
|
366
434
|
|
|
367
435
|
def create(self, environment: Environment, *args):
|
|
368
436
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
@@ -388,28 +456,28 @@ class FunctionInstanceProvider(InstanceProvider):
|
|
|
388
456
|
|
|
389
457
|
# constructor
|
|
390
458
|
|
|
391
|
-
def __init__(self, clazz : Type, method
|
|
392
|
-
super().__init__(clazz, return_type, eager, scope)
|
|
459
|
+
def __init__(self, clazz : Type, method: TypeDescriptor.MethodDescriptor, eager = True, scope = "singleton"):
|
|
460
|
+
super().__init__(clazz, method.return_type, eager, scope)
|
|
393
461
|
|
|
394
|
-
self.method = method
|
|
462
|
+
self.method : TypeDescriptor.MethodDescriptor = method
|
|
395
463
|
|
|
396
464
|
# implement
|
|
397
465
|
|
|
398
466
|
def get_dependencies(self) -> (list[Type],int):
|
|
399
|
-
return [self.host], 1
|
|
467
|
+
return [self.host, *self.method.param_types], 1 + len(self.method.param_types)
|
|
400
468
|
|
|
401
469
|
def create(self, environment: Environment, *args):
|
|
402
470
|
Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
|
|
403
471
|
|
|
404
|
-
instance = self.method(*args) # args[0]=self
|
|
472
|
+
instance = self.method.method(*args) # args[0]=self
|
|
405
473
|
|
|
406
474
|
return environment.created(instance)
|
|
407
475
|
|
|
408
476
|
def report(self) -> str:
|
|
409
|
-
return f"{self.host.__name__}.{self.method.__name__}"
|
|
477
|
+
return f"{self.host.__name__}.{self.method.get_name()}({', '.join(t.__name__ for t in self.method.param_types)}) -> {self.type.__qualname__}"
|
|
410
478
|
|
|
411
479
|
def __str__(self):
|
|
412
|
-
return f"FunctionInstanceProvider({self.host.__name__}.{self.method.__name__} -> {self.type.__name__})"
|
|
480
|
+
return f"FunctionInstanceProvider({self.host.__name__}.{self.method.get_name()}({', '.join(t.__name__ for t in self.method.param_types)}) -> {self.type.__name__})"
|
|
413
481
|
|
|
414
482
|
class FactoryInstanceProvider(InstanceProvider):
|
|
415
483
|
"""
|
|
@@ -440,7 +508,7 @@ class FactoryInstanceProvider(InstanceProvider):
|
|
|
440
508
|
return environment.created(args[0].create())
|
|
441
509
|
|
|
442
510
|
def report(self) -> str:
|
|
443
|
-
return f"{self.host.__name__}.create"
|
|
511
|
+
return f"{self.host.__name__}.create() -> {self.type.__name__} "
|
|
444
512
|
|
|
445
513
|
def __str__(self):
|
|
446
514
|
return f"FactoryInstanceProvider({self.host.__name__} -> {self.type.__name__})"
|
|
@@ -449,6 +517,12 @@ class FactoryInstanceProvider(InstanceProvider):
|
|
|
449
517
|
class Lifecycle(Enum):
|
|
450
518
|
"""
|
|
451
519
|
This enum defines the lifecycle phases that can be processed by lifecycle processors.
|
|
520
|
+
Phases are:
|
|
521
|
+
|
|
522
|
+
- ON_INJECT
|
|
523
|
+
- ON_INIT
|
|
524
|
+
- ON_RUNNING
|
|
525
|
+
- ON_DESTROY
|
|
452
526
|
"""
|
|
453
527
|
|
|
454
528
|
__slots__ = []
|
|
@@ -504,7 +578,8 @@ class Providers:
|
|
|
504
578
|
__slots__ = [
|
|
505
579
|
"dependencies",
|
|
506
580
|
"providers",
|
|
507
|
-
"provider_dependencies"
|
|
581
|
+
"provider_dependencies",
|
|
582
|
+
"path"
|
|
508
583
|
]
|
|
509
584
|
|
|
510
585
|
# constructor
|
|
@@ -512,6 +587,7 @@ class Providers:
|
|
|
512
587
|
def __init__(self, providers: Dict[Type, EnvironmentInstanceProvider]):
|
|
513
588
|
self.dependencies : list[EnvironmentInstanceProvider] = []
|
|
514
589
|
self.providers = providers
|
|
590
|
+
self.path = []
|
|
515
591
|
self.provider_dependencies : dict[EnvironmentInstanceProvider, list[EnvironmentInstanceProvider]] = {}
|
|
516
592
|
|
|
517
593
|
# public
|
|
@@ -537,7 +613,14 @@ class Providers:
|
|
|
537
613
|
|
|
538
614
|
return provider
|
|
539
615
|
|
|
616
|
+
def push(self, provider):
|
|
617
|
+
self.path.append(provider)
|
|
618
|
+
|
|
619
|
+
def pop(self):
|
|
620
|
+
self.path.pop()
|
|
621
|
+
|
|
540
622
|
def next(self):
|
|
623
|
+
self.path.clear()
|
|
541
624
|
self.dependencies.clear()
|
|
542
625
|
|
|
543
626
|
def get_provider(self, type: Type) -> EnvironmentInstanceProvider:
|
|
@@ -558,7 +641,7 @@ class Providers:
|
|
|
558
641
|
cycle = ""
|
|
559
642
|
|
|
560
643
|
first = True
|
|
561
|
-
for p in self.
|
|
644
|
+
for p in self.path:
|
|
562
645
|
if not first:
|
|
563
646
|
cycle += " -> "
|
|
564
647
|
|
|
@@ -589,6 +672,10 @@ class Providers:
|
|
|
589
672
|
else:
|
|
590
673
|
candidates.append(provider)
|
|
591
674
|
|
|
675
|
+
@classmethod
|
|
676
|
+
def is_registered(cls,type: Type) -> bool:
|
|
677
|
+
return Providers.providers.get(type, None) is not None
|
|
678
|
+
|
|
592
679
|
# add factories lazily
|
|
593
680
|
|
|
594
681
|
@classmethod
|
|
@@ -603,7 +690,7 @@ class Providers:
|
|
|
603
690
|
cache: Dict[Type,AbstractInstanceProvider] = {}
|
|
604
691
|
|
|
605
692
|
context: ConditionContext = {
|
|
606
|
-
"requires_feature":
|
|
693
|
+
"requires_feature": environment.has_feature,
|
|
607
694
|
"requires_class": lambda clazz : cache.get(clazz, None) is not None # ? only works if the class is in the cache already?
|
|
608
695
|
}
|
|
609
696
|
|
|
@@ -659,10 +746,13 @@ class Providers:
|
|
|
659
746
|
if type is provider.get_type():
|
|
660
747
|
raise ProviderCollisionException(f"type {type.__name__} already registered", existing_provider, provider)
|
|
661
748
|
|
|
662
|
-
if
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
749
|
+
if existing_provider.get_type() is not type:
|
|
750
|
+
# only overwrite if the existing provider is not specific
|
|
751
|
+
|
|
752
|
+
if isinstance(existing_provider, AmbiguousProvider):
|
|
753
|
+
cast(AmbiguousProvider, existing_provider).add_provider(provider)
|
|
754
|
+
else:
|
|
755
|
+
cache[type] = AmbiguousProvider(type, existing_provider, provider)
|
|
666
756
|
|
|
667
757
|
# recursion
|
|
668
758
|
|
|
@@ -714,8 +804,7 @@ def register_factories(cls: Type):
|
|
|
714
804
|
if return_type is None:
|
|
715
805
|
raise DIRegistrationException(f"{cls.__name__}.{method.method.__name__} expected to have a return type")
|
|
716
806
|
|
|
717
|
-
Providers.register(FunctionInstanceProvider(cls, method
|
|
718
|
-
create_decorator.args[1]))
|
|
807
|
+
Providers.register(FunctionInstanceProvider(cls, method, create_decorator.args[0], create_decorator.args[1]))
|
|
719
808
|
def order(prio = 0):
|
|
720
809
|
def decorator(cls):
|
|
721
810
|
Decorators.add(cls, order, prio)
|
|
@@ -740,6 +829,10 @@ def injectable(eager=True, scope="singleton"):
|
|
|
740
829
|
def factory(eager=True, scope="singleton"):
|
|
741
830
|
"""
|
|
742
831
|
Decorator that needs to be used on a class that implements the Factory interface.
|
|
832
|
+
|
|
833
|
+
Args:
|
|
834
|
+
eager (bool): If True, the corresponding object will be created eagerly when the environment is created.
|
|
835
|
+
scope (str): The scope of the factory, e.g. "singleton", "request", "environment".
|
|
743
836
|
"""
|
|
744
837
|
def decorator(cls):
|
|
745
838
|
Decorators.add(cls, factory)
|
|
@@ -754,6 +847,10 @@ def factory(eager=True, scope="singleton"):
|
|
|
754
847
|
def create(eager=True, scope="singleton"):
|
|
755
848
|
"""
|
|
756
849
|
Any method annotated with @create will be registered as a factory method.
|
|
850
|
+
|
|
851
|
+
Args:
|
|
852
|
+
eager (bool): If True, the corresponding object will be created eagerly when the environment is created.
|
|
853
|
+
scope (str): The scope of the factory, e.g. "singleton", "request", "environment".
|
|
757
854
|
"""
|
|
758
855
|
def decorator(func):
|
|
759
856
|
Decorators.add(func, create, eager, scope)
|
|
@@ -763,7 +860,7 @@ def create(eager=True, scope="singleton"):
|
|
|
763
860
|
|
|
764
861
|
def on_init():
|
|
765
862
|
"""
|
|
766
|
-
Methods annotated with
|
|
863
|
+
Methods annotated with `@on_init` will be called when the instance is created."""
|
|
767
864
|
def decorator(func):
|
|
768
865
|
Decorators.add(func, on_init)
|
|
769
866
|
return func
|
|
@@ -772,7 +869,7 @@ def on_init():
|
|
|
772
869
|
|
|
773
870
|
def on_running():
|
|
774
871
|
"""
|
|
775
|
-
Methods annotated with
|
|
872
|
+
Methods annotated with `@on_running` will be called when the container up and running."""
|
|
776
873
|
def decorator(func):
|
|
777
874
|
Decorators.add(func, on_running)
|
|
778
875
|
return func
|
|
@@ -781,7 +878,7 @@ def on_running():
|
|
|
781
878
|
|
|
782
879
|
def on_destroy():
|
|
783
880
|
"""
|
|
784
|
-
Methods annotated with
|
|
881
|
+
Methods annotated with `@on_destroy` will be called when the instance is destroyed.
|
|
785
882
|
"""
|
|
786
883
|
def decorator(func):
|
|
787
884
|
Decorators.add(func, on_destroy)
|
|
@@ -789,18 +886,19 @@ def on_destroy():
|
|
|
789
886
|
|
|
790
887
|
return decorator
|
|
791
888
|
|
|
792
|
-
def
|
|
889
|
+
def module(imports: Optional[list[Type]] = None):
|
|
793
890
|
"""
|
|
794
|
-
This annotation is used to mark classes that control the
|
|
795
|
-
relative to the module of the class. All
|
|
891
|
+
This annotation is used to mark classes that control the discovery process of injectables based on their location
|
|
892
|
+
relative to the module of the class. All `@injectable`s and `@factory`s that are located in the same or any sub-module will
|
|
796
893
|
be registered and managed accordingly.
|
|
797
|
-
|
|
798
|
-
|
|
894
|
+
|
|
895
|
+
Args:
|
|
896
|
+
imports (Optional[list[Type]]): Optional list of imported module types
|
|
799
897
|
"""
|
|
800
898
|
def decorator(cls):
|
|
801
899
|
Providers.register(ClassInstanceProvider(cls, True))
|
|
802
900
|
|
|
803
|
-
Decorators.add(cls,
|
|
901
|
+
Decorators.add(cls, module, imports)
|
|
804
902
|
Decorators.add(cls, injectable) # do we need that?
|
|
805
903
|
|
|
806
904
|
return cls
|
|
@@ -873,11 +971,28 @@ def conditional(*conditions: Condition):
|
|
|
873
971
|
class Environment:
|
|
874
972
|
"""
|
|
875
973
|
Central class that manages the lifecycle of instances and their dependencies.
|
|
974
|
+
|
|
975
|
+
Usage:
|
|
976
|
+
|
|
977
|
+
```python
|
|
978
|
+
@injectable()
|
|
979
|
+
class Foo:
|
|
980
|
+
def __init__(self):
|
|
981
|
+
|
|
982
|
+
@module()
|
|
983
|
+
class Module:
|
|
984
|
+
def __init__(self):
|
|
985
|
+
pass
|
|
986
|
+
|
|
987
|
+
environment = Environment(Module)
|
|
988
|
+
|
|
989
|
+
foo = environment.get(Foo) # will create an instance of Foo
|
|
990
|
+
```
|
|
876
991
|
"""
|
|
877
992
|
|
|
878
993
|
# static data
|
|
879
994
|
|
|
880
|
-
logger = logging.getLogger(
|
|
995
|
+
logger = logging.getLogger("aspyx.di") # __name__ = module name
|
|
881
996
|
|
|
882
997
|
instance : 'Environment' = None
|
|
883
998
|
|
|
@@ -951,9 +1066,33 @@ class Environment:
|
|
|
951
1066
|
|
|
952
1067
|
def get_type_package(type: Type):
|
|
953
1068
|
module_name = type.__module__
|
|
954
|
-
module = sys.modules
|
|
1069
|
+
module = sys.modules.get(module_name)
|
|
1070
|
+
|
|
1071
|
+
if not module:
|
|
1072
|
+
raise ImportError(f"Module {module_name} not found")
|
|
1073
|
+
|
|
1074
|
+
# Try to get the package
|
|
1075
|
+
|
|
1076
|
+
package = getattr(module, '__package__', None)
|
|
955
1077
|
|
|
956
|
-
|
|
1078
|
+
# Fallback: if module is __main__, try to infer from the module name if possible
|
|
1079
|
+
|
|
1080
|
+
if not package:
|
|
1081
|
+
if module_name == '__main__':
|
|
1082
|
+
# Try to resolve real name via __file__
|
|
1083
|
+
path = getattr(module, '__file__', None)
|
|
1084
|
+
if path:
|
|
1085
|
+
Environment.logger.warning(
|
|
1086
|
+
"Module is __main__; consider running via -m to preserve package context")
|
|
1087
|
+
return ''
|
|
1088
|
+
|
|
1089
|
+
# Try to infer package name from module name
|
|
1090
|
+
|
|
1091
|
+
parts = module_name.split('.')
|
|
1092
|
+
if len(parts) > 1:
|
|
1093
|
+
return '.'.join(parts[:-1])
|
|
1094
|
+
|
|
1095
|
+
return package or ''
|
|
957
1096
|
|
|
958
1097
|
def import_package(name: str):
|
|
959
1098
|
"""Import a package and all its submodules recursively."""
|
|
@@ -987,7 +1126,7 @@ class Environment:
|
|
|
987
1126
|
|
|
988
1127
|
# sanity check
|
|
989
1128
|
|
|
990
|
-
decorator = TypeDescriptor.for_type(env).get_decorator(
|
|
1129
|
+
decorator = TypeDescriptor.for_type(env).get_decorator(module)
|
|
991
1130
|
if decorator is None:
|
|
992
1131
|
raise DIRegistrationException(f"{env.__name__} is not an environment class")
|
|
993
1132
|
|
|
@@ -1031,6 +1170,9 @@ class Environment:
|
|
|
1031
1170
|
# construct eager objects for local providers
|
|
1032
1171
|
|
|
1033
1172
|
for provider in set(self.providers.values()):
|
|
1173
|
+
if isinstance(provider, EnvironmentInstanceProvider):
|
|
1174
|
+
provider.print_tree()
|
|
1175
|
+
|
|
1034
1176
|
if provider.is_eager():
|
|
1035
1177
|
provider.create(self)
|
|
1036
1178
|
|
|
@@ -1136,10 +1278,11 @@ class Environment:
|
|
|
1136
1278
|
"""
|
|
1137
1279
|
Create or return a cached instance for the given type.
|
|
1138
1280
|
|
|
1139
|
-
|
|
1281
|
+
Args:
|
|
1140
1282
|
type (Type): The desired type
|
|
1141
1283
|
|
|
1142
|
-
Returns:
|
|
1284
|
+
Returns:
|
|
1285
|
+
T: The requested instance
|
|
1143
1286
|
"""
|
|
1144
1287
|
provider = self.providers.get(type, None)
|
|
1145
1288
|
if provider is None:
|
|
@@ -1413,11 +1556,12 @@ class ThreadScope(Scope):
|
|
|
1413
1556
|
def get(self, provider: AbstractInstanceProvider, environment: Environment, arg_provider: Callable[[], list]):
|
|
1414
1557
|
if not hasattr(self._local, "value"):
|
|
1415
1558
|
self._local.value = provider.create(environment, *arg_provider())
|
|
1559
|
+
|
|
1416
1560
|
return self._local.value
|
|
1417
1561
|
|
|
1418
1562
|
# internal class that is required to import technical instance providers
|
|
1419
1563
|
|
|
1420
|
-
@
|
|
1564
|
+
@module()
|
|
1421
1565
|
class Boot:
|
|
1422
1566
|
# class
|
|
1423
1567
|
|
aspyx/exception/__init__.py
CHANGED
|
@@ -32,9 +32,6 @@ def handle():
|
|
|
32
32
|
|
|
33
33
|
return decorator
|
|
34
34
|
|
|
35
|
-
class ErrorContext():
|
|
36
|
-
pass
|
|
37
|
-
|
|
38
35
|
class Handler:
|
|
39
36
|
# constructor
|
|
40
37
|
|
|
@@ -43,8 +40,13 @@ class Handler:
|
|
|
43
40
|
self.instance = instance
|
|
44
41
|
self.handler = handler
|
|
45
42
|
|
|
46
|
-
def handle(self, exception: BaseException):
|
|
47
|
-
self.handler(self.instance, exception)
|
|
43
|
+
def handle(self, exception: BaseException) -> BaseException:
|
|
44
|
+
result = self.handler(self.instance, exception)
|
|
45
|
+
|
|
46
|
+
if result is not None:
|
|
47
|
+
return result
|
|
48
|
+
else:
|
|
49
|
+
return exception
|
|
48
50
|
|
|
49
51
|
class Chain:
|
|
50
52
|
# constructor
|
|
@@ -55,11 +57,11 @@ class Chain:
|
|
|
55
57
|
|
|
56
58
|
# public
|
|
57
59
|
|
|
58
|
-
def handle(self, exception: BaseException):
|
|
59
|
-
self.handler.handle(exception)
|
|
60
|
+
def handle(self, exception: BaseException) -> BaseException:
|
|
61
|
+
return self.handler.handle(exception)
|
|
60
62
|
|
|
61
63
|
class Invocation:
|
|
62
|
-
def __init__(self, exception:
|
|
64
|
+
def __init__(self, exception: BaseException, chain: Chain):
|
|
63
65
|
self.exception = exception
|
|
64
66
|
self.chain = chain
|
|
65
67
|
self.current = self.chain
|
|
@@ -74,17 +76,26 @@ class ExceptionManager:
|
|
|
74
76
|
|
|
75
77
|
exception_handler_classes = []
|
|
76
78
|
|
|
77
|
-
invocation = ThreadLocal()
|
|
79
|
+
invocation = ThreadLocal[Invocation]()
|
|
78
80
|
|
|
79
81
|
# class methods
|
|
80
82
|
|
|
81
83
|
@classmethod
|
|
82
|
-
def proceed(cls):
|
|
84
|
+
def proceed(cls) -> BaseException:
|
|
85
|
+
"""
|
|
86
|
+
proceed with the next most applicable handler
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
BaseException: the resulting exception
|
|
90
|
+
|
|
91
|
+
"""
|
|
83
92
|
invocation = cls.invocation.get()
|
|
84
93
|
|
|
85
94
|
invocation.current = invocation.current.next
|
|
86
95
|
if invocation.current is not None:
|
|
87
|
-
invocation.current.handle(invocation.exception)
|
|
96
|
+
return invocation.current.handle(invocation.exception)
|
|
97
|
+
else:
|
|
98
|
+
return invocation.exception
|
|
88
99
|
|
|
89
100
|
# constructor
|
|
90
101
|
|
|
@@ -93,7 +104,6 @@ class ExceptionManager:
|
|
|
93
104
|
self.handler : list[Handler] = []
|
|
94
105
|
self.cache: Dict[Type, Chain] = {}
|
|
95
106
|
self.lock = RLock()
|
|
96
|
-
self.current_context: Optional[ErrorContext] = None
|
|
97
107
|
|
|
98
108
|
# internal
|
|
99
109
|
|
|
@@ -145,7 +155,7 @@ class ExceptionManager:
|
|
|
145
155
|
|
|
146
156
|
# chain
|
|
147
157
|
|
|
148
|
-
for i in range(0, len(chain) -
|
|
158
|
+
for i in range(0, len(chain) - 1):
|
|
149
159
|
chain[i].next = chain[i + 1]
|
|
150
160
|
|
|
151
161
|
if len(chain) > 0:
|
|
@@ -153,16 +163,23 @@ class ExceptionManager:
|
|
|
153
163
|
else:
|
|
154
164
|
return None
|
|
155
165
|
|
|
156
|
-
def handle(self, exception:
|
|
166
|
+
def handle(self, exception: BaseException) -> BaseException:
|
|
157
167
|
"""
|
|
158
|
-
handle an exception by invoking the most applicable handler (
|
|
159
|
-
|
|
168
|
+
handle an exception by invoking the most applicable handler (according to mro)
|
|
169
|
+
and return a possible modified exception as a result.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
exception (BaseException): the exception
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
BaseException: the resulting exception
|
|
160
176
|
"""
|
|
161
177
|
chain = self.get_handlers(type(exception))
|
|
162
178
|
if chain is not None:
|
|
163
|
-
|
|
164
179
|
self.invocation.set(Invocation(exception, chain))
|
|
165
180
|
try:
|
|
166
|
-
chain.handle(exception)
|
|
181
|
+
return chain.handle(exception)
|
|
167
182
|
finally:
|
|
168
183
|
self.invocation.clear()
|
|
184
|
+
else:
|
|
185
|
+
return exception # hmmm?
|
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
|
|
|
@@ -14,6 +15,7 @@ class DynamicProxy(Generic[T]):
|
|
|
14
15
|
by intercepting method calls at runtime and handling them as needed.
|
|
15
16
|
|
|
16
17
|
Usage:
|
|
18
|
+
```python
|
|
17
19
|
class MyHandler(DynamicProxy.InvocationHandler):
|
|
18
20
|
def invoke(self, invocation):
|
|
19
21
|
print(f"Intercepted: {invocation.name}")
|
|
@@ -22,17 +24,23 @@ class DynamicProxy(Generic[T]):
|
|
|
22
24
|
|
|
23
25
|
proxy = DynamicProxy.create(SomeClass, MyHandler())
|
|
24
26
|
proxy.some_method(args) # Will be intercepted by MyHandler.invoke
|
|
25
|
-
|
|
26
|
-
Attributes:
|
|
27
|
-
type: The proxied class type.
|
|
28
|
-
invocation_handler: The handler that processes intercepted method calls.
|
|
27
|
+
```
|
|
29
28
|
"""
|
|
30
29
|
# inner class
|
|
31
30
|
|
|
32
31
|
class Invocation:
|
|
33
|
-
|
|
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):
|
|
34
42
|
self.type = type
|
|
35
|
-
self.
|
|
43
|
+
self.method = method
|
|
36
44
|
self.args = args
|
|
37
45
|
self.kwargs = kwargs
|
|
38
46
|
|
|
@@ -40,12 +48,20 @@ class DynamicProxy(Generic[T]):
|
|
|
40
48
|
def invoke(self, invocation: 'DynamicProxy.Invocation'):
|
|
41
49
|
pass
|
|
42
50
|
|
|
51
|
+
async def invoke_async(self, invocation: 'DynamicProxy.Invocation'):
|
|
52
|
+
return self.invoke(invocation)
|
|
53
|
+
|
|
43
54
|
# class methods
|
|
44
55
|
|
|
45
56
|
@classmethod
|
|
46
57
|
def create(cls, type: Type[T], invocation_handler: 'DynamicProxy.InvocationHandler') -> T:
|
|
47
58
|
return DynamicProxy(type, invocation_handler)
|
|
48
59
|
|
|
60
|
+
__slots__ = [
|
|
61
|
+
"type",
|
|
62
|
+
"invocation_handler"
|
|
63
|
+
]
|
|
64
|
+
|
|
49
65
|
# constructor
|
|
50
66
|
|
|
51
67
|
def __init__(self, type: Type[T], invocation_handler: 'DynamicProxy.InvocationHandler'):
|
|
@@ -55,7 +71,16 @@ class DynamicProxy(Generic[T]):
|
|
|
55
71
|
# public
|
|
56
72
|
|
|
57
73
|
def __getattr__(self, name):
|
|
58
|
-
|
|
59
|
-
|
|
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))
|
|
60
85
|
|
|
61
|
-
|
|
86
|
+
return sync_wrapper
|