aspyx 1.5.2__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
@@ -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, Providers, ClassInstanceProvider, Environment, PostProcessor
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 len(self.types) > 0:
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 len(self.decorators) > 0:
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 len(self.names) > 0:
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 len(self.patterns) > 0:
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 len(self.belonging_to) > 0:
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 len(self.types) > 0:
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 len(self.decorators) > 0:
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 len(self.names) > 0:
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 len(self.patterns) > 0:
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 len(args) > 0 or len(kwargs) > 0: # as soon as we have args, we replace the current ones
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 len(args) > 0 or len(kwargs) > 0: # as soon as we have args, we replace the current ones
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 len(befores) > 0 or len(arounds) > 0 or len(afters) > 0 or len(errors) > 0:
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 len(chain) > 0:
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:
@@ -2,9 +2,11 @@
2
2
  A module with threading related utilities
3
3
  """
4
4
  from .thread_local import ThreadLocal
5
+ from .context_local import ContextLocal
5
6
 
6
7
  imports = [ThreadLocal]
7
8
 
8
9
  __all__ = [
9
10
  "ThreadLocal",
11
+ "ContextLocal",
10
12
  ]
@@ -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)
aspyx/util/__init__.py CHANGED
@@ -2,7 +2,15 @@
2
2
  This module provides utility functions.
3
3
  """
4
4
  from .stringbuilder import StringBuilder
5
+ from .logger import Logger
6
+ from .serialization import TypeSerializer, TypeDeserializer, get_serializer, get_deserializer
5
7
 
6
8
  __all__ = [
7
9
  "StringBuilder",
10
+ "Logger",
11
+
12
+ "TypeSerializer",
13
+ "TypeDeserializer",
14
+ "get_serializer",
15
+ "get_deserializer"
8
16
  ]
aspyx/util/logger.py ADDED
@@ -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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspyx
3
- Version: 1.5.2
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,23 +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=UqBpBI2nW6_8CLmY1j0F4cE2QVBQDgry9LWShVuxZKM,19367
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=_pK4yTS5FJYj2SW1ijdBVD_6qrPUM9elMgZki2pf-Tw,5237
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
- aspyx/threading/__init__.py,sha256=3clmbCDP37GPan3dWtxTQvpg0Ti4aFzruAbUClkHGi0,147
17
+ aspyx/threading/__init__.py,sha256=YWqLk-MOtSg4i3cdzRZUBR25okbbftRRxkaEdfrdMZo,207
18
+ aspyx/threading/context_local.py,sha256=2I-942IHbR0jCrM1N6Mo56VJwuNWMs2f3R8NJsHygBI,1270
18
19
  aspyx/threading/thread_local.py,sha256=86dNtbA4k2B-rNUUnZgn3_pU0DAojgLrRnh8RL6zf1E,1196
19
- aspyx/util/__init__.py,sha256=8H2yKkXu3nkRGeTerb8ialzKGfvzUx44XUWFUYcYuQM,125
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
20
23
  aspyx/util/stringbuilder.py,sha256=a-0T4YEXSJFUuQ3ztKN1ZPARkh8dIGMSkNEEJHRN7dc,856
21
- aspyx-1.5.2.dist-info/METADATA,sha256=mRlR5o-1WlYVvThk483KJTv738uNq5y02VkquUUsYDk,26490
22
- aspyx-1.5.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- aspyx-1.5.2.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
24
- aspyx-1.5.2.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