aspyx 1.0.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.

@@ -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,6 +9,7 @@ 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:
9
14
  __slots__ = [
10
15
  "decorator",
@@ -39,25 +44,25 @@ class TypeDescriptor:
39
44
  self.clazz = cls
40
45
  self.method = method
41
46
  self.decorators: list[DecoratorDescriptor] = Decorators.get(method)
42
- self.paramTypes : list[Type] = []
47
+ self.param_types : list[Type] = []
43
48
 
44
49
  type_hints = get_type_hints(method)
45
50
  sig = signature(method)
46
51
 
47
- for name, param in sig.parameters.items():
52
+ for name, _ in sig.parameters.items():
48
53
  if name != 'self':
49
- self.paramTypes.append(type_hints.get(name, object))
54
+ self.param_types.append(type_hints.get(name, object))
50
55
 
51
- self.returnType = type_hints.get('return', None)
56
+ self.return_type = type_hints.get('return', None)
52
57
 
53
- def get_decorator(self, decorator):
58
+ def get_decorator(self, decorator) -> Optional[DecoratorDescriptor]:
54
59
  for dec in self.decorators:
55
60
  if dec.decorator is decorator:
56
61
  return dec
57
62
 
58
63
  return None
59
64
 
60
- def has_decorator(self, decorator):
65
+ def has_decorator(self, decorator) -> bool:
61
66
  for dec in self.decorators:
62
67
  if dec.decorator is decorator:
63
68
  return True
@@ -67,8 +72,6 @@ class TypeDescriptor:
67
72
  def __str__(self):
68
73
  return f"Method({self.method.__name__})"
69
74
 
70
- # class methods
71
-
72
75
  # class properties
73
76
 
74
77
  _cache = WeakKeyDictionary()
@@ -81,6 +84,7 @@ class TypeDescriptor:
81
84
  if descriptor is None:
82
85
  descriptor = TypeDescriptor(clazz)
83
86
  cls._cache[clazz] = descriptor
87
+
84
88
  return descriptor
85
89
 
86
90
  # constructor
@@ -88,27 +92,25 @@ class TypeDescriptor:
88
92
  def __init__(self, cls):
89
93
  self.cls = cls
90
94
  self.decorators = Decorators.get(cls)
91
- self.methods: Dict[str, TypeDescriptor.MethodDescriptor] = dict()
92
- self.localMethods: Dict[str, TypeDescriptor.MethodDescriptor] = dict()
95
+ self.methods: Dict[str, TypeDescriptor.MethodDescriptor] = {}
96
+ self.local_methods: Dict[str, TypeDescriptor.MethodDescriptor] = {}
93
97
 
94
98
  # check superclasses
95
99
 
96
- 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]
97
101
 
98
- for superType in self.superTypes:
99
- self.methods = self.methods | superType.methods
102
+ for super_type in self.super_types:
103
+ self.methods = self.methods | super_type.methods
100
104
 
101
105
  # methods
102
106
 
103
107
  for name, member in self._get_local_members(cls):
104
108
  method = TypeDescriptor.MethodDescriptor(cls, member)
105
- self.localMethods[name] = method
109
+ self.local_methods[name] = method
106
110
  self.methods[name] = method
107
111
 
108
112
  # internal
109
113
 
110
- #isinstance(attr, classmethod)
111
-
112
114
  def _get_local_members(self, cls):
113
115
  return [
114
116
  (name, value)
@@ -127,13 +129,19 @@ class TypeDescriptor:
127
129
 
128
130
  def has_decorator(self, decorator) -> bool:
129
131
  for dec in self.decorators:
130
- if dec.decorator.__name__ == decorator.__name__:
132
+ if dec.decorator is decorator:
131
133
  return True
132
134
 
133
135
  return False
134
136
 
135
- def get_local_method(self, name) -> Optional[MethodDescriptor]:
136
- 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())
137
142
 
138
- def get_method(self, name) -> Optional[MethodDescriptor]:
139
- 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: 1.0.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,7 +25,7 @@ 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
@@ -34,11 +34,13 @@ Dynamic: license-file
34
34
 
35
35
  ![Pylint](https://github.com/coolsamson7/aspyx/actions/workflows/pylint.yml/badge.svg)
36
36
  ![Build Status](https://github.com/coolsamson7/aspyx/actions/workflows/ci.yml/badge.svg)
37
-
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)
@@ -53,6 +55,7 @@ Dynamic: license-file
53
55
  - [Custom scopes](#custom-scopes)
54
56
  - [AOP](#aop)
55
57
  - [Configuration](#configuration)
58
+ - [Reflection](#reflection)
56
59
 
57
60
  # Introduction
58
61
 
@@ -71,12 +74,13 @@ The following features are supported
71
74
  - container instances that relate to environment classes and manage the lifecylce of related objects
72
75
  - hierarchical environments
73
76
 
77
+ The library is thread-safe!
78
+
74
79
  Let's look at a simple example
75
80
 
76
81
  ```python
77
82
  from aspyx.di import injectable, on_init, on_destroy, environment, Environment
78
83
 
79
-
80
84
  @injectable()
81
85
  class Foo:
82
86
  def __init__(self):
@@ -85,13 +89,12 @@ class Foo:
85
89
  def hello(msg: str):
86
90
  print(f"hello {msg}")
87
91
 
88
-
89
92
  @injectable() # eager and singleton by default
90
93
  class Bar:
91
94
  def __init__(self, foo: Foo): # will inject the Foo dependency
92
95
  self.foo = foo
93
96
 
94
- @on_init() # a lifecycle callback called after the constructor
97
+ @on_init() # a lifecycle callback called after the constructor and all possible injections
95
98
  def init(self):
96
99
  ...
97
100
 
@@ -101,41 +104,40 @@ class Bar:
101
104
 
102
105
  @environment()
103
106
  class SampleEnvironment:
104
- # constructor
105
-
106
107
  def __init__(self):
107
108
  pass
108
109
 
109
-
110
110
  # go, forrest
111
111
 
112
- environment = SampleEnvironment(Configuration)
112
+ environment = Environment(SampleEnvironment)
113
113
 
114
114
  bar = env.get(Bar)
115
- bar.foo.hello("Andi")
115
+
116
+ bar.foo.hello("world")
116
117
  ```
117
118
 
118
- 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 :-)
119
120
 
120
121
  Let's add some aspects...
121
122
 
122
123
  ```python
124
+
123
125
  @advice
124
126
  class SampleAdvice:
125
- def __init__(self):
127
+ def __init__(self): # could inject additional stuff
126
128
  pass
127
129
 
128
130
  @before(methods().named("hello").of_type(Foo))
129
- def callBefore(self, invocation: Invocation):
131
+ def call_before(self, invocation: Invocation):
130
132
  print("before Foo.hello(...)")
131
133
 
132
134
  @error(methods().named("hello").of_type(Foo))
133
- def callError(self, invocation: Invocation):
135
+ def call_error(self, invocation: Invocation):
134
136
  print("error Foo.hello(...)")
135
137
  print(invocation.exception)
136
138
 
137
139
  @around(methods().named("hello"))
138
- def callAround(self, invocation: Invocation):
140
+ def call_around(self, invocation: Invocation):
139
141
  print("around Foo.hello()")
140
142
 
141
143
  return invocation.proceed()
@@ -150,6 +152,14 @@ The invocation parameter stores the complete context of the current execution, w
150
152
 
151
153
  Let's look at the details
152
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
+
153
163
  # Registration
154
164
 
155
165
  Different mechanisms are available that make classes eligible for injection
@@ -171,12 +181,14 @@ All referenced types will be injected by the environemnt.
171
181
 
172
182
  Only eligible types are allowed, of course!
173
183
 
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`
174
190
 
175
- The decorator accepts the keyword arguments
176
- - `eager=True` if `True`, the container will create the instances automatically while booting the environment
177
- - `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
178
-
179
- 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.
180
192
 
181
193
  ## Class Factory
182
194
 
@@ -386,17 +398,17 @@ class SampleAdvice:
386
398
  pass
387
399
 
388
400
  @before(methods().named("hello").of_type(Foo))
389
- def callBefore(self, invocation: Invocation):
401
+ def call_before(self, invocation: Invocation):
390
402
  # arguments: invocation.args
391
403
  print("before Foo.hello(...)")
392
404
 
393
405
  @error(methods().named("hello").of_type(Foo))
394
- def callError(self, invocation: Invocation):
406
+ def call_error(self, invocation: Invocation):
395
407
  print("error Foo.hello(...)")
396
408
  print(invocation.exception)
397
409
 
398
410
  @around(methods().named("hello"))
399
- def callAround(self, invocation: Invocation):
411
+ def call_around(self, invocation: Invocation):
400
412
  print("around Foo.hello()")
401
413
 
402
414
  return invocation.proceed() # will leave a result in invocation.result or invocation.exception in case of an exception
@@ -417,7 +429,7 @@ All methods are expected to hava single `Invocation` parameter, that stores, the
417
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.
418
430
  If the `proceed` is called with parameters, they will replace the original parameters!
419
431
 
420
- 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.
421
433
  A fluent interface is used describe the mapping.
422
434
  The parameters restrict either methods or classes and are constructed by a call to either `methods()` or `classes()`.
423
435
 
@@ -431,7 +443,20 @@ Both add the fluent methods:
431
443
  - `decorated_with(type: Type)`
432
444
  defines decorators on methods or classes
433
445
 
434
- 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
+ ```
435
460
 
436
461
  # Configuration
437
462
 
@@ -452,10 +477,11 @@ This concept relies on a central object `ConfigurationManager` that stores the o
452
477
 
453
478
  ```python
454
479
  class ConfigurationSource(ABC):
455
- def __init__(self, manager: ConfigurationManager):
456
- manager._register(self)
480
+ def __init__(self):
457
481
  pass
458
482
 
483
+ ...
484
+
459
485
  @abstractmethod
460
486
  def load(self) -> dict:
461
487
  pass
@@ -467,14 +493,12 @@ As a default environment variables are already supported.
467
493
 
468
494
  Other sources can be added dynamically by just registering them.
469
495
 
496
+ **Example**:
470
497
  ```python
471
498
  @injectable()
472
499
  class SampleConfigurationSource(ConfigurationSource):
473
- # constructor
474
-
475
- def __init__(self, manager: ConfigurationManager):
476
- super().__init__(manager)
477
-
500
+ def __init__(self):
501
+ super().__init__()
478
502
 
479
503
  def load(self) -> dict:
480
504
  return {
@@ -487,6 +511,43 @@ class SampleConfigurationSource(ConfigurationSource):
487
511
  }
488
512
  ```
489
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
+
490
551
 
491
552
 
492
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=IdMNkPmMaVhRNr8heE8msGO3KYEc69pf2pjcJ8_rp4E,31169
3
- aspyx/di/aop/__init__.py,sha256=2mv6o38HHoBzF_nx8rsrvuOlUlNgYITmNRcQJVT2Bk8,213
4
- aspyx/di/aop/aop.py,sha256=stgSLSBpOd9XV_qo7KEazinq8Ul_csWbCx1w30dioWw,14647
5
- aspyx/di/configuration/__init__.py,sha256=YgUk1bHVYq1EJ9W4D_CJ9ZM-3kje-vzPNMxBdUm0Vlg,211
6
- aspyx/di/configuration/configuration.py,sha256=6EG2kf2v5pk9fTeCQUD5j6Jo5v00CFd5Nz7qXyYmxKw,5725
7
- aspyx/reflection/__init__.py,sha256=jZAjw5NDrtXENmlBsQ0u9MobcNkfEuT0Oey8IWNYh34,204
8
- aspyx/reflection/proxy.py,sha256=Re643BJAgbQjmDXO5JAvj4mu3RTpe_HF3TcFQ1euArg,1910
9
- aspyx/reflection/reflection.py,sha256=LIUu-SxA_GJ1JurbQKTrvMUrE05OOcXxXOu65zRWubw,4004
10
- aspyx-1.0.0.dist-info/licenses/LICENSE,sha256=n4jfx_MNj7cBtPhhI7MCoB_K35cj1icP9yJ4Rh4vlvY,1070
11
- aspyx-1.0.0.dist-info/METADATA,sha256=3AuH9LBUHpPfxu9BcmawTaaJIHTBWwHuFpP_gBasyh0,15227
12
- aspyx-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- aspyx-1.0.0.dist-info/top_level.txt,sha256=A_ZwhBY_ybIgjZlztd44eaOrWqkJAndiqjGlbJ3tR_I,6
14
- aspyx-1.0.0.dist-info/RECORD,,
File without changes