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.
Files changed (61) hide show
  1. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/PKG-INFO +4 -2
  2. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/app.py +40 -10
  3. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/evaluators/__init__.py +1 -2
  4. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/evaluators/common.py +20 -57
  5. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/evaluators/eval.py +3 -7
  6. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/evaluators/results_store.py +2 -5
  7. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/evaluators/simple.py +2 -5
  8. boilermaker_servicebus-1.0.0.dev5/boilermaker/evaluators/task_graph.py +416 -0
  9. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/exc.py +17 -3
  10. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/retries.py +1 -1
  11. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/service_bus.py +1 -1
  12. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/storage/base.py +20 -1
  13. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/storage/blob_storage.py +84 -35
  14. boilermaker_servicebus-1.0.0.dev5/boilermaker/task/__init__.py +20 -0
  15. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/task/graph.py +278 -66
  16. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/task/result.py +0 -1
  17. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/task/task.py +2 -1
  18. boilermaker_servicebus-1.0.0.dev5/boilermaker/task/types.py +18 -0
  19. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/tracing.py +1 -3
  20. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker_servicebus.egg-info/PKG-INFO +4 -2
  21. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker_servicebus.egg-info/SOURCES.txt +1 -0
  22. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker_servicebus.egg-info/requires.txt +4 -1
  23. boilermaker_servicebus-1.0.0.dev5/examples/task_graph_example.py +274 -0
  24. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/pyproject.toml +14 -16
  25. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/conftest.py +4 -0
  26. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/evaluators/test_common.py +7 -21
  27. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/evaluators/test_eval.py +1 -4
  28. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/evaluators/test_simple.py +13 -39
  29. boilermaker_servicebus-1.0.0.dev5/tests/evaluators/test_task_graphs.py +1494 -0
  30. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/graph_factories.py +11 -18
  31. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/storage/test_blob_storage.py +172 -26
  32. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/task/test_graph.py +865 -138
  33. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/task/test_graph_cycle_detection.py +35 -0
  34. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/task/test_result.py +1 -3
  35. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/task/test_task.py +0 -2
  36. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/test_app.py +104 -4
  37. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/test_retries.py +27 -3
  38. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/test_sample.py +1 -3
  39. boilermaker_servicebus-1.0.0.dev2/boilermaker/evaluators/task_graph.py +0 -226
  40. boilermaker_servicebus-1.0.0.dev2/boilermaker/task/__init__.py +0 -6
  41. boilermaker_servicebus-1.0.0.dev2/examples/task_graph_example.py +0 -127
  42. boilermaker_servicebus-1.0.0.dev2/tests/evaluators/test_task_graphs.py +0 -564
  43. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/LICENSE +0 -0
  44. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/README.md +0 -0
  45. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/__init__.py +0 -0
  46. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/config.py +0 -0
  47. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/failure.py +0 -0
  48. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/sample.py +0 -0
  49. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/storage/__init__.py +0 -0
  50. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker/task/task_id.py +0 -0
  51. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker_servicebus.egg-info/dependency_links.txt +0 -0
  52. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/boilermaker_servicebus.egg-info/top_level.txt +0 -0
  53. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/examples/basic.py +0 -0
  54. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/examples/callbacks.py +0 -0
  55. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/setup.cfg +0 -0
  56. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/evaluators/conftest.py +0 -0
  57. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/evaluators/test_eval_factory.py +0 -0
  58. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/evaluators/test_results_store.py +0 -0
  59. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/task/helpers.py +0 -0
  60. {boilermaker_servicebus-1.0.0.dev2 → boilermaker_servicebus-1.0.0.dev5}/tests/test_config.py +0 -0
  61. {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.dev2
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.4
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 # type: ignore
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
- # Publish all ready tasks (should be root nodes with no dependencies)
417
- for task in graph.generate_ready_tasks():
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, Callable
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,