aspyx 1.5.1__py3-none-any.whl → 1.5.3__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
@@ -3,6 +3,7 @@ This module provides aspect-oriented programming (AOP) capabilities for Python a
3
3
  """
4
4
  from __future__ import annotations
5
5
 
6
+ import functools
6
7
  from abc import ABC, abstractmethod
7
8
  import inspect
8
9
  import re
@@ -13,7 +14,7 @@ from enum import auto, Enum
13
14
  from typing import Optional, Dict, Type, Callable
14
15
 
15
16
  from aspyx.reflection import Decorators, TypeDescriptor
16
- from aspyx.di import injectable, order, Providers, ClassInstanceProvider, Environment, PostProcessor
17
+ from aspyx.di import injectable, order, Environment, PostProcessor
17
18
 
18
19
  class AOPException(Exception):
19
20
  """
@@ -64,7 +65,7 @@ class AspectTarget(ABC):
64
65
  def __init__(self):
65
66
  self._clazz = None
66
67
  self._instance = None
67
- self._async = False
68
+ self._async : Optional[bool] = None
68
69
  self._function = None
69
70
  self._type = None
70
71
 
@@ -118,6 +119,16 @@ class AspectTarget(ABC):
118
119
  self._async = True
119
120
  return self
120
121
 
122
+ def that_are_sync(self) -> AspectTarget:
123
+ """
124
+ matches methods that are sync
125
+
126
+ Returns:
127
+ AspectTarget: self
128
+ """
129
+ self._async = False
130
+ return self
131
+
121
132
  def of_type(self, type: Type) -> AspectTarget:
122
133
  """
123
134
  matches methods belonging to a class or classes that are subclasses of the specified type
@@ -186,25 +197,25 @@ class ClassAspectTarget(AspectTarget):
186
197
  #descriptor = TypeDescriptor.for_type(func)
187
198
  # type
188
199
 
189
- if len(self.types) > 0:
200
+ if self.types:
190
201
  if next((type for type in self.types if issubclass(clazz, type)), None) is None:
191
202
  return False
192
203
 
193
204
  # decorators
194
205
 
195
- if len(self.decorators) > 0:
206
+ if self.decorators:
196
207
  if next((decorator for decorator in self.decorators if class_descriptor.has_decorator(decorator)), None) is None:
197
208
  return False
198
209
 
199
210
  # names
200
211
 
201
- if len(self.names) > 0:
212
+ if self.names:
202
213
  if next((name for name in self.names if name == clazz.__name__), None) is None:
203
214
  return False
204
215
 
205
216
  # patterns
206
217
 
207
- if len(self.patterns) > 0:
218
+ if self.patterns:
208
219
  if next((pattern for pattern in self.patterns if re.fullmatch(pattern, clazz.__name__) is not None), None) is None:
209
220
  return False
210
221
 
@@ -217,10 +228,14 @@ class MethodAspectTarget(AspectTarget):
217
228
  An AspectTarget matching methods
218
229
  """
219
230
 
220
- # properties
231
+ __slots__ = ["belonging_to"]
221
232
 
222
- __slots__ = [ ]
233
+ # constructor
223
234
 
235
+ def __init__(self):
236
+ super().__init__()
237
+
238
+ self.belonging_to : list[ClassAspectTarget] = []
224
239
  # public
225
240
 
226
241
  def _matches_self(self, clazz : Type, func):
@@ -228,32 +243,44 @@ class MethodAspectTarget(AspectTarget):
228
243
 
229
244
  method_descriptor = descriptor.get_method(func.__name__)
230
245
 
246
+ # classes
247
+
248
+ if self.belonging_to:
249
+ match = False
250
+ for classes in self.belonging_to:
251
+ if classes._matches(clazz, func):
252
+ match = True
253
+ break
254
+
255
+ if not match:
256
+ return False
257
+
231
258
  # async
232
259
 
233
- if self._async is not method_descriptor.is_async():
260
+ if self._async is not None and self._async is not method_descriptor.is_async():
234
261
  return False
235
262
 
236
263
  # type
237
264
 
238
- if len(self.types) > 0:
265
+ if self.types:
239
266
  if next((type for type in self.types if issubclass(clazz, type)), None) is None:
240
267
  return False
241
268
 
242
269
  # decorators
243
270
 
244
- if len(self.decorators) > 0:
271
+ if self.decorators:
245
272
  if next((decorator for decorator in self.decorators if method_descriptor.has_decorator(decorator)), None) is None:
246
273
  return False
247
274
 
248
275
  # names
249
276
 
