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.
- {tasktiger-0.26.2/tasktiger.egg-info → tasktiger-0.27.0}/PKG-INFO +13 -1
- {tasktiger-0.26.2 → tasktiger-0.27.0}/README.rst +12 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/__init__.py +1 -1
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/_internal.py +2 -2
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/schedule.py +5 -3
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/stats.py +12 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/task.py +3 -5
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/tasktiger.py +8 -6
- tasktiger-0.27.0/tasktiger/types.py +9 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/worker.py +9 -3
- {tasktiger-0.26.2 → tasktiger-0.27.0/tasktiger.egg-info}/PKG-INFO +13 -1
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_workers.py +37 -0
- tasktiger-0.26.2/tasktiger/types.py +0 -3
- {tasktiger-0.26.2 → tasktiger-0.27.0}/LICENSE +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/MANIFEST.in +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/pyproject.toml +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/setup.cfg +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/setup.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/exceptions.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/executor.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/flask_script.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/logging.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/lua/move_task.lua +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/lua/semaphore.lua +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/migrations.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/redis_scripts.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/redis_semaphore.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/retry.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/rollbar.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/runner.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/test_helpers.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/timeouts.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger/utils.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger.egg-info/SOURCES.txt +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger.egg-info/dependency_links.txt +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger.egg-info/entry_points.txt +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger.egg-info/requires.txt +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tasktiger.egg-info/top_level.txt +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/__init__.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/config.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/conftest.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/tasks.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/tasks_periodic.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_base.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_context_manager.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_lazy_init.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_logging.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_migrations.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_periodic.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_queue_size.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_redis_scripts.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_semaphore.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_stats.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/test_task.py +0 -0
- {tasktiger-0.26.2 → tasktiger-0.27.0}/tests/utils.py +0 -0
- {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.
|
|
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
|
|
@@ -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) ->
|
|
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
|
|
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
|
-
) ->
|
|
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
|
-
) ->
|
|
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[
|
|
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
|
-
|
|
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.
|
|
871
|
-
|
|
872
|
-
|
|
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.
|
|
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()]
|
|
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
|
|
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
|
|
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
|
|
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
|