aspyx 1.6.1__tar.gz → 1.8.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.

Files changed (42) hide show
  1. {aspyx-1.6.1 → aspyx-1.8.0}/PKG-INFO +1 -1
  2. {aspyx-1.6.1 → aspyx-1.8.0}/pyproject.toml +1 -1
  3. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/di/di.py +1 -1
  4. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/reflection/__init__.py +3 -2
  5. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/reflection/reflection.py +29 -1
  6. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/util/__init__.py +3 -0
  7. aspyx-1.8.0/src/aspyx/util/copy_on_write_cache.py +40 -0
  8. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/util/serialization.py +41 -12
  9. aspyx-1.8.0/tests/test_serialization.py +56 -0
  10. {aspyx-1.6.1 → aspyx-1.8.0}/.gitignore +0 -0
  11. {aspyx-1.6.1 → aspyx-1.8.0}/LICENSE +0 -0
  12. {aspyx-1.6.1 → aspyx-1.8.0}/README.md +0 -0
  13. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/__init__.py +0 -0
  14. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/di/__init__.py +0 -0
  15. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/di/aop/__init__.py +0 -0
  16. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/di/aop/aop.py +0 -0
  17. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/di/configuration/__init__.py +0 -0
  18. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/di/configuration/configuration.py +0 -0
  19. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/di/configuration/env_configuration_source.py +0 -0
  20. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/di/configuration/yaml_configuration_source.py +0 -0
  21. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/di/threading/__init__.py +0 -0
  22. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/di/threading/synchronized.py +0 -0
  23. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/exception/__init__.py +0 -0
  24. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/exception/exception_manager.py +0 -0
  25. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/reflection/proxy.py +0 -0
  26. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/threading/__init__.py +0 -0
  27. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/threading/context_local.py +0 -0
  28. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/threading/thread_local.py +0 -0
  29. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/util/logger.py +0 -0
  30. {aspyx-1.6.1 → aspyx-1.8.0}/src/aspyx/util/stringbuilder.py +0 -0
  31. {aspyx-1.6.1 → aspyx-1.8.0}/tests/config.yaml +0 -0
  32. {aspyx-1.6.1 → aspyx-1.8.0}/tests/config1.yaml +0 -0
  33. {aspyx-1.6.1 → aspyx-1.8.0}/tests/di_import.py +0 -0
  34. {aspyx-1.6.1 → aspyx-1.8.0}/tests/sub_import.py +0 -0
  35. {aspyx-1.6.1 → aspyx-1.8.0}/tests/test_aop.py +0 -0
  36. {aspyx-1.6.1 → aspyx-1.8.0}/tests/test_configuration.py +0 -0
  37. {aspyx-1.6.1 → aspyx-1.8.0}/tests/test_cycle.py +0 -0
  38. {aspyx-1.6.1 → aspyx-1.8.0}/tests/test_decorator.py +0 -0
  39. {aspyx-1.6.1 → aspyx-1.8.0}/tests/test_di.py +0 -0
  40. {aspyx-1.6.1 → aspyx-1.8.0}/tests/test_exception_manager.py +0 -0
  41. {aspyx-1.6.1 → aspyx-1.8.0}/tests/test_proxy.py +0 -0
  42. {aspyx-1.6.1 → aspyx-1.8.0}/tests/test_reflection.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspyx
3
- Version: 1.6.1
3
+ Version: 1.8.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aspyx"
3
- version = "1.6.1"
3
+ version = "1.8.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"
@@ -975,7 +975,7 @@ class Environment:
975
975
  """
976
976
 
977
977
  def add_provider(type: Type, provider: AbstractInstanceProvider):
978
- Environment.logger.info("\tadd provider %s for %s", provider, type)
978
+ Environment.logger.debug("\tadd provider %s for %s", provider, type)
979
979
 
980
980
  self.providers[type] = provider
981
981
 
@@ -2,11 +2,12 @@
2
2
  This module provides tools for dynamic proxy creation and reflection
3
3
  """
4
4
  from .proxy import DynamicProxy
5
- from .reflection import Decorators, TypeDescriptor, DecoratorDescriptor
5
+ from .reflection import Decorators, TypeDescriptor, DecoratorDescriptor, get_method_class
6
6
 
7
7
  __all__ = [
8
8
  "DynamicProxy",
9
9
  "Decorators",
10
10
  "DecoratorDescriptor",
11
- "TypeDescriptor"
11
+ "TypeDescriptor",
12
+ "get_method_class"
12
13
  ]
@@ -12,6 +12,27 @@ from types import FunctionType
12
12
  from typing import Callable, get_type_hints, Type, Dict, Optional
13
13
  from weakref import WeakKeyDictionary
