aspyx 1.5.3__py3-none-any.whl → 1.6.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/di/aop/aop.py CHANGED
@@ -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
 
@@ -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)
aspyx/util/__init__.py CHANGED
@@ -3,8 +3,14 @@ This module provides utility functions.
3
3
  """
4
4
  from .stringbuilder import StringBuilder
5
5
  from .logger import Logger
6
+ from .serialization import TypeSerializer, TypeDeserializer, get_serializer, get_deserializer
6
7
 
7
8
  __all__ = [
8
9
  "StringBuilder",
9
- "Logger"
10
+ "Logger",
11
+
12
+ "TypeSerializer",
13
+ "TypeDeserializer",
14
+ "get_serializer",
15
+ "get_deserializer"
10
16
  ]
aspyx/util/logger.py CHANGED
@@ -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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspyx
3
- Version: 1.5.3
3
+ Version: 1.6.0
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
@@ -146,7 +148,7 @@ environment = Environment(SampleModule)
146
148
 
147
149
  # fetch an instance
148
150
 
149
- bar = env.get(Bar)
151
+ bar = env.read(Bar)
150
152
 
151
153
  bar.foo.hello("world")
152
154
  ```
@@ -734,11 +736,13 @@ given a specific exception.
734
736
  The handlers are declared by annoting a class with `@exception_handler` and decorating specific methods with `@handle`
735
737
 
736
738
  **Example**:
739
+
737
740
  ```python
738
741
  class DerivedException(Exception):
739
742
  def __init__(self):
740
743
  pass
741
744
 
745
+
742
746
  @module()
743
747
  class SampleModule:
744
748
  # constructor
@@ -750,6 +754,7 @@ class SampleModule:
750
754
  def create_exception_manager(self) -> ExceptionManager:
751
755
  return ExceptionManager()
752
756
 
757
+
753
758
  @injectable()
754
759
  @exception_handler()
755
760
  class TestExceptionHandler:
@@ -778,9 +783,10 @@ class ExceptionAdvice:
778
783
  def handle_error(self, invocation: Invocation):
779
784
  self.exceptionManager.handle(invocation.exception)
780
785
 
781
- environment = Environment(SampleEnvironment)
782
786
 
783
- environment.get(ExceptionManager).handle(DerivedException())
787
+ environment = Environment(SampleEnvironment)
788
+
789
+ environment.read(ExceptionManager).handle(DerivedException())
784
790
  ```
785
791
 
786
792
  The exception maanger will first call the most appropriate method.
@@ -2,25 +2,26 @@ aspyx/__init__.py,sha256=MsSFjiLMLJZ7QhUPpVBWKiyDnCzryquRyr329NoCACI,2
2
2
  aspyx/di/__init__.py,sha256=AGVU2VBWQyBxSssvbk_GOKrYWIYtcmSoIlupz-Oqxi4,1138
3
3
  aspyx/di/di.py,sha256=V_BAV6DmFCoepPqAXhBz2GW6NwYaKokHb03HMz6A5Sw,44639
4
4
  aspyx/di/aop/__init__.py,sha256=rn6LSpzFtUOlgaBATyhLRWBzFmZ6XoVKA9B8SgQzYEI,746
5
- aspyx/di/aop/aop.py,sha256=RaZRmQ1Ie6jZqYm0X93hWQ89xHn8vpDSnsmRgfAV68k,19180
5
+ aspyx/di/aop/aop.py,sha256=y300DG713Gcn97CdTKuBdFL2jaS5ouW6J0azZk0Byws,19181
6
6
  aspyx/di/configuration/__init__.py,sha256=flM9A79J2wfA5I8goQbxs4tTqYustR9tn_9s0YO2WJQ,484
7
7
  aspyx/di/configuration/configuration.py,sha256=cXW40bPXiUZ9hUtBoZkSATT3nLrDPWsSqxtgASIBQaM,4375
8
8
  aspyx/di/configuration/env_configuration_source.py,sha256=FXPvREzq2ZER6_GG5xdpx154TQQDxZVf7LW7cvaylAk,1446
9
9
  aspyx/di/configuration/yaml_configuration_source.py,sha256=NDl3SeoLMNVlzHgfP-Ysvhco1tRew_zFnBL5gGy2WRk,550
10
10
  aspyx/di/threading/__init__.py,sha256=qrWdaq7MewQ2UmZy4J0Dn6BhY-ahfiG3xsv-EHqoqSE,191
11
11
  aspyx/di/threading/synchronized.py,sha256=6JOg5BXWrRIS5nRPH9iWR7T-kUglO4qWBQpLwhy99pI,1325
12
- aspyx/exception/__init__.py,sha256=OZwv-C3ZHD0Eg1rohCQMj575WLJ7lfYuk6PZD6sh1MA,211
13
- aspyx/exception/exception_manager.py,sha256=CVOjdI-y4m5GiE3TvSbwLpXXoAYqEyQLMN7jQhJYRn8,5228
12
+ aspyx/exception/__init__.py,sha256=HfK0kk1Tcw9QaUYgIyMeBFDAIE83pkTrIGYUnoKJXPE,231
13
+ aspyx/exception/exception_manager.py,sha256=wTxLjSVZ_vYfeo06PKZZS9hbrv_dRCwQotSwowUHLZY,5475
14
14
  aspyx/reflection/__init__.py,sha256=r2sNJrfHDpuqaIYu4fTYsoo046gpgn4VTd7bsS3mQJY,282
15
15
  aspyx/reflection/proxy.py,sha256=1-pgw-TNORFXbV0gowFZqGd-bcWv1ny69bJhq8TLsKs,2761
16
16
  aspyx/reflection/reflection.py,sha256=BcqXJcMO36Gzq71O4aBsMqPynmBhYLI8V6J7DOInPAM,9142
17
17
  aspyx/threading/__init__.py,sha256=YWqLk-MOtSg4i3cdzRZUBR25okbbftRRxkaEdfrdMZo,207
18
- aspyx/threading/context_local.py,sha256=IBjqfmuGgQSMB3EhYW9etcKEnErMQKaAVHD45s1mqOA,1111
18
+ aspyx/threading/context_local.py,sha256=2I-942IHbR0jCrM1N6Mo56VJwuNWMs2f3R8NJsHygBI,1270
19
19
  aspyx/threading/thread_local.py,sha256=86dNtbA4k2B-rNUUnZgn3_pU0DAojgLrRnh8RL6zf1E,1196
20
- aspyx/util/__init__.py,sha256=Uu6uZySb5v9WHS8ZooHmRXAxK_BhhKPUotv-LZyGQAI,165
21
- aspyx/util/logger.py,sha256=S358l7RpBTSZtN8nivVLgX6HAp32vOQV-SDHDH0n9gc,547
20
+ aspyx/util/__init__.py,sha256=B7QK3alguZqExBFGzx-OpHpYeoIc3ZGAL7pJnq8NNvI,352
21
+ aspyx/util/logger.py,sha256=Hti5JyajdPXlf_1jvVT3e6Gf9eLyAsIVJRdNBMahbJs,608
22
+ aspyx/util/serialization.py,sha256=BRsg-2S7E3IukJqxZGm7FvjTvV5d6rcyrpJT3aKoVmM,4252
22
23
  aspyx/util/stringbuilder.py,sha256=a-0T4YEXSJFUuQ3ztKN1ZPARkh8dIGMSkNEEJHRN7dc,856
23
- aspyx-1.5.3.dist-info/METADATA,sha256=hPk9M5CUZDxxvrzf0s1hTnpfw-XTJu1gPqgTzAVf6w0,26490
24
- aspyx-1.5.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
- aspyx-1.5.3.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
26
- aspyx-1.5.3.dist-info/RECORD,,
24
+ aspyx-1.6.0.dist-info/METADATA,sha256=fYorC9QmB3Yo6sQwu4TfMpJOI9eQk02P3Tmz6UCBrgo,26562
25
+ aspyx-1.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ aspyx-1.6.0.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
27
+ aspyx-1.6.0.dist-info/RECORD,,
File without changes