python-neva 3.3.0__py3-none-any.whl → 3.5.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.
@@ -175,6 +175,10 @@ class ServiceProvider(abc.ABC):
175
175
  _ = self.provider.decorate(source, provides=interface, scope=scope, when=when)
176
176
  return self
177
177
 
178
+ def from_context(self, interface: type, scope: Scope | None = None) -> None:
179
+ """Declares an interface as provided by the current context."""
180
+ _ = self.provider.from_context(provides=interface, scope=scope)
181
+
178
182
  @abc.abstractmethod
179
183
  def register(self) -> Result[Self, str]:
180
184
  """Register services into the application container.
@@ -1,7 +1,20 @@
1
- from neva.events.contracts.dispatcher import EventDispatcher
1
+ from neva.events.contracts.dispatcher import (
2
+ AsyncBeforeDispatchHook,
3
+ BeforeDispatchHook,
4
+ EventDispatcher,
5
+ SyncBeforeDispatchHook,
6
+ )
2
7
  from neva.events.contracts.event import Event
3
8
  from neva.events.contracts.handler import EventHandler
4
9
  from neva.events.contracts.listener import EventListener
5
10
 
6
11
 
7
- __all__ = ["Event", "EventDispatcher", "EventHandler", "EventListener"]
12
+ __all__ = [
13
+ "AsyncBeforeDispatchHook",
14
+ "BeforeDispatchHook",
15
+ "Event",
16
+ "EventDispatcher",
17
+ "EventHandler",
18
+ "EventListener",
19
+ "SyncBeforeDispatchHook",
20
+ ]
@@ -1,10 +1,29 @@
1
- from typing import Protocol
1
+ from typing import Protocol, runtime_checkable
2
2
 
3
3
  from neva.events.contracts.event import Event
4
4
  from neva.events.contracts.listener import EventListener
5
5
  from neva.support import Result
6
6
 
7
7
 
8
+ @runtime_checkable
9
+ class AsyncBeforeDispatchHook(Protocol):
10
+ """Asynchronous hook to be called before listeners are invoked."""
11
+
12
+ async def __call__(self, context: Event) -> None:
13
+ """Execute the hook."""
14
+
15
+
16
+ @runtime_checkable
17
+ class SyncBeforeDispatchHook(Protocol):
18
+ """Synchronous hook to be called before listeners are invoked."""
19
+
20
+ def __call__(self, context: Event) -> None:
21
+ """Execute the hook."""
22
+
23
+
24
+ BeforeDispatchHook = AsyncBeforeDispatchHook | SyncBeforeDispatchHook
25
+
26
+
8
27
  class EventDispatcher(Protocol):
9
28
  """Event dispatcher protocol."""
10
29
 
@@ -15,6 +34,10 @@ class EventDispatcher(Protocol):
15
34
  """Dispatch an event to all registered listeners."""
16
35
  ...
17
36
 
37
+ async def before_dispatch(self, hook: BeforeDispatchHook) -> None:
38
+ """Register a hook to be called before listeners are invoked."""
39
+ ...
40
+
18
41
  def listen[T: Event](
19
42
  self,
20
43
  event_cls: type[T],
neva/events/dispatcher.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """Base implementation of the event dispatcher."""
2
2
 
3
- from typing import Callable, Protocol, override, runtime_checkable
3
+ import inspect
4
+ from typing import override
4
5
 
5
6
  from neva.arch.application import Application
6
7
  from neva.database.manager import DatabaseManager
@@ -10,19 +11,6 @@ from neva.events.event_registry import EventRegistry
10
11
  from neva.support import Err, Nothing, Result, Some
11
12
 
12
13
 
13
- @runtime_checkable
14
- class AsyncBeforeDispatchHook(Protocol):
15
- async def __call__(self, context: contracts.Event) -> None: ...
16
-
17
-
18
- @runtime_checkable
19
- class SyncBeforeDispatchHook(Protocol):
20
- def __call__(self, context: contracts.Event) -> None: ...
21
-
22
-
23
- BeforeDispatchHook = AsyncBeforeDispatchHook | SyncBeforeDispatchHook
24
-
25
-
26
14
  class EventDispatcher(contracts.EventDispatcher):
27
15
  """Event dispatcher implementation."""
28
16
 
@@ -35,7 +23,7 @@ class EventDispatcher(contracts.EventDispatcher):
35
23
  self._registry: EventRegistry = registry
36
24
  self._app: Application = app
37
25
  self._db: DatabaseManager = db
38
- self._before_dispatch_hooks: list[BeforeDispatchHook] = []
26
+ self._before_dispatch_hooks: list[contracts.BeforeDispatchHook] = []
39
27
 
40
28
  async def _apply_before_dispatch(self, event: contracts.Event) -> None:
41
29
  """Extension hook called before listeners are invoked.
