aspyx 1.5.3__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.3 → aspyx-1.6.0}/PKG-INFO +10 -4
- {aspyx-1.5.3 → aspyx-1.6.0}/README.md +7 -3
- {aspyx-1.5.3 → aspyx-1.6.0}/pyproject.toml +4 -2
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/di/aop/aop.py +1 -1
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/exception/__init__.py +2 -1
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/exception/exception_manager.py +11 -1
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/threading/context_local.py +10 -1
- aspyx-1.6.0/src/aspyx/util/__init__.py +16 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/util/logger.py +6 -2
- aspyx-1.6.0/src/aspyx/util/serialization.py +137 -0
- aspyx-1.5.3/src/aspyx/util/__init__.py +0 -10
- {aspyx-1.5.3 → aspyx-1.6.0}/.gitignore +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/LICENSE +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/di/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/di/aop/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/di/configuration/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/di/configuration/configuration.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/di/configuration/env_configuration_source.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/di/configuration/yaml_configuration_source.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/di/di.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/di/threading/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/di/threading/synchronized.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/reflection/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/reflection/proxy.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/reflection/reflection.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/threading/__init__.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/threading/thread_local.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/src/aspyx/util/stringbuilder.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/tests/config.yaml +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/tests/config1.yaml +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/tests/di_import.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/tests/sub_import.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/tests/test_aop.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/tests/test_configuration.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/tests/test_cycle.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/tests/test_decorator.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/tests/test_di.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/tests/test_exception_manager.py +0 -0
- {aspyx-1.5.3 → aspyx-1.6.0}/tests/test_proxy.py +0 -0
- {aspyx-1.5.3 → 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]
|
|
@@ -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)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|