flowevent 1.2__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.
eventflow/__init__.py ADDED
@@ -0,0 +1,123 @@
1
+ """Synchronous and asyncio-native in-process event delivery."""
2
+
3
+ from importlib import import_module
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ __version__ = "1.2"
7
+
8
+ from .actions import (
9
+ AsyncEventBusActions,
10
+ EventBusActions,
11
+ EventErrorContext,
12
+ EventTimeoutContext,
13
+ )
14
+ from .async_event_bus import AsyncEventBus
15
+ from .base import BaseEventBus
16
+ from .callback import CallbackContext, CallbackSubscription
17
+ from .contracts import DeliveryMode, ErrorPolicy, EventBusStats, PublishResult
18
+ from .engine import AsyncEventEngine, EventEngine
19
+ from .errors import (
20
+ CallbackTimeoutError,
21
+ EventBusClosedError,
22
+ EventBusError,
23
+ EventShutdownTimeoutError,
24
+ InvalidEventHandlerError,
25
+ )
26
+ from .event_bus import EventBus
27
+ from .filtering import EventFilter
28
+ from .history import EventHistory, MemoryEventHistory
29
+ from .subscription import AsyncSubscription, Subscription
30
+
31
+ if TYPE_CHECKING:
32
+ from .context import EventEnvelope, correlation_context, current_correlation_id
33
+ from .observability import (
34
+ ExecutionMetrics,
35
+ PipelineObservation,
36
+ PipelineObserver,
37
+ PipelineStats,
38
+ PipelineStatsSnapshot,
39
+ )
40
+ from .pipeline import AsyncPipeline, Pipeline, PipelineExecutionError
41
+ from .scopes import AsyncCancelHandle, AsyncSubscriptionScope, CancelHandle, SubscriptionScope
42
+ from .stream import AsyncStream
43
+
44
+ # Optional execution components load only when requested, keeping legacy bus
45
+ # imports independent of pipeline, stream, context and observability modules.
46
+ _OPTIONAL_EXPORTS = {
47
+ **dict.fromkeys(("EventEnvelope", "correlation_context", "current_correlation_id"), ".context"),
48
+ **dict.fromkeys(
49
+ (
50
+ "ExecutionMetrics",
51
+ "PipelineObservation",
52
+ "PipelineObserver",
53
+ "PipelineStats",
54
+ "PipelineStatsSnapshot",
55
+ ),
56
+ ".observability",
57
+ ),
58
+ **dict.fromkeys(("AsyncPipeline", "Pipeline", "PipelineExecutionError"), ".pipeline"),
59
+ **dict.fromkeys(
60
+ ("AsyncCancelHandle", "AsyncSubscriptionScope", "CancelHandle", "SubscriptionScope"),
61
+ ".scopes",
62
+ ),
63
+ "AsyncStream": ".stream",
64
+ }
65
+
66
+
67
+ def __getattr__(name: str) -> Any:
68
+ module = _OPTIONAL_EXPORTS.get(name)
69
+ if module is None:
70
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
71
+ value = getattr(import_module(module, __name__), name)
72
+ globals()[name] = value
73
+ return value
74
+
75
+
76
+ def __dir__() -> list[str]:
77
+ return sorted(set(globals()) | set(__all__))
78
+
79
+
80
+ __all__ = [
81
+ "__version__",
82
+ "AsyncEventBus",
83
+ "AsyncEventBusActions",
84
+ "AsyncEventEngine",
85
+ "AsyncSubscription",
86
+ "BaseEventBus",
87
+ "CallbackContext",
88
+ "CallbackSubscription",
89
+ "CallbackTimeoutError",
90
+ "DeliveryMode",
91
+ "ErrorPolicy",
92
+ "EventBus",
93
+ "EventBusActions",
94
+ "EventBusClosedError",
95
+ "EventBusError",
96
+ "EventBusStats",
97
+ "EventEngine",
98
+ "EventFilter",
99
+ "EventErrorContext",
100
+ "EventHistory",
101
+ "EventShutdownTimeoutError",
102
+ "EventTimeoutContext",
103
+ "InvalidEventHandlerError",
104
+ "MemoryEventHistory",
105
+ "PublishResult",
106
+ "Subscription",
107
+ "AsyncCancelHandle",
108
+ "AsyncPipeline",
109
+ "AsyncStream",
110
+ "AsyncSubscriptionScope",
111
+ "CancelHandle",
112
+ "EventEnvelope",
113
+ "ExecutionMetrics",
114
+ "Pipeline",
115
+ "PipelineExecutionError",
116
+ "PipelineObservation",
117
+ "PipelineObserver",
118
+ "PipelineStats",
119
+ "PipelineStatsSnapshot",
120
+ "SubscriptionScope",
121
+ "correlation_context",
122
+ "current_correlation_id",
123
+ ]
@@ -0,0 +1,22 @@
1
+ """Callback guards shared by opt-in execution and lifecycle components."""
2
+
3
+ import inspect
4
+ from typing import TypeVar
5
+
6
+ T = TypeVar("T")
7
+
8
+
9
+ def is_async_callable(callback: object) -> bool:
10
+ if not callable(callback):
11
+ return False
12
+ return inspect.iscoroutinefunction(callback) or inspect.iscoroutinefunction(
13
+ type(callback).__call__
14
+ )
15
+
16
+
17
+ def synchronous_result(result: T) -> T:
18
+ if result is not None and inspect.isawaitable(result):
19
+ if inspect.iscoroutine(result):
20
+ result.close()
21
+ raise TypeError("Synchronous callback returned an awaitable")
22
+ return result
@@ -0,0 +1,31 @@
1
+ """Wait for owned cleanup even when the caller is cancelled repeatedly."""
2
+
3
+ import asyncio
4
+ from collections.abc import Awaitable
5
+ from typing import TypeVar
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ async def finish_cleanup(operation: Awaitable[T]) -> T:
11
+ task = asyncio.ensure_future(operation)
12
+ if task is asyncio.current_task():
13
+ raise RuntimeError("Cleanup cannot await itself")
14
+ cancellation: asyncio.CancelledError | None = None
15
+ if not task.done():
16
+ completed = asyncio.Event()
17
+ task.add_done_callback(lambda _task: completed.set())
18
+ while not task.done():
19
+ try:
20
+ await completed.wait()
21
+ except asyncio.CancelledError as exc:
22
+ cancellation = exc
23
+ try:
24
+ result = task.result()
25
+ except BaseException as error:
26
+ if cancellation is not None:
27
+ raise cancellation from error
28
+ raise
29
+ if cancellation is not None:
30
+ raise cancellation
31
+ return result
eventflow/actions.py ADDED
@@ -0,0 +1,53 @@
1
+ """Optional hooks shared by every operation on an event bus."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Awaitable, Callable
6
+ from dataclasses import dataclass
7
+ from typing import Generic, TypeVar
8
+
9
+ from .contracts import PublishResult
10
+ from .filtering import EventFilter
11
+
12
+
13
+ T = TypeVar("T")
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class EventErrorContext(Generic[T]):
18
+ event: T
19
+ error: Exception
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class EventTimeoutContext(Generic[T]):
24
+ event_filter: EventFilter[T]
25
+ timeout: float | None
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class EventBusActions(Generic[T]):
30
+ on_error: Callable[[EventErrorContext[T]], None] | None = None
31
+ on_timeout: Callable[[EventTimeoutContext[T]], None] | None = None
32
+ on_before: Callable[[T], None] | None = None
33
+ on_after: Callable[[T, PublishResult], None] | None = None
34
+
35
+ def __post_init__(self) -> None:
36
+ for name in ("on_before", "on_after", "on_error", "on_timeout"):
37
+ action = getattr(self, name)
38
+ if action is not None and not callable(action):
39
+ raise TypeError(f"Event action {name} must be callable")
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class AsyncEventBusActions(Generic[T]):
44
+ on_error: Callable[[EventErrorContext[T]], Awaitable[None]] | None = None
45
+ on_timeout: Callable[[EventTimeoutContext[T]], Awaitable[None]] | None = None
46
+ on_before: Callable[[T], Awaitable[None]] | None = None
47
+ on_after: Callable[[T, PublishResult], Awaitable[None]] | None = None
48
+
49
+ def __post_init__(self) -> None:
50
+ for name in ("on_before", "on_after", "on_error", "on_timeout"):
51
+ action = getattr(self, name)
52
+ if action is not None and not callable(action):
53
+ raise TypeError(f"Async event action {name} must be callable")