@@ -48,13 +36,12 @@ class EventDispatcher(contracts.EventDispatcher):
48
36
  event: The event about to be dispatched.
49
37
  """
50
38
  for hook in self._before_dispatch_hooks:
51
- match hook:
52
- case AsyncBeforeDispatchHook():
53
- await hook(event)
54
- case SyncBeforeDispatchHook():
55
- hook(event)
39
+ result = hook(event)
40
+ if inspect.isawaitable(result):
41
+ await result
56
42
 
57
- async def before_dispatch(self, hook: BeforeDispatchHook) -> None:
43
+ @override
44
+ async def before_dispatch(self, hook: contracts.BeforeDispatchHook) -> None:
58
45
  """Register a hook to be called before listeners are invoked."""
59
46
  self._before_dispatch_hooks.append(hook)
60
47
 
@@ -1,14 +1,24 @@
1
1
  """Type stub for App facade."""
2
2
 
3
- from typing import override
3
+ from contextlib import AbstractAsyncContextManager
4
+ from typing import Any, Self, override
4
5
 
5
6
  from neva.arch import Facade
7
+ from neva.arch.scopes import BaseScope
6
8
  from neva.support import Result
7
9
 
8
10
  class App(Facade):
9
11
  @classmethod
10
12
  @override
11
13
  def get_facade_accessor(cls) -> type: ...
14
+ @classmethod
15
+ async def make_async[T](cls, interface: type[T]) -> Result[T, str]:
16
+ """Resolve and instanciate a type from the container.
17
+
18
+ Returns:
19
+ Result containing the resolved type instance or an error message.
20
+ """
21
+
12
22
  @classmethod
13
23
  def make[T](cls, interface: type[T]) -> Result[T, str]:
14
24
  """Resolve an interface from the container by its alias.
@@ -23,3 +33,15 @@ class App(Facade):
23
33
  Result containing the resolved service instance or an error message.
24
34
 
25
35
  """
36
+
37
+ @classmethod
38
+ async def scope(
39
+ cls,
40
+ scope: BaseScope | None = None,
41
+ context: dict[type, Any] | None = None,
42
+ ) -> AbstractAsyncContextManager[Self]:
43
+ """Enter a new scope.
44
+
45
+ Yields:
46
+ The application instance with the new scope.
47
+ """
@@ -5,7 +5,7 @@ from contextlib import contextmanager
5
5
  from typing import TYPE_CHECKING, override
6
6
 
7
7
  from neva.arch import Facade
8
- from neva.events import EventDispatcher
8
+ from neva.events import contracts
9
9
 
10
10
 
11
11
  if TYPE_CHECKING:
@@ -23,7 +23,7 @@ class Event(Facade):
23
23
  Returns:
24
24
  EventDispatcher class.
25
25
  """
26
- return EventDispatcher
26
+ return contracts.EventDispatcher
27
27
 
28
28
  @classmethod
29
29
  def fake(cls) -> "EventFake":
@@ -2,9 +2,8 @@
2
2
 
3
3
  from typing import override
4
4
 
5
- from neva import events
6
5
  from neva.arch import Facade
7
- from neva.events import EventListener
6
+ from neva.events import contracts
8
7
  from neva.support import Result
9
8
 
10
9
  class Event(Facade):
@@ -14,10 +13,15 @@ class Event(Facade):
14
13
  @override
15
14
  def get_facade_accessor(cls) -> type: ...
16
15
  @classmethod
17
- async def dispatch(cls, event: events.Event) -> list[Result[None, str]]: ...
16
+ async def before_dispatch(cls, hook: contracts.BeforeDispatchHook) -> None:
17
+ """Register a hook to be called before listeners are invoked."""
18
18
  @classmethod
