aspyx 1.2.0__py3-none-any.whl → 1.4.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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspyx
3
- Version: 1.2.0
3
+ Version: 1.4.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
@@ -28,6 +28,8 @@ License: MIT License
28
28
  Requires-Python: >=3.9
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
+ Provides-Extra: dev
32
+ Requires-Dist: mkdocstrings-python; extra == "dev"
31
33
  Dynamic: license-file
32
34
 
33
35
  # aspyx
@@ -37,15 +39,19 @@ Dynamic: license-file
37
39
  ![Python Versions](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12-blue)
38
40
  ![License](https://img.shields.io/github/license/coolsamson7/aspyx)
39
41
  ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
42
+ [![PyPI](https://img.shields.io/pypi/v/aspyx)](https://pypi.org/project/aspyx/)
43
+ [![Docs](https://img.shields.io/badge/docs-online-blue?logo=github)](https://coolsamson7.github.io/aspyx/)
40
44
 
41
- ## Table of Contents
45
+ ## Table of Contents
42
46
 
43
- - [Introduction](#aspyx)
47
+ - [Motivation](#motivation)
48
+ - [Introduction](#introduction)
44
49
  - [Installation](#installation)
45
50
  - [Registration](#registration)
46
51
  - [Class](#class)
47
52
  - [Class Factory](#class-factory)
48
53
  - [Method](#method)
54
+ - [Conditional](#conditional)
49
55
  - [Environment](#environment)
50
56
  - [Definition](#definition)
51
57
  - [Retrieval](#retrieval)
@@ -55,27 +61,48 @@ Dynamic: license-file
55
61
  - [Post Processors](#post-processors)
56
62
  - [Custom scopes](#custom-scopes)
57
63
  - [AOP](#aop)
64
+ - [Threading](#threading)
58
65
  - [Configuration](#configuration)
59
66
  - [Reflection](#reflection)
67
+ - [Exceptions](#exceptions)
60
68
  - [Version History](#version-history)
61
69
 
70
+ # Motivation
71
+
72
+ While working on AI-related projects in Python, I was looking for a dependency injection (DI) framework. After evaluating existing options, my impression was that the most either lacked key features — such as integrated AOP — or had APIs that felt overly technical and complex, which made me develop a library on my own with the following goals
73
+
74
+ - bring both di and AOP features together in a lightweight library,
75
+ - be as minimal invasive as possible,
76
+ - offering mechanisms to easily extend and customize features without touching the core,
77
+ - while still offering a _simple_ and _readable_ api that doesnt overwhelm developers
78
+
79
+ Especially the AOP integration definitely makes sense, as aspects on their own also usually require a context, which in a DI world is simply injected.
80
+
62
81
  # Introduction
63
82
 
64
- Aspyx is a small python libary, that adds support for both dependency injection and aop.
83
+ Aspyx is a lightweight Python library that provides both Dependency Injection (DI) and Aspect-Oriented Programming (AOP) support.
65
84
 
66
- The following features are supported
85
+ The following DI features are supported
67
86
  - constructor and setter injection
87
+ - injection of configuration variables
88
+ - possibility to define custom injections
68
89
  - post processors
69
- - factory classes and methods
70
- - support for eager construction
71
- - support for singleton and request scopes
72
- - possibilty to add custom scopes
73
- - lifecycle events methods
74
- - bundling of injectable object sets by environment classes including recursive imports and inheritance
75
- - container instances that relate to environment classes and manage the lifecylce of related objects
90
+ - support for factory classes and methods
91
+ - support for eager and lazy construction
92
+ - support for scopes singleton, request and thread
93
+ - possibility to add custom scopes
94
+ - conditional registration of classes and factories ( aka profiles in spring )
95
+ - lifecycle events methods `on_init`, `on_destroy`, `on_running`
96
+ - bundling of injectable objects according to their module location including recursive imports and inheritance
97
+ - instantiation of - possibly multiple - container instances - so called environments - that manage the lifecycle of related objects
76
98
  - hierarchical environments
77
99
 
78
- The library is thread-safe!
100
+ With respect to AOP:
101
+ - support for before, around, after and error aspects
102
+ - simple fluent interface to specify which methods are targeted by an aspect
103
+ - sync and async method support
104
+
105
+ The library is thread-safe and heavily performance optimized as most of the runtime information is precomputed and cached!
79
106
 
80
107
  Let's look at a simple example
81
108
 
@@ -87,7 +114,7 @@ class Foo:
87
114
  def __init__(self):
88
115
  pass
89
116
 
90
- def hello(msg: str):
117
+ def hello(self, msg: str):
91
118
  print(f"hello {msg}")
92
119
 
93
120
  @injectable() # eager and singleton by default
@@ -108,16 +135,18 @@ class SampleEnvironment:
108
135
  def __init__(self):
109
136
  pass
110
137
 
111
- # go, forrest
138
+ # create environment
112
139
 
113
140
  environment = Environment(SampleEnvironment)
114
141
 
142
+ # fetch an instance
143
+
115
144
  bar = env.get(Bar)
116
145
 
117
146
  bar.foo.hello("world")
118
147
  ```
119
148
 
120
- The concepts should be pretty familiar as well as the names which are a combination of Spring and Angular names :-)
149
+ The concepts should be pretty familiar as well as the names as they are inspired by both Spring and Angular.
121
150
 
122
151
  Let's add some aspects...
123
152
 
@@ -130,17 +159,15 @@ class SampleAdvice:
130
159
 
131
160
  @before(methods().named("hello").of_type(Foo))
132
161
  def call_before(self, invocation: Invocation):
133
- print("before Foo.hello(...)")
162
+ ...
134
163
 
135
164
  @error(methods().named("hello").of_type(Foo))
136
165
  def call_error(self, invocation: Invocation):
137
- print("error Foo.hello(...)")
138
- print(invocation.exception)
166
+ ... # exception accessible in invocation.exception
139
167
 
140
168
  @around(methods().named("hello"))
141
169
  def call_around(self, invocation: Invocation):
142
- print("around Foo.hello()")
143
-
170
+ ...
144
171
  return invocation.proceed()
145
172
  ```
146
173
 
@@ -155,11 +182,11 @@ Let's look at the details
155
182
 
156
183
  # Installation
157
184
 
158
- `pip install aspyx`
185
+ Just install from PyPI with
159
186
 
160
- The library is tested with all Python version > 3.9
187
+ `pip install aspyx`
161
188
 
162
- Ready to go...
189
+ The library is tested with all Python version >= 3.9
163
190
 
164
191
  # Registration
165
192
 
@@ -177,8 +204,8 @@ class Foo:
177
204
  def __init__(self):
178
205
  pass
179
206
  ```
180
- Please make sure, that the class defines a local constructor, as this is required to determine injected instances.
181
- All referenced types will be injected by the environemnt.
207
+ ⚠️ **Attention:** Please make sure, that the class defines a local constructor, as this is _required_ to determine injected instances.
208
+ All referenced types will be injected by the environment.
182
209
 
183
210
  Only eligible types are allowed, of course!
184
211
 
@@ -233,6 +260,22 @@ class Foo:
233
260
 
234
261
  The same arguments as in `@injectable` are possible.
235
262
 
263
+ ## Conditional
264
+
265
+ All `@injectable` declarations can be supplemented with
266
+
267
+ ```python
268
+ @conditional(<condition>, ..., <condition>)
269
+ ```
270
+
271
+ decorators that act as filters in the context of an environment.
272
+
273
+ Valid conditions are created by:
274
+ - `requires_class(clazz: Type)`
275
+ the injectable is valid, if the specified class is registered as well.
276
+ - `requires_feature(feature: str)`
277
+ the injectable is valid, if the environment defines the specified feature.
278
+
236
279
  # Environment
237
280
 
238
281
  ## Definition
@@ -250,13 +293,35 @@ environment = Environment(SampleEnvironment)
250
293
  ```
251
294
 
252
295
  The default is that all eligible classes, that are implemented in the containing module or in any submodule will be managed.
296
+ THe container will import the module and its children automatically. No need to add artificial import statements!
297
+
298
+
299
+ By adding the parameter `features: list[str]`, it is possible to filter injectables by evaluating the corresponding `@conditional` decorators.
300
+
301
+ **Example**:
302
+ ```python
303
+
304
+ @injectable()
305
+ @conditional(requires_feature("dev"))
306
+ class DevOnly:
307
+ def __init__(self):
308
+ pass
309
+
310
+ @environment()
311
+ class SampleEnvironmen():
312
+ def __init__(self):
313
+ pass
314
+
315
+ environment = Environment(SampleEnvironment, features=["dev"])
316
+ ```
317
+
253
318
 
254
319
  By adding an `imports: list[Type]` parameter, specifying other environment types, it will register the appropriate classes recursively.
255
320
 
256
321
  **Example**:
257
322
  ```python
258
323
  @environment()
259
- class SampleEnvironmen(imports=[OtherEnvironment])):
324
+ class SampleEnvironmen(imports=[OtherEnvironment]):
260
325
  def __init__(self):
261
326
  pass
262
327
  ```
@@ -289,7 +354,7 @@ The container knows about class hierarchies and is able to `get` base classes, a
289
354
 
290
355
  In case of ambiguities, it will throw an exception.
291
356
 
292
- 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. )
357
+ Note 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. )
293
358
 
294
359
  # Instantiation logic
295
360
 
@@ -302,7 +367,7 @@ Constructing a new instance involves a number of steps executed in this order
302
367
  different decorators can mark methods that should be called during the lifecycle ( here the construction ) of an instance.
303
368
  These are various injection possibilities as well as an optional final `on_init` call
304
369
  - PostProcessors
305
- Any custom post processors, that can add isde effects or modify the instances
370
+ Any custom post processors, that can add side effects or modify the instances
306
371
 
307
372
  ## Injection methods
308
373
 
@@ -337,7 +402,7 @@ It is possible to mark specific lifecyle methods.
337
402
  - `@on_init()`
338
403
  called after the constructor and all other injections.
339
404
  - `@on_running()`
340
- called an environment has initialized all eager objects.
405
+ called after an environment has initialized completely ( e.g. created all eager objects ).
341
406
  - `@on_destroy()`
342
407
  called during shutdown of the environment
343
408
 
@@ -355,7 +420,7 @@ class SamplePostProcessor(PostProcessor):
355
420
 
356
421
  Any implementing class of `PostProcessor` that is eligible for injection will be called by passing the new instance.
357
422
 
358
- Please be aware, that a post processor will only handle instances _after_ its _own_ registration.
423
+ Note that a post processor will only handle instances _after_ its _own_ registration.
359
424
 
360
425
  As injectables within a single file will be handled in the order as they are declared, a post processor will only take effect for all classes after its declaration!
361
426
 
@@ -372,7 +437,7 @@ def get(self, provider: AbstractInstanceProvider, environment: Environment, argP
372
437
  Arguments are:
373
438
  - `provider` the actual provider that will create an instance
374
439
  - `environment`the requesting environment
375
- - `argPovider` a function that can be called to compute the required arguments recursively
440
+ - `argProvider` a function that can be called to compute the required arguments recursively
376
441
 
377
442
  **Example**: The simplified code of the singleton provider ( disregarding locking logic )
378
443
 
@@ -397,49 +462,74 @@ class SingletonScope(Scope):
397
462
 
398
463
  # AOP
399
464
 
400
- It is possible to define different Aspects, that will be part of method calling flow. This logic fits nicely in the library, since the DI framework controls the instantiation logic and can handle aspects within a regular post processor.
465
+ It is possible to define different aspects, that will be part of method calling flow. This logic fits nicely in the library, since the DI framework controls the instantiation logic and can handle aspects within a regular post processor.
401
466
 
402
467
  Advice classes need to be part of classes that add a `@advice()` decorator and can define methods that add aspects.
403
468
 
404
469
  ```python
405
- @advice()
470
+ @advice
406
471
  class SampleAdvice:
407
472
  def __init__(self): # could inject dependencies
408
473
  pass
409
474
 
410
475
  @before(methods().named("hello").of_type(Foo))
411
476
  def call_before(self, invocation: Invocation):
412
- # arguments: invocation.args
413
- print("before Foo.hello(...)")
477
+ # arguments: invocation.args and invocation.kwargs
478
+ ...
479
+
480
+ @after(methods().named("hello").of_type(Foo))
481
+ def call_after(self, invocation: Invocation):
482
+ # arguments: invocation.args and invocation.kwargs
483
+ ...
414
484
 
415
485
  @error(methods().named("hello").of_type(Foo))
416
486
  def call_error(self, invocation: Invocation):
417
- print("error Foo.hello(...)")
418
- print(invocation.exception)
487
+ # error: invocation.exception
488
+ ...
419
489
 
420
490
  @around(methods().named("hello"))
421
491
  def call_around(self, invocation: Invocation):
422
- print("around Foo.hello()")
423
-
424
- return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
492
+ try:
493
+ ...
494
+ return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
495
+ finally:
496
+ ...
425
497
  ```
426
498
 
427
499
  Different aspects - with the appropriate decorator - are possible:
428
500
  - `before`
429
501
  methods that will be executed _prior_ to the original method
430
502
  - `around`
431
- methods that will be executed _around_ to the original method giving it the possibility add side effects or even change the parameters.
503
+ methods that will be executed _around_ to the original method allowing you to add side effects or even modify parameters.
432
504
  - `after`
433
- methods that will be executed _after_ to the original method
505
+ methods that will be executed _after_ to the original method
434
506
  - `error`
435
- methods that will be executed in case of a caught exception, which can be retrieved by `invocation.exception`
507
+ methods that will be executed in case of a caught exception
508
+
509
+ The different aspects can be supplemented with an `@order(<prio>)` decorator that controls the execution order based on the passed number. Smaller values get executed first.
510
+
511
+ All methods are expected to have single `Invocation` parameter, that stores
512
+
513
+ - `func` the target function
514
+ - `args` the supplied args ( including the `self` instance as the first element)
515
+ - `kwargs` the keywords args
516
+ - `result` the result ( initially `None`)
517
+ - `exception` a possible caught exception ( initially `None`)
436
518
 
437
- All methods are expected to hava single `Invocation` parameter, that stores, the function, args and kwargs, the return value and possible exceptions.
519
+ ⚠️ **Attention:** 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.
438
520
 
439
- 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.
440
521
  If the `proceed` is called with parameters, they will replace the original parameters!
441
522
 
442
- The argument list to the corresponding decorators control, how aspects are associated with which methods.
523
+ **Example**: Parameter modifications
524
+
525
+ ```python
526
+ @around(methods().named("say"))
527
+ def call_around(self, invocation: Invocation):
528
+ return invocation.proceed(invocation.args[0], invocation.args[1] + "!") # 0 is self!
529
+ ```
530
+
531
+ The argument list to the corresponding decorators control which methods are targeted by the advice.
532
+
443
533
  A fluent interface is used describe the mapping.
444
534
  The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
445
535
 
@@ -448,6 +538,8 @@ Both add the fluent methods:
448
538
  defines the matching classes
449
539
  - `named(name: str)`
450
540
  defines method or class names
541
+ - `that_are_async()`
542
+ defines async methods
451
543
  - `matches(re: str)`
452
544
  defines regular expressions for methods or classes
453
545
  - `decorated_with(type: Type)`
@@ -458,7 +550,7 @@ The fluent methods `named`, `matches` and `of_type` can be called multiple times
458
550
  **Example**: react on both `transactional` decorators on methods or classes
459
551
 
460
552
  ```python
461
- @injectable()
553
+ @advice
462
554
  class TransactionAdvice:
463
555
  def __init__(self):
464
556
  pass
@@ -468,6 +560,24 @@ class TransactionAdvice:
468
560
  ...
469
561
  ```
470
562
 
563
+ With respect to async methods, you need to make sure, to replace a `proceed()` with a `await proceed_async()` to have the overall chain async!
564
+
565
+ # Threading
566
+
567
+ A handy decorator `@synchronized` in combination with the respective advice is implemented that automatically synchronizes methods with a `RLock` associated with the instance.
568
+
569
+ **Example**:
570
+ ```python
571
+ @injectable()
572
+ class Foo:
573
+ def __init__(self):
574
+ pass
575
+
576
+ @synchronized()
577
+ def execute_synchronized(self):
578
+ ...
579
+ ```
580
+
471
581
  # Configuration
472
582
 
473
583
  It is possible to inject configuration values, by decorating methods with `@inject-value(<name>)` given a configuration key.
@@ -483,9 +593,9 @@ class Foo:
483
593
  ...
484
594
  ```
485
595
 
486
- If required a coercion will be executed.
596
+ If required type coercion will be applied.
487
597
 
488
- This concept relies on a central object `ConfigurationManager` that stores the overall configuration values as provided by so called configuration sources that are defined as follows.
598
+ Configuration values are managed centrally using a `ConfigurationManager`, which aggregates values from various configuration sources that are defined as follows.
489
599
 
490
600
  ```python
491
601
  class ConfigurationSource(ABC):
@@ -562,7 +672,7 @@ class SampleEnvironment:
562
672
 
563
673
  As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
564
674
 
565
- After beeing instatiated with
675
+ After being instantiated with
566
676
 
567
677
  ```python
568
678
  TypeDescriptor.for_type(<type>)
@@ -578,7 +688,7 @@ it offers the methods
578
688
  - `get_decorator(decorator) -> Optional[DecoratorDescriptor]`
579
689
  return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
580
690
 
581
- The returned method descriptors offer:
691
+ The returned method descriptors provide:
582
692
  - `param_types`
583
693
  list of arg types
584
694
  - `return_type`
@@ -602,6 +712,87 @@ def transactional(scope):
602
712
  return decorator
603
713
  ```
604
714
 
715
+ # Exceptions
716
+
717
+ The class `ExceptionManager` is used to collect dynamic handlers for specific exceptions and is able to dispatch to the concrete functions
718
+ given a specific exception.
719
+
720
+ The handlers are declared by annoting a class with `@exception_handler` and decorating specific methods with `@handle`
721
+
722
+ **Example**:
723
+ ```python
724
+ class DerivedException(Exception):
725
+ def __init__(self):
726
+ pass
727
+
728
+ @environment()
729
+ class SampleEnvironment:
730
+ # constructor
731
+
732
+ def __init__(self):
733
+ pass
734
+
735
+ @create()
736
+ def create_exception_manager(self) -> ExceptionManager:
737
+ return ExceptionManager()
738
+
739
+ @injectable()
740
+ @exception_handler()
741
+ class TestExceptionHandler:
742
+ def __init__(self):
743
+ pass
744
+
745
+ @handle()
746
+ def handle_derived_exception(self, exception: DerivedException):
747
+ ExceptionManager.proceed()
748
+
749
+ @handle()
750
+ def handle_exception(self, exception: Exception):
751
+ pass
752
+
753
+ @handle()
754
+ def handle_base_exception(self, exception: BaseException):
755
+ pass
756
+
757
+
758
+ @advice
759
+ class ExceptionAdvice:
760
+ def __init__(self, exceptionManager: ExceptionManager):
761
+ self.exceptionManager = exceptionManager
762
+
763
+ @error(methods().of_type(Service))
764
+ def handle_error(self, invocation: Invocation):
765
+ self.exceptionManager.handle(invocation.exception)
766
+
767
+ environment = Environment(SampleEnvironment)
768
+
769
+ environment.get(ExceptionManager).handle(DerivedException())
770
+ ```
771
+
772
+ The exception maanger will first call the most appropriate method.
773
+ Any `ExceptionManager.proceed()` will in turn call the next most applicable method ( if available).
774
+
775
+ Together with a simple around advice we can now add exception handling to any method:
776
+
777
+ **Example**:
778
+ ```python
779
+ @injectable()
780
+ class Service:
781
+ def __init__(self):
782
+ pass
783
+
784
+ def throw(self):
785
+ raise DerivedException()
786
+
787
+ @advice
788
+ class ExceptionAdvice:
789
+ def __init__(self, exceptionManager: ExceptionManager):
790
+ self.exceptionManager = exceptionManager
791
+
792
+ @error(methods().of_type(Service))
793
+ def handle_error(self, invocation: Invocation):
794
+ self.exceptionManager.handle(invocation.exception)
795
+ ```
605
796
 
606
797
  # Version History
607
798
 
@@ -618,6 +809,17 @@ def transactional(scope):
618
809
 
619
810
  - added `YamlConfigurationSource`
620
811
 
812
+ **1.3.0**
813
+
814
+ - added `@conditional`
815
+ - added support for `async` advices
816
+
817
+
818
+ **1.4.0**
819
+
820
+ - bugfixes
821
+ - added `@ExceptionManager`
822
+
621
823
 
622
824
 
623
825
 
@@ -0,0 +1,25 @@
1
+ aspyx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ aspyx/di/__init__.py,sha256=OfETLGaquTbFHhhDRzzGtnSu-KkMj68aDdEaVU19KoI,1107
3
+ aspyx/di/di.py,sha256=qoOChsiUBla52UaclRlX6o5ZxRylaFF4IneanvwISos,42095
4
+ aspyx/di/aop/__init__.py,sha256=nOABex49zSyMZ2w1ezwX3Q3yrOcQRSDjDtSj0DwKVbQ,233
5
+ aspyx/di/aop/aop.py,sha256=36p3jCnNtrDL11jTi7NNpW1eGO_BqKL0s5rErsPdxps,17520
6
+ aspyx/di/configuration/__init__.py,sha256=mweJ3tZX1YJfY1d4ra-i0TWEcF3EwXBpGbHrKg1Kc6E,380
7
+ aspyx/di/configuration/configuration.py,sha256=KfPjrlUhhmEOUxdJiXePt5RGxKc8JczkWqlEBjpWQTg,4362
8
+ aspyx/di/configuration/env_configuration_source.py,sha256=FXPvREzq2ZER6_GG5xdpx154TQQDxZVf7LW7cvaylAk,1446
9
+ aspyx/di/configuration/yaml_configuration_source.py,sha256=NDl3SeoLMNVlzHgfP-Ysvhco1tRew_zFnBL5gGy2WRk,550
10
+ aspyx/di/threading/__init__.py,sha256=qrWdaq7MewQ2UmZy4J0Dn6BhY-ahfiG3xsv-EHqoqSE,191
11
+ aspyx/di/threading/synchronized.py,sha256=BQ9PjMQUJsF5r-qWaDgvqg3AvFm_R9QZdKB49EkoelQ,1263
12
+ aspyx/exception/__init__.py,sha256=2Jo0a_fZK8_U9SpPZ0j4aeAXJZ28uw6g-20TH_85JqY,200
13
+ aspyx/exception/exception_manager.py,sha256=8H5fbbcpzLxiK7OI-EZaXyX5Db4uZt9-VrAx5LMiSm8,4692
14
+ aspyx/reflection/__init__.py,sha256=r2sNJrfHDpuqaIYu4fTYsoo046gpgn4VTd7bsS3mQJY,282
15
+ aspyx/reflection/proxy.py,sha256=zJ6Psd6zWfFABdrKOf4cULt3gibyqCRdcR6z8WKIkzE,1982
16
+ aspyx/reflection/reflection.py,sha256=bzH5KVJ5X5ycQu5SG7BFZtioWN7sa1w1Y-xR_Y-oN6w,7113
17
+ aspyx/threading/__init__.py,sha256=_j_AQ4t1ecRaKIb9KCTT_EV0b7hivNML-2wV2XF7G6Y,125
18
+ aspyx/threading/thread_local.py,sha256=nOSS2DM1rIHmzdU9_fjxaUF3oXCaRTBHwe76IdwMqC8,1158
19
+ aspyx/util/__init__.py,sha256=8H2yKkXu3nkRGeTerb8ialzKGfvzUx44XUWFUYcYuQM,125
20
+ aspyx/util/stringbuilder.py,sha256=L3MkHAo4CJrBXuWmaRQASIa9EAs8O_ea7EjZoLsvp08,811
21
+ aspyx-1.4.0.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
22
+ aspyx-1.4.0.dist-info/METADATA,sha256=S9Qcqc9v6VHJSU76dGT_iEo1Z4ghvpp8nE7uxoc2NkI,25213
23
+ aspyx-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
+ aspyx-1.4.0.dist-info/top_level.txt,sha256=A_ZwhBY_ybIgjZlztd44eaOrWqkJAndiqjGlbJ3tR_I,6
25
+ aspyx-1.4.0.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- aspyx/di/__init__.py,sha256=xdb2lsKh00uGMFCWYavhUEMGH15OSeAhUC-iSosqHqU,935
2
- aspyx/di/di.py,sha256=evfhTznmPNRDjamthSPisMpDhGZJdNmEREUhdJ9nsTE,35571
3
- aspyx/di/aop/__init__.py,sha256=nOABex49zSyMZ2w1ezwX3Q3yrOcQRSDjDtSj0DwKVbQ,233
4
- aspyx/di/aop/aop.py,sha256=3GKN6sGlsZbJ7_WSxQvHZNFYouAfU4Eq6H5cBBB_e_4,14455
5
- aspyx/di/configuration/__init__.py,sha256=mweJ3tZX1YJfY1d4ra-i0TWEcF3EwXBpGbHrKg1Kc6E,380
6
- aspyx/di/configuration/configuration.py,sha256=KfPjrlUhhmEOUxdJiXePt5RGxKc8JczkWqlEBjpWQTg,4362
7
- aspyx/di/configuration/env_configuration_source.py,sha256=Xh8g3AuQdgk89nG6GKA4iKILXaqHecD0KqMW2w91hXs,1445
8
- aspyx/di/configuration/yaml_configuration_source.py,sha256=LM-5J6IRxBBbBAjDeGIFsmiT-61WuGv1sU_zXWNzhkI,549
9
- aspyx/di/util/__init__.py,sha256=8H2yKkXu3nkRGeTerb8ialzKGfvzUx44XUWFUYcYuQM,125
10
- aspyx/di/util/stringbuilder.py,sha256=JywkLxZfaQUUWjSB5wvqA6a6Cfs3sW1jbaZ1z4U0-CQ,540
11
- aspyx/reflection/__init__.py,sha256=r2sNJrfHDpuqaIYu4fTYsoo046gpgn4VTd7bsS3mQJY,282
12
- aspyx/reflection/proxy.py,sha256=zJ6Psd6zWfFABdrKOf4cULt3gibyqCRdcR6z8WKIkzE,1982
13
- aspyx/reflection/reflection.py,sha256=2oYJKYysH3ULmTzbXdjGBCB1iaLbWh3IPKSnorVLshU,5719
14
- aspyx-1.2.0.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
15
- aspyx-1.2.0.dist-info/METADATA,sha256=wXSu9Clxg4ZZCoiRAhiZzAR1Wcqtjv4NZie4XilJNSs,19009
16
- aspyx-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
- aspyx-1.2.0.dist-info/top_level.txt,sha256=A_ZwhBY_ybIgjZlztd44eaOrWqkJAndiqjGlbJ3tR_I,6
18
- aspyx-1.2.0.dist-info/RECORD,,
File without changes
File without changes