250
- if len(self.names) > 0:
277
+ if self.names:
251
278
  if next((name for name in self.names if name == func.__name__), None) is None:
252
279
  return False
253
280
 
254
281
  # patterns
255
282
 
256
- if len(self.patterns) > 0:
283
+ if self.patterns:
257
284
  if next((pattern for pattern in self.patterns if re.fullmatch(pattern, func.__name__) is not None), None) is None:
258
285
  return False
259
286
 
@@ -261,7 +288,14 @@ class MethodAspectTarget(AspectTarget):
261
288
 
262
289
  return True
263
290
 
264
- def methods() -> AspectTarget:
291
+ # fluent
292
+
293
+ def declared_by(self, classes: ClassAspectTarget) -> MethodAspectTarget:
294
+ self.belonging_to.append(classes)
295
+
296
+ return self
297
+
298
+ def methods() -> MethodAspectTarget:
265
299
  """
266
300
  Create a new AspectTarget instance to define method aspect targets.
267
301
 
@@ -270,7 +304,7 @@ def methods() -> AspectTarget:
270
304
  """
271
305
  return MethodAspectTarget()
272
306
 
273
- def classes() -> AspectTarget:
307
+ def classes() -> ClassAspectTarget:
274
308
  """
275
309
  Create a new AspectTarget instance to define class aspect targets.
276
310
 
@@ -440,7 +474,7 @@ class Invocation:
440
474
  """
441
475
  Proceed to the next aspect in the around chain up to the original method.
442
476
  """
443
- 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
444
478
  self.args = args
445
479
  self.kwargs = kwargs
446
480
 
@@ -452,7 +486,7 @@ class Invocation:
452
486
  """
453
487
  Proceed to the next aspect in the around chain up to the original method.
454
488
  """
455
- 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
456
490
  self.args = args
457
491
  self.kwargs = kwargs
458
492
 
@@ -541,7 +575,7 @@ class Advices:
541
575
  afters = cls.collect(clazz, member, AspectType.AFTER, environment)
542
576
  errors = cls.collect(clazz, member, AspectType.ERROR, environment)
543
577
 
544
- 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:
545
579
  return Aspects(
546
580
  before=befores,
547
581
  around=arounds,
@@ -563,7 +597,7 @@ def advice(cls):
563
597
  Classes decorated with `@advice` are treated as advice classes.
564
598
  They can contain methods decorated with `@before`, `@after`, `@around`, or `@error` to define aspects.
565
599
  """
566
- Providers.register(ClassInstanceProvider(cls, True))
600
+ #Providers.register(ClassInstanceProvider(cls, True))
567
601
 
568
602
  Decorators.add(cls, advice)
569
603
 
@@ -673,13 +707,15 @@ class AdviceProcessor(PostProcessor):
673
707
  for member, aspects in aspect_dict.items():
674
708
  Environment.logger.debug("add aspects for %s:%s", type(instance), member.__name__)
675
709
 
676
- def wrap(jp):
710
+ def wrap(jp, member=member):
711
+ @functools.wraps(member)
677
712
  def sync_wrapper(*args, **kwargs):
678
713
  return Invocation(member, jp).call(*args, **kwargs)
679
714
 
680
715
  return sync_wrapper
681
716
 
682
- def wrap_async(jp):
717
+ def wrap_async(jp, member=member):
718
+ @functools.wraps(member)
683
719
  async def async_wrapper(*args, **kwargs):
684
720
  return await Invocation(member, jp).call_async(*args, **kwargs)
685
721
 
