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 CHANGED
@@ -1,3 +1,3 @@
1
- from docket.cli import app
1
+ from .cli import app
2
2
 
3
3
  app()
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
- return execution.get_argument(self.parameter)
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 = f"{function.__name__}:{uuid4()}"
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 Any, Awaitable, Callable, Hashable, Literal, Mapping, Self, cast
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 docket.instrumentation import message_getter
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"worker:{uuid4()}"
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
- logger.info("Starting worker %r with the following tasks:", self.name)
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
- 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)
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
- except Exception:
572
- duration = log_context["duration"] = time.time() - start
573
- TASKS_FAILED.add(1, counter_labels)
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
- arrow = "↫" if retried else "↩"
582
- logger.exception(
583
- "%s [%s] %s", arrow, ms(duration), call, extra=log_context
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
- finally:
586
- TASKS_RUNNING.add(-1, counter_labels)
587
- TASKS_COMPLETED.add(1, counter_labels)
588
- TASK_DURATION.record(duration, counter_labels)
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.2
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,,
@@ -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,,