boilermaker-servicebus 1.0.0.dev2__tar.gz → 1.0.0.dev5__tar.gz
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.
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/PKG-INFO +4 -2
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/app.py +40 -10
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/evaluators/__init__.py +1 -2
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/evaluators/common.py +20 -57
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/evaluators/eval.py +3 -7
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/evaluators/results_store.py +2 -5
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/evaluators/simple.py +2 -5
- boilermaker_servicebus-1.0.0.dev5/boilermaker/evaluators/task_graph.py +416 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/exc.py +17 -3
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/retries.py +1 -1
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/service_bus.py +1 -1
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/storage/base.py +20 -1
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/storage/blob_storage.py +84 -35
- boilermaker_servicebus-1.0.0.dev5/boilermaker/task/__init__.py +20 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/task/graph.py +278 -66
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/task/result.py +0 -1
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/task/task.py +2 -1
- boilermaker_servicebus-1.0.0.dev5/boilermaker/task/types.py +18 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/tracing.py +1 -3
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker_servicebus.egg-info/PKG-INFO +4 -2
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker_servicebus.egg-info/SOURCES.txt +1 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker_servicebus.egg-info/requires.txt +4 -1
- boilermaker_servicebus-1.0.0.dev5/examples/task_graph_example.py +274 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/pyproject.toml +14 -16
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/conftest.py +4 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/evaluators/test_common.py +7 -21
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/evaluators/test_eval.py +1 -4
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/evaluators/test_simple.py +13 -39
- boilermaker_servicebus-1.0.0.dev5/tests/evaluators/test_task_graphs.py +1494 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/graph_factories.py +11 -18
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/storage/test_blob_storage.py +172 -26
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/task/test_graph.py +865 -138
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/task/test_graph_cycle_detection.py +35 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/task/test_result.py +1 -3
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/task/test_task.py +0 -2
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/test_app.py +104 -4
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/test_retries.py +27 -3
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/test_sample.py +1 -3
- boilermaker_servicebus-1.0.0.dev2/boilermaker/evaluators/task_graph.py +0 -226
- boilermaker_servicebus-1.0.0.dev2/boilermaker/task/__init__.py +0 -6
- boilermaker_servicebus-1.0.0.dev2/examples/task_graph_example.py +0 -127
- boilermaker_servicebus-1.0.0.dev2/tests/evaluators/test_task_graphs.py +0 -564
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/LICENSE +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/README.md +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/__init__.py +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/config.py +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/failure.py +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/sample.py +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/storage/__init__.py +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/task/task_id.py +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker_servicebus.egg-info/dependency_links.txt +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker_servicebus.egg-info/top_level.txt +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/examples/basic.py +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/examples/callbacks.py +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/setup.cfg +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/evaluators/conftest.py +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/evaluators/test_eval_factory.py +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/evaluators/test_results_store.py +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/task/helpers.py +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/test_config.py +0 -0
- {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/test_service_bus.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: boilermaker-servicebus
|
|
3
|
-
Version: 1.0.0.
|
|
3
|
+
Version: 1.0.0.dev5
|
|
4
4
|
Summary: An async python Background task system using Azure Service Bus Queues
|
|
5
5
|
Author-email: Erik Aker <eaker@mulliganfunding.com>
|
|
6
6
|
License: Apache License
|
|
@@ -210,7 +210,7 @@ Project-URL: Issues, https://github.com/MulliganFunding/boilermaker-servicebus/i
|
|
|
210
210
|
Requires-Python: >=3.11
|
|
211
211
|
Description-Content-Type: text/markdown
|
|
212
212
|
License-File: LICENSE
|
|
213
|
-
Requires-Dist: aio-azure-clients-toolbox>=1.0
|
|
213
|
+
Requires-Dist: aio-azure-clients-toolbox>=1.1.0
|
|
214
214
|
Requires-Dist: anyio>=4.11.0
|
|
215
215
|
Requires-Dist: azure-core-tracing-opentelemetry>=1.0.0b12
|
|
216
216
|
Requires-Dist: azure-servicebus>=7.14.2
|
|
@@ -219,6 +219,8 @@ Requires-Dist: opentelemetry-api>=1.34.0
|
|
|
219
219
|
Requires-Dist: pydantic>=2.12.2
|
|
220
220
|
Requires-Dist: pydantic-settings>=2.11.0
|
|
221
221
|
Requires-Dist: uuid-utils>=0.11.1
|
|
222
|
+
Provides-Extra: repl
|
|
223
|
+
Requires-Dist: ipython; extra == "repl"
|
|
222
224
|
Dynamic: license-file
|
|
223
225
|
|
|
224
226
|
# Boilermaker
|
|
@@ -12,7 +12,7 @@ import typing
|
|
|
12
12
|
import weakref
|
|
13
13
|
from functools import wraps
|
|
14
14
|
|
|
15
|
-
from aio_azure_clients_toolbox import AzureServiceBus, ManagedAzureServiceBusSender
|
|
15
|
+
from aio_azure_clients_toolbox import AzureServiceBus, ManagedAzureServiceBusSender
|
|
16
16
|
from anyio import create_task_group, open_signal_receiver
|
|
17
17
|
from anyio.abc import CancelScope
|
|
18
18
|
from azure.servicebus import ServiceBusReceivedMessage
|
|
@@ -163,7 +163,7 @@ class Boilermaker:
|
|
|
163
163
|
raise ValueError(f"Function must be async: {fn_name}")
|
|
164
164
|
|
|
165
165
|
task = Task.default(fn_name, **options)
|
|
166
|
-
self.function_registry[fn_name] = fn
|
|
166
|
+
self.function_registry[fn_name] = typing.cast(TaskHandler, fn) # why must cast here
|
|
167
167
|
self.task_registry[fn_name] = task
|
|
168
168
|
logger.info(f"Registered background function fn={fn_name}")
|
|
169
169
|
return self
|
|
@@ -322,9 +322,7 @@ class Boilermaker:
|
|
|
322
322
|
await app.apply_async(cleanup_temp_files, delay=300)
|
|
323
323
|
"""
|
|
324
324
|
task = self.create_task(fn, *args, policy=policy, **kwargs)
|
|
325
|
-
return await self.publish_task(
|
|
326
|
-
task, delay=delay, publish_attempts=publish_attempts
|
|
327
|
-
)
|
|
325
|
+
return await self.publish_task(task, delay=delay, publish_attempts=publish_attempts)
|
|
328
326
|
|
|
329
327
|
@tracer.start_as_current_span("boilermaker.publish-task")
|
|
330
328
|
async def publish_task(
|
|
@@ -361,13 +359,12 @@ class Boilermaker:
|
|
|
361
359
|
results: list[int] = await self.service_bus_client.send_message(
|
|
362
360
|
task.model_dump_json(),
|
|
363
361
|
delay=delay,
|
|
362
|
+
unique_msg_id=str(task.task_id),
|
|
364
363
|
)
|
|
365
364
|
if results and len(results) == 1:
|
|
366
365
|
sequence_number = results[0]
|
|
367
366
|
task.mark_published(sequence_number)
|
|
368
|
-
logger.debug(
|
|
369
|
-
f"Published task {task.task_id} to queue with sequence_number={sequence_number}"
|
|
370
|
-
)
|
|
367
|
+
logger.debug(f"Published task {task.task_id} to queue with sequence_number={sequence_number}")
|
|
371
368
|
return task
|
|
372
369
|
|
|
373
370
|
except (
|
|
@@ -406,6 +403,13 @@ class Boilermaker:
|
|
|
406
403
|
[f"Expected graph_id={graph.graph_id}, found {task.graph_id=}"],
|
|
407
404
|
)
|
|
408
405
|
|
|
406
|
+
for task in graph.fail_children.values():
|
|
407
|
+
if task.graph_id != graph.graph_id:
|
|
408
|
+
raise BoilermakerAppException(
|
|
409
|
+
"All failure callback tasks must have graph_id matching graph",
|
|
410
|
+
[f"Expected graph_id={graph.graph_id}, found {task.graph_id=}"],
|
|
411
|
+
)
|
|
412
|
+
|
|
409
413
|
# Store the graph definition and all pending task results
|
|
410
414
|
# If this graph has already been stored, this should fail.
|
|
411
415
|
try:
|
|
@@ -413,8 +417,34 @@ class Boilermaker:
|
|
|
413
417
|
except BoilermakerStorageError as exc:
|
|
414
418
|
raise BoilermakerAppException("Error storing TaskGraph to storage", [str(exc)]) from exc
|
|
415
419
|
|
|
416
|
-
#
|
|
417
|
-
|
|
420
|
+
# Reload to get ETags on pending result blobs (required for ETag-guarded Scheduled writes)
|
|
421
|
+
try:
|
|
422
|
+
loaded_graph = await self.results_storage.load_graph(graph.graph_id)
|
|
423
|
+
except BoilermakerStorageError as exc:
|
|
424
|
+
raise BoilermakerAppException("Error loading TaskGraph after storage", [str(exc)]) from exc
|
|
425
|
+
|
|
426
|
+
if not loaded_graph:
|
|
427
|
+
raise BoilermakerAppException("TaskGraph not found after storing — this should not happen", [])
|
|
428
|
+
|
|
429
|
+
# Publish root tasks with ETag-guarded Scheduled write (same protocol as continue_graph)
|
|
430
|
+
for task in loaded_graph.generate_ready_tasks():
|
|
431
|
+
try:
|
|
432
|
+
result = loaded_graph.schedule_task(task.task_id)
|
|
433
|
+
await self.results_storage.store_task_result(result, etag=result.etag)
|
|
434
|
+
except BoilermakerStorageError:
|
|
435
|
+
logger.error(
|
|
436
|
+
f"Failed to write Scheduled status for root task {task.task_id} in graph "
|
|
437
|
+
f"{graph.graph_id}. Not publishing to avoid inconsistent state.",
|
|
438
|
+
exc_info=True,
|
|
439
|
+
)
|
|
440
|
+
continue
|
|
441
|
+
except ValueError:
|
|
442
|
+
logger.error(
|
|
443
|
+
f"schedule_task raised ValueError for root task {task.task_id} in graph "
|
|
444
|
+
f"{graph.graph_id}. Skipping.",
|
|
445
|
+
exc_info=True,
|
|
446
|
+
)
|
|
447
|
+
continue
|
|
418
448
|
await self.publish_task(task)
|
|
419
449
|
|
|
420
450
|
return graph
|
|
@@ -4,12 +4,11 @@ import typing
|
|
|
4
4
|
from azure.servicebus.aio import ServiceBusReceiver
|
|
5
5
|
|
|
6
6
|
from boilermaker.storage.base import StorageInterface
|
|
7
|
-
from boilermaker.task import Task
|
|
7
|
+
from boilermaker.task import Task, TaskHandler
|
|
8
8
|
|
|
9
9
|
from .common import (
|
|
10
10
|
MessageActions,
|
|
11
11
|
TaskEvaluatorBase,
|
|
12
|
-
TaskHandler,
|
|
13
12
|
TaskHandlerRegistry,
|
|
14
13
|
TaskPublisher,
|
|
15
14
|
)
|
|
@@ -3,7 +3,7 @@ import logging
|
|
|
3
3
|
import traceback
|
|
4
4
|
import typing
|
|
5
5
|
from abc import abstractmethod
|
|
6
|
-
from collections.abc import Awaitable
|
|
6
|
+
from collections.abc import Awaitable
|
|
7
7
|
from functools import cached_property
|
|
8
8
|
from json.decoder import JSONDecodeError
|
|
9
9
|
|
|
@@ -21,14 +21,12 @@ from pydantic import ValidationError
|
|
|
21
21
|
from boilermaker import exc, sample
|
|
22
22
|
from boilermaker.storage import StorageInterface
|
|
23
23
|
from boilermaker.task import Task, TaskResult, TaskStatus
|
|
24
|
+
from boilermaker.task import types as task_types
|
|
24
25
|
|
|
25
26
|
tracer: trace.Tracer = trace.get_tracer(__name__)
|
|
26
27
|
logger = logging.getLogger("boilermaker.app")
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
# Common Types used when evaluating tasks
|
|
30
|
-
TaskHandler: typing.TypeAlias = Callable[..., Awaitable[typing.Any]]
|
|
31
|
-
TaskHandlerRegistry: typing.TypeAlias = dict[str, TaskHandler]
|
|
29
|
+
TaskHandlerRegistry: typing.TypeAlias = dict[str, task_types.TaskHandler]
|
|
32
30
|
|
|
33
31
|
|
|
34
32
|
class TaskPublisher(typing.Protocol):
|
|
@@ -52,9 +50,7 @@ class MessageActions:
|
|
|
52
50
|
"""
|
|
53
51
|
|
|
54
52
|
@classmethod
|
|
55
|
-
async def task_decoder(
|
|
56
|
-
cls, msg: ServiceBusReceivedMessage, receiver: ServiceBusReceiver
|
|
57
|
-
) -> Task | None:
|
|
53
|
+
async def task_decoder(cls, msg: ServiceBusReceivedMessage, receiver: ServiceBusReceiver) -> Task | None:
|
|
58
54
|
"""Decode a ServiceBusReceivedMessage into a Task."""
|
|
59
55
|
try:
|
|
60
56
|
task = Task.model_validate_json(str(msg))
|
|
@@ -62,10 +58,7 @@ class MessageActions:
|
|
|
62
58
|
return task
|
|
63
59
|
except (JSONDecodeError, ValidationError):
|
|
64
60
|
# This task is not parseable
|
|
65
|
-
log_err_msg = (
|
|
66
|
-
f"Invalid task sequence_number={msg.sequence_number} "
|
|
67
|
-
f"exc_info={traceback.format_exc()}"
|
|
68
|
-
)
|
|
61
|
+
log_err_msg = f"Invalid task sequence_number={msg.sequence_number} exc_info={traceback.format_exc()}"
|
|
69
62
|
logger.error(log_err_msg)
|
|
70
63
|
await receiver.dead_letter_message(
|
|
71
64
|
msg,
|
|
@@ -75,17 +68,13 @@ class MessageActions:
|
|
|
75
68
|
return None
|
|
76
69
|
|
|
77
70
|
@staticmethod
|
|
78
|
-
async def abandon_message(
|
|
79
|
-
msg: ServiceBusReceivedMessage, receiver: ServiceBusReceiver
|
|
80
|
-
) -> None:
|
|
71
|
+
async def abandon_message(msg: ServiceBusReceivedMessage, receiver: ServiceBusReceiver) -> None:
|
|
81
72
|
"""Abandon the message being processed."""
|
|
82
73
|
if msg is not None:
|
|
83
74
|
sequence_number = msg.sequence_number
|
|
84
75
|
try:
|
|
85
76
|
await receiver.abandon_message(msg)
|
|
86
|
-
logger.warning(
|
|
87
|
-
f"Abandoning message: returned to queue {sequence_number=}"
|
|
88
|
-
)
|
|
77
|
+
logger.warning(f"Abandoning message: returned to queue {sequence_number=}")
|
|
89
78
|
except (
|
|
90
79
|
MessageAlreadySettled,
|
|
91
80
|
MessageLockLostError,
|
|
@@ -95,18 +84,13 @@ class MessageActions:
|
|
|
95
84
|
logger.error(err_msg)
|
|
96
85
|
raise exc.BoilermakerTaskLeaseLost(err_msg) from None
|
|
97
86
|
except ServiceBusError as sb_exc:
|
|
98
|
-
err_msg = (
|
|
99
|
-
f"ServiceBusError requeuing message {sequence_number=} "
|
|
100
|
-
f"exc_info={traceback.format_exc()}"
|
|
101
|
-
)
|
|
87
|
+
err_msg = f"ServiceBusError requeuing message {sequence_number=} exc_info={traceback.format_exc()}"
|
|
102
88
|
logger.error(err_msg)
|
|
103
89
|
raise exc.BoilermakerServiceBusError(err_msg) from sb_exc
|
|
104
90
|
return None
|
|
105
91
|
|
|
106
92
|
@staticmethod
|
|
107
|
-
async def complete_message(
|
|
108
|
-
msg: ServiceBusReceivedMessage, receiver: ServiceBusReceiver
|
|
109
|
-
):
|
|
93
|
+
async def complete_message(msg: ServiceBusReceivedMessage, receiver: ServiceBusReceiver):
|
|
110
94
|
"""Complete the current message being processed."""
|
|
111
95
|
try:
|
|
112
96
|
await receiver.complete_message(msg)
|
|
@@ -115,10 +99,7 @@ class MessageActions:
|
|
|
115
99
|
MessageLockLostError,
|
|
116
100
|
SessionLockLostError,
|
|
117
101
|
):
|
|
118
|
-
logmsg = (
|
|
119
|
-
f"Failed to settle message sequence_number={msg.sequence_number} "
|
|
120
|
-
f"exc_info={traceback.format_exc()}"
|
|
121
|
-
)
|
|
102
|
+
logmsg = f"Failed to settle message sequence_number={msg.sequence_number} exc_info={traceback.format_exc()}"
|
|
122
103
|
logger.error(logmsg)
|
|
123
104
|
raise exc.BoilermakerTaskLeaseLost(msg) from None
|
|
124
105
|
except ServiceBusError as sb_exc:
|
|
@@ -150,8 +131,7 @@ class MessageActions:
|
|
|
150
131
|
SessionLockLostError,
|
|
151
132
|
):
|
|
152
133
|
logmsg = (
|
|
153
|
-
f"Failed to deadletter message sequence_number={msg.sequence_number} "
|
|
154
|
-
f"exc_info={traceback.format_exc()}"
|
|
134
|
+
f"Failed to deadletter message sequence_number={msg.sequence_number} exc_info={traceback.format_exc()}"
|
|
155
135
|
)
|
|
156
136
|
logger.error(logmsg)
|
|
157
137
|
raise exc.BoilermakerTaskLeaseLost(logmsg) from None
|
|
@@ -180,8 +160,7 @@ class MessageActions:
|
|
|
180
160
|
return await receiver.renew_message_lock(msg)
|
|
181
161
|
except (MessageLockLostError, MessageAlreadySettled, SessionLockLostError):
|
|
182
162
|
logmsg = (
|
|
183
|
-
f"Failed to renew message lock sequence_number={msg.sequence_number} "
|
|
184
|
-
f"exc_info={traceback.format_exc()}"
|
|
163
|
+
f"Failed to renew message lock sequence_number={msg.sequence_number} exc_info={traceback.format_exc()}"
|
|
185
164
|
)
|
|
186
165
|
logger.error(logmsg)
|
|
187
166
|
raise exc.BoilermakerTaskLeaseLost(logmsg) from None
|
|
@@ -203,9 +182,7 @@ class MessageActions:
|
|
|
203
182
|
):
|
|
204
183
|
"""Deadletter or complete the current task based on its configuration."""
|
|
205
184
|
if task.msg is None:
|
|
206
|
-
logger.warning(
|
|
207
|
-
"No current message to settle for deadletter_or_complete_task"
|
|
208
|
-
)
|
|
185
|
+
logger.warning("No current message to settle for deadletter_or_complete_task")
|
|
209
186
|
return None
|
|
210
187
|
|
|
211
188
|
if task.should_dead_letter:
|
|
@@ -272,22 +249,16 @@ class TaskEvaluatorBase:
|
|
|
272
249
|
# Message handling actions
|
|
273
250
|
async def abandon_current_message(self) -> None:
|
|
274
251
|
if self.current_msg is not None:
|
|
275
|
-
return await MessageActions.abandon_message(
|
|
276
|
-
self.current_msg, self._receiver
|
|
277
|
-
)
|
|
252
|
+
return await MessageActions.abandon_message(self.current_msg, self._receiver)
|
|
278
253
|
|
|
279
254
|
async def complete_message(self) -> None:
|
|
280
255
|
if self.current_msg is not None:
|
|
281
|
-
return await MessageActions.complete_message(
|
|
282
|
-
self.current_msg, self._receiver
|
|
283
|
-
)
|
|
256
|
+
return await MessageActions.complete_message(self.current_msg, self._receiver)
|
|
284
257
|
|
|
285
258
|
async def renew_message_lock(self) -> datetime.datetime | None:
|
|
286
259
|
"""Renew the lock on the current message being processed."""
|
|
287
260
|
if self.current_msg is not None:
|
|
288
|
-
return await MessageActions.renew_message_lock(
|
|
289
|
-
self.current_msg, self._receiver
|
|
290
|
-
)
|
|
261
|
+
return await MessageActions.renew_message_lock(self.current_msg, self._receiver)
|
|
291
262
|
return None
|
|
292
263
|
|
|
293
264
|
async def deadletter_or_complete_task(
|
|
@@ -295,9 +266,7 @@ class TaskEvaluatorBase:
|
|
|
295
266
|
reason: str,
|
|
296
267
|
detail: Exception | str | None = None,
|
|
297
268
|
) -> None:
|
|
298
|
-
return await MessageActions.deadletter_or_complete_task(
|
|
299
|
-
self.task, self._receiver, reason, detail=detail
|
|
300
|
-
)
|
|
269
|
+
return await MessageActions.deadletter_or_complete_task(self.task, self._receiver, reason, detail=detail)
|
|
301
270
|
|
|
302
271
|
async def __call__(self) -> TaskResult | None:
|
|
303
272
|
"""Call pre-processing hook and then `message_handler`."""
|
|
@@ -306,9 +275,7 @@ class TaskEvaluatorBase:
|
|
|
306
275
|
if not await self.pre_process():
|
|
307
276
|
return None
|
|
308
277
|
except exc.BoilermakerUnregisteredFunction:
|
|
309
|
-
await self.deadletter_or_complete_task(
|
|
310
|
-
"ExpectationFailed", detail="Pre-processing expectation failed"
|
|
311
|
-
)
|
|
278
|
+
await self.deadletter_or_complete_task("ExpectationFailed", detail="Pre-processing expectation failed")
|
|
312
279
|
return TaskResult(
|
|
313
280
|
task_id=self.task.task_id,
|
|
314
281
|
graph_id=self.task.graph_id,
|
|
@@ -318,9 +285,7 @@ class TaskEvaluatorBase:
|
|
|
318
285
|
)
|
|
319
286
|
except Exception:
|
|
320
287
|
logger.error("Exception in pre_process", exc_info=True)
|
|
321
|
-
await self.deadletter_or_complete_task(
|
|
322
|
-
"ProcessingError", detail="Pre-processing exception"
|
|
323
|
-
)
|
|
288
|
+
await self.deadletter_or_complete_task("ProcessingError", detail="Pre-processing exception")
|
|
324
289
|
return TaskResult(
|
|
325
290
|
task_id=self.task.task_id,
|
|
326
291
|
graph_id=self.task.graph_id,
|
|
@@ -351,7 +316,5 @@ class TaskEvaluatorBase:
|
|
|
351
316
|
return False
|
|
352
317
|
|
|
353
318
|
if not self.function_registry.get(self.task.function_name):
|
|
354
|
-
raise exc.BoilermakerUnregisteredFunction(
|
|
355
|
-
f"Missing registered function {self.task.function_name}"
|
|
356
|
-
)
|
|
319
|
+
raise exc.BoilermakerUnregisteredFunction(f"Missing registered function {self.task.function_name}")
|
|
357
320
|
return True
|
|
@@ -7,8 +7,7 @@ from boilermaker.exc import BoilermakerUnregisteredFunction
|
|
|
7
7
|
from boilermaker.failure import TaskFailureResult
|
|
8
8
|
from boilermaker.retries import RetryException
|
|
9
9
|
from boilermaker.task import Task, TaskResult, TaskStatus
|
|
10
|
-
|
|
11
|
-
from .common import TaskHandler
|
|
10
|
+
from boilermaker.task.types import TaskHandler
|
|
12
11
|
|
|
13
12
|
logger = logging.getLogger("boilermaker.app")
|
|
14
13
|
|
|
@@ -45,9 +44,7 @@ async def eval_task(
|
|
|
45
44
|
logger.info(f"[{task.function_name}] Begin Task {task.sequence_number=}")
|
|
46
45
|
|
|
47
46
|
if function is None:
|
|
48
|
-
raise BoilermakerUnregisteredFunction(
|
|
49
|
-
f"Function {task.function_name} not found in registry"
|
|
50
|
-
)
|
|
47
|
+
raise BoilermakerUnregisteredFunction(f"Function {task.function_name} not found in registry")
|
|
51
48
|
|
|
52
49
|
try:
|
|
53
50
|
result = await function(
|
|
@@ -78,8 +75,7 @@ async def eval_task(
|
|
|
78
75
|
status=TaskStatus.Success,
|
|
79
76
|
)
|
|
80
77
|
logger.info(
|
|
81
|
-
f"[{task.function_name}] Completed Task {task.sequence_number=} "
|
|
82
|
-
f"in {time.monotonic() - start:.3f}s"
|
|
78
|
+
f"[{task.function_name}] Completed Task {task.sequence_number=} in {time.monotonic() - start:.3f}s"
|
|
83
79
|
)
|
|
84
80
|
except RetryException as retry:
|
|
85
81
|
# A retry has been requested:
|
|
@@ -62,9 +62,7 @@ class ResultsStorageTaskEvaluator(TaskEvaluatorBase):
|
|
|
62
62
|
await self.complete_message()
|
|
63
63
|
message_settled = True
|
|
64
64
|
except exc.BoilermakerTaskLeaseLost:
|
|
65
|
-
logger.error(
|
|
66
|
-
f"Lost message lease when trying to complete early for task {self.task.function_name}"
|
|
67
|
-
)
|
|
65
|
+
logger.error(f"Lost message lease when trying to complete early for task {self.task.function_name}")
|
|
68
66
|
return TaskResult(
|
|
69
67
|
task_id=self.task.task_id,
|
|
70
68
|
graph_id=self.task.graph_id,
|
|
@@ -89,8 +87,7 @@ class ResultsStorageTaskEvaluator(TaskEvaluatorBase):
|
|
|
89
87
|
message_settled = True
|
|
90
88
|
except exc.BoilermakerTaskLeaseLost:
|
|
91
89
|
logger.error(
|
|
92
|
-
f"Lost message lease when trying to deadletter/complete "
|
|
93
|
-
f" for task {self.task.function_name}"
|
|
90
|
+
f"Lost message lease when trying to deadletter/complete for task {self.task.function_name}"
|
|
94
91
|
)
|
|
95
92
|
return TaskResult(
|
|
96
93
|
task_id=self.task.task_id,
|
|
@@ -45,9 +45,7 @@ class NoStorageEvaluator(TaskEvaluatorBase):
|
|
|
45
45
|
await self.complete_message()
|
|
46
46
|
message_settled = True
|
|
47
47
|
except exc.BoilermakerTaskLeaseLost:
|
|
48
|
-
logger.error(
|
|
49
|
-
f"Lost message lease when trying to complete early for task {self.task.function_name}"
|
|
50
|
-
)
|
|
48
|
+
logger.error(f"Lost message lease when trying to complete early for task {self.task.function_name}")
|
|
51
49
|
return TaskResult(
|
|
52
50
|
task_id=self.task.task_id,
|
|
53
51
|
graph_id=self.task.graph_id,
|
|
@@ -71,8 +69,7 @@ class NoStorageEvaluator(TaskEvaluatorBase):
|
|
|
71
69
|
message_settled = True
|
|
72
70
|
except exc.BoilermakerTaskLeaseLost:
|
|
73
71
|
logger.error(
|
|
74
|
-
f"Lost message lease when trying to deadletter/complete "
|
|
75
|
-
f" for task {self.task.function_name}"
|
|
72
|
+
f"Lost message lease when trying to deadletter/complete for task {self.task.function_name}"
|
|
76
73
|
)
|
|
77
74
|
return TaskResult(
|
|
78
75
|
task_id=self.task.task_id,
|