19
- def listen[T: events.Event](
19
+ async def dispatch(cls, event: contracts.Event) -> list[Result[None, str]]:
20
+ """Dispatch an event to all registered listeners."""
21
+ @classmethod
22
+ def listen[T: contracts.Event](
20
23
  cls,
21
24
  event_cls: type[T],
22
- listener_cls: type[EventListener[T]],
23
- ) -> None: ...
25
+ listener_cls: type[contracts.EventListener[T]],
26
+ ) -> None:
27
+ """Register a listener for an event."""
neva/testing/fakes.py CHANGED
@@ -1,19 +1,20 @@
1
1
  """Testing fakes for Neva facades."""
2
2
 
3
3
  from collections.abc import Callable
4
- from typing import Any
4
+ from typing import override
5
5
 
6
- from neva.events.event import Event as BaseEvent
6
+ from neva.events import contracts
7
7
  from neva.support import Result
8
8
 
9
9
 
10
- class EventFake:
10
+ class EventFake(contracts.EventDispatcher):
11
11
  """Recording fake for the Event facade. No listeners are dispatched."""
12
12
 
13
13
  def __init__(self) -> None:
14
- self._dispatched: list[BaseEvent[Any]] = []
14
+ self._dispatched: list[contracts.Event] = []
15
15
 
16
- async def dispatch(self, event: BaseEvent[Any]) -> list[Result[None, str]]:
16
+ @override
17
+ async def dispatch(self, event: contracts.Event) -> list[Result[None, str]]:
17
18
  """Record the event without invoking any listeners.
18
19
 
19
20
  Returns:
@@ -22,10 +23,17 @@ class EventFake:
22
23
  self._dispatched.append(event)
23
24
  return []
24
25
 
25
- def listen[T: BaseEvent[Any]](self, event_cls: type[T], listener_cls: type) -> None:
26
+ @override
27
+ def listen[T: contracts.Event](
28
+ self, event_cls: type[T], listener_cls: type[contracts.EventListener[T]]
29
+ ) -> None:
26
30
  """No-op."""
27
31
 
28
- def dispatched[E: BaseEvent[Any]](self, event_cls: type[E]) -> list[E]:
32
+ @override
33
+ async def before_dispatch(self, hook: contracts.BeforeDispatchHook) -> None:
34
+ """Register a hook to be called before listeners are invoked."""
35
+
36
+ def dispatched[E: contracts.Event](self, event_cls: type[E]) -> list[E]:
29
37
  """Return all recorded events of the given type.
30
38
 
31
39
  Returns:
@@ -33,7 +41,7 @@ class EventFake:
33
41
  """
34
42
  return [e for e in self._dispatched if isinstance(e, event_cls)]
35
43
 
36
- def assert_dispatched[E: BaseEvent[Any]](
44
+ def assert_dispatched[E: contracts.Event](
37
45
  self,
38
46
  event_cls: type[E],
39
47
  *,
@@ -61,7 +69,7 @@ class EventFake:
61
69
  if match is not None and not any(match(e) for e in matching):
62
70
  raise AssertionError(f"No {event_cls.__name__} matched the predicate")
63
71
 
64
- def assert_not_dispatched[E: BaseEvent[Any]](self, event_cls: type[E]) -> None:
72
+ def assert_not_dispatched[E: contracts.Event](self, event_cls: type[E]) -> None:
65
73
  """Assert that no event of the given type was dispatched.
66
74
 
67
75
  Args:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-neva
3
- Version: 3.3.0
3
+ Version: 3.5.0
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: aiosqlite>=0.20.0
@@ -5,7 +5,7 @@ neva/arch/facade.py,sha256=1kYVhyYto8zSyOZV6sAYJ3xWzQDJRzoVV-e_EarTp0Q,7927
5
5
  neva/arch/markers.py,sha256=STdhmW88qV8uogIa9kSD9gs-uMRaglZI8HOw3PrhX1Q,103
6
6
  neva/arch/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  neva/arch/scopes.py,sha256=Gu0kJk39Yv8LCCzxjdu0lIE0FXtnsG36LGKDuNin2CE,87