14
14
 
15
+ def get_method_class(method):
16
+ """
17
+ return the class of the specified method
18
+ Args:
19
+ method: the method
20
+
21
+ Returns:
22
+ the class of the specified method
23
+
24
+ """
25
+ if inspect.ismethod(method) or inspect.isfunction(method):
26
+ qualname = method.__qualname__
27
+ module = inspect.getmodule(method)
28
+ if module:
29
+ cls_name = qualname.split('.<locals>', 1)[0].rsplit('.', 1)[0]
30
+ cls = getattr(module, cls_name, None)
31
+ if inspect.isclass(cls):
32
+ return cls
33
+
34
+ return None
35
+
15
36
 
16
37
  class DecoratorDescriptor:
17
38
  """
@@ -94,7 +115,12 @@ class TypeDescriptor:
94
115
  """
95
116
  This class provides a way to introspect Python classes, their methods, decorators, and type hints.
96
117
  """
97
- # inner class
118
+ # inner classes
119
+
120
+ class ParameterDescriptor:
121
+ def __init__(self, name: str, type: Type):
122
+ self.name = name
123
+ self.type = type
98
124
 
99
125
  class MethodDescriptor:
100
126
  """
@@ -107,12 +133,14 @@ class TypeDescriptor:
107
133
  self.method = method
108
134
  self.decorators: list[DecoratorDescriptor] = Decorators.get(method)
109
135
  self.param_types : list[Type] = []
136
+ self.params: list[TypeDescriptor.ParameterDescriptor] = []
110
137
 
111
138
  type_hints = get_type_hints(method)
112
139
  sig = signature(method)
113
140
 
114
141
  for name, _ in sig.parameters.items():
115
142
  if name != 'self':
143
+ self.params.append(TypeDescriptor.ParameterDescriptor(name, type_hints.get(name)))
116
144
  self.param_types.append(type_hints.get(name, object))
117
145
 
118
146
  self.return_type = type_hints.get('return', None)
@@ -4,11 +4,14 @@ This module provides utility functions.
4
4
  from .stringbuilder import StringBuilder
5
5
  from .logger import Logger
6
6
  from .serialization import TypeSerializer, TypeDeserializer, get_serializer, get_deserializer
7
+ from .copy_on_write_cache import CopyOnWriteCache
7
8
 
