mersal-polling 0.1.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.
- mersal_polling/__init__.py +21 -0
- mersal_polling/config.py +65 -0
- mersal_polling/default_poller.py +37 -0
- mersal_polling/error_handler_poller_wrapper.py +21 -0
- mersal_polling/message_completion_handler.py +93 -0
- mersal_polling/plugin.py +245 -0
- mersal_polling/poller.py +23 -0
- mersal_polling/poller_with_timeout.py +28 -0
- mersal_polling/py.typed +0 -0
- mersal_polling-0.1.0.dist-info/METADATA +12 -0
- mersal_polling-0.1.0.dist-info/RECORD +12 -0
- mersal_polling-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .config import (
|
|
2
|
+
PollingConfig,
|
|
3
|
+
)
|
|
4
|
+
from .default_poller import DefaultPoller
|
|
5
|
+
from .message_completion_handler import (
|
|
6
|
+
message_completion_event_publisher,
|
|
7
|
+
register_message_completion_publishers,
|
|
8
|
+
)
|
|
9
|
+
from .poller import Poller, PollingResult
|
|
10
|
+
from .poller_with_timeout import PollerWithTimeout, PollingTimeoutError
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"DefaultPoller",
|
|
14
|
+
"Poller",
|
|
15
|
+
"PollerWithTimeout",
|
|
16
|
+
"PollingConfig",
|
|
17
|
+
"PollingResult",
|
|
18
|
+
"PollingTimeoutError",
|
|
19
|
+
"message_completion_event_publisher",
|
|
20
|
+
"register_message_completion_publishers",
|
|
21
|
+
]
|
mersal_polling/config.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Any, Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
from mersal_polling.plugin import PollingPlugin
|
|
6
|
+
from mersal_polling.poller import Poller
|
|
7
|
+
|
|
8
|
+
__all__ = (
|
|
9
|
+
"FailedCompletionCorrelation",
|
|
10
|
+
"PollingConfig",
|
|
11
|
+
"SuccessfulCompletionCorrelation",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
EventType = TypeVar("EventType")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class SuccessfulCompletionCorrelation(Generic[EventType]):
|
|
20
|
+
"""Correlates an event with the successful completion of a message (usually a command).
|
|
21
|
+
|
|
22
|
+
The correlation is either based on a custom callback that is given the event
|
|
23
|
+
and should return the message id or by default; the correlation id will be used.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
message_id_getter: Callable[[EventType], Any] | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class FailedCompletionCorrelation(Generic[EventType]):
|
|
31
|
+
"""Correlates an event with the failed completion of a message (usually a command).
|
|
32
|
+
|
|
33
|
+
The correlation is either based on a custom callback that is given the event
|
|
34
|
+
and should return the message id or by default; the correlation id will be used.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
message_id_getter: Callable[[EventType], Any] | None = None
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
Callback that builds a custom exception to pass to the poller for the failed case.
|
|
41
|
+
"""
|
|
42
|
+
exception_builder: Callable[[EventType], Exception] | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class PollingConfig:
|
|
47
|
+
"""Configuration for the polling functionality.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
poller: The poller instance to use
|
|
51
|
+
successful_completion_events_map: Map of event types to successful completion correlations
|
|
52
|
+
failed_completion_events_map: Map of event types to failed completion correlations
|
|
53
|
+
auto_publish_completion_events: Whether to automatically publish message completion events
|
|
54
|
+
exclude_from_completion_events: Message types to exclude from automatic completion events
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
poller: Poller
|
|
58
|
+
successful_completion_events_map: dict[type, SuccessfulCompletionCorrelation] = field(default_factory=dict)
|
|
59
|
+
failed_completion_events_map: dict[type, FailedCompletionCorrelation] = field(default_factory=dict)
|
|
60
|
+
auto_publish_completion_events: bool = True
|
|
61
|
+
exclude_from_completion_events: set[type] = field(default_factory=set)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def plugin(self) -> PollingPlugin:
|
|
65
|
+
return PollingPlugin(self)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
3
|
+
from anyio import Event
|
|
4
|
+
|
|
5
|
+
from .poller import Poller, PollingResult
|
|
6
|
+
|
|
7
|
+
__all__ = ("DefaultPoller",)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DefaultPoller(Poller):
|
|
11
|
+
def __init__(self) -> None:
|
|
12
|
+
self.results: dict[uuid.UUID, PollingResult] = {}
|
|
13
|
+
self.events: dict[uuid.UUID, Event] = {}
|
|
14
|
+
|
|
15
|
+
async def poll(self, message_id: uuid.UUID) -> PollingResult:
|
|
16
|
+
# Check if result already exists
|
|
17
|
+
message = self.results.get(message_id)
|
|
18
|
+
if message:
|
|
19
|
+
return message
|
|
20
|
+
|
|
21
|
+
# Create an event for this message_id if it doesn't exist
|
|
22
|
+
if message_id not in self.events:
|
|
23
|
+
self.events[message_id] = Event()
|
|
24
|
+
|
|
25
|
+
# Wait for the event to be set
|
|
26
|
+
await self.events[message_id].wait()
|
|
27
|
+
|
|
28
|
+
# Return the result (should be available now)
|
|
29
|
+
return self.results[message_id]
|
|
30
|
+
|
|
31
|
+
async def push(self, message_id: uuid.UUID, exception: Exception | None = None) -> None:
|
|
32
|
+
# Store the result
|
|
33
|
+
self.results[message_id] = PollingResult(message_id, exception)
|
|
34
|
+
|
|
35
|
+
# If there's a waiting event, trigger it
|
|
36
|
+
if message_id in self.events:
|
|
37
|
+
self.events[message_id].set()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from mersal.messages import TransportMessage
|
|
2
|
+
from mersal.retry import ErrorHandler
|
|
3
|
+
from mersal.transport import TransactionContext
|
|
4
|
+
from mersal_polling.poller import Poller
|
|
5
|
+
|
|
6
|
+
__all__ = ("ErrorHandlerPollerWrapper",)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ErrorHandlerPollerWrapper(ErrorHandler):
|
|
10
|
+
def __init__(self, poller: Poller, error_handler: ErrorHandler) -> None:
|
|
11
|
+
self.poller = poller
|
|
12
|
+
self.error_handler = error_handler
|
|
13
|
+
|
|
14
|
+
async def handle_poison_message(
|
|
15
|
+
self,
|
|
16
|
+
message: TransportMessage,
|
|
17
|
+
transaction_context: TransactionContext,
|
|
18
|
+
exception: Exception,
|
|
19
|
+
) -> None:
|
|
20
|
+
await self.error_handler.handle_poison_message(message, transaction_context, exception)
|
|
21
|
+
await self.poller.push(message.headers.message_id, exception)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
from mersal.messages.message_completed_event import MessageCompletedEvent
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from collections.abc import Awaitable, Callable
|
|
10
|
+
|
|
11
|
+
from mersal.activation import HandlerActivator
|
|
12
|
+
from mersal.app import Mersal
|
|
13
|
+
from mersal.pipeline import MessageContext
|
|
14
|
+
from mersal.types import AsyncAnyCallable
|
|
15
|
+
|
|
16
|
+
__all__ = (
|
|
17
|
+
"message_completion_event_publisher",
|
|
18
|
+
"register_message_completion_publishers",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def message_completion_event_publisher(
|
|
23
|
+
message_context: MessageContext,
|
|
24
|
+
app: Mersal,
|
|
25
|
+
_: list[AsyncAnyCallable],
|
|
26
|
+
) -> Callable[[Any], Awaitable[None]]:
|
|
27
|
+
"""Create a handler that publishes a MessageCompletedEvent.
|
|
28
|
+
|
|
29
|
+
This function creates a message handler that publishes a MessageCompletedEvent
|
|
30
|
+
when invoked, allowing for tracking message processing completion.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
message_context: The message context for the current message
|
|
34
|
+
app: The Mersal application instance
|
|
35
|
+
_: Unused parameter for compatibility with handler factory signature
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
A message handler function that publishes a completion event
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
async def handler(_: Any) -> None:
|
|
42
|
+
"""Handler that publishes a MessageCompletedEvent.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
_: The message (not used in this handler)
|
|
46
|
+
"""
|
|
47
|
+
completed_message_id = message_context.headers.message_id
|
|
48
|
+
published_message_id = uuid.uuid4()
|
|
49
|
+
await app.publish(
|
|
50
|
+
MessageCompletedEvent(completed_message_id=completed_message_id),
|
|
51
|
+
headers={"message_id": published_message_id},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return handler
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def register_message_completion_publishers(
|
|
58
|
+
activator: HandlerActivator,
|
|
59
|
+
exclude_types: set[type] | None = None,
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Register message completion event publishers for all message types.
|
|
62
|
+
|
|
63
|
+
This function registers handlers that publish MessageCompletedEvent for all
|
|
64
|
+
message types that already have handlers registered in the activator, except
|
|
65
|
+
for those in the exclude_types set.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
activator: The handler activator to register completion publishers with
|
|
69
|
+
exclude_types: Message types to exclude from completion event publishing
|
|
70
|
+
"""
|
|
71
|
+
# Create exclude set (always exclude MessageCompletedEvent itself)
|
|
72
|
+
exclude: set[type] = set() if exclude_types is None else exclude_types.copy()
|
|
73
|
+
exclude.add(MessageCompletedEvent)
|
|
74
|
+
|
|
75
|
+
# Get all registered message types
|
|
76
|
+
completion_event_registry: set[type] = set()
|
|
77
|
+
|
|
78
|
+
# Register completion event publishers for all message types
|
|
79
|
+
for message_type in activator.registered_message_types:
|
|
80
|
+
if message_type not in exclude and message_type not in completion_event_registry:
|
|
81
|
+
# For each message type, register a handler that publishes completion events
|
|
82
|
+
def completion_publisher_factory(
|
|
83
|
+
message_context: MessageContext,
|
|
84
|
+
app: Mersal,
|
|
85
|
+
) -> Callable[[Any], Awaitable[None]]:
|
|
86
|
+
return message_completion_event_publisher(
|
|
87
|
+
message_context=message_context,
|
|
88
|
+
app=app,
|
|
89
|
+
_=[],
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
activator.register(message_type, completion_publisher_factory)
|
|
93
|
+
completion_event_registry.add(message_type)
|
mersal_polling/plugin.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from mersal.activation import HandlerActivator
|
|
6
|
+
from mersal.lifespan import LifespanHandler
|
|
7
|
+
from mersal.messages import MessageCompletedEvent
|
|
8
|
+
from mersal.plugins import Plugin
|
|
9
|
+
from mersal.retry import ErrorHandler
|
|
10
|
+
from mersal_polling.error_handler_poller_wrapper import (
|
|
11
|
+
ErrorHandlerPollerWrapper,
|
|
12
|
+
)
|
|
13
|
+
from mersal_polling.message_completion_handler import register_message_completion_publishers
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Awaitable, Callable
|
|
17
|
+
|
|
18
|
+
from mersal.app import Mersal
|
|
19
|
+
from mersal.configuration import StandardConfigurator
|
|
20
|
+
from mersal.pipeline.message_context import MessageContext
|
|
21
|
+
from mersal_polling.config import (
|
|
22
|
+
FailedCompletionCorrelation,
|
|
23
|
+
PollingConfig,
|
|
24
|
+
SuccessfulCompletionCorrelation,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = ("PollingPlugin",)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PollingPlugin(Plugin):
|
|
31
|
+
"""Plugin that adds polling functionality to a Mersal application.
|
|
32
|
+
|
|
33
|
+
This plugin enables waiting for message processing completion through polling.
|
|
34
|
+
It registers handlers for MessageCompletedEvent and custom completion events,
|
|
35
|
+
and can automatically add completion event publishers for registered message types.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, config: PollingConfig) -> None:
|
|
39
|
+
"""Initialize a new polling plugin.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
config: The configuration for the polling plugin
|
|
43
|
+
"""
|
|
44
|
+
self._poller = config.poller
|
|
45
|
+
self._successfull_completion_events_map = config.successful_completion_events_map
|
|
46
|
+
self._failed_completion_events_map = config.failed_completion_events_map
|
|
47
|
+
self._auto_publish_completion_events = config.auto_publish_completion_events
|
|
48
|
+
self._exclude_from_completion_events = config.exclude_from_completion_events
|
|
49
|
+
|
|
50
|
+
def __call__(self, configurator: StandardConfigurator) -> None:
|
|
51
|
+
"""Configure the Mersal application with polling functionality.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
configurator: The standard configurator for the application
|
|
55
|
+
"""
|
|
56
|
+
# Configure event subscriptions for MessageCompletedEvent and other events
|
|
57
|
+
self._configure_event_subscriptions(configurator)
|
|
58
|
+
|
|
59
|
+
# Integrate with error handling
|
|
60
|
+
self._configure_error_handler(configurator)
|
|
61
|
+
|
|
62
|
+
# Register handlers for completion events
|
|
63
|
+
self._configure_completion_event_handlers(configurator)
|
|
64
|
+
|
|
65
|
+
# If auto-publish is enabled, register completion event publishers for all message types
|
|
66
|
+
if self._auto_publish_completion_events:
|
|
67
|
+
self._configure_auto_completion_event_publishing(configurator)
|
|
68
|
+
|
|
69
|
+
def _configure_event_subscriptions(self, configurator: StandardConfigurator) -> None:
|
|
70
|
+
"""Configure event subscriptions for the polling plugin.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
configurator: The standard configurator for the application
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def decorate(configurator: StandardConfigurator) -> Any:
|
|
77
|
+
events_to_subscribe_to: list[type] = [
|
|
78
|
+
MessageCompletedEvent,
|
|
79
|
+
]
|
|
80
|
+
lifespan_handler: LifespanHandler = configurator.get(LifespanHandler) # type: ignore[type-abstract]
|
|
81
|
+
app: Mersal = configurator.mersal
|
|
82
|
+
|
|
83
|
+
# Add all custom completion events
|
|
84
|
+
for event in self._successfull_completion_events_map:
|
|
85
|
+
events_to_subscribe_to.append(event)
|
|
86
|
+
for event in self._failed_completion_events_map:
|
|
87
|
+
events_to_subscribe_to.append(event)
|
|
88
|
+
|
|
89
|
+
# Register a startup hook to subscribe to all events
|
|
90
|
+
lifespan_handler.register_on_startup_hook(self._subscribe(app, events_to_subscribe_to)) # type: ignore[arg-type]
|
|
91
|
+
|
|
92
|
+
return lifespan_handler
|
|
93
|
+
|
|
94
|
+
configurator.decorate(LifespanHandler, decorate)
|
|
95
|
+
|
|
96
|
+
def _configure_error_handler(self, configurator: StandardConfigurator) -> None:
|
|
97
|
+
"""Configure error handling integration with polling.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
configurator: The standard configurator for the application
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def decorate_error_handler(configurator: StandardConfigurator) -> Any:
|
|
104
|
+
error_handler: ErrorHandler = configurator.get(ErrorHandler) # type: ignore[type-abstract]
|
|
105
|
+
return ErrorHandlerPollerWrapper(self._poller, error_handler)
|
|
106
|
+
|
|
107
|
+
configurator.decorate(ErrorHandler, decorate_error_handler)
|
|
108
|
+
|
|
109
|
+
def _configure_completion_event_handlers(self, configurator: StandardConfigurator) -> None:
|
|
110
|
+
"""Configure handlers for message completion events.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
configurator: The standard configurator for the application
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def decorate_activator(configurator: StandardConfigurator) -> Any:
|
|
117
|
+
activator: HandlerActivator = configurator.get(HandlerActivator) # type: ignore[type-abstract]
|
|
118
|
+
|
|
119
|
+
# Register handler for MessageCompletedEvent
|
|
120
|
+
activator.register(
|
|
121
|
+
MessageCompletedEvent,
|
|
122
|
+
lambda __, _: self._message_completed_event_handler,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Register handlers for custom success completion events
|
|
126
|
+
for (
|
|
127
|
+
event_type,
|
|
128
|
+
success_correlator,
|
|
129
|
+
) in self._successfull_completion_events_map.items():
|
|
130
|
+
activator.register(
|
|
131
|
+
event_type,
|
|
132
|
+
lambda message_context, # type: ignore[misc]
|
|
133
|
+
_,
|
|
134
|
+
sc=success_correlator: self._successfull_custom_completion_event_handler_factory(
|
|
135
|
+
sc, message_context
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Register handlers for custom failure completion events
|
|
140
|
+
for (
|
|
141
|
+
event_type,
|
|
142
|
+
failure_correlator,
|
|
143
|
+
) in self._failed_completion_events_map.items():
|
|
144
|
+
activator.register(
|
|
145
|
+
event_type,
|
|
146
|
+
lambda message_context, # type: ignore[misc]
|
|
147
|
+
_,
|
|
148
|
+
fc=failure_correlator: self._failed_custom_completion_event_handler_factory(fc, message_context),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return activator
|
|
152
|
+
|
|
153
|
+
configurator.decorate(HandlerActivator, decorate_activator)
|
|
154
|
+
|
|
155
|
+
def _configure_auto_completion_event_publishing(self, configurator: StandardConfigurator) -> None:
|
|
156
|
+
"""Configure automatic completion event publishing.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
configurator: The standard configurator for the application
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def decorate_activator(configurator: StandardConfigurator) -> Any:
|
|
163
|
+
activator: HandlerActivator = configurator.get(HandlerActivator) # type: ignore[type-abstract]
|
|
164
|
+
register_message_completion_publishers(activator, self._exclude_from_completion_events)
|
|
165
|
+
return activator
|
|
166
|
+
|
|
167
|
+
configurator.decorate(HandlerActivator, decorate_activator)
|
|
168
|
+
|
|
169
|
+
async def _message_completed_event_handler(self, event: MessageCompletedEvent) -> None:
|
|
170
|
+
"""Handle MessageCompletedEvent by updating the poller.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
event: The message completed event
|
|
174
|
+
"""
|
|
175
|
+
await self._poller.push(event.completed_message_id, None)
|
|
176
|
+
|
|
177
|
+
def _successfull_custom_completion_event_handler_factory(
|
|
178
|
+
self,
|
|
179
|
+
correlator: SuccessfulCompletionCorrelation,
|
|
180
|
+
message_context: MessageContext,
|
|
181
|
+
) -> Callable[[Any], Awaitable[None]]:
|
|
182
|
+
"""Create a handler for custom successful completion events.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
correlator: The correlation configuration
|
|
186
|
+
message_context: The message context
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
A handler function that processes the event
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
async def _custom_completion_event_handler(event: Any) -> None:
|
|
193
|
+
if message_id_getter := correlator.message_id_getter:
|
|
194
|
+
message_id = message_id_getter(event)
|
|
195
|
+
else:
|
|
196
|
+
message_id = message_context.headers.correlation_id
|
|
197
|
+
await self._poller.push(message_id, None)
|
|
198
|
+
|
|
199
|
+
return _custom_completion_event_handler
|
|
200
|
+
|
|
201
|
+
def _failed_custom_completion_event_handler_factory(
|
|
202
|
+
self, correlator: FailedCompletionCorrelation, message_context: MessageContext
|
|
203
|
+
) -> Callable[[Any], Awaitable[None]]:
|
|
204
|
+
"""Create a handler for custom failure completion events.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
correlator: The correlation configuration
|
|
208
|
+
message_context: The message context
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
A handler function that processes the event
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
async def _custom_completion_event_handler(event: Any) -> None:
|
|
215
|
+
if message_id_getter := correlator.message_id_getter:
|
|
216
|
+
message_id = message_id_getter(event)
|
|
217
|
+
else:
|
|
218
|
+
message_id = message_context.headers.correlation_id
|
|
219
|
+
|
|
220
|
+
if exception_builder := correlator.exception_builder:
|
|
221
|
+
exception = exception_builder(event)
|
|
222
|
+
else:
|
|
223
|
+
exception = Exception("Event error")
|
|
224
|
+
await self._poller.push(message_id, exception)
|
|
225
|
+
|
|
226
|
+
return _custom_completion_event_handler
|
|
227
|
+
|
|
228
|
+
def _subscribe(self, app: Mersal, events: list[type]) -> Callable[[list[type]], Awaitable[None]]:
|
|
229
|
+
"""Create a function that subscribes to the given events.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
app: The Mersal application
|
|
233
|
+
events: The list of event types to subscribe to
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
A function that performs the subscriptions
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
async def subscribe(
|
|
240
|
+
events: list[type] = events,
|
|
241
|
+
) -> None:
|
|
242
|
+
for e in events:
|
|
243
|
+
await app.subscribe(e)
|
|
244
|
+
|
|
245
|
+
return subscribe
|
mersal_polling/poller.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, Protocol
|
|
3
|
+
|
|
4
|
+
__all__ = (
|
|
5
|
+
"Poller",
|
|
6
|
+
"PollingResult",
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class PollingResult:
|
|
12
|
+
message_id: Any
|
|
13
|
+
exception: Exception | None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Poller(Protocol):
|
|
17
|
+
async def poll(self, message_id: Any) -> PollingResult: ...
|
|
18
|
+
|
|
19
|
+
async def push(
|
|
20
|
+
self,
|
|
21
|
+
message_id: Any,
|
|
22
|
+
exception: Exception | None = None,
|
|
23
|
+
) -> None: ...
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
3
|
+
from anyio import fail_after
|
|
4
|
+
|
|
5
|
+
from mersal.exceptions import MersalExceptionError
|
|
6
|
+
|
|
7
|
+
from .poller import Poller, PollingResult
|
|
8
|
+
|
|
9
|
+
__all__ = (
|
|
10
|
+
"PollerWithTimeout",
|
|
11
|
+
"PollingTimeoutError",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PollingTimeoutError(MersalExceptionError):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PollerWithTimeout:
|
|
20
|
+
def __init__(self, poller: Poller) -> None:
|
|
21
|
+
self._poller = poller
|
|
22
|
+
|
|
23
|
+
async def poll(self, message_id: uuid.UUID, timeout: float = 30) -> PollingResult:
|
|
24
|
+
try:
|
|
25
|
+
with fail_after(timeout):
|
|
26
|
+
return await self._poller.poll(message_id)
|
|
27
|
+
except TimeoutError as e:
|
|
28
|
+
raise PollingTimeoutError() from e
|
mersal_polling/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mersal-polling
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author-email: Abdulhaq Emhemmed <abdulhaq.emhemmed@gmail.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: mersal
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# mersal-polling
|
|
11
|
+
|
|
12
|
+
Describe your project here.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
mersal_polling/__init__.py,sha256=t-oEGj1muawyoI1cf-dOzEvHsjfRZQcZ3BtHDF_fdpY,559
|
|
2
|
+
mersal_polling/config.py,sha256=nFosx1a3xlNd2u3TxCBxofYcmjEATCvDmw_9YH2JWDs,2311
|
|
3
|
+
mersal_polling/default_poller.py,sha256=ZMcI4mUyooI9Tr-Dhfij9g_XPZJAd4PraRK031O4Bmk,1140
|
|
4
|
+
mersal_polling/error_handler_poller_wrapper.py,sha256=I4CC_m9YCAI2gr1vHs4MIihPqAjEdsx1Z6EJO9Jj8Q4,759
|
|
5
|
+
mersal_polling/message_completion_handler.py,sha256=l040OVm6_DTsbhJBvmMh0qa21ML4_MWZ0Oq9PmaA_6M,3344
|
|
6
|
+
mersal_polling/plugin.py,sha256=j0rQIb3DZVl1Jsb4PB_BTvIwlsvc4FvCRUjAa56pLfI,9562
|
|
7
|
+
mersal_polling/poller.py,sha256=2_JExVWr4_DTsLrQyqShb-vECcVyXJiv_QPVfxwrCPE,416
|
|
8
|
+
mersal_polling/poller_with_timeout.py,sha256=Rxd-IJDBHT7dJRWp1TaQej-FcIaaEkxK9e940XarRGQ,648
|
|
9
|
+
mersal_polling/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
mersal_polling-0.1.0.dist-info/METADATA,sha256=f7VohAqhJ1nLB9iGZzBulaQNN2rtkHmmUfgo7k2MvDY,288
|
|
11
|
+
mersal_polling-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
12
|
+
mersal_polling-0.1.0.dist-info/RECORD,,
|