8
- neva/arch/service_provider.py,sha256=zEFwix2cL6l1Frv9w1OMtnL_zjkl_BVpGBhRaNK6bb0,5636
8
+ neva/arch/service_provider.py,sha256=SqL3uQGBh7aDIc_Lcp8Ijr3fdOtB8wbsmMnjU4GuUy4,5862
9
9
  neva/arch/integrations/__init__.py,sha256=cXuqC4JfZdGYLlh6POgYUVXUT2NQ84276eC-RYTyFxk,75
10
10
  neva/arch/integrations/faststream.py,sha256=AOwfpfdL3O5HLAx80SwUGQb_hKEeAkFOUyIXtjODaHM,1696
11
11
  neva/config/__init__.py,sha256=Ygfnh-MRRKYDdf54_LeMSHwmck5VpNHTdnnMtGEFLtU,121
@@ -21,15 +21,15 @@ neva/database/provider.py,sha256=l2be4Ri53RtQq1-qzohy3cqzJQpTaNzqXGVhXPKUCXc,167
21
21
  neva/database/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  neva/database/transaction.py,sha256=_0Dsao_vhCJT5YRUmrHOLb0uxvfdE-5k3n0yMPLC1o8,5167
23
23
  neva/events/__init__.py,sha256=Gpi5q-pNlIi-q23U2ZOsNf8Y9rB6AQtsBbbBFU0tkFc,776
24
- neva/events/dispatcher.py,sha256=fFhF39sba1VOiwLa2eHzib5qWk4n_SOoWiYkmZL044o,4686
24
+ neva/events/dispatcher.py,sha256=KR5uDZbnNwrMCI6s9S_SoNrNHx2z2lugoHk1-g3fzXA,4288
25
25
  neva/events/event.py,sha256=q-KyyxNkU3AN2YSaujuNPIoXQMXhGOwClTap5y8fU5I,282
26
26
  neva/events/event_registry.py,sha256=IZnpR3oLyvt5K2pn9KMCBw5W4JP7Ok_zMKI2SgQoXVM,1945
27
27
  neva/events/listener.py,sha256=2d5RHVKO0pU-eL_evLReTG0DIoX9tZyOcn8PWLeECZo,1435
28
28
  neva/events/policy.py,sha256=I67ziob0IEVeXdkilNPpj2f48gEz2EZneR1Fz8Nlt2U,154
29
29
  neva/events/provider.py,sha256=cVxADLv7VXCK6gTMJsDNRzspMOH2lrIkvMRhqVh2cK4,954
30
30
  neva/events/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- neva/events/contracts/__init__.py,sha256=6-xRFR_5t6PbU-owl0WtM8mmdvmNvuHPUyL1sbFfqU8,293
32
- neva/events/contracts/dispatcher.py,sha256=jJ4qLM_5-bXcBPH_txnbwHXAs6GFB080MiDkEViatTk,594
31
+ neva/events/contracts/__init__.py,sha256=t1VafYtzOZIF78V8X3K_Fg79Hw4FjSqasjLs0EQVIFA,489
32
+ neva/events/contracts/dispatcher.py,sha256=pzCv_ZLS5ju2IKVMxL5f-fFipEqQZc6zPiIVtJhKJHc,1274
33
33
  neva/events/contracts/event.py,sha256=kUUECex_AlmaPJ-fXRg32eMkiqNS75CHldOr7bbpFqo,89
34
34
  neva/events/contracts/handler.py,sha256=SwLuuRtXhCzGkcBBLdH3MjUAkjEr0_rhqgE6dkWVJxI,327
35
35
  neva/events/contracts/listener.py,sha256=0xwPrjprkMzXu9vY1Z-EjSLFdvOUpomiE65hH16nfxA,465
@@ -73,24 +73,24 @@ neva/support/strconv.py,sha256=cvYRtJVG3IuhQzZCqJD1nVm3g0wxRu-IIIFK16Mq7d8,1296
73
73
  neva/support/time.py,sha256=QYH1F38V33F1C-rlH1Xbrblnaue1-BnercFYUBMmq88,351
74
74
  neva/support/facade/__init__.py,sha256=aysM4AyDAxAgv8uZCka49t2vLHeVraTNAHeCIpXLd-s,465
