aspyx 1.5.2__tar.gz → 1.6.0__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.2 → aspyx-1.6.0}/PKG-INFO +10 -4
- {aspyx-1.5.2 → aspyx-1.6.0}/README.md +7 -3
- {aspyx-1.5.2 → aspyx-1.6.0}/pyproject.toml +4 -2
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/di/aop/aop.py +14 -14
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/exception/__init__.py +2 -1
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/exception/exception_manager.py +12 -2
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/threading/__init__.py +2 -0
- aspyx-1.6.0/src/aspyx/threading/context_local.py +57 -0
- aspyx-1.6.0/src/aspyx/util/__init__.py +16 -0
- aspyx-1.6.0/src/aspyx/util/logger.py +19 -0
- aspyx-1.6.0/src/aspyx/util/serialization.py +137 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/tests/test_decorator.py +1 -1
- {aspyx-1.5.2 → aspyx-1.6.0}/tests/test_di.py +0 -1
- aspyx-1.5.2/src/aspyx/util/__init__.py +0 -8
- {aspyx-1.5.2 → aspyx-1.6.0}/.gitignore +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/LICENSE +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/__init__.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/di/__init__.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/di/aop/__init__.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/di/configuration/__init__.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/di/configuration/configuration.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/di/configuration/env_configuration_source.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/di/configuration/yaml_configuration_source.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/di/di.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/di/threading/__init__.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/di/threading/synchronized.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/reflection/__init__.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/reflection/proxy.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/reflection/reflection.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/threading/thread_local.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/src/aspyx/util/stringbuilder.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/tests/config.yaml +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/tests/config1.yaml +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/tests/di_import.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/tests/sub_import.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/tests/test_aop.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/tests/test_configuration.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/tests/test_cycle.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/tests/test_exception_manager.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/tests/test_proxy.py +0 -0
- {aspyx-1.5.2 → aspyx-1.6.0}/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.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.
|
|
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
|
|
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.
|
|
@@ -114,7 +114,7 @@ environment = Environment(SampleModule)
|
|
|
114
114
|
|
|
115
115
|
# fetch an instance
|
|
116
116
|
|
|
117
|
-
bar = env.
|
|
117
|
+
bar = env.read(Bar)
|
|
118
118
|
|
|
119
119
|
bar.foo.hello("world")
|
|
120
120
|
```
|
|
@@ -702,11 +702,13 @@ given a specific exception.
|
|
|
702
702
|
The handlers are declared by annoting a class with `@exception_handler` and decorating specific methods with `@handle`
|
|
703
703
|
|
|
704
704
|
**Example**:
|
|
705
|
+
|
|
705
706
|
```python
|
|
706
707
|
class DerivedException(Exception):
|
|
707
708
|
def __init__(self):
|
|
708
709
|
pass
|
|
709
710
|
|
|
711
|
+
|
|
710
712
|
@module()
|
|
711
713
|
class SampleModule:
|
|
712
714
|
# constructor
|
|
@@ -718,6 +720,7 @@ class SampleModule:
|
|
|
718
720
|
def create_exception_manager(self) -> ExceptionManager:
|
|
719
721
|
return ExceptionManager()
|
|
720
722
|
|
|
723
|
+
|
|
721
724
|
@injectable()
|
|
722
725
|
@exception_handler()
|
|
723
726
|
class TestExceptionHandler:
|
|
@@ -746,9 +749,10 @@ class ExceptionAdvice:
|
|
|
746
749
|
def handle_error(self, invocation: Invocation):
|
|
747
750
|
self.exceptionManager.handle(invocation.exception)
|
|
748
751
|
|
|
749
|
-
environment = Environment(SampleEnvironment)
|
|
750
752
|
|
|
751
|
-
environment
|
|
753
|
+
environment = Environment(SampleEnvironment)
|
|
754
|
+
|
|
755
|
+
environment.read(ExceptionManager).handle(DerivedException())
|
|
752
756
|
```
|
|
753
757
|
|
|
754
758
|
The exception maanger will first call the most appropriate method.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aspyx"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.6.0"
|
|
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]
|
|
@@ -14,7 +14,7 @@ from enum import auto, Enum
|
|
|
14
14
|
from typing import Optional, Dict, Type, Callable
|
|
15
15
|
|
|
16
16
|
from aspyx.reflection import Decorators, TypeDescriptor
|
|
17
|
-
from aspyx.di import injectable, order,
|
|
17
|
+
from aspyx.di import injectable, order, Environment, PostProcessor
|
|
18
18
|
|
|
19
19
|
class AOPException(Exception):
|
|
20
20
|
"""
|
|
@@ -197,25 +197,25 @@ class ClassAspectTarget(AspectTarget):
|
|
|
197
197
|
#descriptor = TypeDescriptor.for_type(func)
|
|
198
198
|
# type
|
|
199
199
|
|
|
200
|
-
if
|
|
200
|
+
if self.types:
|
|
201
201
|
if next((type for type in self.types if issubclass(clazz, type)), None) is None:
|
|
202
202
|
return False
|
|
203
203
|
|
|
204
204
|
# decorators
|
|
205
205
|
|
|
206
|
-
if
|
|
206
|
+
if self.decorators:
|
|
207
207
|
if next((decorator for decorator in self.decorators if class_descriptor.has_decorator(decorator)), None) is None:
|
|
208
208
|
return False
|
|
209
209
|
|
|
210
210
|
# names
|
|
211
211
|
|
|
212
|
-
if
|
|
212
|
+
if self.names:
|
|
213
213
|
if next((name for name in self.names if name == clazz.__name__), None) is None:
|
|
214
214
|
return False
|
|
215
215
|
|
|
216
216
|
# patterns
|
|
217
217
|
|
|
218
|
-
if
|
|
218
|
+
if self.patterns:
|
|
219
219
|
if next((pattern for pattern in self.patterns if re.fullmatch(pattern, clazz.__name__) is not None), None) is None:
|
|
220
220
|
return False
|
|
221
221
|
|
|
@@ -245,7 +245,7 @@ class MethodAspectTarget(AspectTarget):
|
|
|
245
245
|
|
|
246
246
|
# classes
|
|
247
247
|
|
|
248
|
-
if
|
|
248
|
+
if self.belonging_to:
|
|
249
249
|
match = False
|
|
250
250
|
for classes in self.belonging_to:
|
|
251
251
|
if classes._matches(clazz, func):
|
|
@@ -262,25 +262,25 @@ class MethodAspectTarget(AspectTarget):
|
|
|
262
262
|
|
|
263
263
|
# type
|
|
264
264
|
|
|
265
|
-
if
|
|
265
|
+
if self.types:
|
|
266
266
|
if next((type for type in self.types if issubclass(clazz, type)), None) is None:
|
|
267
267
|
return False
|
|
268
268
|
|
|
269
269
|
# decorators
|
|
270
270
|
|
|
271
|
-
if
|
|
271
|
+
if self.decorators:
|
|
272
272
|
if next((decorator for decorator in self.decorators if method_descriptor.has_decorator(decorator)), None) is None:
|
|
273
273
|
return False
|
|
274
274
|
|
|
275
275
|
# names
|
|
276
276
|
|
|
277
|
-
if
|
|
277
|
+
if self.names:
|
|
278
278
|
if next((name for name in self.names if name == func.__name__), None) is None:
|
|
279
279
|
return False
|
|
280
280
|
|
|
281
281
|
# patterns
|
|
282
282
|
|
|
283
|
-
if
|
|
283
|
+
if self.patterns:
|
|
284
284
|
if next((pattern for pattern in self.patterns if re.fullmatch(pattern, func.__name__) is not None), None) is None:
|
|
285
285
|
return False
|
|
286
286
|
|
|
@@ -474,7 +474,7 @@ class Invocation:
|
|
|
474
474
|
"""
|
|
475
475
|
Proceed to the next aspect in the around chain up to the original method.
|
|
476
476
|
"""
|
|
477
|
-
if
|
|
477
|
+
if args or kwargs: # as soon as we have args, we replace the current ones
|
|
478
478
|
self.args = args
|
|
479
479
|
self.kwargs = kwargs
|
|
480
480
|
|
|
@@ -486,11 +486,11 @@ class Invocation:
|
|
|
486
486
|
"""
|
|
487
487
|
Proceed to the next aspect in the around chain up to the original method.
|
|
488
488
|
"""
|
|
489
|
-
if
|
|
489
|
+
if args or kwargs: # as soon as we have args, we replace the current ones
|
|
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
|
|
|
@@ -575,7 +575,7 @@ class Advices:
|
|
|
575
575
|
afters = cls.collect(clazz, member, AspectType.AFTER, environment)
|
|
576
576
|
errors = cls.collect(clazz, member, AspectType.ERROR, environment)
|
|
577
577
|
|
|
578
|
-
if
|
|
578
|
+
if befores or arounds or afters or errors:
|
|
579
579
|
return Aspects(
|
|
580
580
|
before=befores,
|
|
581
581
|
around=arounds,
|
|
@@ -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
|
|
|
@@ -160,7 +170,7 @@ class ExceptionManager:
|
|
|
160
170
|
for i in range(0, len(chain) - 1):
|
|
161
171
|
chain[i].next = chain[i + 1]
|
|
162
172
|
|
|
163
|
-
if
|
|
173
|
+
if chain:
|
|
164
174
|
return chain[0]
|
|
165
175
|
else:
|
|
166
176
|
return None
|
|
@@ -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:
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
context utility
|
|
3
|
+
"""
|
|
4
|
+
import contextvars
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from typing import Generic, Optional, TypeVar, Any
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
|
|
10
|
+
class ContextLocal(Generic[T]):
|
|
11
|
+
"""
|
|
12
|
+
A context local value holder
|
|
13
|
+
"""
|
|
14
|
+
# constructor
|
|
15
|
+
|
|
16
|
+
def __init__(self, name: str, default: Optional[T] = None):
|
|
17
|
+
self.var = contextvars.ContextVar(name, default=default)
|
|
18
|
+
|
|
19
|
+
# public
|
|
20
|
+
|
|
21
|
+
def get(self) -> Optional[T]:
|
|
22
|
+
"""
|
|
23
|
+
return the current value or invoke the optional factory to compute one
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Optional[T]: the value associated with the current thread
|
|
27
|
+
"""
|
|
28
|
+
return self.var.get()
|
|
29
|
+
|
|
30
|
+
def set(self, value: Optional[T]) -> Any:
|
|
31
|
+
"""
|
|
32
|
+
set a value in the current thread
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
value: the value
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
a token that can be used as an argument to `reset`
|
|
39
|
+
"""
|
|
40
|
+
return self.var.set(value)
|
|
41
|
+
|
|
42
|
+
def reset(self, token) -> None:
|
|
43
|
+
"""
|
|
44
|
+
clear the value in the current thread
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
token: the token to clear
|
|
48
|
+
"""
|
|
49
|
+
self.var.reset(token)
|
|
50
|
+
|
|
51
|
+
@contextmanager
|
|
52
|
+
def use(self, value):
|
|
53
|
+
token = self.set(value)
|
|
54
|
+
try:
|
|
55
|
+
yield
|
|
56
|
+
finally:
|
|
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
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logging utility class
|
|
3
|
+
"""
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Optional, Dict
|
|
7
|
+
|
|
8
|
+
class Logger:
|
|
9
|
+
"""just syntactic sugar"""
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def configure(cls,
|
|
13
|
+
default_level: int = logging.INFO,
|
|
14
|
+
format: str = "[%(asctime)s] %(levelname)s in %(filename)s:%(lineno)d - %(message)s",
|
|
15
|
+
levels: Optional[Dict[str, int]] = None, stream=sys.stdout):
|
|
16
|
+
logging.basicConfig(level=default_level, format=format)
|
|
17
|
+
if levels is not None:
|
|
18
|
+
for name, level in levels.items():
|
|
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)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|