pydocket 0.0.2__py3-none-any.whl → 0.1.0__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/cli.py +457 -22
- docket/dependencies.py +1 -1
- docket/docket.py +363 -6
- docket/execution.py +274 -5
- docket/instrumentation.py +18 -0
- docket/tasks.py +7 -0
- docket/worker.py +193 -86
- pydocket-0.1.0.dist-info/METADATA +388 -0
- pydocket-0.1.0.dist-info/RECORD +16 -0
- pydocket-0.0.2.dist-info/METADATA +0 -36
- pydocket-0.0.2.dist-info/RECORD +0 -16
- {pydocket-0.0.2.dist-info → pydocket-0.1.0.dist-info}/WHEEL +0 -0
- {pydocket-0.0.2.dist-info → pydocket-0.1.0.dist-info}/entry_points.txt +0 -0
- {pydocket-0.0.2.dist-info → pydocket-0.1.0.dist-info}/licenses/LICENSE +0 -0
docket/cli.py
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import enum
|
|
3
|
+
import importlib
|
|
3
4
|
import logging
|
|
5
|
+
import os
|
|
4
6
|
import socket
|
|
5
7
|
import sys
|
|
6
|
-
from datetime import timedelta
|
|
7
|
-
from
|
|
8
|
+
from datetime import datetime, timedelta, timezone
|
|
9
|
+
from functools import partial
|
|
10
|
+
from typing import Annotated, Any, Collection
|
|
8
11
|
|
|
9
12
|
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.table import Table
|
|
10
15
|
|
|
11
16
|
from . import __version__, tasks
|
|
12
|
-
from .docket import Docket
|
|
17
|
+
from .docket import Docket, DocketSnapshot, WorkerInfo
|
|
18
|
+
from .execution import Operator
|
|
13
19
|
from .worker import Worker
|
|
14
20
|
|
|
15
21
|
app: typer.Typer = typer.Typer(
|
|
@@ -33,6 +39,14 @@ class LogFormat(enum.StrEnum):
|
|
|
33
39
|
JSON = "json"
|
|
34
40
|
|
|
35
41
|
|
|
42
|
+
def local_time(when: datetime) -> str:
|
|
43
|
+
return when.astimezone().strftime("%Y-%m-%d %H:%M:%S %z")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def default_worker_name() -> str:
|
|
47
|
+
return f"{socket.gethostname()}#{os.getpid()}"
|
|
48
|
+
|
|
49
|
+
|
|
36
50
|
def duration(duration_str: str | timedelta) -> timedelta:
|
|
37
51
|
"""
|
|
38
52
|
Parse a duration string into a timedelta.
|
|
@@ -100,6 +114,41 @@ def set_logging_level(level: LogLevel) -> None:
|
|
|
100
114
|
logging.getLogger().setLevel(level)
|
|
101
115
|
|
|
102
116
|
|
|
117
|
+
def handle_strike_wildcard(value: str) -> str | None:
|
|
118
|
+
if value in ("", "*"):
|
|
119
|
+
return None
|
|
120
|
+
return value
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def interpret_python_value(value: str | None) -> Any:
|
|
124
|
+
if value is None:
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
type, _, value = value.rpartition(":")
|
|
128
|
+
if not type:
|
|
129
|
+
# without a type hint, we assume the value is a string
|
|
130
|
+
return value
|
|
131
|
+
|
|
132
|
+
module_name, _, member_name = type.rpartition(".")
|
|
133
|
+
module = importlib.import_module(module_name or "builtins")
|
|
134
|
+
member = getattr(module, member_name)
|
|
135
|
+
|
|
136
|
+
# special cases for common useful types
|
|
137
|
+
if member is timedelta:
|
|
138
|
+
return timedelta(seconds=int(value))
|
|
139
|
+
elif member is bool:
|
|
140
|
+
return value.lower() == "true"
|
|
141
|
+
else:
|
|
142
|
+
return member(value)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@app.command(
|
|
146
|
+
help="Print the version of docket",
|
|
147
|
+
)
|
|
148
|
+
def version() -> None:
|
|
149
|
+
print(__version__)
|
|
150
|
+
|
|
151
|
+
|
|
103
152
|
@app.command(
|
|
104
153
|
help="Start a worker to process tasks",
|
|
105
154
|
)
|
|
@@ -136,7 +185,7 @@ def worker(
|
|
|
136
185
|
help="The name of the worker",
|
|
137
186
|
envvar="DOCKET_WORKER_NAME",
|
|
138
187
|
),
|
|
139
|
-
] =
|
|
188
|
+
] = default_worker_name(),
|
|
140
189
|
logging_level: Annotated[
|
|
141
190
|
LogLevel,
|
|
142
191
|
typer.Option(
|
|
@@ -153,11 +202,11 @@ def worker(
|
|
|
153
202
|
callback=set_logging_format,
|
|
154
203
|
),
|
|
155
204
|
] = LogFormat.RICH if sys.stdout.isatty() else LogFormat.PLAIN,
|
|
156
|
-
|
|
205
|
+
concurrency: Annotated[
|
|
157
206
|
int,
|
|
158
207
|
typer.Option(
|
|
159
|
-
help="The number of tasks to
|
|
160
|
-
envvar="
|
|
208
|
+
help="The maximum number of tasks to process concurrently",
|
|
209
|
+
envvar="DOCKET_WORKER_CONCURRENCY",
|
|
161
210
|
),
|
|
162
211
|
] = 10,
|
|
163
212
|
redelivery_timeout: Annotated[
|
|
@@ -192,7 +241,7 @@ def worker(
|
|
|
192
241
|
docket_name=docket_,
|
|
193
242
|
url=url,
|
|
194
243
|
name=name,
|
|
195
|
-
|
|
244
|
+
concurrency=concurrency,
|
|
196
245
|
redelivery_timeout=redelivery_timeout,
|
|
197
246
|
reconnection_delay=reconnection_delay,
|
|
198
247
|
until_finished=until_finished,
|
|
@@ -201,7 +250,139 @@ def worker(
|
|
|
201
250
|
)
|
|
202
251
|
|
|
203
252
|
|
|
204
|
-
@app.command(help="
|
|
253
|
+
@app.command(help="Strikes a task or parameters from the docket")
|
|
254
|
+
def strike(
|
|
255
|
+
function: Annotated[
|
|
256
|
+
str,
|
|
257
|
+
typer.Argument(
|
|
258
|
+
help="The function to strike",
|
|
259
|
+
callback=handle_strike_wildcard,
|
|
260
|
+
),
|
|
261
|
+
] = "*",
|
|
262
|
+
parameter: Annotated[
|
|
263
|
+
str,
|
|
264
|
+
typer.Argument(
|
|
265
|
+
help="The parameter to strike",
|
|
266
|
+
callback=handle_strike_wildcard,
|
|
267
|
+
),
|
|
268
|
+
] = "*",
|
|
269
|
+
operator: Annotated[
|
|
270
|
+
Operator,
|
|
271
|
+
typer.Argument(
|
|
272
|
+
help="The operator to compare the value against",
|
|
273
|
+
),
|
|
274
|
+
] = Operator.EQUAL,
|
|
275
|
+
value: Annotated[
|
|
276
|
+
str | None,
|
|
277
|
+
typer.Argument(
|
|
278
|
+
help="The value to strike from the docket",
|
|
279
|
+
),
|
|
280
|
+
] = None,
|
|
281
|
+
docket_: Annotated[
|
|
282
|
+
str,
|
|
283
|
+
typer.Option(
|
|
284
|
+
"--docket",
|
|
285
|
+
help="The name of the docket",
|
|
286
|
+
envvar="DOCKET_NAME",
|
|
287
|
+
),
|
|
288
|
+
] = "docket",
|
|
289
|
+
url: Annotated[
|
|
290
|
+
str,
|
|
291
|
+
typer.Option(
|
|
292
|
+
help="The URL of the Redis server",
|
|
293
|
+
envvar="DOCKET_URL",
|
|
294
|
+
),
|
|
295
|
+
] = "redis://localhost:6379/0",
|
|
296
|
+
) -> None:
|
|
297
|
+
if not function and not parameter:
|
|
298
|
+
raise typer.BadParameter(
|
|
299
|
+
message="Must provide either a function and/or a parameter",
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
value_ = interpret_python_value(value)
|
|
303
|
+
if parameter:
|
|
304
|
+
function_name = f"{function or '(all tasks)'}"
|
|
305
|
+
print(f"Striking {function_name} {parameter} {operator} {value_!r}")
|
|
306
|
+
else:
|
|
307
|
+
print(f"Striking {function}")
|
|
308
|
+
|
|
309
|
+
async def run() -> None:
|
|
310
|
+
async with Docket(name=docket_, url=url) as docket:
|
|
311
|
+
await docket.strike(function, parameter, operator, value_)
|
|
312
|
+
|
|
313
|
+
asyncio.run(run())
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@app.command(help="Restores a task or parameters to the Docket")
|
|
317
|
+
def restore(
|
|
318
|
+
function: Annotated[
|
|
319
|
+
str,
|
|
320
|
+
typer.Argument(
|
|
321
|
+
help="The function to restore",
|
|
322
|
+
callback=handle_strike_wildcard,
|
|
323
|
+
),
|
|
324
|
+
] = "*",
|
|
325
|
+
parameter: Annotated[
|
|
326
|
+
str,
|
|
327
|
+
typer.Argument(
|
|
328
|
+
help="The parameter to restore",
|
|
329
|
+
callback=handle_strike_wildcard,
|
|
330
|
+
),
|
|
331
|
+
] = "*",
|
|
332
|
+
operator: Annotated[
|
|
333
|
+
Operator,
|
|
334
|
+
typer.Argument(
|
|
335
|
+
help="The operator to compare the value against",
|
|
336
|
+
),
|
|
337
|
+
] = Operator.EQUAL,
|
|
338
|
+
value: Annotated[
|
|
339
|
+
str | None,
|
|
340
|
+
typer.Argument(
|
|
341
|
+
help="The value to restore to the docket",
|
|
342
|
+
),
|
|
343
|
+
] = None,
|
|
344
|
+
docket_: Annotated[
|
|
345
|
+
str,
|
|
346
|
+
typer.Option(
|
|
347
|
+
"--docket",
|
|
348
|
+
help="The name of the docket",
|
|
349
|
+
envvar="DOCKET_NAME",
|
|
350
|
+
),
|
|
351
|
+
] = "docket",
|
|
352
|
+
url: Annotated[
|
|
353
|
+
str,
|
|
354
|
+
typer.Option(
|
|
355
|
+
help="The URL of the Redis server",
|
|
356
|
+
envvar="DOCKET_URL",
|
|
357
|
+
),
|
|
358
|
+
] = "redis://localhost:6379/0",
|
|
359
|
+
) -> None:
|
|
360
|
+
if not function and not parameter:
|
|
361
|
+
raise typer.BadParameter(
|
|
362
|
+
message="Must provide either a function and/or a parameter",
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
value_ = interpret_python_value(value)
|
|
366
|
+
if parameter:
|
|
367
|
+
function_name = f"{function or '(all tasks)'}"
|
|
368
|
+
print(f"Striking {function_name} {parameter} {operator} {value_!r}")
|
|
369
|
+
else:
|
|
370
|
+
print(f"Restoring {function}")
|
|
371
|
+
|
|
372
|
+
async def run() -> None:
|
|
373
|
+
async with Docket(name=docket_, url=url) as docket:
|
|
374
|
+
await docket.restore(function, parameter, operator, value_)
|
|
375
|
+
|
|
376
|
+
asyncio.run(run())
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
tasks_app: typer.Typer = typer.Typer(
|
|
380
|
+
help="Run docket's built-in tasks", no_args_is_help=True
|
|
381
|
+
)
|
|
382
|
+
app.add_typer(tasks_app, name="tasks")
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
@tasks_app.command(help="Adds a trace task to the Docket")
|
|
205
386
|
def trace(
|
|
206
387
|
docket_: Annotated[
|
|
207
388
|
str,
|
|
@@ -224,21 +405,104 @@ def trace(
|
|
|
224
405
|
help="The message to print",
|
|
225
406
|
),
|
|
226
407
|
] = "Howdy!",
|
|
227
|
-
|
|
228
|
-
|
|
408
|
+
delay: Annotated[
|
|
409
|
+
timedelta,
|
|
229
410
|
typer.Option(
|
|
230
|
-
|
|
231
|
-
help="
|
|
411
|
+
parser=duration,
|
|
412
|
+
help="The delay before the task is added to the docket",
|
|
232
413
|
),
|
|
233
|
-
] =
|
|
414
|
+
] = timedelta(seconds=0),
|
|
415
|
+
) -> None:
|
|
416
|
+
async def run() -> None:
|
|
417
|
+
async with Docket(name=docket_, url=url) as docket:
|
|
418
|
+
when = datetime.now(timezone.utc) + delay
|
|
419
|
+
execution = await docket.add(tasks.trace, when)(message)
|
|
420
|
+
print(
|
|
421
|
+
f"Added {execution.function.__name__} task {execution.key!r} to "
|
|
422
|
+
f"the docket {docket.name!r}"
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
asyncio.run(run())
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
@tasks_app.command(help="Adds a fail task to the Docket")
|
|
429
|
+
def fail(
|
|
430
|
+
docket_: Annotated[
|
|
431
|
+
str,
|
|
432
|
+
typer.Option(
|
|
433
|
+
"--docket",
|
|
434
|
+
help="The name of the docket",
|
|
435
|
+
envvar="DOCKET_NAME",
|
|
436
|
+
),
|
|
437
|
+
] = "docket",
|
|
438
|
+
url: Annotated[
|
|
439
|
+
str,
|
|
440
|
+
typer.Option(
|
|
441
|
+
help="The URL of the Redis server",
|
|
442
|
+
envvar="DOCKET_URL",
|
|
443
|
+
),
|
|
444
|
+
] = "redis://localhost:6379/0",
|
|
445
|
+
message: Annotated[
|
|
446
|
+
str,
|
|
447
|
+
typer.Argument(
|
|
448
|
+
help="The message to print",
|
|
449
|
+
),
|
|
450
|
+
] = "Howdy!",
|
|
451
|
+
delay: Annotated[
|
|
452
|
+
timedelta,
|
|
453
|
+
typer.Option(
|
|
454
|
+
parser=duration,
|
|
455
|
+
help="The delay before the task is added to the docket",
|
|
456
|
+
),
|
|
457
|
+
] = timedelta(seconds=0),
|
|
234
458
|
) -> None:
|
|
235
459
|
async def run() -> None:
|
|
236
460
|
async with Docket(name=docket_, url=url) as docket:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
execution
|
|
461
|
+
when = datetime.now(timezone.utc) + delay
|
|
462
|
+
execution = await docket.add(tasks.fail, when)(message)
|
|
463
|
+
print(
|
|
464
|
+
f"Added {execution.function.__name__} task {execution.key!r} to "
|
|
465
|
+
f"the docket {docket.name!r}"
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
asyncio.run(run())
|
|
469
|
+
|
|
241
470
|
|
|
471
|
+
@tasks_app.command(help="Adds a sleep task to the Docket")
|
|
472
|
+
def sleep(
|
|
473
|
+
docket_: Annotated[
|
|
474
|
+
str,
|
|
475
|
+
typer.Option(
|
|
476
|
+
"--docket",
|
|
477
|
+
help="The name of the docket",
|
|
478
|
+
envvar="DOCKET_NAME",
|
|
479
|
+
),
|
|
480
|
+
] = "docket",
|
|
481
|
+
url: Annotated[
|
|
482
|
+
str,
|
|
483
|
+
typer.Option(
|
|
484
|
+
help="The URL of the Redis server",
|
|
485
|
+
envvar="DOCKET_URL",
|
|
486
|
+
),
|
|
487
|
+
] = "redis://localhost:6379/0",
|
|
488
|
+
seconds: Annotated[
|
|
489
|
+
float,
|
|
490
|
+
typer.Argument(
|
|
491
|
+
help="The number of seconds to sleep",
|
|
492
|
+
),
|
|
493
|
+
] = 1,
|
|
494
|
+
delay: Annotated[
|
|
495
|
+
timedelta,
|
|
496
|
+
typer.Option(
|
|
497
|
+
parser=duration,
|
|
498
|
+
help="The delay before the task is added to the docket",
|
|
499
|
+
),
|
|
500
|
+
] = timedelta(seconds=0),
|
|
501
|
+
) -> None:
|
|
502
|
+
async def run() -> None:
|
|
503
|
+
async with Docket(name=docket_, url=url) as docket:
|
|
504
|
+
when = datetime.now(timezone.utc) + delay
|
|
505
|
+
execution = await docket.add(tasks.sleep, when)(seconds)
|
|
242
506
|
print(
|
|
243
507
|
f"Added {execution.function.__name__} task {execution.key!r} to "
|
|
244
508
|
f"the docket {docket.name!r}"
|
|
@@ -247,8 +511,179 @@ def trace(
|
|
|
247
511
|
asyncio.run(run())
|
|
248
512
|
|
|
249
513
|
|
|
250
|
-
|
|
251
|
-
|
|
514
|
+
def relative_time(now: datetime, when: datetime) -> str:
|
|
515
|
+
delta = now - when
|
|
516
|
+
if delta < -timedelta(minutes=30):
|
|
517
|
+
return f"at {local_time(when)}"
|
|
518
|
+
elif delta < timedelta(0):
|
|
519
|
+
return f"in {-delta}"
|
|
520
|
+
elif delta < timedelta(minutes=30):
|
|
521
|
+
return f"{delta} ago"
|
|
522
|
+
else:
|
|
523
|
+
return f"at {local_time(when)}"
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
@app.command(help="Shows a snapshot of what's on the docket right now")
|
|
527
|
+
def snapshot(
|
|
528
|
+
docket_: Annotated[
|
|
529
|
+
str,
|
|
530
|
+
typer.Option(
|
|
531
|
+
"--docket",
|
|
532
|
+
help="The name of the docket",
|
|
533
|
+
envvar="DOCKET_NAME",
|
|
534
|
+
),
|
|
535
|
+
] = "docket",
|
|
536
|
+
url: Annotated[
|
|
537
|
+
str,
|
|
538
|
+
typer.Option(
|
|
539
|
+
help="The URL of the Redis server",
|
|
540
|
+
envvar="DOCKET_URL",
|
|
541
|
+
),
|
|
542
|
+
] = "redis://localhost:6379/0",
|
|
543
|
+
) -> None:
|
|
544
|
+
async def run() -> DocketSnapshot:
|
|
545
|
+
async with Docket(name=docket_, url=url) as docket:
|
|
546
|
+
return await docket.snapshot()
|
|
547
|
+
|
|
548
|
+
snapshot = asyncio.run(run())
|
|
549
|
+
|
|
550
|
+
relative = partial(relative_time, snapshot.taken)
|
|
551
|
+
|
|
552
|
+
console = Console()
|
|
553
|
+
|
|
554
|
+
summary_lines = [
|
|
555
|
+
f"Docket: {docket_!r}",
|
|
556
|
+
f"as of {local_time(snapshot.taken)}",
|
|
557
|
+
(
|
|
558
|
+
f"{len(snapshot.workers)} workers, "
|
|
559
|
+
f"{len(snapshot.running)}/{snapshot.total_tasks} running"
|
|
560
|
+
),
|
|
561
|
+
]
|
|
562
|
+
table = Table(title="\n".join(summary_lines))
|
|
563
|
+
table.add_column("When", style="green")
|
|
564
|
+
table.add_column("Function", style="cyan")
|
|
565
|
+
table.add_column("Key", style="cyan")
|
|
566
|
+
table.add_column("Worker", style="yellow")
|
|
567
|
+
table.add_column("Started", style="green")
|
|
568
|
+
|
|
569
|
+
for execution in snapshot.running:
|
|
570
|
+
table.add_row(
|
|
571
|
+
relative(execution.when),
|
|
572
|
+
execution.function.__name__,
|
|
573
|
+
execution.key,
|
|
574
|
+
execution.worker,
|
|
575
|
+
relative(execution.started),
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
for execution in snapshot.future:
|
|
579
|
+
table.add_row(
|
|
580
|
+
relative(execution.when),
|
|
581
|
+
execution.function.__name__,
|
|
582
|
+
execution.key,
|
|
583
|
+
"",
|
|
584
|
+
"",
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
console.print(table)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
workers_app: typer.Typer = typer.Typer(
|
|
591
|
+
help="Look at the workers on a docket", no_args_is_help=True
|
|
252
592
|
)
|
|
253
|
-
|
|
254
|
-
|
|
593
|
+
app.add_typer(workers_app, name="workers")
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def print_workers(
|
|
597
|
+
docket_name: str,
|
|
598
|
+
workers: Collection[WorkerInfo],
|
|
599
|
+
highlight_task: str | None = None,
|
|
600
|
+
) -> None:
|
|
601
|
+
sorted_workers = sorted(workers, key=lambda w: w.last_seen, reverse=True)
|
|
602
|
+
|
|
603
|
+
table = Table(title=f"Workers in Docket: {docket_name}")
|
|
604
|
+
|
|
605
|
+
table.add_column("Name", style="cyan")
|
|
606
|
+
table.add_column("Last Seen", style="green")
|
|
607
|
+
table.add_column("Tasks", style="yellow")
|
|
608
|
+
|
|
609
|
+
now = datetime.now(timezone.utc)
|
|
610
|
+
|
|
611
|
+
for worker in sorted_workers:
|
|
612
|
+
time_ago = now - worker.last_seen
|
|
613
|
+
|
|
614
|
+
tasks = [
|
|
615
|
+
f"[bold]{task}[/bold]" if task == highlight_task else task
|
|
616
|
+
for task in sorted(worker.tasks)
|
|
617
|
+
]
|
|
618
|
+
|
|
619
|
+
table.add_row(
|
|
620
|
+
worker.name,
|
|
621
|
+
f"{time_ago} ago",
|
|
622
|
+
"\n".join(tasks) if tasks else "(none)",
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
console = Console()
|
|
626
|
+
console.print(table)
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
@workers_app.command(name="ls", help="List all workers on the docket")
|
|
630
|
+
def list_workers(
|
|
631
|
+
docket_: Annotated[
|
|
632
|
+
str,
|
|
633
|
+
typer.Option(
|
|
634
|
+
"--docket",
|
|
635
|
+
help="The name of the docket",
|
|
636
|
+
envvar="DOCKET_NAME",
|
|
637
|
+
),
|
|
638
|
+
] = "docket",
|
|
639
|
+
url: Annotated[
|
|
640
|
+
str,
|
|
641
|
+
typer.Option(
|
|
642
|
+
help="The URL of the Redis server",
|
|
643
|
+
envvar="DOCKET_URL",
|
|
644
|
+
),
|
|
645
|
+
] = "redis://localhost:6379/0",
|
|
646
|
+
) -> None:
|
|
647
|
+
async def run() -> Collection[WorkerInfo]:
|
|
648
|
+
async with Docket(name=docket_, url=url) as docket:
|
|
649
|
+
return await docket.workers()
|
|
650
|
+
|
|
651
|
+
workers = asyncio.run(run())
|
|
652
|
+
|
|
653
|
+
print_workers(docket_, workers)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
@workers_app.command(
|
|
657
|
+
name="for-task",
|
|
658
|
+
help="List the workers on the docket that can process a certain task",
|
|
659
|
+
)
|
|
660
|
+
def workers_for_task(
|
|
661
|
+
task: Annotated[
|
|
662
|
+
str,
|
|
663
|
+
typer.Argument(
|
|
664
|
+
help="The name of the task",
|
|
665
|
+
),
|
|
666
|
+
],
|
|
667
|
+
docket_: Annotated[
|
|
668
|
+
str,
|
|
669
|
+
typer.Option(
|
|
670
|
+
"--docket",
|
|
671
|
+
help="The name of the docket",
|
|
672
|
+
envvar="DOCKET_NAME",
|
|
673
|
+
),
|
|
674
|
+
] = "docket",
|
|
675
|
+
url: Annotated[
|
|
676
|
+
str,
|
|
677
|
+
typer.Option(
|
|
678
|
+
help="The URL of the Redis server",
|
|
679
|
+
envvar="DOCKET_URL",
|
|
680
|
+
),
|
|
681
|
+
] = "redis://localhost:6379/0",
|
|
682
|
+
) -> None:
|
|
683
|
+
async def run() -> Collection[WorkerInfo]:
|
|
684
|
+
async with Docket(name=docket_, url=url) as docket:
|
|
685
|
+
return await docket.task_workers(task)
|
|
686
|
+
|
|
687
|
+
workers = asyncio.run(run())
|
|
688
|
+
|
|
689
|
+
print_workers(docket_, workers, highlight_task=task)
|
docket/dependencies.py
CHANGED
|
@@ -59,7 +59,7 @@ def TaskKey() -> str:
|
|
|
59
59
|
class _TaskLogger(Dependency):
|
|
60
60
|
def __call__(
|
|
61
61
|
self, docket: Docket, worker: Worker, execution: Execution
|
|
62
|
-
) -> logging.LoggerAdapter:
|
|
62
|
+
) -> logging.LoggerAdapter[logging.Logger]:
|
|
63
63
|
logger = logging.getLogger(f"docket.task.{execution.function.__name__}")
|
|
64
64
|
|
|
65
65
|
extra = {
|