75
75
  neva/support/facade/app.py,sha256=xdL9qpeQnJtAMomxfrImM82qM2YV2eQFsckmUNE2w7I,297
76
- neva/support/facade/app.pyi,sha256=QBk-1cRpcNPsxQM0cGT0IYU1B4DQeExhzf1w8Sbax2g,643
76
+ neva/support/facade/app.pyi,sha256=gTAKp_r8DUqOj-oggAyAuK1WvR2AzX63-G2U5NixHbM,1304
77
77
  neva/support/facade/config.py,sha256=cqmtwAekHE4qWH2LqzmY3BAbyAadvZYfPgLHyTcCnJE,358
78
78
  neva/support/facade/config.pyi,sha256=LpI6xwqURRlWPcohXBxTL-iWTHq3MMMNdu670Q_5jnk,2549
79
79
  neva/support/facade/crypt.py,sha256=BUx2woDla3TrvVOVRNzOg97zfCaXIOqIz5EdoN3BvlA,298
80
80
  neva/support/facade/crypt.pyi,sha256=_MV_qXDATaCs1pZTOw_-qPx_jjH_nG4-tUBOMX4nZIE,857
81
81
  neva/support/facade/db.py,sha256=mXMzuCKf390ICCBgzaub1ZoMqUBgOQByQYMEM3YnN_0,324
82
82
  neva/support/facade/db.pyi,sha256=UQFMgBuw0BGquMnuUNULC8Pdo7lDCVTCNkDaUmIZJhw,1911
83
- neva/support/facade/event.py,sha256=sRU-fxaRT59Fe-u2WTS5L9ncGkGLBndKa9gKbqwAq3M,1232
84
- neva/support/facade/event.pyi,sha256=wI1NlIg8p6j9jispsA6pI-kvNp81nwUM3ZP8O0zFaHk,558
83
+ neva/support/facade/event.py,sha256=BkJoof1hYTxhMBOxLrq21NIgEPIiLBlj18Pttl0FHa8,1236
84
+ neva/support/facade/event.pyi,sha256=7R6osyTZbHt_zkCfQ-uZnTLfyZTeN1bi3exsgl3-36k,817
85
85
  neva/support/facade/hash.py,sha256=tGhsvfGovt-mcRXSjDKf4jt1n7ib0eDPXFvRUfVxLUc,292
86
86
  neva/support/facade/hash.pyi,sha256=jTs3E5ZF5cp76jrgH70DEog8gzfYIcIV2PrO9xskHyI,1656
87
87
  neva/support/facade/log.py,sha256=_uLoHB9tkpHkiqEJXazbaqfiKUOtv3NS71ZCZAprzOI,347
88
88
  neva/support/facade/log.pyi,sha256=Xi7gIzw9qRqmD4D3PcW06_HUtgLWEOdsHqs1B0jh1-k,1647
89
89
  neva/testing/__init__.py,sha256=vt5zxG1N84vn_YwP44jhYPWYnyb02-cha3m-1l-qoEw,207
90
- neva/testing/fakes.py,sha256=5HuuwMeAKhFSZfn9T8raQIjdgHkjFeZsFXDud0T4v7Q,2674
90
+ neva/testing/fakes.py,sha256=w2dnFaO6x14l0_si7iBNELMCxaFi8VsVOSQ6PmTlclE,2936
91
91
  neva/testing/fixtures.py,sha256=oiPa5ntUkW-SbySDvwEHXtti8NqSwI-R0CT1_ezTx_Y,1445
92
92
  neva/testing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
93
  neva/testing/test_case.py,sha256=zJBZJeKI-sv-EOaXNuSJ0V2Zp_6hdk64W4dOh0eP8RI,3290
94
- python_neva-3.3.0.dist-info/METADATA,sha256=7J-vn_EdbJAX-DgR0zsWf8NWVIq2BPd5Fp5YYiZcbic,846
95
- python_neva-3.3.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
96
- python_neva-3.3.0.dist-info/RECORD,,
94
+ python_neva-3.5.0.dist-info/METADATA,sha256=rcHS4PoUAyXqpkmfej_UhyUaBgnb5dV-x8ZbwMWbXFQ,846
95
+ python_neva-3.5.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
96
+ python_neva-3.5.0.dist-info/RECORD,,