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 +123 -0
- eventflow/_callbacks.py +22 -0
- eventflow/_lifecycle.py +31 -0
- eventflow/actions.py +53 -0
- eventflow/async_event_bus.py +640 -0
- eventflow/base.py +180 -0
- eventflow/callback.py +318 -0
- eventflow/compatibility.py +14 -0
- eventflow/context.py +103 -0
- eventflow/contracts.py +40 -0
- eventflow/engine.py +466 -0
- eventflow/errors.py +21 -0
- eventflow/event_bus.py +594 -0
- eventflow/filtering.py +57 -0
- eventflow/history.py +111 -0
- eventflow/observability.py +133 -0
- eventflow/pipeline/__init__.py +7 -0
- eventflow/pipeline/async_.py +129 -0
- eventflow/pipeline/base.py +203 -0
- eventflow/pipeline/contracts.py +24 -0
- eventflow/pipeline/registry.py +95 -0
- eventflow/pipeline/sync.py +92 -0
- eventflow/py.typed +0 -0
- eventflow/scopes.py +185 -0
- eventflow/stream.py +241 -0
- eventflow/subscription.py +116 -0
- flowevent-1.2.dist-info/METADATA +1171 -0
- flowevent-1.2.dist-info/RECORD +31 -0
- flowevent-1.2.dist-info/WHEEL +5 -0
- flowevent-1.2.dist-info/licenses/LICENSE +21 -0
- flowevent-1.2.dist-info/top_level.txt +1 -0
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
|
+
]
|
eventflow/_callbacks.py
ADDED
|
@@ -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
|
eventflow/_lifecycle.py
ADDED
|
@@ -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")
|