pydocket 0.6.1__py3-none-any.whl → 0.6.2__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/__init__.py +11 -9
- docket/cli.py +8 -0
- docket/dependencies.py +41 -1
- docket/execution.py +5 -0
- docket/worker.py +25 -1
- {pydocket-0.6.1.dist-info → pydocket-0.6.2.dist-info}/METADATA +1 -1
- pydocket-0.6.2.dist-info/RECORD +16 -0
- pydocket-0.6.1.dist-info/RECORD +0 -16
- {pydocket-0.6.1.dist-info → pydocket-0.6.2.dist-info}/WHEEL +0 -0
- {pydocket-0.6.1.dist-info → pydocket-0.6.2.dist-info}/entry_points.txt +0 -0
- {pydocket-0.6.1.dist-info → pydocket-0.6.2.dist-info}/licenses/LICENSE +0 -0
docket/__init__.py
CHANGED
|
@@ -17,6 +17,7 @@ from .dependencies import (
|
|
|
17
17
|
ExponentialRetry,
|
|
18
18
|
Perpetual,
|
|
19
19
|
Retry,
|
|
20
|
+
TaskArgument,
|
|
20
21
|
TaskKey,
|
|
21
22
|
TaskLogger,
|
|
22
23
|
Timeout,
|
|
@@ -26,19 +27,20 @@ from .execution import Execution
|
|
|
26
27
|
from .worker import Worker
|
|
27
28
|
|
|
28
29
|
__all__ = [
|
|
29
|
-
"
|
|
30
|
-
"Worker",
|
|
31
|
-
"Execution",
|
|
30
|
+
"__version__",
|
|
32
31
|
"CurrentDocket",
|
|
33
|
-
"CurrentWorker",
|
|
34
32
|
"CurrentExecution",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
33
|
+
"CurrentWorker",
|
|
34
|
+
"Depends",
|
|
35
|
+
"Docket",
|
|
36
|
+
"Execution",
|
|
38
37
|
"ExponentialRetry",
|
|
39
38
|
"Logged",
|
|
40
39
|
"Perpetual",
|
|
40
|
+
"Retry",
|
|
41
|
+
"TaskArgument",
|
|
42
|
+
"TaskKey",
|
|
43
|
+
"TaskLogger",
|
|
41
44
|
"Timeout",
|
|
42
|
-
"
|
|
43
|
-
"__version__",
|
|
45
|
+
"Worker",
|
|
44
46
|
]
|
docket/cli.py
CHANGED
|
@@ -245,6 +245,13 @@ def worker(
|
|
|
245
245
|
envvar="DOCKET_WORKER_SCHEDULING_RESOLUTION",
|
|
246
246
|
),
|
|
247
247
|
] = timedelta(milliseconds=250),
|
|
248
|
+
schedule_automatic_tasks: Annotated[
|
|
249
|
+
bool,
|
|
250
|
+
typer.Option(
|
|
251
|
+
"--schedule-automatic-tasks",
|
|
252
|
+
help="Schedule automatic tasks",
|
|
253
|
+
),
|
|
254
|
+
] = True,
|
|
248
255
|
until_finished: Annotated[
|
|
249
256
|
bool,
|
|
250
257
|
typer.Option(
|
|
@@ -270,6 +277,7 @@ def worker(
|
|
|
270
277
|
reconnection_delay=reconnection_delay,
|
|
271
278
|
minimum_check_interval=minimum_check_interval,
|
|
272
279
|
scheduling_resolution=scheduling_resolution,
|
|
280
|
+
schedule_automatic_tasks=schedule_automatic_tasks,
|
|
273
281
|
until_finished=until_finished,
|
|
274
282
|
metrics_port=metrics_port,
|
|
275
283
|
tasks=tasks,
|
docket/dependencies.py
CHANGED
|
@@ -79,6 +79,22 @@ def TaskKey() -> str:
|
|
|
79
79
|
return cast(str, _TaskKey())
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
class _TaskArgument(Dependency):
|
|
83
|
+
parameter: str | None
|
|
84
|
+
|
|
85
|
+
def __init__(self, parameter: str | None = None) -> None:
|
|
86
|
+
self.parameter = parameter
|
|
87
|
+
|
|
88
|
+
async def __aenter__(self) -> Any:
|
|
89
|
+
assert self.parameter is not None
|
|
90
|
+
execution = self.execution.get()
|
|
91
|
+
return execution.get_argument(self.parameter)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def TaskArgument(parameter: str | None = None) -> Any:
|
|
95
|
+
return cast(Any, _TaskArgument(parameter))
|
|
96
|
+
|
|
97
|
+
|
|
82
98
|
class _TaskLogger(Dependency):
|
|
83
99
|
async def __aenter__(self) -> logging.LoggerAdapter[logging.Logger]:
|
|
84
100
|
execution = self.execution.get()
|
|
@@ -275,6 +291,11 @@ class _Depends(Dependency, Generic[R]):
|
|
|
275
291
|
parameters = get_dependency_parameters(function)
|
|
276
292
|
|
|
277
293
|
for parameter, dependency in parameters.items():
|
|
294
|
+
# Special case for TaskArguments, they are "magical" and infer the parameter
|
|
295
|
+
# they refer to from the parameter name (unless otherwise specified)
|
|
296
|
+
if isinstance(dependency, _TaskArgument) and not dependency.parameter:
|
|
297
|
+
dependency.parameter = parameter
|
|
298
|
+
|
|
278
299
|
arguments[parameter] = await stack.enter_async_context(dependency)
|
|
279
300
|
|
|
280
301
|
return arguments
|
|
@@ -338,6 +359,12 @@ def validate_dependencies(function: TaskFunction) -> None:
|
|
|
338
359
|
)
|
|
339
360
|
|
|
340
361
|
|
|
362
|
+
class FailedDependency:
|
|
363
|
+
def __init__(self, parameter: str, error: Exception) -> None:
|
|
364
|
+
self.parameter = parameter
|
|
365
|
+
self.error = error
|
|
366
|
+
|
|
367
|
+
|
|
341
368
|
@asynccontextmanager
|
|
342
369
|
async def resolved_dependencies(
|
|
343
370
|
worker: "Worker", execution: Execution
|
|
@@ -361,6 +388,19 @@ async def resolved_dependencies(
|
|
|
361
388
|
arguments[parameter] = kwargs[parameter]
|
|
362
389
|
continue
|
|
363
390
|
|
|
364
|
-
|
|
391
|
+
# Special case for TaskArguments, they are "magical" and infer the parameter
|
|
392
|
+
# they refer to from the parameter name (unless otherwise specified). At
|
|
393
|
+
# the top-level task function call, it doesn't make sense to specify one
|
|
394
|
+
# _without_ a parameter name, so we'll call that a failed dependency.
|
|
395
|
+
if isinstance(dependency, _TaskArgument) and not dependency.parameter:
|
|
396
|
+
arguments[parameter] = FailedDependency(
|
|
397
|
+
parameter, ValueError("No parameter name specified")
|
|
398
|
+
)
|
|
399
|
+
continue
|
|
400
|
+
|
|
401
|
+
try:
|
|
402
|
+
arguments[parameter] = await stack.enter_async_context(dependency)
|
|
403
|
+
except Exception as error:
|
|
404
|
+
arguments[parameter] = FailedDependency(parameter, error)
|
|
365
405
|
|
|
366
406
|
yield arguments
|
docket/execution.py
CHANGED
|
@@ -83,6 +83,11 @@ class Execution:
|
|
|
83
83
|
"docket.attempt": self.attempt,
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
def get_argument(self, parameter: str) -> Any:
|
|
87
|
+
signature = get_signature(self.function)
|
|
88
|
+
bound_args = signature.bind(*self.args, **self.kwargs)
|
|
89
|
+
return bound_args.arguments[parameter]
|
|
90
|
+
|
|
86
91
|
def call_repr(self) -> str:
|
|
87
92
|
arguments: list[str] = []
|
|
88
93
|
function_name = self.function.__name__
|
docket/worker.py
CHANGED
|
@@ -22,6 +22,7 @@ from docket.execution import get_signature
|
|
|
22
22
|
|
|
23
23
|
from .dependencies import (
|
|
24
24
|
Dependency,
|
|
25
|
+
FailedDependency,
|
|
25
26
|
Perpetual,
|
|
26
27
|
Retry,
|
|
27
28
|
Timeout,
|
|
@@ -71,6 +72,7 @@ class Worker:
|
|
|
71
72
|
reconnection_delay: timedelta
|
|
72
73
|
minimum_check_interval: timedelta
|
|
73
74
|
scheduling_resolution: timedelta
|
|
75
|
+
schedule_automatic_tasks: bool
|
|
74
76
|
|
|
75
77
|
def __init__(
|
|
76
78
|
self,
|
|
@@ -81,6 +83,7 @@ class Worker:
|
|
|
81
83
|
reconnection_delay: timedelta = timedelta(seconds=5),
|
|
82
84
|
minimum_check_interval: timedelta = timedelta(milliseconds=250),
|
|
83
85
|
scheduling_resolution: timedelta = timedelta(milliseconds=250),
|
|
86
|
+
schedule_automatic_tasks: bool = True,
|
|
84
87
|
) -> None:
|
|
85
88
|
self.docket = docket
|
|
86
89
|
self.name = name or f"worker:{uuid4()}"
|
|
@@ -89,6 +92,7 @@ class Worker:
|
|
|
89
92
|
self.reconnection_delay = reconnection_delay
|
|
90
93
|
self.minimum_check_interval = minimum_check_interval
|
|
91
94
|
self.scheduling_resolution = scheduling_resolution
|
|
95
|
+
self.schedule_automatic_tasks = schedule_automatic_tasks
|
|
92
96
|
|
|
93
97
|
async def __aenter__(self) -> Self:
|
|
94
98
|
self._heartbeat_task = asyncio.create_task(self._heartbeat())
|
|
@@ -134,6 +138,7 @@ class Worker:
|
|
|
134
138
|
reconnection_delay: timedelta = timedelta(seconds=5),
|
|
135
139
|
minimum_check_interval: timedelta = timedelta(milliseconds=100),
|
|
136
140
|
scheduling_resolution: timedelta = timedelta(milliseconds=250),
|
|
141
|
+
schedule_automatic_tasks: bool = True,
|
|
137
142
|
until_finished: bool = False,
|
|
138
143
|
metrics_port: int | None = None,
|
|
139
144
|
tasks: list[str] = ["docket.tasks:standard_tasks"],
|
|
@@ -151,6 +156,7 @@ class Worker:
|
|
|
151
156
|
reconnection_delay=reconnection_delay,
|
|
152
157
|
minimum_check_interval=minimum_check_interval,
|
|
153
158
|
scheduling_resolution=scheduling_resolution,
|
|
159
|
+
schedule_automatic_tasks=schedule_automatic_tasks,
|
|
154
160
|
) as worker:
|
|
155
161
|
if until_finished:
|
|
156
162
|
await worker.run_until_finished()
|
|
@@ -220,7 +226,8 @@ class Worker:
|
|
|
220
226
|
async def _worker_loop(self, redis: Redis, forever: bool = False):
|
|
221
227
|
worker_stopping = asyncio.Event()
|
|
222
228
|
|
|
223
|
-
|
|
229
|
+
if self.schedule_automatic_tasks:
|
|
230
|
+
await self._schedule_all_automatic_perpetual_tasks()
|
|
224
231
|
|
|
225
232
|
scheduler_task = asyncio.create_task(
|
|
226
233
|
self._scheduler_loop(redis, worker_stopping)
|
|
@@ -520,6 +527,23 @@ class Worker:
|
|
|
520
527
|
await self._delete_known_task(redis, execution)
|
|
521
528
|
|
|
522
529
|
try:
|
|
530
|
+
dependency_failures = {
|
|
531
|
+
k: v
|
|
532
|
+
for k, v in dependencies.items()
|
|
533
|
+
if isinstance(v, FailedDependency)
|
|
534
|
+
}
|
|
535
|
+
if dependency_failures:
|
|
536
|
+
raise ExceptionGroup(
|
|
537
|
+
(
|
|
538
|
+
"Failed to resolve dependencies for parameter(s): "
|
|
539
|
+
+ ", ".join(dependency_failures.keys())
|
|
540
|
+
),
|
|
541
|
+
[
|
|
542
|
+
dependency.error
|
|
543
|
+
for dependency in dependency_failures.values()
|
|
544
|
+
],
|
|
545
|
+
)
|
|
546
|
+
|
|
523
547
|
if timeout := get_single_dependency_of_type(dependencies, Timeout):
|
|
524
548
|
await self._run_function_with_timeout(
|
|
525
549
|
execution, dependencies, timeout
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydocket
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
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
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
docket/__init__.py,sha256=sY1T_NVsXQNOmOhOnfYmZ95dcE_52Ov6DSIVIMZp-1w,869
|
|
2
|
+
docket/__main__.py,sha256=Vkuh7aJ-Bl7QVpVbbkUksAd_hn05FiLmWbc-8kbhZQ4,34
|
|
3
|
+
docket/annotations.py,sha256=6sCgQxsgOjBN6ithFdXulXq4CPNSdyFocwyJ1gK9v2Q,1688
|
|
4
|
+
docket/cli.py,sha256=znHN7eqaD_PFpSFn7iXa_uZlKzVWDrKkrmOd1CNuZRk,20561
|
|
5
|
+
docket/dependencies.py,sha256=ykuJpL_MZMHUPX6ORys1YMHLCjS2Rd8vrQhYu8od-Ro,11682
|
|
6
|
+
docket/docket.py,sha256=KJxgiyOskEHsRQOmfgLpJCYDNNleHI-vEKK3uBPL_K8,21420
|
|
7
|
+
docket/execution.py,sha256=MXrLYjvhPzwqjjQx8CoDCbLqSyT_GI7kqGJtfKiemkY,13790
|
|
8
|
+
docket/instrumentation.py,sha256=bZlGA02JoJcY0J1WGm5_qXDfY0AXKr0ZLAYu67wkeKY,4611
|
|
9
|
+
docket/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
docket/tasks.py,sha256=RIlSM2omh-YDwVnCz6M5MtmK8T_m_s1w2OlRRxDUs6A,1437
|
|
11
|
+
docket/worker.py,sha256=DzqwMWdMuieVNt6J4_99zER7dGoVjVBPS4NlmQJXNdc,27347
|
|
12
|
+
pydocket-0.6.2.dist-info/METADATA,sha256=eFFi2KLLfn9-4fv1tWRSlT4o83R0B0n3NUleZttA86s,13092
|
|
13
|
+
pydocket-0.6.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
+
pydocket-0.6.2.dist-info/entry_points.txt,sha256=4WOk1nUlBsUT5O3RyMci2ImuC5XFswuopElYcLHtD5k,47
|
|
15
|
+
pydocket-0.6.2.dist-info/licenses/LICENSE,sha256=YuVWU_ZXO0K_k2FG8xWKe5RGxV24AhJKTvQmKfqXuyk,1087
|
|
16
|
+
pydocket-0.6.2.dist-info/RECORD,,
|
pydocket-0.6.1.dist-info/RECORD
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
docket/__init__.py,sha256=124XWbyQQHO1lhCoLQ-oheZnu4vNDHIaq4Whb7z3ogI,831
|
|
2
|
-
docket/__main__.py,sha256=Vkuh7aJ-Bl7QVpVbbkUksAd_hn05FiLmWbc-8kbhZQ4,34
|
|
3
|
-
docket/annotations.py,sha256=6sCgQxsgOjBN6ithFdXulXq4CPNSdyFocwyJ1gK9v2Q,1688
|
|
4
|
-
docket/cli.py,sha256=OWql6QFthSbvRCGkIg-ufo26F48z0eCmzRXJYOdyAEc,20309
|
|
5
|
-
docket/dependencies.py,sha256=pkjseBZjdSpgW9g2H4cZ_RXIRZ2ZfdngBCXJGUcbmao,10052
|
|
6
|
-
docket/docket.py,sha256=KJxgiyOskEHsRQOmfgLpJCYDNNleHI-vEKK3uBPL_K8,21420
|
|
7
|
-
docket/execution.py,sha256=f3LLt9bC7ExEZhgde5OBo1faKLYv-8ryfNLXSswo318,13579
|
|
8
|
-
docket/instrumentation.py,sha256=bZlGA02JoJcY0J1WGm5_qXDfY0AXKr0ZLAYu67wkeKY,4611
|
|
9
|
-
docket/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
docket/tasks.py,sha256=RIlSM2omh-YDwVnCz6M5MtmK8T_m_s1w2OlRRxDUs6A,1437
|
|
11
|
-
docket/worker.py,sha256=NrzmfpjHjQaGS8CoTOiKM5Bn88tPh_q2hz9f4hFegSk,26280
|
|
12
|
-
pydocket-0.6.1.dist-info/METADATA,sha256=mxI1OHWe9W9bAyi8QiH69eMSsSk1Dm2oDvh301BJFgo,13092
|
|
13
|
-
pydocket-0.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
-
pydocket-0.6.1.dist-info/entry_points.txt,sha256=4WOk1nUlBsUT5O3RyMci2ImuC5XFswuopElYcLHtD5k,47
|
|
15
|
-
pydocket-0.6.1.dist-info/licenses/LICENSE,sha256=YuVWU_ZXO0K_k2FG8xWKe5RGxV24AhJKTvQmKfqXuyk,1087
|
|
16
|
-
pydocket-0.6.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|