aspyx 0.1.0__py3-none-any.whl → 1.0.1__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/reflection/proxy.py CHANGED
@@ -1,3 +1,6 @@
1
+ """
2
+ Dynamic proxies for method interception and delegation.
3
+ """
1
4
  from typing import Generic, TypeVar, Type
2
5
 
3
6
  T = TypeVar("T")
@@ -22,7 +25,7 @@ class DynamicProxy(Generic[T]):
22
25
 
23
26
  Attributes:
24
27
  type: The proxied class type.
25
- invocationHandler: The handler that processes intercepted method calls.
28
+ invocation_handler: The handler that processes intercepted method calls.
26
29
  """
27
30
  # inner class
28
31
 
@@ -40,19 +43,19 @@ class DynamicProxy(Generic[T]):
40
43
  # class methods
41
44
 
42
45
  @classmethod
43
- def create(cls, type: Type[T], invocationHandler: 'DynamicProxy.InvocationHandler') -> T:
44
- return DynamicProxy(type, invocationHandler)
46
+ def create(cls, type: Type[T], invocation_handler: 'DynamicProxy.InvocationHandler') -> T:
47
+ return DynamicProxy(type, invocation_handler)
45
48
 
46
49
  # constructor
47
50
 
48
- def __init__(self, type: Type[T], invocationHandler: 'DynamicProxy.InvocationHandler'):
51
+ def __init__(self, type: Type[T], invocation_handler: 'DynamicProxy.InvocationHandler'):
49
52
  self.type = type
50
- self.invocationHandler = invocationHandler
53
+ self.invocation_handler = invocation_handler
51
54
 
52
55
  # public
53
56
 
54
57
  def __getattr__(self, name):
55
58
  def wrapper(*args, **kwargs):
56
- return self.invocationHandler.invoke(DynamicProxy.Invocation(self.type, name, *args, **kwargs))
59
+ return self.invocation_handler.invoke(DynamicProxy.Invocation(self.type, name, *args, **kwargs))
57
60
 
58
- return wrapper
61
+ return wrapper
@@ -1,3 +1,7 @@
1
+ """
2
+ This module provides a TypeDescriptor class that allows introspection of Python classes,
3
+ including their methods, decorators, and type hints. It supports caching for performance
4
+ """
1
5
  from __future__ import annotations
2
6
 
3
7
  import inspect
@@ -5,7 +9,13 @@ from inspect import signature, getmembers
5
9
  from typing import Callable, get_type_hints, Type, Dict, Optional
6
10
  from weakref import WeakKeyDictionary
7
11
 
12
+
8
13
  class DecoratorDescriptor:
14
+ __slots__ = [
15
+ "decorator",
16
+ "args"
17
+ ]
18
+
9
19
  def __init__(self, decorator, *args):
10
20
  self.decorator = decorator
11
21
  self.args = args
@@ -34,27 +44,27 @@ class TypeDescriptor:
34
44
  self.clazz = cls
35
45
  self.method = method
36
46
  self.decorators: list[DecoratorDescriptor] = Decorators.get(method)
37
- self.paramTypes : list[Type] = []
47
+ self.param_types : list[Type] = []
38
48
 
39
49
  type_hints = get_type_hints(method)
40
50
  sig = signature(method)
41
51
 
42
- for name, param in sig.parameters.items():
52
+ for name, _ in sig.parameters.items():
43
53
  if name != 'self':
44
- self.paramTypes.append(type_hints.get(name, object))
54
+ self.param_types.append(type_hints.get(name, object))
45
55
 
46
- self.returnType = type_hints.get('return', None)
56
+ self.return_type = type_hints.get('return', None)
47
57
 
48
- def get_decorator(self, decorator):
58
+ def get_decorator(self, decorator) -> Optional[DecoratorDescriptor]:
49
59
  for dec in self.decorators:
50
- if dec.decorator == decorator:
60
+ if dec.decorator is decorator:
51
61
  return dec
52
62
 
53
63
  return None
54
64
 
55
- def has_decorator(self, decorator):
65
+ def has_decorator(self, decorator) -> bool:
56
66
  for dec in self.decorators:
57
- if dec.decorator == decorator:
67
+ if dec.decorator is decorator:
58
68
  return True
59
69
 
60
70
  return False
@@ -62,8 +72,6 @@ class TypeDescriptor:
62
72
  def __str__(self):
63
73
  return f"Method({self.method.__name__})"
64
74
 
65
- # class methods
66
-
67
75
  # class properties
68
76
 
69
77
  _cache = WeakKeyDictionary()
@@ -76,6 +84,7 @@ class TypeDescriptor:
76
84
  if descriptor is None:
77
85
  descriptor = TypeDescriptor(clazz)
78
86
  cls._cache[clazz] = descriptor
87
+
79
88
  return descriptor
80
89
 
81
90
  # constructor
@@ -83,27 +92,25 @@ class TypeDescriptor:
83
92
  def __init__(self, cls):
84
93
  self.cls = cls
85
94
  self.decorators = Decorators.get(cls)
86
- self.methods: Dict[str, TypeDescriptor.MethodDescriptor] = dict()
87
- self.localMethods: Dict[str, TypeDescriptor.MethodDescriptor] = dict()
95
+ self.methods: Dict[str, TypeDescriptor.MethodDescriptor] = {}
96
+ self.local_methods: Dict[str, TypeDescriptor.MethodDescriptor] = {}
88
97
 
89
98
  # check superclasses
90
99
 
91
- self.superTypes = [TypeDescriptor.for_type(x) for x in cls.__bases__ if not x is object]
100
+ self.super_types = [TypeDescriptor.for_type(x) for x in cls.__bases__ if not x is object]
92
101
 
93
- for superType in self.superTypes:
94
- self.methods = self.methods | superType.methods
102
+ for super_type in self.super_types:
103
+ self.methods = self.methods | super_type.methods
95
104
 
96
105
  # methods
97
106
 
98
107
  for name, member in self._get_local_members(cls):
99
108
  method = TypeDescriptor.MethodDescriptor(cls, member)
100
- self.localMethods[name] = method
109
+ self.local_methods[name] = method
101
110
  self.methods[name] = method
102
111
 
103
112
  # internal
104
113
 
105
- #isinstance(attr, classmethod)
106
-
107
114
  def _get_local_members(self, cls):
108
115
  return [
109
116
  (name, value)
@@ -127,8 +134,14 @@ class TypeDescriptor:
127
134
 
128
135
  return False
129
136
 
130
- def get_local_method(self, name) -> Optional[MethodDescriptor]:
131
- return self.localMethods.get(name, None)
137
+ def get_methods(self, local = False) -> list[TypeDescriptor.MethodDescriptor]:
138
+ if local:
139
+ return list(self.local_methods.values())
140
+ else:
141
+ return list(self.methods.values())
132
142
 
133
- def get_method(self, name) -> Optional[MethodDescriptor]:
134
- return self.methods.get(name, None)
143
+ def get_method(self, name: str, local = False) -> Optional[TypeDescriptor.MethodDescriptor]:
144
+ if local:
145
+ return self.local_methods.get(name, None)
146
+ else:
147
+ return self.methods.get(name, None)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspyx
3
- Version: 0.1.0
3
+ Version: 1.0.1
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
@@ -25,20 +25,22 @@ License: MIT License
25
25
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
26
  SOFTWARE.
27
27
 
28
- Requires-Python: >=3.8
28
+ Requires-Python: >=3.9
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  Dynamic: license-file
32
32
 
33
33
  # aspyx
34
34
 
35
- ![Pylint](https://github.com/andreasernst/aspyx/actions/workflows/pylint.yml/badge.svg)
36
- ![Build Status](https://github.com/andreasernst/aspyx/actions/workflows/ci.yml/badge.svg)
37
-
35
+ ![Pylint](https://github.com/coolsamson7/aspyx/actions/workflows/pylint.yml/badge.svg)
36
+ ![Build Status](https://github.com/coolsamson7/aspyx/actions/workflows/ci.yml/badge.svg)
37
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/aspyx)
38
+ ![License](https://img.shields.io/github/license/coolsamson7/aspyx)
38
39
 
39
40
  ## Table of Contents
40
41
 
41
42
  - [Introduction](#aspyx)
43
+ - [Installation](#installation)
42
44
  - [Registration](#registration)
43
45
  - [Class](#class)
44
46
  - [Class Factory](#class-factory)
@@ -46,11 +48,14 @@ Dynamic: license-file
46
48
  - [Environment](#environment)
47
49
  - [Definition](#definition)
48
50
  - [Retrieval](#retrieval)
49
- - [Lifecycle methods](#lifecycle-methods)
50
- - [Post Processors](#post-processors)
51
+ - [Instantiation logic](#instantiation-logic)
52
+ - [Injection Methods](#injection-methods)
53
+ - [Lifecycle Methods](#lifecycle-methods)
54
+ - [Post Processors](#post-processors)
51
55
  - [Custom scopes](#custom-scopes)
52
56
  - [AOP](#aop)
53
57
  - [Configuration](#configuration)
58
+ - [Reflection](#reflection)
54
59
 
55
60
  # Introduction
56
61
 
@@ -69,12 +74,13 @@ The following features are supported
69
74
  - container instances that relate to environment classes and manage the lifecylce of related objects
70
75
  - hierarchical environments
71
76
 
77
+ The library is thread-safe!
78
+
72
79
  Let's look at a simple example
73
80
 
74
81
  ```python
75
82
  from aspyx.di import injectable, on_init, on_destroy, environment, Environment
76
83
 
77
-
78
84
  @injectable()
79
85
  class Foo:
80
86
  def __init__(self):
@@ -83,13 +89,12 @@ class Foo:
83
89
  def hello(msg: str):
84
90
  print(f"hello {msg}")
85
91
 
86
-
87
92
  @injectable() # eager and singleton by default
88
93
  class Bar:
89
94
  def __init__(self, foo: Foo): # will inject the Foo dependency
90
95
  self.foo = foo
91
96
 
92
- @on_init() # a lifecycle callback called after the constructor
97
+ @on_init() # a lifecycle callback called after the constructor and all possible injections
93
98
  def init(self):
94
99
  ...
95
100
 
@@ -99,41 +104,40 @@ class Bar:
99
104
 
100
105
  @environment()
101
106
  class SampleEnvironment:
102
- # constructor
103
-
104
107
  def __init__(self):
105
108
  pass
106
109
 
107
-
108
110
  # go, forrest
109
111
 
110
- environment = SampleEnvironment(Configuration)
112
+ environment = Environment(SampleEnvironment)
111
113
 
112
114
  bar = env.get(Bar)
113
- bar.foo.hello("Andi")
115
+
116
+ bar.foo.hello("world")
114
117
  ```
115
118
 
116
- The concepts should be pretty familiar , as well as the names which are a combination of Spring and Angular names :-)
119
+ The concepts should be pretty familiar as well as the names which are a combination of Spring and Angular names :-)
117
120
 
118
121
  Let's add some aspects...
119
122
 
120
123
  ```python
124
+
121
125
  @advice
122
126
  class SampleAdvice:
123
- def __init__(self):
127
+ def __init__(self): # could inject additional stuff
124
128
  pass
125
129
 
126
130
  @before(methods().named("hello").of_type(Foo))
127
- def callBefore(self, invocation: Invocation):
131
+ def call_before(self, invocation: Invocation):
128
132
  print("before Foo.hello(...)")
129
133
 
130
134
  @error(methods().named("hello").of_type(Foo))
131
- def callError(self, invocation: Invocation):
135
+ def call_error(self, invocation: Invocation):
132
136
  print("error Foo.hello(...)")
133
137
  print(invocation.exception)
134
138
 
135
139
  @around(methods().named("hello"))
136
- def callAround(self, invocation: Invocation):
140
+ def call_around(self, invocation: Invocation):
137
141
  print("around Foo.hello()")
138
142
 
139
143
  return invocation.proceed()
@@ -148,6 +152,14 @@ The invocation parameter stores the complete context of the current execution, w
148
152
 
149
153
  Let's look at the details
150
154
 
155
+ # Installation
156
+
157
+ `pip install aspyx`
158
+
159
+ The library is tested with Python version > 3.8
160
+
161
+ Ready to go...
162
+
151
163
  # Registration
152
164
 
153
165
  Different mechanisms are available that make classes eligible for injection
@@ -164,16 +176,19 @@ class Foo:
164
176
  def __init__(self):
165
177
  pass
166
178
  ```
167
- Please make sure, that the class defines a constructor, as this is required to determine injected instances.
168
-
169
- The constructor can only define parameter types that are known as well to the container!
179
+ Please make sure, that the class defines a local constructor, as this is required to determine injected instances.
180
+ All referenced types will be injected by the environemnt.
170
181
 
182
+ Only eligible types are allowed, of course!
171
183
 
172
- The decorator accepts the keyword arguments
173
- - `eager=True` if `True`, the container will create the instances automatically while booting the environment
174
- - `scope="singleton"` defines how often instances will be created. `singleton` will create it only once - per environment -, while `request` will recreate it on every injection request
184
+ The decorator accepts the keyword arguments
185
+ - `eager : boolean`
186
+ if `True`, the container will create the instances automatically while booting the environment. This is the default.
187
+ - `scope: str`
188
+ the name of a scope which will determine how often instances will be created.
189
+ `singleton` will create it only once - per environment -, while `request` will recreate it on every injection request. The default is `singleton`
175
190
 
176
- Other scopes can be defined. Please check the corresponding chapter.
191
+ Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
177
192
 
178
193
  ## Class Factory
179
194
 
@@ -268,15 +283,55 @@ In case of ambiguities, it will throw an exception.
268
283
 
269
284
  Please be aware, that a base class are not _required_ to be annotated with `@injectable`, as this would mean, that it could be created on its own as well. ( Which is possible as well, btw. )
270
285
 
271
- # Lifecycle methods
286
+ # Instantiation logic
287
+
288
+ Constructing a new instance involves a number of steps executed in this order
289
+ - Constructor call
290
+ the constructor is called with the resolved parameters
291
+ - Advice injection
292
+ All methods involving aspects are updated
293
+ - Lifecycle methods
294
+ different decorators can mark methods that should be called during the lifecycle ( here the construction ) of an instance.
295
+ These are various injection possibilities as well as an optional final `on_init` call
296
+ - PostProcessors
297
+ Any custom post processors, that can add isde effects or modify the instances
298
+
299
+ ## Injection methods
300
+
301
+ Different decorators are implemented, that call methods with computed values
302
+
303
+ - `@inject`
304
+ the method is called with all resolved parameter types ( same as the constructor call)
305
+ - `@inject_environment`
306
+ the method is called with the creating environment as a single parameter
307
+ - `@value()`
308
+ the method is called with a resolved configuration value. Check the corresponding chapter
309
+
310
+ **Example**:
311
+ ```python
312
+ @injectable()
313
+ class Foo:
314
+ def __init__(self):
315
+ pass
316
+
317
+ @inject_environment()
318
+ def initEnvironment(self, env: Environment):
319
+ ...
320
+
321
+ @inject()
322
+ def set(self, baz: Baz) -> None:
323
+ ...
324
+ ```
325
+
326
+ ## Lifecycle methods
272
327
 
273
- It is possible to declare methods that will be called from the container
328
+ It is possible to mark specific lifecle methods.
274
329
  - `@on_init()`
275
330
  called after the constructor and all other injections.
276
331
  - `@on_destroy()`
277
- called after the container has been shut down
332
+ called during shutdown of the environment
278
333
 
279
- # Post Processors
334
+ ## Post Processors
280
335
 
281
336
  As part of the instantiation logic it is possible to define post processors that execute any side effect on newly created instances.
282
337
 
@@ -343,17 +398,17 @@ class SampleAdvice:
343
398
  pass
344
399
 
345
400
  @before(methods().named("hello").of_type(Foo))
346
- def callBefore(self, invocation: Invocation):
401
+ def call_before(self, invocation: Invocation):
347
402
  # arguments: invocation.args
348
403
  print("before Foo.hello(...)")
349
404
 
350
405
  @error(methods().named("hello").of_type(Foo))
351
- def callError(self, invocation: Invocation):
406
+ def call_error(self, invocation: Invocation):
352
407
  print("error Foo.hello(...)")
353
408
  print(invocation.exception)
354
409
 
355
410
  @around(methods().named("hello"))
356
- def callAround(self, invocation: Invocation):
411
+ def call_around(self, invocation: Invocation):
357
412
  print("around Foo.hello()")
358
413
 
359
414
  return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
@@ -374,7 +429,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
374
429
  It is essential for `around` methods to call `proceed()` on the invocation, which will call the next around method in the chain and finally the original method.
375
430
  If the `proceed` is called with parameters, they will replace the original parameters!
376
431
 
377
- The arguments to the corresponding decorators control, how aspects are associated with which methods.
432
+ The argument list to the corresponding decorators control, how aspects are associated with which methods.
378
433
  A fluent interface is used describe the mapping.
379
434
  The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
380
435
 
@@ -388,7 +443,20 @@ Both add the fluent methods:
388
443
  - `decorated_with(type: Type)`
389
444
  defines decorators on methods or classes
390
445
 
391
- The fluent methods `named`, `matches` and `of_type` can be called multiple timess!
446
+ The fluent methods `named`, `matches` and `of_type` can be called multiple times!
447
+
448
+ **Example**:
449
+
450
+ ```python
451
+ @injectable()
452
+ class TransactionAdvice:
453
+ def __init__(self):
454
+ pass
455
+
456
+ @around(methods().decorated_with(transactional), classes().decorated_with(transactional))
457
+ def establish_transaction(self, invocation: Invocation):
458
+ ...
459
+ ```
392
460
 
393
461
  # Configuration
394
462
 
@@ -409,10 +477,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
409
477
 
410
478
  ```python
411
479
  class ConfigurationSource(ABC):
412
- def __init__(self, manager: ConfigurationManager):
413
- manager._register(self)
480
+ def __init__(self):
414
481
  pass
415
482
 
483
+ ...
484
+
416
485
  @abstractmethod
417
486
  def load(self) -> dict:
418
487
  pass
@@ -424,14 +493,12 @@ As a default environment variables are already supported.
424
493
 
425
494
  Other sources can be added dynamically by just registering them.
426
495
 
496
+ **Example**:
427
497
  ```python
428
498
  @injectable()
429
499
  class SampleConfigurationSource(ConfigurationSource):
430
- # constructor
431
-
432
- def __init__(self, manager: ConfigurationManager):
433
- super().__init__(manager)
434
-
500
+ def __init__(self):
501
+ super().__init__()
435
502
 
436
503
  def load(self) -> dict:
437
504
  return {
@@ -444,6 +511,43 @@ class SampleConfigurationSource(ConfigurationSource):
444
511
  }
445
512
  ```
446
513
 
514
+ # Reflection
515
+
516
+ As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
517
+
518
+ After beeing instatiated with
519
+
520
+ ```python
521
+ TypeDescriptor.for_type(<type>)
522
+ ```
523
+
524
+ it offers the methods
525
+ - `get_methods(local=False)`
526
+ - `get_method(name: str, local=False)`
527
+ - `has_decorator(decorator) -> bool`
528
+ - `get_decorator(decorator)`
529
+
530
+ The returned method descriptors offer:
531
+ - `param_types`
532
+ - `return_type`
533
+ - `has_decorator(decorator)`
534
+ - `get_decorator(decorator)`
535
+
536
+ The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
537
+
538
+ Whenver you define a custom decorator, you will need to register it accordingly.
539
+
540
+ **Example**:
541
+ ```python
542
+ def transactional():
543
+ def decorator(func):
544
+ Decorators.add(func, transactional)
545
+ return func
546
+
547
+ return decorator
548
+ ```
549
+
550
+
447
551
 
448
552
 
449
553
 
@@ -0,0 +1,14 @@
1
+ aspyx/di/__init__.py,sha256=G13Cz1MMElBKNWsrhgTEIvhmIKj5MX4uqK_Apke4w8I,897
2
+ aspyx/di/di.py,sha256=elT3XJokxYxPJ9mx528MAU-pmm9lIZF8_AXKGkjyhBo,31014
3
+ aspyx/di/aop/__init__.py,sha256=nOABex49zSyMZ2w1ezwX3Q3yrOcQRSDjDtSj0DwKVbQ,233
4
+ aspyx/di/aop/aop.py,sha256=1RUgijk8RsiXWTizfNQX1IfHF7b96kCPdUgahX_J4Io,14457
5
+ aspyx/di/configuration/__init__.py,sha256=Zw7h-OlbJD7LyJvzkgyF0EmVqra6oN_Pt0HuUdjTPTA,249
6
+ aspyx/di/configuration/configuration.py,sha256=dHYoM3jC43QeDVlT6-mGDj05Sz6-Nm91LNE2TfQT1UU,5740
7
+ aspyx/reflection/__init__.py,sha256=r2sNJrfHDpuqaIYu4fTYsoo046gpgn4VTd7bsS3mQJY,282
8
+ aspyx/reflection/proxy.py,sha256=zJ6Psd6zWfFABdrKOf4cULt3gibyqCRdcR6z8WKIkzE,1982
9
+ aspyx/reflection/reflection.py,sha256=NkBK94kjJ7rQpPsK4mzHzX5TLSlWRM3mbNVzkwOZo4o,4379
10
+ aspyx-1.0.1.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
11
+ aspyx-1.0.1.dist-info/METADATA,sha256=69OakKuzM1UK0YTgH-y6-473YWhylxQvt8v993WsqiU,16798
12
+ aspyx-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
+ aspyx-1.0.1.dist-info/top_level.txt,sha256=A_ZwhBY_ybIgjZlztd44eaOrWqkJAndiqjGlbJ3tR_I,6
14
+ aspyx-1.0.1.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- aspyx/di/__init__.py,sha256=US6i6s94TIpk4eiRFKYm9JB0NnU9F_3RAoIoXa-OnTI,800
2
- aspyx/di/di.py,sha256=6A_-x6NrPArcKddlntpDj41yNOF0Gvbte1wSosv9UCI,30035
3
- aspyx/di/aop/__init__.py,sha256=2mv6o38HHoBzF_nx8rsrvuOlUlNgYITmNRcQJVT2Bk8,213
4
- aspyx/di/aop/aop.py,sha256=y5kwSzWWzKsPQNP3RUhxiNA0q5o_T_eGDE5vScOiPTM,13798
5
- aspyx/di/configuration/__init__.py,sha256=YgUk1bHVYq1EJ9W4D_CJ9ZM-3kje-vzPNMxBdUm0Vlg,211
6
- aspyx/di/configuration/configuration.py,sha256=-9QgOnC9HyWk9d3N8UQCRYHNtk4qDXjxUBaHC3RvAYc,5685
7
- aspyx/reflection/__init__.py,sha256=jZAjw5NDrtXENmlBsQ0u9MobcNkfEuT0Oey8IWNYh34,204
8
- aspyx/reflection/proxy.py,sha256=Re643BJAgbQjmDXO5JAvj4mu3RTpe_HF3TcFQ1euArg,1910
9
- aspyx/reflection/reflection.py,sha256=DS2J3CfxZ0BpdzGBOBU9c1H3R0XCTE34Zx5k2OWn8Iw,3925
10
- aspyx-0.1.0.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
11
- aspyx-0.1.0.dist-info/METADATA,sha256=Ch6Qb4hxI_vFRJ09nZgRKSiAoIhH8xERLT9a8qDUgqA,13906
12
- aspyx-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- aspyx-0.1.0.dist-info/top_level.txt,sha256=A_ZwhBY_ybIgjZlztd44eaOrWqkJAndiqjGlbJ3tR_I,6
14
- aspyx-0.1.0.dist-info/RECORD,,
File without changes