aspyx/di/di.py CHANGED
@@ -552,7 +552,7 @@ class PostProcessor(LifecycleProcessor):
552
552
  """
553
553
  __slots__ = []
554
554
 
555
-
555
+ @abstractmethod
556
556
  def process(self, instance: object, environment: Environment):
557
557
  pass
558
558
 
@@ -689,7 +689,7 @@ class Providers:
689
689
  return True
690
690
 
691
691
  def is_injectable(type: Type) -> bool:
692
- if type is object:
692
+ if type in [object, ABC]:
693
693
  return False
694
694
 
695
695
  if inspect.isabstract(type):
@@ -998,13 +998,13 @@ class Environment:
998
998
  # inherit providers from parent
999
999
 
1000
1000
  for provider_type, inherited_provider in self.parent.providers.items():
1001
+ provider = inherited_provider
1001
1002
  if inherited_provider.get_scope() == "environment":
1002
1003
  # replace with own environment instance provider
1003
1004
  provider = EnvironmentInstanceProvider(self, cast(EnvironmentInstanceProvider, inherited_provider).provider)
1004
1005
  provider.dependencies = [] # ??
1005
- add_provider(provider_type, provider)
1006
- else:
1007
- add_provider(provider_type, inherited_provider)
1006
+
1007
+ add_provider(provider_type, provider)
1008
1008
 
1009
1009
  # inherit processors as is unless they have an environment scope
1010
1010
 
@@ -1012,8 +1012,7 @@ class Environment:
1012
1012
  if self.providers[type(processor)].get_scope() != "environment":
1013
1013
  self.lifecycle_processors.append(processor)
1014
1014
  else:
1015
- # create and remember
1016
- self.lifecycle_processors.append(self.get(type(processor)))
1015
+ self.get(type(processor)) # will automatically be appended
1017
1016
  else:
1018
1017
  self.providers[SingletonScope] = SingletonScopeInstanceProvider()
1019
1018
  self.providers[RequestScope] = RequestScopeInstanceProvider()
@@ -6,6 +6,7 @@ from __future__ import annotations
6
6
  import threading
7
7
  from weakref import WeakKeyDictionary
8
8
 
9
+ from aspyx.di import injectable
9
10
  from aspyx.reflection import Decorators
10
11
  from aspyx.di.aop import advice, around, methods, Invocation
11
12
 
@@ -20,6 +21,7 @@ def synchronized():
20
21
  return decorator
21
22
 
22
23
  @advice
24
+ @injectable()
23
25
  class SynchronizeAdvice:
24
26
  # constructor
25
27
 
@@ -38,7 +40,7 @@ class SynchronizeAdvice:
38
40
 
39
41
  # around
40
42
 
41
- @around(methods().decorated_with(synchronized))
43
+ @around(methods().decorated_with(synchronized).that_are_sync())
42
44
  def synchronize_sync(self, invocation: Invocation):
43
45
  with self.get_lock(invocation.args[0]):
44
46
  return invocation.proceed()
@@ -107,6 +107,24 @@ class ExceptionManager:
107
107
 
108
108
  # internal
109
109
 
110
+ def collect_handlers(self, instance: Any):
111
+ type_descriptor = TypeDescriptor.for_type(type(instance))
112
+
113
+ # analyze methods
114
+
115
+ for method in type_descriptor.get_methods():
116
+ if method.has_decorator(handle):
117
+ if len(method.param_types) == 1:
118
+ exception_type = method.param_types[0]
119
+
120
+ self.handler.append(Handler(
121
+ exception_type,
122
+ instance,
123
+ method.method,
124
+ ))
125
+ else:
126
+ print(f"handler {method.method} expected to have one parameter")
127
+
110
128
  @inject_environment()
111
129
  def set_environment(self, environment: Environment):
112
130
  self.environment = environment
@@ -114,23 +132,7 @@ class ExceptionManager:
114
132
  @on_running()
115
133
  def setup(self):
116
134
  for handler_class in self.exception_handler_classes:
117
- type_descriptor = TypeDescriptor.for_type(handler_class)
118
- instance = self.environment.get(handler_class)
119
-
120
- # analyze methods
121
-
122
- for method in type_descriptor.get_methods():
123
- if method.has_decorator(handle):
124
- if len(method.param_types) == 1:
125
- exception_type = method.param_types[0]
126
-
127
- self.handler.append(Handler(
128
- exception_type,
129
- instance,
130
- method.method,
131
- ))
132
- else:
133
- print(f"handler {method.method} expected to have one parameter")
135
+ self.collect_handlers(self.environment.get(handler_class))
134
136
 
135
137
  def get_handlers(self, clazz: Type) -> Optional[Chain]:
136
138
  chain = self.cache.get(clazz, None)
@@ -158,7 +160,7 @@ class ExceptionManager:
158
160
  for i in range(0, len(chain) - 1):
159
161
  chain[i].next = chain[i + 1]
160
162
 
161
- if len(chain) > 0:
163
+ if chain:
162
164
  return chain[0]
163
165
  else:
164
166
  return None
aspyx/reflection/proxy.py CHANGED
@@ -1,7 +1,9 @@
1
1
  """
2
2
  Dynamic proxies for method interception and delegation.
