pydocket 0.2.0__py3-none-any.whl → 0.2.1__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.

Potentially problematic release.


This version of pydocket might be problematic. Click here for more details.

docket/worker.py CHANGED
@@ -7,6 +7,7 @@ from types import TracebackType
7
7
  from typing import (
8
8
  TYPE_CHECKING,
9
9
  Any,
10
+ Callable,
10
11
  Mapping,
11
12
  Protocol,
12
13
  Self,
@@ -63,6 +64,11 @@ class _stream_due_tasks(Protocol):
63
64
  class Worker:
64
65
  docket: Docket
65
66
  name: str
67
+ concurrency: int
68
+ redelivery_timeout: timedelta
69
+ reconnection_delay: timedelta
70
+ minimum_check_interval: timedelta
71
+ _strike_conditions: list[Callable[[Execution], bool]] = []
66
72
 
67
73
  def __init__(
68
74
  self,
@@ -80,6 +86,10 @@ class Worker:
80
86
  self.reconnection_delay = reconnection_delay
81
87
  self.minimum_check_interval = minimum_check_interval
82
88
 
89
+ self._strike_conditions = [
90
+ docket.strike_list.is_stricken,
91
+ ]
92
+
83
93
  async def __aenter__(self) -> Self:
84
94
  self._heartbeat_task = asyncio.create_task(self._heartbeat())
85
95
 
@@ -151,6 +161,35 @@ class Worker:
151
161
  """Run the worker indefinitely."""
152
162
  return await self._run(forever=True) # pragma: no cover
153
163
 
164
+ async def run_at_most(self, iterations_by_key: Mapping[str, int]) -> None:
165
+ """
166
+ Run the worker until there are no more tasks to process, but limit specified
167
+ task keys to a maximum number of iterations.
168
+
169
+ This is particularly useful for testing self-perpetuating tasks that would
170
+ otherwise run indefinitely.
171
+
172
+ Args:
173
+ iterations_by_key: Maps task keys to their maximum allowed executions
174
+ """
175
+ execution_counts: dict[str, int] = {key: 0 for key in iterations_by_key}
176
+
177
+ def has_reached_max_iterations(execution: Execution) -> bool:
178
+ if execution.key not in iterations_by_key:
179
+ return False
180
+
181
+ if execution_counts[execution.key] >= iterations_by_key[execution.key]:
182
+ return True
183
+
184
+ execution_counts[execution.key] += 1
185
+ return False
186
+
187
+ self._strike_conditions.insert(0, has_reached_max_iterations)
188
+ try:
189
+ await self.run_until_finished()
190
+ finally:
191
+ self._strike_conditions.remove(has_reached_max_iterations)
192
+
154
193
  async def _run(self, forever: bool = False) -> None:
155
194
  logger.info("Starting worker %r with the following tasks:", self.name)
156
195
  for task_name, task in self.docket.tasks.items():
@@ -322,7 +361,7 @@ class Worker:
322
361
  await process_completed_tasks()
323
362
 
324
363
  async def _execute(self, message: RedisMessage) -> None:
325
- log_context: dict[str, str | float] = self._log_context()
364
+ log_context: Mapping[str, str | float] = self._log_context()
326
365
 
327
366
  function_name = message[b"function"].decode()
328
367
  function = self.docket.tasks.get(function_name)
@@ -334,13 +373,13 @@ class Worker:
334
373
 
335
374
  execution = Execution.from_message(function, message)
336
375
 
337
- log_context |= execution.specific_labels()
376
+ log_context = {**log_context, **execution.specific_labels()}
338
377
  counter_labels = {**self.labels(), **execution.general_labels()}
339
378
 
340
379
  arrow = "↬" if execution.attempt > 1 else "↪"
341
380
  call = execution.call_repr()
342
381
 
343
- if self.docket.strike_list.is_stricken(execution):
382
+ if any(condition(execution) for condition in self._strike_conditions):
344
383
  arrow = "🗙"
345
384
  logger.warning("%s %s", arrow, call, extra=log_context)
346
385
  TASKS_STRICKEN.add(1, counter_labels | {"docket.where": "worker"})
@@ -354,7 +393,7 @@ class Worker:
354
393
 
355
394
  start = datetime.now(timezone.utc)
356
395
  punctuality = start - execution.when
357
- log_context["punctuality"] = punctuality.total_seconds()
396
+ log_context = {**log_context, "punctuality": punctuality.total_seconds()}
358
397
  duration = timedelta(0)
359
398
 
360
399
  TASKS_STARTED.add(1, counter_labels)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydocket
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A distributed background task system for Python functions
5
5
  Project-URL: Homepage, https://github.com/chrisguidry/docket
6
6
  Project-URL: Bug Tracker, https://github.com/chrisguidry/docket/issues
@@ -8,9 +8,9 @@ docket/execution.py,sha256=ShP8MoLmxEslk2pAuhKi6KEEKbHdneyQukR9oQwXdjQ,11732
8
8
  docket/instrumentation.py,sha256=SUVhVFf8AX2HAfmi0HPTT_QvQezlGPJEKs_1YAmrCbA,4454
9
9
  docket/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  docket/tasks.py,sha256=RIlSM2omh-YDwVnCz6M5MtmK8T_m_s1w2OlRRxDUs6A,1437
11
- docket/worker.py,sha256=UZIPfAsIhsBsr2tBCgGGkLKU1mJs_nnP8-Retwl3218,19104
12
- pydocket-0.2.0.dist-info/METADATA,sha256=X8Yqvi_cqCqYaGu6ZGr4dMvxqcvy6otYvt-J2jwCHOs,13092
13
- pydocket-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
- pydocket-0.2.0.dist-info/entry_points.txt,sha256=4WOk1nUlBsUT5O3RyMci2ImuC5XFswuopElYcLHtD5k,47
15
- pydocket-0.2.0.dist-info/licenses/LICENSE,sha256=YuVWU_ZXO0K_k2FG8xWKe5RGxV24AhJKTvQmKfqXuyk,1087
16
- pydocket-0.2.0.dist-info/RECORD,,
11
+ docket/worker.py,sha256=DH15hW8QBGHaZdOdkpH7bjYtLEydi4sGh-Ei8lEXGOo,20556
12
+ pydocket-0.2.1.dist-info/METADATA,sha256=9DxwXrPzeTCOlxDGn9JUOzQN-k6OjhAJbiRPeMhcNNo,13092
13
+ pydocket-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
+ pydocket-0.2.1.dist-info/entry_points.txt,sha256=4WOk1nUlBsUT5O3RyMci2ImuC5XFswuopElYcLHtD5k,47
15
+ pydocket-0.2.1.dist-info/licenses/LICENSE,sha256=YuVWU_ZXO0K_k2FG8xWKe5RGxV24AhJKTvQmKfqXuyk,1087
16
+ pydocket-0.2.1.dist-info/RECORD,,