python-neva 3.1.0__py3-none-any.whl → 3.2.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.
- neva/arch/application.py +11 -6
- neva/arch/markers.py +4 -0
- neva/arch/scopes.py +5 -0
- neva/arch/service_provider.py +83 -2
- neva/events/dispatcher.py +19 -5
- neva/events/provider.py +2 -0
- {python_neva-3.1.0.dist-info → python_neva-3.2.0.dist-info}/METADATA +2 -2
- {python_neva-3.1.0.dist-info → python_neva-3.2.0.dist-info}/RECORD +9 -7
- {python_neva-3.1.0.dist-info → python_neva-3.2.0.dist-info}/WHEEL +1 -1
neva/arch/application.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
from collections import OrderedDict
|
|
5
|
-
from collections.abc import
|
|
5
|
+
from collections.abc import AsyncGenerator, Sequence
|
|
6
6
|
from contextlib import AsyncExitStack, asynccontextmanager
|
|
7
7
|
from contextvars import ContextVar
|
|
8
8
|
from pathlib import Path
|
|
@@ -13,6 +13,7 @@ from dishka.provider import BaseProvider
|
|
|
13
13
|
from typing_extensions import deprecated
|
|
14
14
|
|
|
15
15
|
from neva.arch.facade import Facade
|
|
16
|
+
from neva.arch.scopes import BaseScope, Scope
|
|
16
17
|
from neva.arch.service_provider import Bootable, ServiceProvider
|
|
17
18
|
from neva.config.loader import ConfigLoader
|
|
18
19
|
from neva.support import Err, Ok, Result
|
|
@@ -41,7 +42,7 @@ class Application:
|
|
|
41
42
|
|
|
42
43
|
self.config: ConfigRepository = ConfigRepository()
|
|
43
44
|
self.providers: OrderedDict[type, ServiceProvider] = OrderedDict()
|
|
44
|
-
self.root_provider: dishka.Provider = dishka.Provider(scope=
|
|
45
|
+
self.root_provider: dishka.Provider = dishka.Provider(scope=Scope.APP)
|
|
45
46
|
|
|
46
47
|
configuration_path = (
|
|
47
48
|
config_path
|
|
@@ -112,7 +113,7 @@ class Application:
|
|
|
112
113
|
source: type | Callable[..., Any],
|
|
113
114
|
*,
|
|
114
115
|
interface: type | None = None,
|
|
115
|
-
scope:
|
|
116
|
+
scope: BaseScope | None = None,
|
|
116
117
|
) -> None:
|
|
117
118
|
"""Binds a source to the container."""
|
|
118
119
|
_ = self.root_provider.provide(
|
|
@@ -146,14 +147,18 @@ class Application:
|
|
|
146
147
|
return Err(f"Failed to resolve service '{interface.__name__}': {e}")
|
|
147
148
|
|
|
148
149
|
@asynccontextmanager
|
|
149
|
-
async def scope(
|
|
150
|
+
async def scope(
|
|
151
|
+
self,
|
|
152
|
+
scope: BaseScope | None = None,
|
|
153
|
+
context: dict[type, Any] | None = None,
|
|
154
|
+
) -> AsyncGenerator[Self]:
|
|
150
155
|
"""Enter a new scope.
|
|
151
156
|
|
|
152
157
|
Yields:
|
|
153
158
|
The application instance with the new scope.
|
|
154
159
|
"""
|
|
155
160
|
parent = _current_container.get(self.container)
|
|
156
|
-
async with parent(scope=scope) as container:
|
|
161
|
+
async with parent(scope=scope, context=context) as container:
|
|
157
162
|
token = _current_container.set(container)
|
|
158
163
|
try:
|
|
159
164
|
yield self
|
|
@@ -161,7 +166,7 @@ class Application:
|
|
|
161
166
|
_current_container.reset(token)
|
|
162
167
|
|
|
163
168
|
@asynccontextmanager
|
|
164
|
-
async def lifespan(self) ->
|
|
169
|
+
async def lifespan(self) -> AsyncGenerator[None]:
|
|
165
170
|
"""Wire the facades and providers."""
|
|
166
171
|
Facade.set_facade_application(self)
|
|
167
172
|
|
neva/arch/markers.py
ADDED
neva/arch/scopes.py
ADDED
neva/arch/service_provider.py
CHANGED
|
@@ -21,6 +21,8 @@ from typing import (
|
|
|
21
21
|
|
|
22
22
|
import dishka
|
|
23
23
|
|
|
24
|
+
from neva.arch.markers import BaseMarker, Marker
|
|
25
|
+
from neva.arch.scopes import BaseScope, Scope
|
|
24
26
|
from neva.support import Result
|
|
25
27
|
|
|
26
28
|
|
|
@@ -69,6 +71,7 @@ class ServiceProvider(abc.ABC):
|
|
|
69
71
|
|
|
70
72
|
app: "Application"
|
|
71
73
|
listen: ClassVar[dict[type[Event], list[type[EventListener[Any]]]]] = {}
|
|
74
|
+
when: ClassVar[Marker | None] = None
|
|
72
75
|
|
|
73
76
|
def __init__(self, app: "Application") -> None:
|
|
74
77
|
"""Initialize the service provider.
|
|
@@ -77,23 +80,101 @@ class ServiceProvider(abc.ABC):
|
|
|
77
80
|
app: The application instance.
|
|
78
81
|
|
|
79
82
|
"""
|
|
80
|
-
self.provider: dishka.Provider = dishka.Provider(
|
|
83
|
+
self.provider: dishka.Provider = dishka.Provider(
|
|
84
|
+
scope=dishka.Scope.APP,
|
|
85
|
+
when=self.when,
|
|
86
|
+
)
|
|
81
87
|
self.app = app
|
|
82
88
|
|
|
89
|
+
def activator(
|
|
90
|
+
self,
|
|
91
|
+
activation_fn: Callable[..., bool],
|
|
92
|
+
*markers: Marker | type[Marker],
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Registers an activator function for the given markers.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
activation_fn: The activator function.
|
|
98
|
+
markers: Markers triggered by this function.
|
|
99
|
+
"""
|
|
100
|
+
_ = self.provider.activate(activation_fn, *markers)
|
|
101
|
+
|
|
83
102
|
def bind(
|
|
84
103
|
self,
|
|
85
104
|
source: type | Callable[..., Any],
|
|
86
105
|
*,
|
|
87
106
|
interface: type | None = None,
|
|
88
|
-
scope:
|
|
107
|
+
scope: BaseScope | None = None,
|
|
108
|
+
when: BaseMarker | None = None,
|
|
109
|
+
cache: bool = True,
|
|
89
110
|
) -> None:
|
|
90
111
|
"""Binds a source to the container."""
|
|
91
112
|
_ = self.provider.provide(
|
|
92
113
|
source=source,
|
|
93
114
|
scope=scope,
|
|
94
115
|
provides=interface,
|
|
116
|
+
cache=cache,
|
|
117
|
+
when=when,
|
|
95
118
|
)
|
|
96
119
|
|
|
120
|
+
def scoped(
|
|
121
|
+
self,
|
|
122
|
+
source: type | Callable[..., Any],
|
|
123
|
+
*,
|
|
124
|
+
interface: type | None = None,
|
|
125
|
+
when: BaseMarker | None = None,
|
|
126
|
+
) -> Self:
|
|
127
|
+
"""Binds the source to the container.
|
|
128
|
+
|
|
129
|
+
Scope is REQUEST by default but a custom scope be provided.
|
|
130
|
+
Dependency declared with this are cached no matter what.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
the provider itself for chaining purposes
|
|
134
|
+
"""
|
|
135
|
+
self.bind(source, interface=interface, scope=Scope.REQUEST, when=when)
|
|
136
|
+
return self
|
|
137
|
+
|
|
138
|
+
def transient(
|
|
139
|
+
self,
|
|
140
|
+
source: type | Callable[..., Any],
|
|
141
|
+
*,
|
|
142
|
+
interface: type | None = None,
|
|
143
|
+
when: BaseMarker | None = None,
|
|
144
|
+
) -> Self:
|
|
145
|
+
"""Binds the source to the container as a transient dependency.
|
|
146
|
+
|
|
147
|
+
Dependencies declared this way have global scope and are never
|
|
148
|
+
cached.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
the provider itself for chaining purposes
|
|
152
|
+
"""
|
|
153
|
+
self.bind(source, interface=interface, when=when, cache=False)
|
|
154
|
+
return self
|
|
155
|
+
|
|
156
|
+
def extend(
|
|
157
|
+
self,
|
|
158
|
+
source: Callable[..., Any],
|
|
159
|
+
*,
|
|
160
|
+
interface: type | None = None,
|
|
161
|
+
scope: dishka.BaseScope | None = None,
|
|
162
|
+
when: BaseMarker | None = None,
|
|
163
|
+
) -> Self:
|
|
164
|
+
"""Extends a dependency declared by another provider.
|
|
165
|
+
|
|
166
|
+
This allows to 'extend' a dependency, maybe even override it entirely.
|
|
167
|
+
Particularly useful if you want to run some code on a dependency declared
|
|
168
|
+
by another provider / package.
|
|
169
|
+
|
|
170
|
+
You may also provide a scope for this.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
the provider itself for chaining purposes
|
|
174
|
+
"""
|
|
175
|
+
_ = self.provider.decorate(source, provides=interface, scope=scope, when=when)
|
|
176
|
+
return self
|
|
177
|
+
|
|
97
178
|
@abc.abstractmethod
|
|
98
179
|
def register(self) -> Result[Self, str]:
|
|
99
180
|
"""Register services into the application container.
|
neva/events/dispatcher.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""Base implementation of the event dispatcher."""
|
|
2
2
|
|
|
3
|
-
from typing import final
|
|
4
|
-
|
|
5
3
|
from neva.arch.application import Application
|
|
6
4
|
from neva.database.manager import DatabaseManager
|
|
7
5
|
from neva.database.transaction import TransactionCallback
|
|
@@ -11,15 +9,30 @@ from neva.events.listener import EventListener
|
|
|
11
9
|
from neva.support import Err, Nothing, Result, Some
|
|
12
10
|
|
|
13
11
|
|
|
14
|
-
@final
|
|
15
12
|
class EventDispatcher:
|
|
16
13
|
"""Event dispatcher implementation."""
|
|
17
14
|
|
|
18
|
-
def __init__(
|
|
19
|
-
self
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
app: Application,
|
|
18
|
+
db: DatabaseManager,
|
|
19
|
+
registry: EventRegistry,
|
|
20
|
+
) -> None:
|
|
21
|
+
self._registry = registry
|
|
20
22
|
self._app = app
|
|
21
23
|
self._db = db
|
|
22
24
|
|
|
25
|
+
async def _before_dispatch(self, event: Event) -> None:
|
|
26
|
+
"""Extension hook called before listeners are invoked.
|
|
27
|
+
|
|
28
|
+
Override in a subclass to add cross-cutting behaviour such as
|
|
29
|
+
persisting the event to an event store. The default implementation
|
|
30
|
+
is a no-op.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
event: The event about to be dispatched.
|
|
34
|
+
"""
|
|
35
|
+
|
|
23
36
|
async def dispatch(self, event: Event) -> list[Result[None, str]]:
|
|
24
37
|
"""Dispatch an event to all registered listeners.
|
|
25
38
|
|
|
@@ -32,6 +45,7 @@ class EventDispatcher:
|
|
|
32
45
|
Returns:
|
|
33
46
|
A list of results, one per listener invocation.
|
|
34
47
|
"""
|
|
48
|
+
await self._before_dispatch(event)
|
|
35
49
|
results: list[Result[None, str]] = []
|
|
36
50
|
listeners = self._registry.get_listeners(event)
|
|
37
51
|
|
neva/events/provider.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from typing import Self, override
|
|
4
4
|
|
|
5
5
|
from neva.arch.service_provider import ServiceProvider
|
|
6
|
+
from neva.events.event_registry import EventRegistry
|
|
6
7
|
from neva.support import Ok, Result
|
|
7
8
|
|
|
8
9
|
|
|
@@ -23,5 +24,6 @@ class EventServiceProvider(ServiceProvider):
|
|
|
23
24
|
"""
|
|
24
25
|
from neva.events.dispatcher import EventDispatcher
|
|
25
26
|
|
|
27
|
+
self.bind(EventRegistry)
|
|
26
28
|
self.bind(EventDispatcher)
|
|
27
29
|
return Ok(self)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-neva
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Requires-Dist: aiosqlite>=0.20.0
|
|
@@ -16,7 +16,7 @@ Requires-Dist: sqlalchemy[asyncio]>=2.0.0
|
|
|
16
16
|
Requires-Dist: structlog>=25.5.0
|
|
17
17
|
Requires-Dist: typer>=0.21.1
|
|
18
18
|
Provides-Extra: fastapi
|
|
19
|
-
Requires-Dist: neva-fastapi>=
|
|
19
|
+
Requires-Dist: neva-fastapi>=1.0.0; extra == 'fastapi'
|
|
20
20
|
Provides-Extra: testing
|
|
21
21
|
Requires-Dist: pytest-asyncio>=0.25.3; extra == 'testing'
|
|
22
22
|
Requires-Dist: pytest>=9.0.2; extra == 'testing'
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
neva/arch/__init__.py,sha256=1ukTl5Ynz5PrGa5e-tFSMaNgVkfhQRrYQGPUqUeXMMg,429
|
|
2
|
-
neva/arch/application.py,sha256=
|
|
2
|
+
neva/arch/application.py,sha256=DH1CXVEsiOCDQIigOXfXI2jYGVZr-TwMgilTQu6qPq0,7258
|
|
3
3
|
neva/arch/config.py,sha256=R8_wMKTwX8vTFqfQhgglDQ9PfB6Yo6mx6OnKg8bnIWw,1042
|
|
4
4
|
neva/arch/facade.py,sha256=1kYVhyYto8zSyOZV6sAYJ3xWzQDJRzoVV-e_EarTp0Q,7927
|
|
5
|
+
neva/arch/markers.py,sha256=STdhmW88qV8uogIa9kSD9gs-uMRaglZI8HOw3PrhX1Q,103
|
|
5
6
|
neva/arch/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
neva/arch/
|
|
7
|
+
neva/arch/scopes.py,sha256=Gu0kJk39Yv8LCCzxjdu0lIE0FXtnsG36LGKDuNin2CE,87
|
|
8
|
+
neva/arch/service_provider.py,sha256=zEFwix2cL6l1Frv9w1OMtnL_zjkl_BVpGBhRaNK6bb0,5636
|
|
7
9
|
neva/arch/integrations/__init__.py,sha256=cXuqC4JfZdGYLlh6POgYUVXUT2NQ84276eC-RYTyFxk,75
|
|
8
10
|
neva/arch/integrations/faststream.py,sha256=AOwfpfdL3O5HLAx80SwUGQb_hKEeAkFOUyIXtjODaHM,1696
|
|
9
11
|
neva/config/__init__.py,sha256=Ygfnh-MRRKYDdf54_LeMSHwmck5VpNHTdnnMtGEFLtU,121
|
|
@@ -19,11 +21,11 @@ neva/database/provider.py,sha256=l2be4Ri53RtQq1-qzohy3cqzJQpTaNzqXGVhXPKUCXc,167
|
|
|
19
21
|
neva/database/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
22
|
neva/database/transaction.py,sha256=_0Dsao_vhCJT5YRUmrHOLb0uxvfdE-5k3n0yMPLC1o8,5167
|
|
21
23
|
neva/events/__init__.py,sha256=xwIAXNcOASpuCY4DJpshZc1gtxQHimzwy-s7c-4QuiQ,746
|
|
22
|
-
neva/events/dispatcher.py,sha256=
|
|
24
|
+
neva/events/dispatcher.py,sha256=mSjOvP1-new7z2QRBY3cbUzZ1Y_mlHSXMRt4NGhB8e0,3295
|
|
23
25
|
neva/events/event.py,sha256=g3vAx-2qiR3QRM_or8G9mK5qx66A2LZiZ3n517R4lGQ,251
|
|
24
26
|
neva/events/event_registry.py,sha256=jucYOUjCERz-ICdPiDrxYw_ySvmPJOcRzYKCgka29qY,1491
|
|
25
27
|
neva/events/listener.py,sha256=-7RQOhVusKN8lopB3T2yfoQVlxX6GOSM5Pk1mPEMMro,1833
|
|
26
|
-
neva/events/provider.py,sha256=
|
|
28
|
+
neva/events/provider.py,sha256=HE7k915m8lQiqBC6kaGtb3ZfoxrgsuXXZmxkaaSnlmM,857
|
|
27
29
|
neva/events/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
30
|
neva/obs/__init__.py,sha256=dVzgljk9Hvo44LI34RcwbDyT42_z4nSnFVmL4GLbKD0,331
|
|
29
31
|
neva/obs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -83,6 +85,6 @@ neva/testing/fakes.py,sha256=5HuuwMeAKhFSZfn9T8raQIjdgHkjFeZsFXDud0T4v7Q,2674
|
|
|
83
85
|
neva/testing/fixtures.py,sha256=oiPa5ntUkW-SbySDvwEHXtti8NqSwI-R0CT1_ezTx_Y,1445
|
|
84
86
|
neva/testing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
85
87
|
neva/testing/test_case.py,sha256=zJBZJeKI-sv-EOaXNuSJ0V2Zp_6hdk64W4dOh0eP8RI,3290
|
|
86
|
-
python_neva-3.
|
|
87
|
-
python_neva-3.
|
|
88
|
-
python_neva-3.
|
|
88
|
+
python_neva-3.2.0.dist-info/METADATA,sha256=2wZeNjt-ooHN3AIU1LE-K636R4shKM0-cIDXUGg1X2g,771
|
|
89
|
+
python_neva-3.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
90
|
+
python_neva-3.2.0.dist-info/RECORD,,
|