3
3
  """
4
+ import functools
4
5
  import inspect
6
+ from abc import ABC, abstractmethod
5
7
  from typing import Generic, TypeVar, Type, Callable
6
8
 
7
9
  T = TypeVar("T")
@@ -44,10 +46,12 @@ class DynamicProxy(Generic[T]):
44
46
  self.args = args
45
47
  self.kwargs = kwargs
46
48
 
47
- class InvocationHandler:
49
+ class InvocationHandler(ABC):
50
+ @abstractmethod
48
51
  def invoke(self, invocation: 'DynamicProxy.Invocation'):
49
52
  pass
50
53
 
54
+ @abstractmethod
51
55
  async def invoke_async(self, invocation: 'DynamicProxy.Invocation'):
52
56
  return self.invoke(invocation)
53
57
 
@@ -73,13 +77,16 @@ class DynamicProxy(Generic[T]):
73
77
  def __getattr__(self, name):
74
78
  method = getattr(self.type, name)
75
79
 
76
- if inspect.iscoroutinefunction(method):
80
+ if inspect.iscoroutinefunction(method):
81
+
82
+ @functools.wraps(method)
77
83
  async def async_wrapper(*args, **kwargs):
78
- return await self.invocation_handler.invoke_async(DynamicProxy.Invocation(self.type, method, *args, **kwargs))
84
+ return await self.invocation_handler.invoke_async(DynamicProxy.Invocation(self.type, method, *args, **kwargs))
79
85
 
80
86
  return async_wrapper
81
87
 
82
88
  else:
89
+ @functools.wraps(method)
83
90
  def sync_wrapper(*args, **kwargs):
84
91
  return self.invocation_handler.invoke(DynamicProxy.Invocation(self.type, method, *args, **kwargs))
85
92
 
@@ -42,11 +42,15 @@ class Decorators:
42
42
  decorator: the decorator
43
43
  *args: any arguments supplied to the decorator
44
44
  """
45
- decorators = getattr(func_or_class, '__decorators__', None)
46
- if decorators is None:
45
+ current = func_or_class.__dict__.get('__decorators__')
46
+ if current is None:
47
47
  setattr(func_or_class, '__decorators__', [DecoratorDescriptor(decorator, *args)])
48
48
  else:
49
- decorators.append(DecoratorDescriptor(decorator, *args))
49
+ # Avoid mutating inherited list
50
+ if '__decorators__' not in func_or_class.__dict__:
51
+ current = list(current)
52
+ setattr(func_or_class, '__decorators__', current)
53
+ current.append(DecoratorDescriptor(decorator, *args))
50
54
 
51
55
  @classmethod
52
56
  def has_decorator(cls, func_or_class, callable: Callable) -> bool:
@@ -82,7 +86,7 @@ class Decorators:
82
86
  if inspect.ismethod(func_or_class):
83
87
  func_or_class = func_or_class.__func__ # unwrap bound method
84
88
 
85
- #return getattr(func_or_class, '__decorators__', []) will return inherited as well
89
+ #return getattr(func_or_class, '__decorators__', []) #will return inherited as well
86
90
  return func_or_class.__dict__.get('__decorators__', [])
87
91
 
88
92
 
@@ -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,48 @@
1
+ import contextvars
2
+ from contextlib import contextmanager
3
+ from typing import Generic, Optional, TypeVar, Any
4
+
5
+ T = TypeVar("T")
6
+
7
+ class ContextLocal(Generic[T]):
8
+ """
9
+ A context local value holder
10
+ """
11
+ # constructor
12
+
13
+ def __init__(self, name: str, default: Optional[T] = None):
14
+ self.var = contextvars.ContextVar(name, default=default)
15
+
16
+ # public
17
+
18
+ def get(self) -> Optional[T]:
19
+ """
20
+ return the current value or invoke the optional factory to compute one
21
+
22
+ Returns:
23
+ Optional[T]: the value associated with the current thread
24
+ """
25
+ return self.var.get()
26
+
27
+ def set(self, value: Optional[T]) -> Any:
28
+ """
29
+ set a value in the current thread
30
+
31
+ Args:
32
+ value: the value
33
+ """
34
+ return self.var.set(value)
35
+
36
+ def reset(self, token) -> None:
37
+ """
38
+ clear the value in the current thread
39
+ """
40
+ self.var.reset(token)
41
+
42
+ @contextmanager
43
+ def use(self, value):
44
+ token = self.set(value)
45
+ try:
46
+ yield
47
+ finally:
48
+ self.reset(token)
aspyx/util/__init__.py CHANGED
@@ -2,7 +2,9 @@
2
2
  This module provides utility functions.
