tasktiger 0.26.2__tar.gz → 0.27.0__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 (56) hide show
  1. {tasktiger-0.26.2/tasktiger.egg-info → tasktiger-0.27.0}/PKG-INFO +13 -1
  2. {tasktiger-0.26.2 → tasktiger-0.27.0}/README.rst +12 -0
  3. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/__init__.py +1 -1
  4. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/_internal.py +2 -2
  5. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/schedule.py +5 -3
  6. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/stats.py +12 -0
  7. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/task.py +3 -5
  8. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/tasktiger.py +8 -6
  9. tasktiger-0.27.0/tasktiger/types.py +9 -0
  10. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/worker.py +9 -3
  11. {tasktiger-0.26.2 → tasktiger-0.27.0/tasktiger.egg-info}/PKG-INFO +13 -1
  12. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_workers.py +37 -0
  13. tasktiger-0.26.2/tasktiger/types.py +0 -3
  14. {tasktiger-0.26.2 → tasktiger-0.27.0}/LICENSE +0 -0
  15. {tasktiger-0.26.2 → tasktiger-0.27.0}/MANIFEST.in +0 -0
  16. {tasktiger-0.26.2 → tasktiger-0.27.0}/pyproject.toml +0 -0
  17. {tasktiger-0.26.2 → tasktiger-0.27.0}/setup.cfg +0 -0
  18. {tasktiger-0.26.2 → tasktiger-0.27.0}/setup.py +0 -0
  19. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/exceptions.py +0 -0
  20. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/executor.py +0 -0
  21. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/flask_script.py +0 -0
  22. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/logging.py +0 -0
  23. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/lua/move_task.lua +0 -0
  24. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/lua/semaphore.lua +0 -0
  25. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/migrations.py +0 -0
  26. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/redis_scripts.py +0 -0
  27. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/redis_semaphore.py +0 -0
  28. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/retry.py +0 -0
  29. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/rollbar.py +0 -0
  30. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/runner.py +0 -0
  31. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/test_helpers.py +0 -0
  32. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/timeouts.py +0 -0
  33. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/utils.py +0 -0
  34. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger.egg-info/SOURCES.txt +0 -0
  35. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger.egg-info/dependency_links.txt +0 -0
  36. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger.egg-info/entry_points.txt +0 -0
  37. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger.egg-info/requires.txt +0 -0
  38. {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger.egg-info/top_level.txt +0 -0
  39. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/__init__.py +0 -0
  40. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/config.py +0 -0
  41. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/conftest.py +0 -0
  42. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/tasks.py +0 -0
  43. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/tasks_periodic.py +0 -0
  44. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_base.py +0 -0
  45. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_context_manager.py +0 -0
  46. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_lazy_init.py +0 -0
  47. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_logging.py +0 -0
  48. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_migrations.py +0 -0
  49. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_periodic.py +0 -0
  50. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_queue_size.py +0 -0
  51. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_redis_scripts.py +0 -0
  52. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_semaphore.py +0 -0
  53. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_stats.py +0 -0
  54. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_task.py +0 -0
  55. {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/utils.py +0 -0
  56. {tasktiger-0.26.2 → tasktiger-0.27.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tasktiger
3
- Version: 0.26.2
3
+ Version: 0.27.0
4
4
  Summary: Python task queue
5
5
  Home-page: http://github.com/closeio/tasktiger
6
6
  License: MIT
@@ -212,6 +212,18 @@ decorate and queue tasks. The constructor takes the following arguments:
212
212
  given queues (and their subqueues), unless explicit queues are passed to
213
213
  the command line.
214
214
 
215
+ - ``STATS_INTERVAL``
216
+
217
+ Interval in seconds between worker task runtime statistics (default: 60).
218
+ Set to 0 to disable the stats thread and its callbacks. Queue maintenance
219
+ can occupy a worker outside task-execution statistics. When enabled,
220
+ ``StatsThread.report_maintenance_start()`` and ``report_maintenance_end()``
221
+ bracket the combined scheduled-task promotion and expired-task recovery
222
+ pass, with the end callback called even if either operation raises.
223
+ The hooks allow custom instrumentation; their defaults are no-ops and do
224
+ not change task runtime or utilization statistics. No maintenance callbacks
225
+ run when maintenance is not due or the worker is stopping.
226
+
215
227
  - ``setup_structlog``
216
228
 
217
229
  If set to True, sets up structured logging using ``structlog`` when
@@ -182,6 +182,18 @@ decorate and queue tasks. The constructor takes the following arguments:
182
182
  given queues (and their subqueues), unless explicit queues are passed to
183
183
  the command line.
184
184
 
185
+ - ``STATS_INTERVAL``
186
+
187
+ Interval in seconds between worker task runtime statistics (default: 60).
188
+ Set to 0 to disable the stats thread and its callbacks. Queue maintenance
189
+ can occupy a worker outside task-execution statistics. When enabled,
190
+ ``StatsThread.report_maintenance_start()`` and ``report_maintenance_end()``
191
+ bracket the combined scheduled-task promotion and expired-task recovery
192
+ pass, with the end callback called even if either operation raises.
193
+ The hooks allow custom instrumentation; their defaults are no-ops and do
194
+ not change task runtime or utilization statistics. No maintenance callbacks
195
+ run when maintenance is not due or the worker is stopping.
196
+
185
197
  - ``setup_structlog``
186
198
 
187
199
  If set to True, sets up structured logging using ``structlog`` when
@@ -12,7 +12,7 @@ from .task import Task
12
12
  from .tasktiger import TaskTiger, run_worker
13
13
  from .worker import Worker
14
14
 
15
- __version__ = "0.26.2"
15
+ __version__ = "0.27.0"
16
16
  __all__ = [
17
17
  "TaskTiger",
18
18
  "Worker",
@@ -15,13 +15,13 @@ from typing import (
15
15
  Iterable,
16
16
  List,
17
17
  Optional,
18
- Tuple,
19
18
  Type,
20
19
  TypedDict,
21
20
  Union,
22
21
  )
23
22
 
24
23
  from .exceptions import TaskImportError
24
+ from .types import SerializedRetryStrategy
25
25
 
26
26
  if TYPE_CHECKING:
27
27
  from .task import Task
@@ -134,7 +134,7 @@ def reversed_dotted_parts(s: str) -> Iterable[str]:
134
134
  yield s[:idx]
135
135
 
136
136
 
137
- def serialize_retry_method(retry_method: Any) -> Tuple[str, Tuple]:
137
+ def serialize_retry_method(retry_method: Any) -> SerializedRetryStrategy:
138
138
  if callable(retry_method):
139
139
  return (serialize_func_name(retry_method), ())
140
140
  else:
@@ -1,5 +1,7 @@
1
1
  import datetime
2
- from typing import Callable, Optional, Tuple
2
+ from typing import Optional
3
+
4
+ from .types import ScheduleStrategy
3
5
 
4
6
  __all__ = ["periodic", "cron_expr"]
5
7
 
@@ -40,7 +42,7 @@ def periodic(
40
42
  weeks: int = 0,
41
43
  start_date: Optional[datetime.datetime] = None,
42
44
  end_date: Optional[datetime.datetime] = None,
43
- ) -> Tuple[Callable[..., Optional[datetime.datetime]], Tuple]:
45
+ ) -> ScheduleStrategy:
44
46
  """
45
47
  Periodic task schedule: Use to schedule a task to run periodically,
46
48
  starting from start_date (or None to be active immediately) until end_date
@@ -94,7 +96,7 @@ def cron_expr(
94
96
  expr: str,
95
97
  start_date: Optional[datetime.datetime] = None,
96
98
  end_date: Optional[datetime.datetime] = None,
97
- ) -> Tuple[Callable[..., Optional[datetime.datetime]], Tuple]:
99
+ ) -> ScheduleStrategy:
98
100
  """
99
101
  Periodic task schedule via cron expression: Use to schedule a task to run periodically,
100
102
  starting from start_date (or None to be active immediately) until end_date
@@ -39,6 +39,18 @@ class StatsThread(threading.Thread):
39
39
  self._task_running = False
40
40
  self._task_start_time = None
41
41
 
42
+ def report_maintenance_start(self) -> None:
43
+ """Signal the start of scheduled-task promotion and expired-task recovery.
44
+
45
+ The default is a no-op and does not alter task statistics.
46
+ """
47
+
48
+ def report_maintenance_end(self) -> None:
49
+ """Signal the end of scheduled-task promotion and expired-task recovery.
50
+
51
+ The default is a no-op and does not alter task statistics.
52
+ """
53
+
42
54
  def compute_stats(self) -> None:
43
55
  now = time.monotonic()
44
56
 
@@ -32,7 +32,7 @@ from ._internal import (
32
32
  )
33
33
  from .exceptions import QueueFullException, TaskImportError, TaskNotFound
34
34
  from .runner import BaseRunner, get_runner_class
35
- from .types import RetryStrategy
35
+ from .types import RetryStrategy, SerializedRetryStrategy
36
36
 
37
37
  if TYPE_CHECKING:
38
38
  from . import TaskTiger
@@ -55,9 +55,7 @@ class Task:
55
55
  lock_key: Optional[Collection[str]] = None,
56
56
  retry: Optional[bool] = None,
57
57
  retry_on: Optional[Collection[Type[BaseException]]] = None,
58
- retry_method: Optional[
59
- Union[Callable[[int], float], Tuple[Callable[..., float], Tuple]]
60
- ] = None,
58
+ retry_method: Optional[Union[Callable[[int], float], RetryStrategy]] = None,
61
59
  max_queue_size: Optional[int] = None,
62
60
  max_stored_executions: Optional[int] = None,
63
61
  runner_class: Optional[Type["BaseRunner"]] = None,
@@ -237,7 +235,7 @@ class Task:
237
235
  return self._data.get("unique_key")
238
236
 
239
237
  @property
240
- def retry_method(self) -> Optional[RetryStrategy]:
238
+ def retry_method(self) -> Optional[SerializedRetryStrategy]:
241
239
  if "retry_method" in self._data:
242
240
  retry_func, retry_args = self._data["retry_method"]
243
241
  return retry_func, retry_args
@@ -36,6 +36,7 @@ from .redis_semaphore import Semaphore
36
36
  from .retry import fixed
37
37
  from .runner import BaseRunner
38
38
  from .task import Task
39
+ from .types import RetryStrategy, ScheduleStrategy
39
40
  from .worker import LOCK_REDIS_KEY, Worker
40
41
 
41
42
  __all__ = ["TaskTiger"]
@@ -312,10 +313,13 @@ class TaskTiger:
312
313
  lock_key: Optional[Collection[str]] = None,
313
314
  retry: Optional[bool] = None,
314
315
  retry_on: Optional[Collection[Type[BaseException]]] = None,
315
- retry_method: Optional[
316
- Union[Callable[[int], float], Tuple[Callable[..., float], Tuple]]
316
+ retry_method: Optional[Union[Callable[[int], float], RetryStrategy]] = None,
317
+ schedule: Optional[
318
+ Union[
319
+ Callable[[datetime.datetime], Optional[datetime.datetime]],
320
+ ScheduleStrategy,
321
+ ]
317
322
  ] = None,
318
- schedule: Optional[Callable] = None,
319
323
  batch: bool = False,
320
324
  max_queue_size: Optional[int] = None,
321
325
  max_stored_executions: Optional[int] = None,
@@ -441,9 +445,7 @@ class TaskTiger:
441
445
  when: Optional[Union[datetime.datetime, datetime.timedelta]] = None,
442
446
  retry: Optional[bool] = None,
443
447
  retry_on: Optional[Collection[Type[BaseException]]] = None,
444
- retry_method: Optional[
445
- Union[Callable[[int], float], Tuple[Callable[..., float], Tuple]]
446
- ] = None,
448
+ retry_method: Optional[Union[Callable[[int], float], RetryStrategy]] = None,
447
449
  max_queue_size: Optional[int] = None,
448
450
  max_stored_executions: Optional[int] = None,
449
451
  runner_class: Optional[Type["BaseRunner"]] = None,
@@ -0,0 +1,9 @@
1
+ import datetime
2
+ from typing import Any, Callable, Optional, Sequence, Tuple
3
+
4
+ RetryStrategy = Tuple[Callable[..., float], Tuple[Any, ...]]
5
+ ScheduleStrategy = Tuple[Callable[..., Optional[datetime.datetime]], Tuple[Any, ...]]
6
+
7
+ # A retry strategy as stored with a task. The function is replaced with its
8
+ # import path, and the arguments come back as a list after a JSON round trip.
9
+ SerializedRetryStrategy = Tuple[str, Sequence[Any]]
@@ -867,9 +867,15 @@ class Worker:
867
867
  time.time() - self._last_task_check > self.config["SELECT_TIMEOUT"]
868
868
  and not self._stop_requested
869
869
  ):
870
- self._worker_queue_scheduled_tasks()
871
- self._worker_queue_expired_tasks()
872
- self._last_task_check = time.time()
870
+ if self.stats_thread:
871
+ self.stats_thread.report_maintenance_start()
872
+ try:
873
+ self._worker_queue_scheduled_tasks()
874
+ self._worker_queue_expired_tasks()
875
+ self._last_task_check = time.time()
876
+ finally:
877
+ if self.stats_thread:
878
+ self.stats_thread.report_maintenance_end()
873
879
 
874
880
  def _queue_periodic_tasks(self) -> None:
875
881
  # Only queue periodic tasks for queues this worker is responsible
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tasktiger
3
- Version: 0.26.2
3
+ Version: 0.27.0
4
4
  Summary: Python task queue
5
5
  Home-page: http://github.com/closeio/tasktiger
6
6
  License: MIT
@@ -212,6 +212,18 @@ decorate and queue tasks. The constructor takes the following arguments:
212
212
  given queues (and their subqueues), unless explicit queues are passed to
213
213
  the command line.
214
214
 
215
+ - ``STATS_INTERVAL``
216
+
217
+ Interval in seconds between worker task runtime statistics (default: 60).
218
+ Set to 0 to disable the stats thread and its callbacks. Queue maintenance
219
+ can occupy a worker outside task-execution statistics. When enabled,
220
+ ``StatsThread.report_maintenance_start()`` and ``report_maintenance_end()``
221
+ bracket the combined scheduled-task promotion and expired-task recovery
222
+ pass, with the end callback called even if either operation raises.
223
+ The hooks allow custom instrumentation; their defaults are no-ops and do
224
+ not change task runtime or utilization statistics. No maintenance callbacks
225
+ run when maintenance is not due or the worker is stopping.
226
+
215
227
  - ``setup_structlog``
216
228
 
217
229
  If set to True, sets up structured logging using ``structlog`` when
@@ -3,12 +3,15 @@
3
3
  import datetime
4
4
  import time
5
5
  from multiprocessing import Process
6
+ from unittest import mock
6
7
 
8
+ import pytest
7
9
  from freezefrog import FreezeTime
8
10
 
9
11
  from tasktiger import Task, Worker
10
12
  from tasktiger._internal import ACTIVE
11
13
  from tasktiger.executor import SyncExecutor
14
+ from tasktiger.stats import StatsThread
12
15
  from tasktiger.worker import LOCK_REDIS_KEY
13
16
 
14
17
  from .config import DELAY
@@ -265,3 +268,37 @@ class TestSyncExecutorWorker:
265
268
  # handled by the executor, the task is still active until it times out
266
269
  # and gets requeued by another worker.
267
270
  ensure_queues(active={"default": 1})
271
+
272
+
273
+ @pytest.mark.parametrize("failing_operation", [None, "scheduled", "expired"])
274
+ def test_maintenance_stats_callbacks(tiger, monkeypatch, failing_operation):
275
+ worker = Worker(tiger)
276
+ worker._queue_set = set()
277
+ calls = mock.Mock()
278
+ calls.stats = mock.Mock(spec=StatsThread)
279
+ worker.stats_thread = calls.stats
280
+ monkeypatch.setattr(worker, "_worker_queue_scheduled_tasks", calls.scheduled)
281
+ monkeypatch.setattr(worker, "_worker_queue_expired_tasks", calls.expired)
282
+
283
+ if failing_operation:
284
+ getattr(calls, failing_operation).side_effect = RuntimeError("maintenance")
285
+ with pytest.raises(RuntimeError, match="maintenance"):
286
+ worker._worker_run()
287
+ else:
288
+ worker._worker_run()
289
+
290
+ expected = [mock.call.stats.report_maintenance_start(), mock.call.scheduled()]
291
+ if failing_operation != "scheduled":
292
+ expected.append(mock.call.expired())
293
+ expected.append(mock.call.stats.report_maintenance_end())
294
+ assert calls.mock_calls == expected
295
+
296
+
297
+ def test_maintenance_without_stats(tiger, monkeypatch):
298
+ worker = Worker(tiger)
299
+ worker._queue_set = set()
300
+ calls = mock.Mock()
301
+ monkeypatch.setattr(worker, "_worker_queue_scheduled_tasks", calls.scheduled)
302
+ monkeypatch.setattr(worker, "_worker_queue_expired_tasks", calls.expired)
303
+ worker._worker_run()
304
+ assert calls.mock_calls == [mock.call.scheduled(), mock.call.expired()]
@@ -1,3 +0,0 @@
1
- from typing import Callable, Tuple
2
-
3
- RetryStrategy = Tuple[Callable[..., float], Tuple]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes