aspyx 1.5.3__tar.gz → 1.6.1__tar.gz
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-1.5.3 → aspyx-1.6.1}/PKG-INFO +32 -65
- {aspyx-1.5.3 → aspyx-1.6.1}/README.md +29 -64
- {aspyx-1.5.3 → aspyx-1.6.1}/pyproject.toml +4 -2
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/aop/aop.py +1 -1
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/configuration/configuration.py +0 -3
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/configuration/env_configuration_source.py +0 -5
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/configuration/yaml_configuration_source.py +0 -2
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/di.py +15 -7
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/exception/__init__.py +2 -1
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/exception/exception_manager.py +11 -1
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/threading/context_local.py +10 -1
- aspyx-1.6.1/src/aspyx/util/__init__.py +16 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/util/logger.py +6 -2
- aspyx-1.6.1/src/aspyx/util/serialization.py +137 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/tests/di_import.py +2 -4
- {aspyx-1.5.3 → aspyx-1.6.1}/tests/sub_import.py +2 -4
- {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_aop.py +1 -5
- {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_configuration.py +0 -14
- {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_di.py +3 -2
- aspyx-1.5.3/src/aspyx/util/__init__.py +0 -10
- {aspyx-1.5.3 → aspyx-1.6.1}/.gitignore +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/LICENSE +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/aop/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/configuration/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/threading/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/threading/synchronized.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/reflection/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/reflection/proxy.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/reflection/reflection.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/threading/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/threading/thread_local.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/util/stringbuilder.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/tests/config.yaml +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/tests/config1.yaml +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_cycle.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_decorator.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_exception_manager.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_proxy.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_reflection.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aspyx
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.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
|
|
@@ -26,6 +26,8 @@ License: MIT License
|
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
License-File: LICENSE
|
|
28
28
|
Requires-Python: >=3.9
|
|
29
|
+
Requires-Dist: cachetools~=5.5.2
|
|
30
|
+
Requires-Dist: pydantic<3.0,>=2.0
|
|
29
31
|
Requires-Dist: python-dotenv~=1.1.0
|
|
30
32
|
Requires-Dist: pyyaml~=6.0.2
|
|
31
33
|
Description-Content-Type: text/markdown
|
|
@@ -115,9 +117,6 @@ from aspyx.di import injectable, on_init, on_destroy, module, Environment
|
|
|
115
117
|
|
|
116
118
|
@injectable()
|
|
117
119
|
class Foo:
|
|
118
|
-
def __init__(self):
|
|
119
|
-
pass
|
|
120
|
-
|
|
121
120
|
def hello(self, msg: str):
|
|
122
121
|
print(f"hello {msg}")
|
|
123
122
|
|
|
@@ -136,9 +135,7 @@ class Bar:
|
|
|
136
135
|
|
|
137
136
|
@module()
|
|
138
137
|
class SampleModule:
|
|
139
|
-
|
|
140
|
-
pass
|
|
141
|
-
|
|
138
|
+
pass
|
|
142
139
|
|
|
143
140
|
# create environment
|
|
144
141
|
|
|
@@ -146,7 +143,7 @@ environment = Environment(SampleModule)
|
|
|
146
143
|
|
|
147
144
|
# fetch an instance
|
|
148
145
|
|
|
149
|
-
bar =
|
|
146
|
+
bar = environment.get(Bar)
|
|
150
147
|
|
|
151
148
|
bar.foo.hello("world")
|
|
152
149
|
```
|
|
@@ -204,10 +201,7 @@ class Foo:
|
|
|
204
201
|
def __init__(self):
|
|
205
202
|
pass
|
|
206
203
|
```
|
|
207
|
-
|
|
208
|
-
All referenced types will be injected by the environment.
|
|
209
|
-
|
|
210
|
-
Only eligible types are allowed, of course!
|
|
204
|
+
If the class defines a constructor, all parameters - which are expected to be registered as well - will be injected automatically.
|
|
211
205
|
|
|
212
206
|
The decorator accepts the keyword arguments
|
|
213
207
|
- `eager : boolean`
|
|
@@ -233,9 +227,6 @@ Classes that implement the `Factory` base class and are annotated with `@factory
|
|
|
233
227
|
```python
|
|
234
228
|
@factory()
|
|
235
229
|
class TestFactory(Factory[Foo]):
|
|
236
|
-
def __init__(self):
|
|
237
|
-
pass
|
|
238
|
-
|
|
239
230
|
def create(self) -> Foo:
|
|
240
231
|
return Foo()
|
|
241
232
|
```
|
|
@@ -250,9 +241,6 @@ Any `injectable` can define methods decorated with `@create()`, that will create
|
|
|
250
241
|
```python
|
|
251
242
|
@injectable()
|
|
252
243
|
class Foo:
|
|
253
|
-
def __init__(self):
|
|
254
|
-
pass
|
|
255
|
-
|
|
256
244
|
@create(scope="request")
|
|
257
245
|
def create(self) -> Baz:
|
|
258
246
|
return Baz()
|
|
@@ -288,8 +276,7 @@ constructor type argument called `module`.
|
|
|
288
276
|
```python
|
|
289
277
|
@module()
|
|
290
278
|
class SampleModule:
|
|
291
|
-
|
|
292
|
-
pass
|
|
279
|
+
pass
|
|
293
280
|
```
|
|
294
281
|
|
|
295
282
|
A module is a regular injectable class decorated with `@module` that controls the discovery of injectable classes, by filtering classes according to their module location relative to this class.
|
|
@@ -309,13 +296,11 @@ By adding the parameter `features: list[str]`, it is possible to filter injectab
|
|
|
309
296
|
@injectable()
|
|
310
297
|
@conditional(requires_feature("dev"))
|
|
311
298
|
class DevOnly:
|
|
312
|
-
|
|
313
|
-
pass
|
|
299
|
+
pass
|
|
314
300
|
|
|
315
301
|
@module()
|
|
316
302
|
class SampleModule():
|
|
317
|
-
|
|
318
|
-
pass
|
|
303
|
+
pass
|
|
319
304
|
|
|
320
305
|
environment = Environment(SampleModule, features=["dev"])
|
|
321
306
|
```
|
|
@@ -327,8 +312,7 @@ By adding an `imports: list[Type]` parameter, specifying other module types, it
|
|
|
327
312
|
```python
|
|
328
313
|
@module()
|
|
329
314
|
class SampleModule(imports=[OtherModule]):
|
|
330
|
-
|
|
331
|
-
pass
|
|
315
|
+
pass
|
|
332
316
|
```
|
|
333
317
|
|
|
334
318
|
Another possibility is to add a parent environment as an `Environment` constructor parameter
|
|
@@ -390,11 +374,8 @@ Different decorators are implemented, that call methods with computed values
|
|
|
390
374
|
```python
|
|
391
375
|
@injectable()
|
|
392
376
|
class Foo:
|
|
393
|
-
def __init__(self):
|
|
394
|
-
pass
|
|
395
|
-
|
|
396
377
|
@inject_environment()
|
|
397
|
-
def
|
|
378
|
+
def set_environment(self, env: Environment):
|
|
398
379
|
...
|
|
399
380
|
|
|
400
381
|
@inject()
|
|
@@ -584,9 +565,6 @@ A handy decorator `@synchronized` in combination with the respective advice is i
|
|
|
584
565
|
```python
|
|
585
566
|
@injectable()
|
|
586
567
|
class Foo:
|
|
587
|
-
def __init__(self):
|
|
588
|
-
pass
|
|
589
|
-
|
|
590
568
|
@synchronized()
|
|
591
569
|
def execute_synchronized(self):
|
|
592
570
|
...
|
|
@@ -599,9 +577,6 @@ It is possible to inject configuration values, by decorating methods with `@inje
|
|
|
599
577
|
```python
|
|
600
578
|
@injectable()
|
|
601
579
|
class Foo:
|
|
602
|
-
def __init__(self):
|
|
603
|
-
pass
|
|
604
|
-
|
|
605
580
|
@inject_value("HOME")
|
|
606
581
|
def inject_home(self, os: str):
|
|
607
582
|
...
|
|
@@ -613,13 +588,13 @@ Configuration values are managed centrally using a `ConfigurationManager`, which
|
|
|
613
588
|
|
|
614
589
|
```python
|
|
615
590
|
class ConfigurationSource(ABC):
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
...
|
|
591
|
+
@inject()
|
|
592
|
+
def set_manager(self, manager: ConfigurationManager):
|
|
593
|
+
manager._register(self)
|
|
620
594
|
|
|
621
595
|
@abstractmethod
|
|
622
596
|
def load(self) -> dict:
|
|
597
|
+
pass
|
|
623
598
|
```
|
|
624
599
|
|
|
625
600
|
The `load` method is able to return a tree-like structure by returning a `dict`.
|
|
@@ -668,11 +643,6 @@ Typically you create the required configuration sources in an environment class,
|
|
|
668
643
|
```python
|
|
669
644
|
@module()
|
|
670
645
|
class SampleModule:
|
|
671
|
-
# constructor
|
|
672
|
-
|
|
673
|
-
def __init__(self):
|
|
674
|
-
pass
|
|
675
|
-
|
|
676
646
|
@create()
|
|
677
647
|
def create_env_source(self) -> EnvConfigurationSource:
|
|
678
648
|
return EnvConfigurationSource()
|
|
@@ -731,41 +701,36 @@ def transactional(scope):
|
|
|
731
701
|
The class `ExceptionManager` is used to collect dynamic handlers for specific exceptions and is able to dispatch to the concrete functions
|
|
732
702
|
given a specific exception.
|
|
733
703
|
|
|
734
|
-
The handlers are declared by annoting a class with `@exception_handler` and decorating specific methods with `@
|
|
704
|
+
The handlers are declared by annoting a class with `@exception_handler` and decorating specific methods with `@catch`
|
|
735
705
|
|
|
736
706
|
**Example**:
|
|
707
|
+
|
|
737
708
|
```python
|
|
738
709
|
class DerivedException(Exception):
|
|
739
710
|
def __init__(self):
|
|
740
711
|
pass
|
|
741
712
|
|
|
713
|
+
|
|
742
714
|
@module()
|
|
743
715
|
class SampleModule:
|
|
744
|
-
# constructor
|
|
745
|
-
|
|
746
|
-
def __init__(self):
|
|
747
|
-
pass
|
|
748
|
-
|
|
749
716
|
@create()
|
|
750
717
|
def create_exception_manager(self) -> ExceptionManager:
|
|
751
718
|
return ExceptionManager()
|
|
752
719
|
|
|
720
|
+
|
|
753
721
|
@injectable()
|
|
754
722
|
@exception_handler()
|
|
755
723
|
class TestExceptionHandler:
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
@handle()
|
|
760
|
-
def handle_derived_exception(self, exception: DerivedException):
|
|
724
|
+
@catch()
|
|
725
|
+
def catch_derived_exception(self, exception: DerivedException):
|
|
761
726
|
ExceptionManager.proceed()
|
|
762
727
|
|
|
763
|
-
@
|
|
764
|
-
def
|
|
728
|
+
@catch()
|
|
729
|
+
def catch_exception(self, exception: Exception):
|
|
765
730
|
pass
|
|
766
731
|
|
|
767
|
-
@
|
|
768
|
-
def
|
|
732
|
+
@catch()
|
|
733
|
+
def catch_base_exception(self, exception: BaseException):
|
|
769
734
|
pass
|
|
770
735
|
|
|
771
736
|
|
|
@@ -778,9 +743,10 @@ class ExceptionAdvice:
|
|
|
778
743
|
def handle_error(self, invocation: Invocation):
|
|
779
744
|
self.exceptionManager.handle(invocation.exception)
|
|
780
745
|
|
|
781
|
-
environment = Environment(SampleEnvironment)
|
|
782
746
|
|
|
783
|
-
environment
|
|
747
|
+
environment = Environment(SampleEnvironment)
|
|
748
|
+
|
|
749
|
+
environment.read(ExceptionManager).handle(DerivedException())
|
|
784
750
|
```
|
|
785
751
|
|
|
786
752
|
The exception maanger will first call the most appropriate method.
|
|
@@ -792,9 +758,6 @@ Together with a simple around advice we can now add exception handling to any me
|
|
|
792
758
|
```python
|
|
793
759
|
@injectable()
|
|
794
760
|
class Service:
|
|
795
|
-
def __init__(self):
|
|
796
|
-
pass
|
|
797
|
-
|
|
798
761
|
def throw(self):
|
|
799
762
|
raise DerivedException()
|
|
800
763
|
|
|
@@ -837,3 +800,7 @@ class ExceptionAdvice:
|
|
|
837
800
|
**1.4.1**
|
|
838
801
|
|
|
839
802
|
- mkdocs
|
|
803
|
+
|
|
804
|
+
**1.6.1**
|
|
805
|
+
|
|
806
|
+
- default constructors not requires anymore
|
|
@@ -83,9 +83,6 @@ from aspyx.di import injectable, on_init, on_destroy, module, Environment
|
|
|
83
83
|
|
|
84
84
|
@injectable()
|
|
85
85
|
class Foo:
|
|
86
|
-
def __init__(self):
|
|
87
|
-
pass
|
|
88
|
-
|
|
89
86
|
def hello(self, msg: str):
|
|
90
87
|
print(f"hello {msg}")
|
|
91
88
|
|
|
@@ -104,9 +101,7 @@ class Bar:
|
|
|
104
101
|
|
|
105
102
|
@module()
|
|
106
103
|
class SampleModule:
|
|
107
|
-
|
|
108
|
-
pass
|
|
109
|
-
|
|
104
|
+
pass
|
|
110
105
|
|
|
111
106
|
# create environment
|
|
112
107
|
|
|
@@ -114,7 +109,7 @@ environment = Environment(SampleModule)
|
|
|
114
109
|
|
|
115
110
|
# fetch an instance
|
|
116
111
|
|
|
117
|
-
bar =
|
|
112
|
+
bar = environment.get(Bar)
|
|
118
113
|
|
|
119
114
|
bar.foo.hello("world")
|
|
120
115
|
```
|
|
@@ -172,10 +167,7 @@ class Foo:
|
|
|
172
167
|
def __init__(self):
|
|
173
168
|
pass
|
|
174
169
|
```
|
|
175
|
-
|
|
176
|
-
All referenced types will be injected by the environment.
|
|
177
|
-
|
|
178
|
-
Only eligible types are allowed, of course!
|
|
170
|
+
If the class defines a constructor, all parameters - which are expected to be registered as well - will be injected automatically.
|
|
179
171
|
|
|
180
172
|
The decorator accepts the keyword arguments
|
|
181
173
|
- `eager : boolean`
|
|
@@ -201,9 +193,6 @@ Classes that implement the `Factory` base class and are annotated with `@factory
|
|
|
201
193
|
```python
|
|
202
194
|
@factory()
|
|
203
195
|
class TestFactory(Factory[Foo]):
|
|
204
|
-
def __init__(self):
|
|
205
|
-
pass
|
|
206
|
-
|
|
207
196
|
def create(self) -> Foo:
|
|
208
197
|
return Foo()
|
|
209
198
|
```
|
|
@@ -218,9 +207,6 @@ Any `injectable` can define methods decorated with `@create()`, that will create
|
|
|
218
207
|
```python
|
|
219
208
|
@injectable()
|
|
220
209
|
class Foo:
|
|
221
|
-
def __init__(self):
|
|
222
|
-
pass
|
|
223
|
-
|
|
224
210
|
@create(scope="request")
|
|
225
211
|
def create(self) -> Baz:
|
|
226
212
|
return Baz()
|
|
@@ -256,8 +242,7 @@ constructor type argument called `module`.
|
|
|
256
242
|
```python
|
|
257
243
|
@module()
|
|
258
244
|
class SampleModule:
|
|
259
|
-
|
|
260
|
-
pass
|
|
245
|
+
pass
|
|
261
246
|
```
|
|
262
247
|
|
|
263
248
|
A module is a regular injectable class decorated with `@module` that controls the discovery of injectable classes, by filtering classes according to their module location relative to this class.
|
|
@@ -277,13 +262,11 @@ By adding the parameter `features: list[str]`, it is possible to filter injectab
|
|
|
277
262
|
@injectable()
|
|
278
263
|
@conditional(requires_feature("dev"))
|
|
279
264
|
class DevOnly:
|
|
280
|
-
|
|
281
|
-
pass
|
|
265
|
+
pass
|
|
282
266
|
|
|
283
267
|
@module()
|
|
284
268
|
class SampleModule():
|
|
285
|
-
|
|
286
|
-
pass
|
|
269
|
+
pass
|
|
287
270
|
|
|
288
271
|
environment = Environment(SampleModule, features=["dev"])
|
|
289
272
|
```
|
|
@@ -295,8 +278,7 @@ By adding an `imports: list[Type]` parameter, specifying other module types, it
|
|
|
295
278
|
```python
|
|
296
279
|
@module()
|
|
297
280
|
class SampleModule(imports=[OtherModule]):
|
|
298
|
-
|
|
299
|
-
pass
|
|
281
|
+
pass
|
|
300
282
|
```
|
|
301
283
|
|
|
302
284
|
Another possibility is to add a parent environment as an `Environment` constructor parameter
|
|
@@ -358,11 +340,8 @@ Different decorators are implemented, that call methods with computed values
|
|
|
358
340
|
```python
|
|
359
341
|
@injectable()
|
|
360
342
|
class Foo:
|
|
361
|
-
def __init__(self):
|
|
362
|
-
pass
|
|
363
|
-
|
|
364
343
|
@inject_environment()
|
|
365
|
-
def
|
|
344
|
+
def set_environment(self, env: Environment):
|
|
366
345
|
...
|
|
367
346
|
|
|
368
347
|
@inject()
|
|
@@ -552,9 +531,6 @@ A handy decorator `@synchronized` in combination with the respective advice is i
|
|
|
552
531
|
```python
|
|
553
532
|
@injectable()
|
|
554
533
|
class Foo:
|
|
555
|
-
def __init__(self):
|
|
556
|
-
pass
|
|
557
|
-
|
|
558
534
|
@synchronized()
|
|
559
535
|
def execute_synchronized(self):
|
|
560
536
|
...
|
|
@@ -567,9 +543,6 @@ It is possible to inject configuration values, by decorating methods with `@inje
|
|
|
567
543
|
```python
|
|
568
544
|
@injectable()
|
|
569
545
|
class Foo:
|
|
570
|
-
def __init__(self):
|
|
571
|
-
pass
|
|
572
|
-
|
|
573
546
|
@inject_value("HOME")
|
|
574
547
|
def inject_home(self, os: str):
|
|
575
548
|
...
|
|
@@ -581,13 +554,13 @@ Configuration values are managed centrally using a `ConfigurationManager`, which
|
|
|
581
554
|
|
|
582
555
|
```python
|
|
583
556
|
class ConfigurationSource(ABC):
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
...
|
|
557
|
+
@inject()
|
|
558
|
+
def set_manager(self, manager: ConfigurationManager):
|
|
559
|
+
manager._register(self)
|
|
588
560
|
|
|
589
561
|
@abstractmethod
|
|
590
562
|
def load(self) -> dict:
|
|
563
|
+
pass
|
|
591
564
|
```
|
|
592
565
|
|
|
593
566
|
The `load` method is able to return a tree-like structure by returning a `dict`.
|
|
@@ -636,11 +609,6 @@ Typically you create the required configuration sources in an environment class,
|
|
|
636
609
|
```python
|
|
637
610
|
@module()
|
|
638
611
|
class SampleModule:
|
|
639
|
-
# constructor
|
|
640
|
-
|
|
641
|
-
def __init__(self):
|
|
642
|
-
pass
|
|
643
|
-
|
|
644
612
|
@create()
|
|
645
613
|
def create_env_source(self) -> EnvConfigurationSource:
|
|
646
614
|
return EnvConfigurationSource()
|
|
@@ -699,41 +667,36 @@ def transactional(scope):
|
|
|
699
667
|
The class `ExceptionManager` is used to collect dynamic handlers for specific exceptions and is able to dispatch to the concrete functions
|
|
700
668
|
given a specific exception.
|
|
701
669
|
|
|
702
|
-
The handlers are declared by annoting a class with `@exception_handler` and decorating specific methods with `@
|
|
670
|
+
The handlers are declared by annoting a class with `@exception_handler` and decorating specific methods with `@catch`
|
|
703
671
|
|
|
704
672
|
**Example**:
|
|
673
|
+
|
|
705
674
|
```python
|
|
706
675
|
class DerivedException(Exception):
|
|
707
676
|
def __init__(self):
|
|
708
677
|
pass
|
|
709
678
|
|
|
679
|
+
|
|
710
680
|
@module()
|
|
711
681
|
class SampleModule:
|
|
712
|
-
# constructor
|
|
713
|
-
|
|
714
|
-
def __init__(self):
|
|
715
|
-
pass
|
|
716
|
-
|
|
717
682
|
@create()
|
|
718
683
|
def create_exception_manager(self) -> ExceptionManager:
|
|
719
684
|
return ExceptionManager()
|
|
720
685
|
|
|
686
|
+
|
|
721
687
|
@injectable()
|
|
722
688
|
@exception_handler()
|
|
723
689
|
class TestExceptionHandler:
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
@handle()
|
|
728
|
-
def handle_derived_exception(self, exception: DerivedException):
|
|
690
|
+
@catch()
|
|
691
|
+
def catch_derived_exception(self, exception: DerivedException):
|
|
729
692
|
ExceptionManager.proceed()
|
|
730
693
|
|
|
731
|
-
@
|
|
732
|
-
def
|
|
694
|
+
@catch()
|
|
695
|
+
def catch_exception(self, exception: Exception):
|
|
733
696
|
pass
|
|
734
697
|
|
|
735
|
-
@
|
|
736
|
-
def
|
|
698
|
+
@catch()
|
|
699
|
+
def catch_base_exception(self, exception: BaseException):
|
|
737
700
|
pass
|
|
738
701
|
|
|
739
702
|
|
|
@@ -746,9 +709,10 @@ class ExceptionAdvice:
|
|
|
746
709
|
def handle_error(self, invocation: Invocation):
|
|
747
710
|
self.exceptionManager.handle(invocation.exception)
|
|
748
711
|
|
|
749
|
-
environment = Environment(SampleEnvironment)
|
|
750
712
|
|
|
751
|
-
environment
|
|
713
|
+
environment = Environment(SampleEnvironment)
|
|
714
|
+
|
|
715
|
+
environment.read(ExceptionManager).handle(DerivedException())
|
|
752
716
|
```
|
|
753
717
|
|
|
754
718
|
The exception maanger will first call the most appropriate method.
|
|
@@ -760,9 +724,6 @@ Together with a simple around advice we can now add exception handling to any me
|
|
|
760
724
|
```python
|
|
761
725
|
@injectable()
|
|
762
726
|
class Service:
|
|
763
|
-
def __init__(self):
|
|
764
|
-
pass
|
|
765
|
-
|
|
766
727
|
def throw(self):
|
|
767
728
|
raise DerivedException()
|
|
768
729
|
|
|
@@ -805,3 +766,7 @@ class ExceptionAdvice:
|
|
|
805
766
|
**1.4.1**
|
|
806
767
|
|
|
807
768
|
- mkdocs
|
|
769
|
+
|
|
770
|
+
**1.6.1**
|
|
771
|
+
|
|
772
|
+
- default constructors not requires anymore
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aspyx"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.6.1"
|
|
4
4
|
description = "A DI and AOP library for Python"
|
|
5
5
|
authors = [{ name = "Andreas Ernst", email = "andreas.ernst7@gmail.com" }]
|
|
6
6
|
readme = "README.md"
|
|
@@ -8,7 +8,9 @@ license = { file = "LICENSE" }
|
|
|
8
8
|
requires-python = ">=3.9"
|
|
9
9
|
dependencies = [
|
|
10
10
|
"python-dotenv~=1.1.0",
|
|
11
|
-
"pyyaml~=6.0.2"
|
|
11
|
+
"pyyaml~=6.0.2",
|
|
12
|
+
"cachetools~= 5.5.2",
|
|
13
|
+
"pydantic>=2.0,<3.0"
|
|
12
14
|
]
|
|
13
15
|
|
|
14
16
|
[build-system]
|
|
@@ -8,6 +8,7 @@ import logging
|
|
|
8
8
|
import importlib
|
|
9
9
|
import pkgutil
|
|
10
10
|
import sys
|
|
11
|
+
import time
|
|
11
12
|
|
|
12
13
|
from abc import abstractmethod, ABC
|
|
13
14
|
from enum import Enum
|
|
@@ -405,12 +406,10 @@ class ClassInstanceProvider(InstanceProvider):
|
|
|
405
406
|
# check constructor
|
|
406
407
|
|
|
407
408
|
init = TypeDescriptor.for_type(self.type).get_method("__init__")
|
|
408
|
-
if init is None:
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
for param in init.param_types:
|
|
413
|
-
types.append(param)
|
|
409
|
+
if init is not None:
|
|
410
|
+
self.params = len(init.param_types)
|
|
411
|
+
for param in init.param_types:
|
|
412
|
+
types.append(param)
|
|
414
413
|
|
|
415
414
|
# check @inject
|
|
416
415
|
|
|
@@ -976,7 +975,7 @@ class Environment:
|
|
|
976
975
|
"""
|
|
977
976
|
|
|
978
977
|
def add_provider(type: Type, provider: AbstractInstanceProvider):
|
|
979
|
-
Environment.logger.
|
|
978
|
+
Environment.logger.info("\tadd provider %s for %s", provider, type)
|
|
980
979
|
|
|
981
980
|
self.providers[type] = provider
|
|
982
981
|
|
|
@@ -989,6 +988,8 @@ class Environment:
|
|
|
989
988
|
if self.parent is None and env is not Boot:
|
|
990
989
|
self.parent = Boot.get_environment() # inherit environment including its manged instances!
|
|
991
990
|
|
|
991
|
+
start = time.perf_counter()
|
|
992
|
+
|
|
992
993
|
self.features = features
|
|
993
994
|
self.providers: Dict[Type, AbstractInstanceProvider] = {}
|
|
994
995
|
self.instances = []
|
|
@@ -1138,6 +1139,13 @@ class Environment:
|
|
|
1138
1139
|
for instance in self.instances:
|
|
1139
1140
|
self.execute_processors(Lifecycle.ON_RUNNING, instance)
|
|
1140
1141
|
|
|
1142
|
+
# done
|
|
1143
|
+
|
|
1144
|
+
end = time.perf_counter()
|
|
1145
|
+
|
|
1146
|
+
Environment.logger.info("created environment for class %s in %s ms, created %s instances", env.__qualname__, 1000 * (end - start), len(self.instances))
|
|
1147
|
+
|
|
1148
|
+
|
|
1141
1149
|
def is_registered_type(self, type: Type) -> bool:
|
|
1142
1150
|
provider = self.providers.get(type, None)
|
|
1143
1151
|
return provider is not None and not isinstance(provider, AmbiguousProvider)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module provides exception handling functions.
|
|
3
3
|
"""
|
|
4
|
-
from .exception_manager import exception_handler, handle, ExceptionManager
|
|
4
|
+
from .exception_manager import exception_handler, handle, catch, ExceptionManager
|
|
5
5
|
|
|
6
6
|
__all__ = [
|
|
7
7
|
"exception_handler",
|
|
8
8
|
"handle",
|
|
9
|
+
"catch",
|
|
9
10
|
"ExceptionManager"
|
|
10
11
|
]
|
|
@@ -32,6 +32,16 @@ def handle():
|
|
|
32
32
|
|
|
33
33
|
return decorator
|
|
34
34
|
|
|
35
|
+
def catch():
|
|
36
|
+
"""
|
|
37
|
+
Any method annotated with @catch will be registered as an exception handler method.
|
|
38
|
+
"""
|
|
39
|
+
def decorator(func):
|
|
40
|
+
Decorators.add(func, handle)
|
|
41
|
+
return func
|
|
42
|
+
|
|
43
|
+
return decorator
|
|
44
|
+
|
|
35
45
|
class Handler:
|
|
36
46
|
# constructor
|
|
37
47
|
|
|
@@ -174,7 +184,7 @@ class ExceptionManager:
|
|
|
174
184
|
exception (BaseException): the exception
|
|
175
185
|
|
|
176
186
|
Returns:
|
|
177
|
-
BaseException: the resulting exception
|
|
187
|
+
BaseException: the resulting - possible transformed - exception
|
|
178
188
|
"""
|
|
179
189
|
chain = self.get_handlers(type(exception))
|
|
180
190
|
if chain is not None:
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"""
|
|
2
|
+
context utility
|
|
3
|
+
"""
|
|
1
4
|
import contextvars
|
|
2
5
|
from contextlib import contextmanager
|
|
3
6
|
from typing import Generic, Optional, TypeVar, Any
|
|
@@ -30,12 +33,18 @@ class ContextLocal(Generic[T]):
|
|
|
30
33
|
|
|
31
34
|
Args:
|
|
32
35
|
value: the value
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
a token that can be used as an argument to `reset`
|
|
33
39
|
"""
|
|
34
40
|
return self.var.set(value)
|
|
35
41
|
|
|
36
42
|
def reset(self, token) -> None:
|
|
37
43
|
"""
|
|
38
44
|
clear the value in the current thread
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
token: the token to clear
|
|
39
48
|
"""
|
|
40
49
|
self.var.reset(token)
|
|
41
50
|
|
|
@@ -45,4 +54,4 @@ class ContextLocal(Generic[T]):
|
|
|
45
54
|
try:
|
|
46
55
|
yield
|
|
47
56
|
finally:
|
|
48
|
-
self.reset(token)
|
|
57
|
+
self.reset(token)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides utility functions.
|
|
3
|
+
"""
|
|
4
|
+
from .stringbuilder import StringBuilder
|
|
5
|
+
from .logger import Logger
|
|
6
|
+
from .serialization import TypeSerializer, TypeDeserializer, get_serializer, get_deserializer
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"StringBuilder",
|
|
10
|
+
"Logger",
|
|
11
|
+
|
|
12
|
+
"TypeSerializer",
|
|
13
|
+
"TypeDeserializer",
|
|
14
|
+
"get_serializer",
|
|
15
|
+
"get_deserializer"
|
|
16
|
+
]
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logging utility class
|
|
3
|
+
"""
|
|
1
4
|
import logging
|
|
5
|
+
import sys
|
|
2
6
|
from typing import Optional, Dict
|
|
3
7
|
|
|
4
8
|
class Logger:
|
|
@@ -8,8 +12,8 @@ class Logger:
|
|
|
8
12
|
def configure(cls,
|
|
9
13
|
default_level: int = logging.INFO,
|
|
10
14
|
format: str = "[%(asctime)s] %(levelname)s in %(filename)s:%(lineno)d - %(message)s",
|
|
11
|
-
levels: Optional[Dict[str, int]] = None):
|
|
15
|
+
levels: Optional[Dict[str, int]] = None, stream=sys.stdout):
|
|
12
16
|
logging.basicConfig(level=default_level, format=format)
|
|
13
17
|
if levels is not None:
|
|
14
18
|
for name, level in levels.items():
|
|
15
|
-
logging.getLogger(name).setLevel(level)
|
|
19
|
+
logging.getLogger(name).setLevel(level)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
deserialization functions
|
|
3
|
+
"""
|
|
4
|
+
from dataclasses import is_dataclass, fields
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from typing import get_origin, get_args, Union
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
class TypeDeserializer:
|
|
11
|
+
# constructor
|
|
12
|
+
|
|
13
|
+
def __init__(self, typ):
|
|
14
|
+
self.typ = typ
|
|
15
|
+
self.deserializer = self._build_deserializer(typ)
|
|
16
|
+
|
|
17
|
+
def __call__(self, value):
|
|
18
|
+
return self.deserializer(value)
|
|
19
|
+
|
|
20
|
+
# internal
|
|
21
|
+
|
|
22
|
+
def _build_deserializer(self, typ):
|
|
23
|
+
origin = get_origin(typ)
|
|
24
|
+
args = get_args(typ)
|
|
25
|
+
|
|
26
|
+
if origin is Union:
|
|
27
|
+
deserializers = [TypeDeserializer(arg) for arg in args if arg is not type(None)]
|
|
28
|
+
def deser_union(value):
|
|
29
|
+
if value is None:
|
|
30
|
+
return None
|
|
31
|
+
for d in deserializers:
|
|
32
|
+
try:
|
|
33
|
+
return d(value)
|
|
34
|
+
except Exception:
|
|
35
|
+
continue
|
|
36
|
+
return value
|
|
37
|
+
return deser_union
|
|
38
|
+
|
|
39
|
+
if isinstance(typ, type) and issubclass(typ, BaseModel):
|
|
40
|
+
return typ.model_validate
|
|
41
|
+
|
|
42
|
+
if is_dataclass(typ):
|
|
43
|
+
field_deserializers = {f.name: TypeDeserializer(f.type) for f in fields(typ)}
|
|
44
|
+
def deser_dataclass(value):
|
|
45
|
+
if is_dataclass(value):
|
|
46
|
+
return value
|
|
47
|
+
|
|
48
|
+
return typ(**{
|
|
49
|
+
k: field_deserializers[k](v) for k, v in value.items()
|
|
50
|
+
})
|
|
51
|
+
return deser_dataclass
|
|
52
|
+
|
|
53
|
+
if origin is list:
|
|
54
|
+
item_deser = TypeDeserializer(args[0]) if args else lambda x: x
|
|
55
|
+
return lambda v: [item_deser(item) for item in v]
|
|
56
|
+
|
|
57
|
+
if origin is dict:
|
|
58
|
+
key_deser = TypeDeserializer(args[0]) if args else lambda x: x
|
|
59
|
+
val_deser = TypeDeserializer(args[1]) if len(args) > 1 else lambda x: x
|
|
60
|
+
return lambda v: {key_deser(k): val_deser(val) for k, val in v.items()}
|
|
61
|
+
|
|
62
|
+
# Fallback
|
|
63
|
+
return lambda v: v
|
|
64
|
+
|
|
65
|
+
class TypeSerializer:
|
|
66
|
+
def __init__(self, typ):
|
|
67
|
+
self.typ = typ
|
|
68
|
+
self.serializer = self._build_serializer(typ)
|
|
69
|
+
|
|
70
|
+
def __call__(self, value):
|
|
71
|
+
return self.serializer(value)
|
|
72
|
+
|
|
73
|
+
def _build_serializer(self, typ):
|
|
74
|
+
origin = get_origin(typ)
|
|
75
|
+
args = get_args(typ)
|
|
76
|
+
|
|
77
|
+
if origin is Union:
|
|
78
|
+
serializers = [TypeSerializer(arg) for arg in args if arg is not type(None)]
|
|
79
|
+
def ser_union(value):
|
|
80
|
+
if value is None:
|
|
81
|
+
return None
|
|
82
|
+
for s in serializers:
|
|
83
|
+
try:
|
|
84
|
+
return s(value)
|
|
85
|
+
except Exception:
|
|
86
|
+
continue
|
|
87
|
+
return value
|
|
88
|
+
return ser_union
|
|
89
|
+
|
|
90
|
+
if isinstance(typ, type) and issubclass(typ, BaseModel):
|
|
91
|
+
return lambda v: v.model_dump() if v is not None else None
|
|
92
|
+
|
|
93
|
+
if is_dataclass(typ):
|
|
94
|
+
field_serializers = {f.name: TypeSerializer(f.type) for f in fields(typ)}
|
|
95
|
+
def ser_dataclass(obj):
|
|
96
|
+
if obj is None:
|
|
97
|
+
return None
|
|
98
|
+
return {k: field_serializers[k](getattr(obj, k)) for k in field_serializers}
|
|
99
|
+
return ser_dataclass
|
|
100
|
+
|
|
101
|
+
if origin is list:
|
|
102
|
+
item_ser = TypeSerializer(args[0]) if args else lambda x: x
|
|
103
|
+
return lambda v: [item_ser(item) for item in v] if v is not None else None
|
|
104
|
+
|
|
105
|
+
if origin is dict:
|
|
106
|
+
key_ser = TypeSerializer(args[0]) if args else lambda x: x
|
|
107
|
+
val_ser = TypeSerializer(args[1]) if len(args) > 1 else lambda x: x
|
|
108
|
+
return lambda v: {key_ser(k): val_ser(val) for k, val in v.items()} if v is not None else None
|
|
109
|
+
|
|
110
|
+
# Fallback: primitive Typen oder unbekannt
|
|
111
|
+
return lambda v: v
|
|
112
|
+
|
|
113
|
+
@lru_cache(maxsize=512)
|
|
114
|
+
def get_deserializer(typ) -> TypeDeserializer:
|
|
115
|
+
"""
|
|
116
|
+
return a function that is able to deserialize a value of the specified type
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
typ: the type
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
|
|
123
|
+
"""
|
|
124
|
+
return TypeDeserializer(typ)
|
|
125
|
+
|
|
126
|
+
@lru_cache(maxsize=512)
|
|
127
|
+
def get_serializer(typ) -> TypeSerializer:
|
|
128
|
+
"""
|
|
129
|
+
return a function that is able to deserialize a value of the specified type
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
typ: the type
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
|
|
136
|
+
"""
|
|
137
|
+
return TypeSerializer(typ)
|
|
@@ -34,16 +34,12 @@ def transactional():
|
|
|
34
34
|
|
|
35
35
|
@module()
|
|
36
36
|
class SampleModule:
|
|
37
|
-
|
|
38
|
-
pass
|
|
37
|
+
pass
|
|
39
38
|
|
|
40
39
|
|
|
41
40
|
@injectable()
|
|
42
41
|
@transactional()
|
|
43
42
|
class Bar:
|
|
44
|
-
def __init__(self):
|
|
45
|
-
pass
|
|
46
|
-
|
|
47
43
|
async def say_async(self, message: str):
|
|
48
44
|
await asyncio.sleep(0.01)
|
|
49
45
|
|
|
@@ -14,11 +14,6 @@ from aspyx.di import injectable, Environment, module, create
|
|
|
14
14
|
|
|
15
15
|
@module()
|
|
16
16
|
class SampleModule:
|
|
17
|
-
# constructor
|
|
18
|
-
|
|
19
|
-
def __init__(self):
|
|
20
|
-
pass
|
|
21
|
-
|
|
22
17
|
@create()
|
|
23
18
|
def create_env_source(self) -> EnvConfigurationSource:
|
|
24
19
|
return EnvConfigurationSource()
|
|
@@ -30,10 +25,6 @@ class SampleModule:
|
|
|
30
25
|
|
|
31
26
|
@injectable()
|
|
32
27
|
class SampleConfigurationSource1(ConfigurationSource):
|
|
33
|
-
# constructor
|
|
34
|
-
|
|
35
|
-
def __init__(self):
|
|
36
|
-
super().__init__()
|
|
37
28
|
|
|
38
29
|
def load(self) -> dict:
|
|
39
30
|
return {
|
|
@@ -43,11 +34,6 @@ class SampleConfigurationSource1(ConfigurationSource):
|
|
|
43
34
|
|
|
44
35
|
@injectable()
|
|
45
36
|
class SampleConfigurationSource2(ConfigurationSource):
|
|
46
|
-
# constructor
|
|
47
|
-
|
|
48
|
-
def __init__(self):
|
|
49
|
-
super().__init__()
|
|
50
|
-
|
|
51
37
|
def load(self) -> dict:
|
|
52
38
|
return {
|
|
53
39
|
"b": {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|