pydocket 0.6.2__py3-none-any.whl → 0.6.3__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/__main__.py +1 -1
- docket/dependencies.py +11 -4
- docket/docket.py +2 -2
- docket/execution.py +43 -4
- docket/worker.py +38 -34
- {pydocket-0.6.2.dist-info → pydocket-0.6.3.dist-info}/METADATA +2 -1
- pydocket-0.6.3.dist-info/RECORD +16 -0
- pydocket-0.6.2.dist-info/RECORD +0 -16
- {pydocket-0.6.2.dist-info → pydocket-0.6.3.dist-info}/WHEEL +0 -0
- {pydocket-0.6.2.dist-info → pydocket-0.6.3.dist-info}/entry_points.txt +0 -0
- {pydocket-0.6.2.dist-info → pydocket-0.6.3.dist-info}/licenses/LICENSE +0 -0
docket/__main__.py
CHANGED
docket/dependencies.py
CHANGED
|
@@ -81,18 +81,25 @@ def TaskKey() -> str:
|
|
|
81
81
|
|
|
82
82
|
class _TaskArgument(Dependency):
|
|
83
83
|
parameter: str | None
|
|
84
|
+
optional: bool
|
|
84
85
|
|
|
85
|
-
def __init__(self, parameter: str | None = None) -> None:
|
|
86
|
+
def __init__(self, parameter: str | None = None, optional: bool = False) -> None:
|
|
86
87
|
self.parameter = parameter
|
|
88
|
+
self.optional = optional
|
|
87
89
|
|
|
88
90
|
async def __aenter__(self) -> Any:
|
|
89
91
|
assert self.parameter is not None
|
|
90
92
|
execution = self.execution.get()
|
|
91
|
-
|
|
93
|
+
try:
|
|
94
|
+
return execution.get_argument(self.parameter)
|
|
95
|
+
except KeyError:
|
|
96
|
+
if self.optional:
|
|
97
|
+
return None
|
|
98
|
+
raise
|
|
92
99
|
|
|
93
100
|
|
|
94
|
-
def TaskArgument(parameter: str | None = None) -> Any:
|
|
95
|
-
return cast(Any, _TaskArgument(parameter))
|
|
101
|
+
def TaskArgument(parameter: str | None = None, optional: bool = False) -> Any:
|
|
102
|
+
return cast(Any, _TaskArgument(parameter, optional))
|
|
96
103
|
|
|
97
104
|
|
|
98
105
|
class _TaskLogger(Dependency):
|
docket/docket.py
CHANGED
|
@@ -23,12 +23,12 @@ from typing import (
|
|
|
23
23
|
cast,
|
|
24
24
|
overload,
|
|
25
25
|
)
|
|
26
|
-
from uuid import uuid4
|
|
27
26
|
|
|
28
27
|
import redis.exceptions
|
|
29
28
|
from opentelemetry import propagate, trace
|
|
30
29
|
from redis.asyncio import ConnectionPool, Redis
|
|
31
30
|
from redis.asyncio.client import Pipeline
|
|
31
|
+
from uuid_extensions import uuid7
|
|
32
32
|
|
|
33
33
|
from .execution import (
|
|
34
34
|
Execution,
|
|
@@ -254,7 +254,7 @@ class Docket:
|
|
|
254
254
|
when = datetime.now(timezone.utc)
|
|
255
255
|
|
|
256
256
|
if key is None:
|
|
257
|
-
key =
|
|
257
|
+
key = str(uuid7())
|
|
258
258
|
|
|
259
259
|
async def scheduler(*args: P.args, **kwargs: P.kwargs) -> Execution:
|
|
260
260
|
execution = Execution(function, args, kwargs, when, key, attempt=1)
|
docket/execution.py
CHANGED
|
@@ -3,15 +3,23 @@ import enum
|
|
|
3
3
|
import inspect
|
|
4
4
|
import logging
|
|
5
5
|
from datetime import datetime
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Awaitable,
|
|
9
|
+
Callable,
|
|
10
|
+
Hashable,
|
|
11
|
+
Literal,
|
|
12
|
+
Mapping,
|
|
13
|
+
Self,
|
|
14
|
+
cast,
|
|
15
|
+
)
|
|
7
16
|
|
|
8
17
|
import cloudpickle # type: ignore[import]
|
|
9
|
-
|
|
10
|
-
from opentelemetry import trace, propagate
|
|
11
18
|
import opentelemetry.context
|
|
19
|
+
from opentelemetry import propagate, trace
|
|
12
20
|
|
|
13
21
|
from .annotations import Logged
|
|
14
|
-
from
|
|
22
|
+
from .instrumentation import message_getter
|
|
15
23
|
|
|
16
24
|
logger: logging.Logger = logging.getLogger(__name__)
|
|
17
25
|
|
|
@@ -117,6 +125,37 @@ class Execution:
|
|
|
117
125
|
return [trace.Link(initiating_context)] if initiating_context.is_valid else []
|
|
118
126
|
|
|
119
127
|
|
|
128
|
+
def compact_signature(signature: inspect.Signature) -> str:
|
|
129
|
+
from .dependencies import Dependency
|
|
130
|
+
|
|
131
|
+
parameters: list[str] = []
|
|
132
|
+
dependencies: int = 0
|
|
133
|
+
|
|
134
|
+
for parameter in signature.parameters.values():
|
|
135
|
+
if isinstance(parameter.default, Dependency):
|
|
136
|
+
dependencies += 1
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
parameter_definition = parameter.name
|
|
140
|
+
if parameter.annotation is not parameter.empty:
|
|
141
|
+
annotation = parameter.annotation
|
|
142
|
+
if hasattr(annotation, "__origin__"):
|
|
143
|
+
annotation = annotation.__args__[0]
|
|
144
|
+
|
|
145
|
+
type_name = getattr(annotation, "__name__", str(annotation))
|
|
146
|
+
parameter_definition = f"{parameter.name}: {type_name}"
|
|
147
|
+
|
|
148
|
+
if parameter.default is not parameter.empty:
|
|
149
|
+
parameter_definition = f"{parameter_definition} = {parameter.default!r}"
|
|
150
|
+
|
|
151
|
+
parameters.append(parameter_definition)
|
|
152
|
+
|
|
153
|
+
if dependencies > 0:
|
|
154
|
+
parameters.append("...")
|
|
155
|
+
|
|
156
|
+
return ", ".join(parameters)
|
|
157
|
+
|
|
158
|
+
|
|
120
159
|
class Operator(enum.StrEnum):
|
|
121
160
|
EQUAL = "=="
|
|
122
161
|
NOT_EQUAL = "!="
|
docket/worker.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
+
import os
|
|
4
|
+
import socket
|
|
3
5
|
import sys
|
|
4
6
|
import time
|
|
5
7
|
from datetime import datetime, timedelta, timezone
|
|
@@ -11,15 +13,12 @@ from typing import (
|
|
|
11
13
|
Self,
|
|
12
14
|
cast,
|
|
13
15
|
)
|
|
14
|
-
from uuid import uuid4
|
|
15
16
|
|
|
16
17
|
from opentelemetry import trace
|
|
17
18
|
from opentelemetry.trace import Tracer
|
|
18
19
|
from redis.asyncio import Redis
|
|
19
20
|
from redis.exceptions import ConnectionError, LockError
|
|
20
21
|
|
|
21
|
-
from docket.execution import get_signature
|
|
22
|
-
|
|
23
22
|
from .dependencies import (
|
|
24
23
|
Dependency,
|
|
25
24
|
FailedDependency,
|
|
@@ -37,6 +36,7 @@ from .docket import (
|
|
|
37
36
|
RedisMessageID,
|
|
38
37
|
RedisReadGroupResponse,
|
|
39
38
|
)
|
|
39
|
+
from .execution import compact_signature, get_signature
|
|
40
40
|
from .instrumentation import (
|
|
41
41
|
QUEUE_DEPTH,
|
|
42
42
|
REDIS_DISRUPTIONS,
|
|
@@ -86,7 +86,7 @@ class Worker:
|
|
|
86
86
|
schedule_automatic_tasks: bool = True,
|
|
87
87
|
) -> None:
|
|
88
88
|
self.docket = docket
|
|
89
|
-
self.name = name or f"
|
|
89
|
+
self.name = name or f"{socket.gethostname()}#{os.getpid()}"
|
|
90
90
|
self.concurrency = concurrency
|
|
91
91
|
self.redelivery_timeout = redelivery_timeout
|
|
92
92
|
self.reconnection_delay = reconnection_delay
|
|
@@ -205,10 +205,7 @@ class Worker:
|
|
|
205
205
|
self._execution_counts = {}
|
|
206
206
|
|
|
207
207
|
async def _run(self, forever: bool = False) -> None:
|
|
208
|
-
|
|
209
|
-
for task_name, task in self.docket.tasks.items():
|
|
210
|
-
signature = get_signature(task)
|
|
211
|
-
logger.info("* %s%s", task_name, signature)
|
|
208
|
+
self._startup_log()
|
|
212
209
|
|
|
213
210
|
while True:
|
|
214
211
|
try:
|
|
@@ -506,6 +503,8 @@ class Worker:
|
|
|
506
503
|
arrow = "↬" if execution.attempt > 1 else "↪"
|
|
507
504
|
logger.info("%s [%s] %s", arrow, ms(punctuality), call, extra=log_context)
|
|
508
505
|
|
|
506
|
+
dependencies: dict[str, Dependency] = {}
|
|
507
|
+
|
|
509
508
|
with tracer.start_as_current_span(
|
|
510
509
|
execution.function.__name__,
|
|
511
510
|
kind=trace.SpanKind.CONSUMER,
|
|
@@ -516,17 +515,17 @@ class Worker:
|
|
|
516
515
|
},
|
|
517
516
|
links=execution.incoming_span_links(),
|
|
518
517
|
):
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
518
|
+
try:
|
|
519
|
+
async with resolved_dependencies(self, execution) as dependencies:
|
|
520
|
+
# Preemptively reschedule the perpetual task for the future, or clear
|
|
521
|
+
# the known task key for this task
|
|
522
|
+
rescheduled = await self._perpetuate_if_requested(
|
|
523
|
+
execution, dependencies
|
|
524
|
+
)
|
|
525
|
+
if not rescheduled:
|
|
526
|
+
async with self.docket.redis() as redis:
|
|
527
|
+
await self._delete_known_task(redis, execution)
|
|
528
528
|
|
|
529
|
-
try:
|
|
530
529
|
dependency_failures = {
|
|
531
530
|
k: v
|
|
532
531
|
for k, v in dependencies.items()
|
|
@@ -568,24 +567,24 @@ class Worker:
|
|
|
568
567
|
logger.info(
|
|
569
568
|
"%s [%s] %s", arrow, ms(duration), call, extra=log_context
|
|
570
569
|
)
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
retried = await self._retry_if_requested(execution, dependencies)
|
|
576
|
-
if not retried:
|
|
577
|
-
retried = await self._perpetuate_if_requested(
|
|
578
|
-
execution, dependencies, timedelta(seconds=duration)
|
|
579
|
-
)
|
|
570
|
+
except Exception:
|
|
571
|
+
duration = log_context["duration"] = time.time() - start
|
|
572
|
+
TASKS_FAILED.add(1, counter_labels)
|
|
580
573
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
574
|
+
retried = await self._retry_if_requested(execution, dependencies)
|
|
575
|
+
if not retried:
|
|
576
|
+
retried = await self._perpetuate_if_requested(
|
|
577
|
+
execution, dependencies, timedelta(seconds=duration)
|
|
584
578
|
)
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
579
|
+
|
|
580
|
+
arrow = "↫" if retried else "↩"
|
|
581
|
+
logger.exception(
|
|
582
|
+
"%s [%s] %s", arrow, ms(duration), call, extra=log_context
|
|
583
|
+
)
|
|
584
|
+
finally:
|
|
585
|
+
TASKS_RUNNING.add(-1, counter_labels)
|
|
586
|
+
TASKS_COMPLETED.add(1, counter_labels)
|
|
587
|
+
TASK_DURATION.record(duration, counter_labels)
|
|
589
588
|
|
|
590
589
|
async def _run_function_with_timeout(
|
|
591
590
|
self,
|
|
@@ -665,6 +664,11 @@ class Worker:
|
|
|
665
664
|
|
|
666
665
|
return True
|
|
667
666
|
|
|
667
|
+
def _startup_log(self) -> None:
|
|
668
|
+
logger.info("Starting worker %r with the following tasks:", self.name)
|
|
669
|
+
for task_name, task in self.docket.tasks.items():
|
|
670
|
+
logger.info("* %s(%s)", task_name, compact_signature(get_signature(task)))
|
|
671
|
+
|
|
668
672
|
@property
|
|
669
673
|
def workers_set(self) -> str:
|
|
670
674
|
return self.docket.workers_set
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydocket
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
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
|
|
@@ -31,6 +31,7 @@ Requires-Dist: python-json-logger>=3.2.1
|
|
|
31
31
|
Requires-Dist: redis>=4.6
|
|
32
32
|
Requires-Dist: rich>=13.9.4
|
|
33
33
|
Requires-Dist: typer>=0.15.1
|
|
34
|
+
Requires-Dist: uuid7>=0.1.0
|
|
34
35
|
Description-Content-Type: text/markdown
|
|
35
36
|
|
|
36
37
|
Docket is a distributed background task system for Python functions with a focus
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
docket/__init__.py,sha256=sY1T_NVsXQNOmOhOnfYmZ95dcE_52Ov6DSIVIMZp-1w,869
|
|
2
|
+
docket/__main__.py,sha256=wcCrL4PjG51r5wVKqJhcoJPTLfHW0wNbD31DrUN0MWI,28
|
|
3
|
+
docket/annotations.py,sha256=6sCgQxsgOjBN6ithFdXulXq4CPNSdyFocwyJ1gK9v2Q,1688
|
|
4
|
+
docket/cli.py,sha256=znHN7eqaD_PFpSFn7iXa_uZlKzVWDrKkrmOd1CNuZRk,20561
|
|
5
|
+
docket/dependencies.py,sha256=-gruEho5jf07Jx9fEh2YBFg4gDSJFm7X5qhQjArVXjU,11910
|
|
6
|
+
docket/docket.py,sha256=r5TNcGmaQuxST56OVKNjFXDsrU5-Ioz3Y_I38PkLqRM,21411
|
|
7
|
+
docket/execution.py,sha256=6KozjnS96byvyCMTQ2-IkcIrPsqaPIVu2HZU0U4Be9E,14813
|
|
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=Xf6_7GyrIUNq1jG8YjbJk5KkRQdvxs0CniF9XW8kdJg,27450
|
|
12
|
+
pydocket-0.6.3.dist-info/METADATA,sha256=LRtykRFP2dcauKjzQDoNpC_xe6aVjvleAN1xS5cSIUY,13120
|
|
13
|
+
pydocket-0.6.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
+
pydocket-0.6.3.dist-info/entry_points.txt,sha256=4WOk1nUlBsUT5O3RyMci2ImuC5XFswuopElYcLHtD5k,47
|
|
15
|
+
pydocket-0.6.3.dist-info/licenses/LICENSE,sha256=YuVWU_ZXO0K_k2FG8xWKe5RGxV24AhJKTvQmKfqXuyk,1087
|
|
16
|
+
pydocket-0.6.3.dist-info/RECORD,,
|
pydocket-0.6.2.dist-info/RECORD
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|