3
3
  """
4
4
  from .stringbuilder import StringBuilder
5
+ from .logger import Logger
5
6
 
6
7
  __all__ = [
7
8
  "StringBuilder",
9
+ "Logger"
8
10
  ]
aspyx/util/logger.py ADDED
@@ -0,0 +1,15 @@
1
+ import logging
2
+ from typing import Optional, Dict
3
+
4
+ class Logger:
5
+ """just syntactic sugar"""
6
+
7
+ @classmethod
8
+ def configure(cls,
9
+ default_level: int = logging.INFO,
10
+ format: str = "[%(asctime)s] %(levelname)s in %(filename)s:%(lineno)d - %(message)s",
11
+ levels: Optional[Dict[str, int]] = None):
12
+ logging.basicConfig(level=default_level, format=format)
13
+ if levels is not None:
14
+ for name, level in levels.items():
15
+ logging.getLogger(name).setLevel(level)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspyx
3
- Version: 1.5.1
3
+ Version: 1.5.3
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,24 +1,26 @@
1
1
  aspyx/__init__.py,sha256=MsSFjiLMLJZ7QhUPpVBWKiyDnCzryquRyr329NoCACI,2
2
2
  aspyx/di/__init__.py,sha256=AGVU2VBWQyBxSssvbk_GOKrYWIYtcmSoIlupz-Oqxi4,1138
3
- aspyx/di/di.py,sha256=O8FUZm68aimUbt7GnIc43XzRa47iFV8Qh696JGOwdEE,44703
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=Cn-fqFW6PznVDM38fPX7mqlSpjGKMsgpJRBSYBv59xY,18403
5
+ aspyx/di/aop/aop.py,sha256=RaZRmQ1Ie6jZqYm0X93hWQ89xHn8vpDSnsmRgfAV68k,19180
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
- aspyx/di/threading/synchronized.py,sha256=BQ9PjMQUJsF5r-qWaDgvqg3AvFm_R9QZdKB49EkoelQ,1263
11
+ aspyx/di/threading/synchronized.py,sha256=6JOg5BXWrRIS5nRPH9iWR7T-kUglO4qWBQpLwhy99pI,1325
12
12
  aspyx/exception/__init__.py,sha256=OZwv-C3ZHD0Eg1rohCQMj575WLJ7lfYuk6PZD6sh1MA,211
13
- aspyx/exception/exception_manager.py,sha256=tv0nb0b2CFPiYWK6wwH9yI8hSc--9Xz9_xQVBwU42wc,5228
13
+ aspyx/exception/exception_manager.py,sha256=CVOjdI-y4m5GiE3TvSbwLpXXoAYqEyQLMN7jQhJYRn8,5228
14
14
  aspyx/reflection/__init__.py,sha256=r2sNJrfHDpuqaIYu4fTYsoo046gpgn4VTd7bsS3mQJY,282
15
- aspyx/reflection/proxy.py,sha256=9zqzmK2HGGx7LxdiBw8MfKRNT8H03h_0I6Y972eKFH8,2582
16
- aspyx/reflection/reflection.py,sha256=HYzbzExG7jzdHG_AggrRxy9yte6Cl192dPsJBRl785Y,8939
17
- aspyx/threading/__init__.py,sha256=3clmbCDP37GPan3dWtxTQvpg0Ti4aFzruAbUClkHGi0,147
15
+ aspyx/reflection/proxy.py,sha256=1-pgw-TNORFXbV0gowFZqGd-bcWv1ny69bJhq8TLsKs,2761
16
+ aspyx/reflection/reflection.py,sha256=BcqXJcMO36Gzq71O4aBsMqPynmBhYLI8V6J7DOInPAM,9142
17
+ aspyx/threading/__init__.py,sha256=YWqLk-MOtSg4i3cdzRZUBR25okbbftRRxkaEdfrdMZo,207
18
+ aspyx/threading/context_local.py,sha256=IBjqfmuGgQSMB3EhYW9etcKEnErMQKaAVHD45s1mqOA,1111
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=Uu6uZySb5v9WHS8ZooHmRXAxK_BhhKPUotv-LZyGQAI,165
21
+ aspyx/util/logger.py,sha256=S358l7RpBTSZtN8nivVLgX6HAp32vOQV-SDHDH0n9gc,547
20
22
  aspyx/util/stringbuilder.py,sha256=a-0T4YEXSJFUuQ3ztKN1ZPARkh8dIGMSkNEEJHRN7dc,856
21
- aspyx-1.5.1.dist-info/METADATA,sha256=U4sRH9owvFuCgQINKFlC84hL5BnQ-5kGavDNb-q8D88,26490
22
- aspyx-1.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- aspyx-1.5.1.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
24
- aspyx-1.5.1.dist-info/RECORD,,
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,,
File without changes