8
9
  __all__ = [
9
10
  "StringBuilder",
10
11
  "Logger",
11
12
 
13
+ "CopyOnWriteCache",
14
+
12
15
  "TypeSerializer",
13
16
  "TypeDeserializer",
14
17
  "get_serializer",
@@ -0,0 +1,40 @@
1
+ from typing import TypeVar, Generic, Optional, Callable
2
+
3
+ K = TypeVar("K")
4
+ V = TypeVar("V")
5
+
6
+ class CopyOnWriteCache(Generic[K, V]):
7
+ """
8
+ cache, that clones the existing dict, whenever a new item is added, avoiding any locks
9
+ """
10
+ # constructor
11
+
12
+ def __init__(self, factory: Optional[Callable[[K], V]] = None) -> None:
13
+ self._cache: dict[K, V] = {}
14
+ self._factory = factory
15
+
16
+ # public
17
+
18
+ def get(self, key: K, factory: Optional[Callable[[K], V]] = None) -> Optional[V]:
19
+ value = self._cache.get(key, None)
20
+ if value is None:
21
+ if factory is not None:
22
+ value = factory(key)
23
+ self.put(key, value)
24
+
25
+ elif self._factory is not None:
26
+ value = self._factory(key)
27
+ self.put(key, value)
28
+
29
+ return value
30
+
31
+ def put(self, key: K, value: V) -> None:
32
+ new_cache = self._cache.copy()
33
+ new_cache[key] = value
34
+ self._cache = new_cache
35
+
36
+ def contains(self, key: K) -> bool:
37
+ return key in self._cache
38
+
39
+ def clear(self) -> None:
40
+ self._cache = {}
@@ -24,7 +24,9 @@ class TypeDeserializer:
24
24
  args = get_args(typ)
25
25
 
26
26
  if origin is Union:
27
- deserializers = [TypeDeserializer(arg) for arg in args if arg is not type(None)]
27
+ # Optional[X] => Union[X, NoneType]
28
+ deserializers = [self._build_deserializer(arg) for arg in args if arg is not type(None)]
29
+
28
30
  def deser_union(value):
29
31
  if value is None:
30
32
  return None
@@ -33,34 +35,61 @@ class TypeDeserializer:
33
35
  return d(value)
34
36
  except Exception:
35
37
  continue
36
- return value
38
+ raise ValueError(f"Cannot deserialize value: {value!r} into Union[{args}]")
39
+
37
40
  return deser_union
38
41
 
39
42
  if isinstance(typ, type) and issubclass(typ, BaseModel):
40
- return typ.model_validate
43
+ field_deserializers = {
44
+ name: self._build_deserializer(field.annotation)
45
+ for name, field in typ.model_fields.items()
46
+ }
47
+
48
+ def deser_model(value):
49
+ if isinstance(value, typ):
50
+ return value
51
+ if not isinstance(value, dict):
52
+ raise TypeError(f"Expected dict to construct {typ.__name__}, got {type(value).__name__}")
53
+ kwargs = {
54
+ k: field_deserializers[k](v)
55
+ for k, v in value.items()
56
+ if k in field_deserializers
57
+ }
58
+ return typ.model_construct(**kwargs)
59
+
60
+ return deser_model
41
61
 
42
62
  if is_dataclass(typ):
43
- field_deserializers = {f.name: TypeDeserializer(f.type) for f in fields(typ)}
63
+ field_deserializers = {
64
+ f.name: self._build_deserializer(f.type) for f in fields(typ)
65
+ }
66
+
44
67
  def deser_dataclass(value):
45
- if is_dataclass(value):
68
+ if isinstance(value, typ):
46
69
  return value
47
-
70
+ if not isinstance(value, dict):
71
+ raise TypeError(f"Expected dict to construct {typ}, got {type(value).__name__}")
48
72
  return typ(**{
49
- k: field_deserializers[k](v) for k, v in value.items()
73
+ k: field_deserializers[k](v) for k, v in value.items() if k in field_deserializers
50
74
  })
75
+
51
76
  return deser_dataclass
52
77
 
53
78
  if origin is list:
54
- item_deser = TypeDeserializer(args[0]) if args else lambda x: x
79
+ item_type = args[0] if args else Any
80
+ item_deser = self._build_deserializer(item_type)
55
81
  return lambda v: [item_deser(item) for item in v]
56
82
 
57
83
  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
84
+ key_type = args[0] if args else Any
85
+ val_type = args[1] if len(args) > 1 else Any
86
+ key_deser = self._build_deserializer(key_type)
87
+ val_deser = self._build_deserializer(val_type)
60
88
  return lambda v: {key_deser(k): val_deser(val) for k, val in v.items()}
61
89
 
62
- # Fallback
63
- return lambda v: v
90
+ # Fallback: primitive types, str, int, etc.
91
+ return lambda v: typ(v) if callable(typ) else v
92
+
64
93
 
65
94
  class TypeSerializer:
66
95
  def __init__(self, typ):
@@ -0,0 +1,56 @@
1
+ from dataclasses import dataclass
2
+ from lib2to3.btm_utils import pysyms
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from aspyx.util import get_serializer, get_deserializer
7
+
8
+ @dataclass
9
+ class EmbeddedDataClass:
10
+ name: str
11
+
12
+ embedded_dataclass = EmbeddedDataClass(name="foo")
13
+
14
+ class EmbeddedPydantic(BaseModel):
15
+ name: str
16
+
17
+ embedded_pydantic = EmbeddedPydantic(name="foo")
18
+
19
+ @dataclass
20
+ class DataClass:
21
+ int_attr : int
22
+ bool_attr: bool
23
+ int_list : list[int]
24
+
25
+ embedded_dataclass : EmbeddedDataClass
26
+ embedded_pydantic: EmbeddedPydantic
27
+
28
+ data_class = DataClass(int_attr=1, bool_attr=True, int_list=[1], embedded_pydantic=embedded_pydantic, embedded_dataclass=embedded_dataclass)
29
+
30
+ class Pydantic(BaseModel):
31
+ int_attr: int
32
+ bool_attr: bool
33
+
34
+ int_list : list[int]
35
+
36
+ embedded_dataclass: EmbeddedDataClass
37
+ embedded_pydantic: EmbeddedPydantic
38
+
39
+ pydantic = Pydantic(int_attr=1, bool_attr=True, int_list=[1], embedded_pydantic=embedded_pydantic, embedded_dataclass=embedded_dataclass)
40
+
41
+ class TestSerialization():
42
+ def test_data_class(self):
43
+ serializer = get_serializer(DataClass)
44
+ deserializer = get_deserializer(DataClass)
45
+
46
+ result = deserializer(serializer(data_class))
47
+
48
+ assert data_class == result
49
+
50
+ def test_pydantic(self):
51
+ serializer = get_serializer(Pydantic)
52
+ deserializer = get_deserializer(Pydantic)
53
+
54
+ result = deserializer(serializer(pydantic))
55
+
56
+ assert pydantic == result
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