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.

Files changed (41) hide show
  1. {aspyx-1.5.3 → aspyx-1.6.1}/PKG-INFO +32 -65
  2. {aspyx-1.5.3 → aspyx-1.6.1}/README.md +29 -64
  3. {aspyx-1.5.3 → aspyx-1.6.1}/pyproject.toml +4 -2
  4. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/aop/aop.py +1 -1
  5. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/configuration/configuration.py +0 -3
  6. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/configuration/env_configuration_source.py +0 -5
  7. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/configuration/yaml_configuration_source.py +0 -2
  8. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/di.py +15 -7
  9. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/exception/__init__.py +2 -1
  10. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/exception/exception_manager.py +11 -1
  11. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/threading/context_local.py +10 -1
  12. aspyx-1.6.1/src/aspyx/util/__init__.py +16 -0
  13. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/util/logger.py +6 -2
  14. aspyx-1.6.1/src/aspyx/util/serialization.py +137 -0
  15. {aspyx-1.5.3 → aspyx-1.6.1}/tests/di_import.py +2 -4
  16. {aspyx-1.5.3 → aspyx-1.6.1}/tests/sub_import.py +2 -4
  17. {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_aop.py +1 -5
  18. {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_configuration.py +0 -14
  19. {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_di.py +3 -2
  20. aspyx-1.5.3/src/aspyx/util/__init__.py +0 -10
  21. {aspyx-1.5.3 → aspyx-1.6.1}/.gitignore +0 -0
  22. {aspyx-1.5.3 → aspyx-1.6.1}/LICENSE +0 -0
  23. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/__init__.py +0 -0
  24. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/__init__.py +0 -0
  25. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/aop/__init__.py +0 -0
  26. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/configuration/__init__.py +0 -0
  27. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/threading/__init__.py +0 -0
  28. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/di/threading/synchronized.py +0 -0
  29. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/reflection/__init__.py +0 -0
  30. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/reflection/proxy.py +0 -0
  31. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/reflection/reflection.py +0 -0
  32. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/threading/__init__.py +0 -0
  33. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/threading/thread_local.py +0 -0
  34. {aspyx-1.5.3 → aspyx-1.6.1}/src/aspyx/util/stringbuilder.py +0 -0
  35. {aspyx-1.5.3 → aspyx-1.6.1}/tests/config.yaml +0 -0
  36. {aspyx-1.5.3 → aspyx-1.6.1}/tests/config1.yaml +0 -0
  37. {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_cycle.py +0 -0
  38. {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_decorator.py +0 -0
  39. {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_exception_manager.py +0 -0
  40. {aspyx-1.5.3 → aspyx-1.6.1}/tests/test_proxy.py +0 -0
  41. {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.5.3
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
- def __init__(self):
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 = env.get(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
- ⚠️ **Attention:** Please make sure, that the class defines a local constructor, as this is _required_ to determine injected instances.
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
- def __init__(self):
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
- def __init__(self):
313
- pass
299
+ pass
314
300
 
315
301
  @module()
316
302
  class SampleModule():
317
- def __init__(self):
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
- def __init__(self):
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 initEnvironment(self, env: Environment):
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
- def __init__(self):
617
- pass
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 `@handle`
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
- def __init__(self):
757
- pass
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
- @handle()
764
- def handle_exception(self, exception: Exception):
728
+ @catch()
729
+ def catch_exception(self, exception: Exception):
765
730
  pass
766
731
 
767
- @handle()
768
- def handle_base_exception(self, exception: BaseException):
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.get(ExceptionManager).handle(DerivedException())
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
- def __init__(self):
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 = env.get(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
- ⚠️ **Attention:** Please make sure, that the class defines a local constructor, as this is _required_ to determine injected instances.
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
- def __init__(self):
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
- def __init__(self):
281
- pass
265
+ pass
282
266
 
283
267
  @module()
284
268
  class SampleModule():
285
- def __init__(self):
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
- def __init__(self):
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 initEnvironment(self, env: Environment):
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
- def __init__(self):
585
- pass
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 `@handle`
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
- def __init__(self):
725
- pass
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
- @handle()
732
- def handle_exception(self, exception: Exception):
694
+ @catch()
695
+ def catch_exception(self, exception: Exception):
733
696
  pass
734
697
 
735
- @handle()
736
- def handle_base_exception(self, exception: BaseException):
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.get(ExceptionManager).handle(DerivedException())
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.5.3"
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]
@@ -490,7 +490,7 @@ class Invocation:
490
490
  self.args = args
491
491
  self.kwargs = kwargs
492
492
 
493
- # next one please...
493
+ # next one, please...
494
494
 
495
495
  return await self.current_aspect.next.call_async(self)
496
496
 
@@ -106,9 +106,6 @@ class ConfigurationSource(ABC):
106
106
 
107
107
  __slots__ = []
108
108
 
109
- def __init__(self):
110
- pass
111
-
112
109
  @inject()
113
110
  def set_manager(self, manager: ConfigurationManager):
114
111
  manager._register(self)
@@ -14,11 +14,6 @@ class EnvConfigurationSource(ConfigurationSource):
14
14
 
15
15
  __slots__ = []
16
16
 
17
- # constructor
18
-
19
- def __init__(self):
20
- super().__init__()
21
-
22
17
  # implement
23
18
 
24
19
  def load(self) -> dict:
@@ -15,8 +15,6 @@ class YamlConfigurationSource(ConfigurationSource):
15
15
  # constructor
16
16
 
17
17
  def __init__(self, file: str):
18
- super().__init__()
19
-
20
18
  self.file = file
21
19
 
22
20
  # implement
@@ -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
- raise DIRegistrationException(f"{self.type.__name__} does not implement __init__")
410
-
411
- self.params = len(init.param_types)
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.debug("\tadd provider %s for %s", provider, type)
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)
@@ -5,10 +5,8 @@ from aspyx.di import module, injectable
5
5
 
6
6
  @module()
7
7
  class ImportedModule:
8
- def __init__(self):
9
- pass
8
+ pass
10
9
 
11
10
  @injectable()
12
11
  class ImportedClass:
13
- def __init__(self):
14
- pass
12
+ pass
@@ -5,10 +5,8 @@ from aspyx.di import module, injectable
5
5
 
6
6
  @module()
7
7
  class SubImportModule:
8
- def __init__(self):
9
- pass
8
+ pass
10
9
 
11
10
  @injectable()
12
11
  class Sub:
13
- def __init__(self):
14
- pass
12
+ pass
@@ -34,16 +34,12 @@ def transactional():
34
34
 
35
35
  @module()
36
36
  class SampleModule:
37
- def __init__(self):
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": {
@@ -69,8 +69,9 @@ class Baz:
69
69
 
70
70
  @injectable()
71
71
  class Bazong:
72
- def __init__(self):
73
- pass
72
+ pass
73
+ #def __init__(self):
74
+ # pass
74
75
 
75
76
  class ConditionalBase:
76
77
  pass
@@ -1,10 +0,0 @@
1
- """
2
- This module provides utility functions.
3
- """
4
- from .stringbuilder import StringBuilder
5
- from .logger import Logger
6
-
7
- __all__ = [
8
- "StringBuilder",
9
- "Logger"
10
- ]
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