prefect-client 3.1.11__py3-none-any.whl → 3.1.13__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.
- prefect/_experimental/sla/__init__.py +0 -0
- prefect/_experimental/sla/client.py +92 -0
- prefect/_experimental/sla/objects.py +61 -0
- prefect/_internal/concurrency/services.py +2 -2
- prefect/_internal/concurrency/threads.py +6 -0
- prefect/_internal/retries.py +6 -3
- prefect/_internal/schemas/validators.py +6 -4
- prefect/_version.py +3 -3
- prefect/artifacts.py +4 -1
- prefect/automations.py +236 -30
- prefect/blocks/__init__.py +3 -3
- prefect/blocks/abstract.py +57 -31
- prefect/blocks/core.py +181 -82
- prefect/blocks/notifications.py +134 -73
- prefect/blocks/redis.py +13 -9
- prefect/blocks/system.py +24 -11
- prefect/blocks/webhook.py +7 -5
- prefect/cache_policies.py +23 -22
- prefect/client/orchestration/__init__.py +103 -2006
- prefect/client/orchestration/_automations/__init__.py +0 -0
- prefect/client/orchestration/_automations/client.py +329 -0
- prefect/client/orchestration/_blocks_documents/__init__.py +0 -0
- prefect/client/orchestration/_blocks_documents/client.py +334 -0
- prefect/client/orchestration/_blocks_schemas/__init__.py +0 -0
- prefect/client/orchestration/_blocks_schemas/client.py +200 -0
- prefect/client/orchestration/_blocks_types/__init__.py +0 -0
- prefect/client/orchestration/_blocks_types/client.py +380 -0
- prefect/client/orchestration/_deployments/__init__.py +0 -0
- prefect/client/orchestration/_deployments/client.py +1128 -0
- prefect/client/orchestration/_flow_runs/__init__.py +0 -0
- prefect/client/orchestration/_flow_runs/client.py +903 -0
- prefect/client/orchestration/_flows/__init__.py +0 -0
- prefect/client/orchestration/_flows/client.py +343 -0
- prefect/client/orchestration/_logs/client.py +16 -14
- prefect/client/schemas/__init__.py +68 -28
- prefect/client/schemas/objects.py +5 -5
- prefect/client/utilities.py +3 -3
- prefect/context.py +15 -1
- prefect/deployments/base.py +13 -4
- prefect/deployments/flow_runs.py +5 -1
- prefect/deployments/runner.py +37 -1
- prefect/deployments/steps/core.py +1 -1
- prefect/deployments/steps/pull.py +8 -3
- prefect/deployments/steps/utility.py +2 -2
- prefect/docker/docker_image.py +13 -9
- prefect/engine.py +33 -11
- prefect/events/cli/automations.py +4 -4
- prefect/events/clients.py +17 -14
- prefect/events/schemas/automations.py +12 -8
- prefect/events/schemas/events.py +5 -1
- prefect/events/worker.py +1 -1
- prefect/filesystems.py +7 -3
- prefect/flow_engine.py +64 -47
- prefect/flows.py +128 -74
- prefect/futures.py +14 -7
- prefect/infrastructure/provisioners/__init__.py +2 -0
- prefect/infrastructure/provisioners/cloud_run.py +4 -4
- prefect/infrastructure/provisioners/coiled.py +249 -0
- prefect/infrastructure/provisioners/container_instance.py +4 -3
- prefect/infrastructure/provisioners/ecs.py +55 -43
- prefect/infrastructure/provisioners/modal.py +5 -4
- prefect/input/actions.py +5 -1
- prefect/input/run_input.py +157 -43
- prefect/logging/configuration.py +3 -3
- prefect/logging/filters.py +2 -2
- prefect/logging/formatters.py +15 -11
- prefect/logging/handlers.py +24 -14
- prefect/logging/highlighters.py +5 -5
- prefect/logging/loggers.py +28 -18
- prefect/logging/logging.yml +1 -1
- prefect/main.py +3 -1
- prefect/results.py +166 -86
- prefect/runner/runner.py +38 -29
- prefect/runner/server.py +3 -1
- prefect/runner/storage.py +18 -18
- prefect/runner/submit.py +19 -12
- prefect/runtime/deployment.py +15 -8
- prefect/runtime/flow_run.py +19 -6
- prefect/runtime/task_run.py +7 -3
- prefect/settings/base.py +17 -7
- prefect/settings/legacy.py +4 -4
- prefect/settings/models/api.py +4 -3
- prefect/settings/models/cli.py +4 -3
- prefect/settings/models/client.py +7 -4
- prefect/settings/models/cloud.py +9 -3
- prefect/settings/models/deployments.py +4 -3
- prefect/settings/models/experiments.py +4 -8
- prefect/settings/models/flows.py +4 -3
- prefect/settings/models/internal.py +4 -3
- prefect/settings/models/logging.py +8 -6
- prefect/settings/models/results.py +4 -3
- prefect/settings/models/root.py +11 -16
- prefect/settings/models/runner.py +8 -5
- prefect/settings/models/server/api.py +6 -3
- prefect/settings/models/server/database.py +120 -25
- prefect/settings/models/server/deployments.py +4 -3
- prefect/settings/models/server/ephemeral.py +7 -4
- prefect/settings/models/server/events.py +6 -3
- prefect/settings/models/server/flow_run_graph.py +4 -3
- prefect/settings/models/server/root.py +4 -3
- prefect/settings/models/server/services.py +15 -12
- prefect/settings/models/server/tasks.py +7 -4
- prefect/settings/models/server/ui.py +4 -3
- prefect/settings/models/tasks.py +10 -5
- prefect/settings/models/testing.py +4 -3
- prefect/settings/models/worker.py +7 -4
- prefect/settings/profiles.py +13 -12
- prefect/settings/sources.py +20 -19
- prefect/states.py +74 -51
- prefect/task_engine.py +43 -33
- prefect/task_runners.py +85 -72
- prefect/task_runs.py +20 -11
- prefect/task_worker.py +14 -9
- prefect/tasks.py +36 -28
- prefect/telemetry/bootstrap.py +13 -9
- prefect/telemetry/run_telemetry.py +15 -13
- prefect/telemetry/services.py +4 -0
- prefect/transactions.py +3 -3
- prefect/types/__init__.py +3 -1
- prefect/utilities/_deprecated.py +38 -0
- prefect/utilities/engine.py +11 -4
- prefect/utilities/filesystem.py +2 -2
- prefect/utilities/generics.py +1 -1
- prefect/utilities/pydantic.py +21 -36
- prefect/utilities/templating.py +25 -1
- prefect/workers/base.py +58 -33
- prefect/workers/process.py +20 -15
- prefect/workers/server.py +4 -5
- {prefect_client-3.1.11.dist-info → prefect_client-3.1.13.dist-info}/METADATA +3 -3
- {prefect_client-3.1.11.dist-info → prefect_client-3.1.13.dist-info}/RECORD +133 -114
- {prefect_client-3.1.11.dist-info → prefect_client-3.1.13.dist-info}/LICENSE +0 -0
- {prefect_client-3.1.11.dist-info → prefect_client-3.1.13.dist-info}/WHEEL +0 -0
- {prefect_client-3.1.11.dist-info → prefect_client-3.1.13.dist-info}/top_level.txt +0 -0
prefect/task_runners.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import abc
|
2
4
|
import asyncio
|
3
5
|
import sys
|
@@ -14,7 +16,6 @@ from typing import (
|
|
14
16
|
Iterable,
|
15
17
|
List,
|
16
18
|
Optional,
|
17
|
-
Set,
|
18
19
|
overload,
|
19
20
|
)
|
20
21
|
|
@@ -39,12 +40,14 @@ from prefect.utilities.callables import (
|
|
39
40
|
from prefect.utilities.collections import isiterable
|
40
41
|
|
41
42
|
if TYPE_CHECKING:
|
43
|
+
import logging
|
44
|
+
|
42
45
|
from prefect.tasks import Task
|
43
46
|
|
44
47
|
P = ParamSpec("P")
|
45
48
|
T = TypeVar("T")
|
46
49
|
R = TypeVar("R")
|
47
|
-
F = TypeVar("F", bound=PrefectFuture, default=PrefectConcurrentFuture)
|
50
|
+
F = TypeVar("F", bound=PrefectFuture[Any], default=PrefectConcurrentFuture[Any])
|
48
51
|
|
49
52
|
|
50
53
|
class TaskRunner(abc.ABC, Generic[F]):
|
@@ -60,11 +63,11 @@ class TaskRunner(abc.ABC, Generic[F]):
|
|
60
63
|
"""
|
61
64
|
|
62
65
|
def __init__(self):
|
63
|
-
self.logger = get_logger(f"task_runner.{self.name}")
|
66
|
+
self.logger: "logging.Logger" = get_logger(f"task_runner.{self.name}")
|
64
67
|
self._started = False
|
65
68
|
|
66
69
|
@property
|
67
|
-
def name(self):
|
70
|
+
def name(self) -> str:
|
68
71
|
"""The name of this task runner"""
|
69
72
|
return type(self).__name__.lower().replace("taskrunner", "")
|
70
73
|
|
@@ -73,32 +76,42 @@ class TaskRunner(abc.ABC, Generic[F]):
|
|
73
76
|
"""Return a new instance of this task runner with the same configuration."""
|
74
77
|
...
|
75
78
|
|
79
|
+
@overload
|
76
80
|
@abc.abstractmethod
|
77
81
|
def submit(
|
78
82
|
self,
|
79
|
-
task: "Task",
|
80
|
-
parameters:
|
81
|
-
wait_for:
|
82
|
-
dependencies:
|
83
|
+
task: "Task[P, Coroutine[Any, Any, R]]",
|
84
|
+
parameters: dict[str, Any],
|
85
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
86
|
+
dependencies: dict[str, set[TaskRunInput]] | None = None,
|
83
87
|
) -> F:
|
84
|
-
|
85
|
-
Submit a task to the task run engine.
|
88
|
+
...
|
86
89
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
90
|
+
@overload
|
91
|
+
@abc.abstractmethod
|
92
|
+
def submit(
|
93
|
+
self,
|
94
|
+
task: "Task[Any, R]",
|
95
|
+
parameters: dict[str, Any],
|
96
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
97
|
+
dependencies: dict[str, set[TaskRunInput]] | None = None,
|
98
|
+
) -> F:
|
99
|
+
...
|
91
100
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
""
|
101
|
+
@abc.abstractmethod
|
102
|
+
def submit(
|
103
|
+
self,
|
104
|
+
task: "Task[P, R]",
|
105
|
+
parameters: dict[str, Any],
|
106
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
107
|
+
dependencies: dict[str, set[TaskRunInput]] | None = None,
|
108
|
+
) -> F:
|
96
109
|
...
|
97
110
|
|
98
111
|
def map(
|
99
112
|
self,
|
100
113
|
task: "Task[P, R]",
|
101
|
-
parameters:
|
114
|
+
parameters: dict[str, Any | unmapped[Any] | allow_failure[Any]],
|
102
115
|
wait_for: Optional[Iterable[PrefectFuture[R]]] = None,
|
103
116
|
) -> PrefectFutureList[F]:
|
104
117
|
"""
|
@@ -138,9 +151,9 @@ class TaskRunner(abc.ABC, Generic[F]):
|
|
138
151
|
# Ensure that any parameters in kwargs are expanded before this check
|
139
152
|
parameters = explode_variadic_parameter(task.fn, parameters)
|
140
153
|
|
141
|
-
iterable_parameters = {}
|
142
|
-
static_parameters = {}
|
143
|
-
annotated_parameters = {}
|
154
|
+
iterable_parameters: dict[str, Any] = {}
|
155
|
+
static_parameters: dict[str, Any] = {}
|
156
|
+
annotated_parameters: dict[str, Any] = {}
|
144
157
|
for key, val in parameters.items():
|
145
158
|
if isinstance(val, (allow_failure, quote)):
|
146
159
|
# Unwrap annotated parameters to determine if they are iterable
|
@@ -172,9 +185,9 @@ class TaskRunner(abc.ABC, Generic[F]):
|
|
172
185
|
|
173
186
|
map_length = list(lengths)[0]
|
174
187
|
|
175
|
-
futures: List[PrefectFuture] = []
|
188
|
+
futures: List[PrefectFuture[Any]] = []
|
176
189
|
for i in range(map_length):
|
177
|
-
call_parameters = {
|
190
|
+
call_parameters: dict[str, Any] = {
|
178
191
|
key: value[i] for key, value in iterable_parameters.items()
|
179
192
|
}
|
180
193
|
call_parameters.update(
|
@@ -204,7 +217,7 @@ class TaskRunner(abc.ABC, Generic[F]):
|
|
204
217
|
|
205
218
|
return PrefectFutureList(futures)
|
206
219
|
|
207
|
-
def __enter__(self):
|
220
|
+
def __enter__(self) -> Self:
|
208
221
|
if self._started:
|
209
222
|
raise RuntimeError("This task runner is already started")
|
210
223
|
|
@@ -212,12 +225,12 @@ class TaskRunner(abc.ABC, Generic[F]):
|
|
212
225
|
self._started = True
|
213
226
|
return self
|
214
227
|
|
215
|
-
def __exit__(self, exc_type, exc_value, traceback):
|
228
|
+
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
216
229
|
self.logger.debug("Stopping task runner")
|
217
230
|
self._started = False
|
218
231
|
|
219
232
|
|
220
|
-
class ThreadPoolTaskRunner(TaskRunner[PrefectConcurrentFuture]):
|
233
|
+
class ThreadPoolTaskRunner(TaskRunner[PrefectConcurrentFuture[R]]):
|
221
234
|
def __init__(self, max_workers: Optional[int] = None):
|
222
235
|
super().__init__()
|
223
236
|
self._executor: Optional[ThreadPoolExecutor] = None
|
@@ -228,16 +241,16 @@ class ThreadPoolTaskRunner(TaskRunner[PrefectConcurrentFuture]):
|
|
228
241
|
)
|
229
242
|
self._cancel_events: Dict[uuid.UUID, threading.Event] = {}
|
230
243
|
|
231
|
-
def duplicate(self) -> "ThreadPoolTaskRunner":
|
244
|
+
def duplicate(self) -> "ThreadPoolTaskRunner[R]":
|
232
245
|
return type(self)(max_workers=self._max_workers)
|
233
246
|
|
234
247
|
@overload
|
235
248
|
def submit(
|
236
249
|
self,
|
237
250
|
task: "Task[P, Coroutine[Any, Any, R]]",
|
238
|
-
parameters:
|
239
|
-
wait_for:
|
240
|
-
dependencies:
|
251
|
+
parameters: dict[str, Any],
|
252
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
253
|
+
dependencies: dict[str, set[TaskRunInput]] | None = None,
|
241
254
|
) -> PrefectConcurrentFuture[R]:
|
242
255
|
...
|
243
256
|
|
@@ -245,19 +258,19 @@ class ThreadPoolTaskRunner(TaskRunner[PrefectConcurrentFuture]):
|
|
245
258
|
def submit(
|
246
259
|
self,
|
247
260
|
task: "Task[Any, R]",
|
248
|
-
parameters:
|
249
|
-
wait_for:
|
250
|
-
dependencies:
|
261
|
+
parameters: dict[str, Any],
|
262
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
263
|
+
dependencies: dict[str, set[TaskRunInput]] | None = None,
|
251
264
|
) -> PrefectConcurrentFuture[R]:
|
252
265
|
...
|
253
266
|
|
254
267
|
def submit(
|
255
268
|
self,
|
256
|
-
task: "Task",
|
257
|
-
parameters:
|
258
|
-
wait_for:
|
259
|
-
dependencies:
|
260
|
-
):
|
269
|
+
task: "Task[P, R | Coroutine[Any, Any, R]]",
|
270
|
+
parameters: dict[str, Any],
|
271
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
272
|
+
dependencies: dict[str, set[TaskRunInput]] | None = None,
|
273
|
+
) -> PrefectConcurrentFuture[R]:
|
261
274
|
"""
|
262
275
|
Submit a task to the task run engine running in a separate thread.
|
263
276
|
|
@@ -289,7 +302,7 @@ class ThreadPoolTaskRunner(TaskRunner[PrefectConcurrentFuture]):
|
|
289
302
|
else:
|
290
303
|
self.logger.debug(f"Submitting task {task.name} to thread pool executor...")
|
291
304
|
|
292
|
-
submit_kwargs = dict(
|
305
|
+
submit_kwargs: dict[str, Any] = dict(
|
293
306
|
task=task,
|
294
307
|
task_run_id=task_run_id,
|
295
308
|
parameters=parameters,
|
@@ -322,8 +335,8 @@ class ThreadPoolTaskRunner(TaskRunner[PrefectConcurrentFuture]):
|
|
322
335
|
def map(
|
323
336
|
self,
|
324
337
|
task: "Task[P, Coroutine[Any, Any, R]]",
|
325
|
-
parameters:
|
326
|
-
wait_for:
|
338
|
+
parameters: dict[str, Any],
|
339
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
327
340
|
) -> PrefectFutureList[PrefectConcurrentFuture[R]]:
|
328
341
|
...
|
329
342
|
|
@@ -331,20 +344,20 @@ class ThreadPoolTaskRunner(TaskRunner[PrefectConcurrentFuture]):
|
|
331
344
|
def map(
|
332
345
|
self,
|
333
346
|
task: "Task[Any, R]",
|
334
|
-
parameters:
|
335
|
-
wait_for:
|
347
|
+
parameters: dict[str, Any],
|
348
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
336
349
|
) -> PrefectFutureList[PrefectConcurrentFuture[R]]:
|
337
350
|
...
|
338
351
|
|
339
352
|
def map(
|
340
353
|
self,
|
341
|
-
task: "Task",
|
342
|
-
parameters:
|
343
|
-
wait_for:
|
344
|
-
):
|
354
|
+
task: "Task[P, R]",
|
355
|
+
parameters: dict[str, Any],
|
356
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
357
|
+
) -> PrefectFutureList[PrefectConcurrentFuture[R]]:
|
345
358
|
return super().map(task, parameters, wait_for)
|
346
359
|
|
347
|
-
def cancel_all(self):
|
360
|
+
def cancel_all(self) -> None:
|
348
361
|
for event in self._cancel_events.values():
|
349
362
|
event.set()
|
350
363
|
self.logger.debug("Set cancel event")
|
@@ -353,12 +366,12 @@ class ThreadPoolTaskRunner(TaskRunner[PrefectConcurrentFuture]):
|
|
353
366
|
self._executor.shutdown(cancel_futures=True)
|
354
367
|
self._executor = None
|
355
368
|
|
356
|
-
def __enter__(self):
|
369
|
+
def __enter__(self) -> Self:
|
357
370
|
super().__enter__()
|
358
371
|
self._executor = ThreadPoolExecutor(max_workers=self._max_workers)
|
359
372
|
return self
|
360
373
|
|
361
|
-
def __exit__(self, exc_type, exc_value, traceback):
|
374
|
+
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
362
375
|
self.cancel_all()
|
363
376
|
if self._executor is not None:
|
364
377
|
self._executor.shutdown(cancel_futures=True)
|
@@ -375,20 +388,20 @@ class ThreadPoolTaskRunner(TaskRunner[PrefectConcurrentFuture]):
|
|
375
388
|
ConcurrentTaskRunner = ThreadPoolTaskRunner
|
376
389
|
|
377
390
|
|
378
|
-
class PrefectTaskRunner(TaskRunner[PrefectDistributedFuture]):
|
391
|
+
class PrefectTaskRunner(TaskRunner[PrefectDistributedFuture[R]]):
|
379
392
|
def __init__(self):
|
380
393
|
super().__init__()
|
381
394
|
|
382
|
-
def duplicate(self) -> "PrefectTaskRunner":
|
395
|
+
def duplicate(self) -> "PrefectTaskRunner[R]":
|
383
396
|
return type(self)()
|
384
397
|
|
385
398
|
@overload
|
386
399
|
def submit(
|
387
400
|
self,
|
388
401
|
task: "Task[P, Coroutine[Any, Any, R]]",
|
389
|
-
parameters:
|
390
|
-
wait_for:
|
391
|
-
dependencies:
|
402
|
+
parameters: dict[str, Any],
|
403
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
404
|
+
dependencies: dict[str, set[TaskRunInput]] | None = None,
|
392
405
|
) -> PrefectDistributedFuture[R]:
|
393
406
|
...
|
394
407
|
|
@@ -396,19 +409,19 @@ class PrefectTaskRunner(TaskRunner[PrefectDistributedFuture]):
|
|
396
409
|
def submit(
|
397
410
|
self,
|
398
411
|
task: "Task[Any, R]",
|
399
|
-
parameters:
|
400
|
-
wait_for:
|
401
|
-
dependencies:
|
412
|
+
parameters: dict[str, Any],
|
413
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
414
|
+
dependencies: dict[str, set[TaskRunInput]] | None = None,
|
402
415
|
) -> PrefectDistributedFuture[R]:
|
403
416
|
...
|
404
417
|
|
405
418
|
def submit(
|
406
419
|
self,
|
407
|
-
task: "Task",
|
408
|
-
parameters:
|
409
|
-
wait_for:
|
410
|
-
dependencies:
|
411
|
-
):
|
420
|
+
task: "Task[P, R]",
|
421
|
+
parameters: dict[str, Any],
|
422
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
423
|
+
dependencies: dict[str, set[TaskRunInput]] | None = None,
|
424
|
+
) -> PrefectDistributedFuture[R]:
|
412
425
|
"""
|
413
426
|
Submit a task to the task run engine running in a separate thread.
|
414
427
|
|
@@ -443,8 +456,8 @@ class PrefectTaskRunner(TaskRunner[PrefectDistributedFuture]):
|
|
443
456
|
def map(
|
444
457
|
self,
|
445
458
|
task: "Task[P, Coroutine[Any, Any, R]]",
|
446
|
-
parameters:
|
447
|
-
wait_for:
|
459
|
+
parameters: dict[str, Any],
|
460
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
448
461
|
) -> PrefectFutureList[PrefectDistributedFuture[R]]:
|
449
462
|
...
|
450
463
|
|
@@ -452,15 +465,15 @@ class PrefectTaskRunner(TaskRunner[PrefectDistributedFuture]):
|
|
452
465
|
def map(
|
453
466
|
self,
|
454
467
|
task: "Task[Any, R]",
|
455
|
-
parameters:
|
456
|
-
wait_for:
|
468
|
+
parameters: dict[str, Any],
|
469
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
457
470
|
) -> PrefectFutureList[PrefectDistributedFuture[R]]:
|
458
471
|
...
|
459
472
|
|
460
473
|
def map(
|
461
474
|
self,
|
462
|
-
task: "Task",
|
463
|
-
parameters:
|
464
|
-
wait_for:
|
465
|
-
):
|
475
|
+
task: "Task[P, R]",
|
476
|
+
parameters: dict[str, Any],
|
477
|
+
wait_for: Iterable[PrefectFuture[Any]] | None = None,
|
478
|
+
) -> PrefectFutureList[PrefectDistributedFuture[R]]:
|
466
479
|
return super().map(task, parameters, wait_for)
|
prefect/task_runs.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import asyncio
|
2
4
|
import atexit
|
3
5
|
import threading
|
4
6
|
import uuid
|
5
|
-
from typing import Callable, Dict, Optional
|
7
|
+
from typing import TYPE_CHECKING, Callable, Dict, Optional
|
6
8
|
|
7
9
|
import anyio
|
8
10
|
from cachetools import TTLCache
|
@@ -15,6 +17,9 @@ from prefect.events.clients import get_events_subscriber
|
|
15
17
|
from prefect.events.filters import EventFilter, EventNameFilter
|
16
18
|
from prefect.logging.loggers import get_logger
|
17
19
|
|
20
|
+
if TYPE_CHECKING:
|
21
|
+
import logging
|
22
|
+
|
18
23
|
|
19
24
|
class TaskRunWaiter:
|
20
25
|
"""
|
@@ -68,19 +73,19 @@ class TaskRunWaiter:
|
|
68
73
|
_instance_lock = threading.Lock()
|
69
74
|
|
70
75
|
def __init__(self):
|
71
|
-
self.logger = get_logger("TaskRunWaiter")
|
72
|
-
self._consumer_task:
|
76
|
+
self.logger: "logging.Logger" = get_logger("TaskRunWaiter")
|
77
|
+
self._consumer_task: "asyncio.Task[None] | None" = None
|
73
78
|
self._observed_completed_task_runs: TTLCache[uuid.UUID, bool] = TTLCache(
|
74
79
|
maxsize=10000, ttl=600
|
75
80
|
)
|
76
81
|
self._completion_events: Dict[uuid.UUID, asyncio.Event] = {}
|
77
|
-
self._completion_callbacks: Dict[uuid.UUID, Callable] = {}
|
82
|
+
self._completion_callbacks: Dict[uuid.UUID, Callable[[], None]] = {}
|
78
83
|
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
79
84
|
self._observed_completed_task_runs_lock = threading.Lock()
|
80
85
|
self._completion_events_lock = threading.Lock()
|
81
86
|
self._started = False
|
82
87
|
|
83
|
-
def start(self):
|
88
|
+
def start(self) -> None:
|
84
89
|
"""
|
85
90
|
Start the TaskRunWaiter service.
|
86
91
|
"""
|
@@ -89,10 +94,12 @@ class TaskRunWaiter:
|
|
89
94
|
self.logger.debug("Starting TaskRunWaiter")
|
90
95
|
loop_thread = get_global_loop()
|
91
96
|
|
92
|
-
if not asyncio.get_running_loop() == loop_thread.
|
97
|
+
if not asyncio.get_running_loop() == loop_thread.loop:
|
93
98
|
raise RuntimeError("TaskRunWaiter must run on the global loop thread.")
|
94
99
|
|
95
|
-
self._loop = loop_thread.
|
100
|
+
self._loop = loop_thread.loop
|
101
|
+
if TYPE_CHECKING:
|
102
|
+
assert self._loop is not None
|
96
103
|
|
97
104
|
consumer_started = asyncio.Event()
|
98
105
|
self._consumer_task = self._loop.create_task(
|
@@ -141,7 +148,7 @@ class TaskRunWaiter:
|
|
141
148
|
except Exception as exc:
|
142
149
|
self.logger.error(f"Error processing event: {exc}")
|
143
150
|
|
144
|
-
def stop(self):
|
151
|
+
def stop(self) -> None:
|
145
152
|
"""
|
146
153
|
Stop the TaskRunWaiter service.
|
147
154
|
"""
|
@@ -155,7 +162,7 @@ class TaskRunWaiter:
|
|
155
162
|
@classmethod
|
156
163
|
async def wait_for_task_run(
|
157
164
|
cls, task_run_id: uuid.UUID, timeout: Optional[float] = None
|
158
|
-
):
|
165
|
+
) -> None:
|
159
166
|
"""
|
160
167
|
Wait for a task run to finish.
|
161
168
|
|
@@ -199,7 +206,9 @@ class TaskRunWaiter:
|
|
199
206
|
instance._completion_events.pop(task_run_id, None)
|
200
207
|
|
201
208
|
@classmethod
|
202
|
-
def add_done_callback(
|
209
|
+
def add_done_callback(
|
210
|
+
cls, task_run_id: uuid.UUID, callback: Callable[[], None]
|
211
|
+
) -> None:
|
203
212
|
"""
|
204
213
|
Add a callback to be called when a task run finishes.
|
205
214
|
|
@@ -219,7 +228,7 @@ class TaskRunWaiter:
|
|
219
228
|
instance._completion_callbacks[task_run_id] = callback
|
220
229
|
|
221
230
|
@classmethod
|
222
|
-
def instance(cls):
|
231
|
+
def instance(cls) -> Self:
|
223
232
|
"""
|
224
233
|
Get the singleton instance of TaskRunWaiter.
|
225
234
|
"""
|
prefect/task_worker.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import asyncio
|
2
4
|
import inspect
|
3
5
|
import os
|
@@ -16,12 +18,12 @@ import pendulum
|
|
16
18
|
import uvicorn
|
17
19
|
from exceptiongroup import BaseExceptionGroup # novermin
|
18
20
|
from fastapi import FastAPI
|
19
|
-
from typing_extensions import ParamSpec, TypeVar
|
21
|
+
from typing_extensions import ParamSpec, Self, TypeVar
|
20
22
|
from websockets.exceptions import InvalidStatusCode
|
21
23
|
|
22
24
|
from prefect import Task
|
23
25
|
from prefect._internal.concurrency.api import create_call, from_sync
|
24
|
-
from prefect.cache_policies import DEFAULT,
|
26
|
+
from prefect.cache_policies import DEFAULT, NO_CACHE
|
25
27
|
from prefect.client.orchestration import get_client
|
26
28
|
from prefect.client.schemas.objects import TaskRun
|
27
29
|
from prefect.client.subscriptions import Subscription
|
@@ -42,7 +44,10 @@ from prefect.utilities.processutils import (
|
|
42
44
|
from prefect.utilities.services import start_client_metrics_server
|
43
45
|
from prefect.utilities.urls import url_for
|
44
46
|
|
45
|
-
|
47
|
+
if TYPE_CHECKING:
|
48
|
+
import logging
|
49
|
+
|
50
|
+
logger: "logging.Logger" = get_logger("task_worker")
|
46
51
|
|
47
52
|
P = ParamSpec("P")
|
48
53
|
R = TypeVar("R", infer_variance=True)
|
@@ -85,7 +90,7 @@ class TaskWorker:
|
|
85
90
|
def __init__(
|
86
91
|
self,
|
87
92
|
*tasks: Task[P, R],
|
88
|
-
limit:
|
93
|
+
limit: int | None = 10,
|
89
94
|
):
|
90
95
|
self.tasks: list["Task[..., Any]"] = []
|
91
96
|
for t in tasks:
|
@@ -93,14 +98,14 @@ class TaskWorker:
|
|
93
98
|
if not isinstance(t, Task):
|
94
99
|
continue
|
95
100
|
|
96
|
-
if t.cache_policy in [None,
|
101
|
+
if t.cache_policy in [None, NO_CACHE, NotSet]:
|
97
102
|
self.tasks.append(
|
98
103
|
t.with_options(persist_result=True, cache_policy=DEFAULT)
|
99
104
|
)
|
100
105
|
else:
|
101
106
|
self.tasks.append(t.with_options(persist_result=True))
|
102
107
|
|
103
|
-
self.task_keys = set(t.task_key for t in tasks if isinstance(t, Task)) # pyright: ignore[reportUnnecessaryIsInstance]
|
108
|
+
self.task_keys: set[str] = set(t.task_key for t in tasks if isinstance(t, Task)) # pyright: ignore[reportUnnecessaryIsInstance]
|
104
109
|
|
105
110
|
self._started_at: Optional[pendulum.DateTime] = None
|
106
111
|
self.stopping: bool = False
|
@@ -154,7 +159,7 @@ class TaskWorker:
|
|
154
159
|
def available_tasks(self) -> Optional[int]:
|
155
160
|
return int(self._limiter.available_tokens) if self._limiter else None
|
156
161
|
|
157
|
-
def handle_sigterm(self, signum: int, frame: object):
|
162
|
+
def handle_sigterm(self, signum: int, frame: object) -> None:
|
158
163
|
"""
|
159
164
|
Shuts down the task worker when a SIGTERM is received.
|
160
165
|
"""
|
@@ -355,14 +360,14 @@ class TaskWorker:
|
|
355
360
|
)
|
356
361
|
await asyncio.wrap_future(future)
|
357
362
|
|
358
|
-
async def execute_task_run(self, task_run: TaskRun):
|
363
|
+
async def execute_task_run(self, task_run: TaskRun) -> None:
|
359
364
|
"""Execute a task run in the task worker."""
|
360
365
|
async with self if not self.started else asyncnullcontext():
|
361
366
|
token_acquired = await self._acquire_token(task_run.id)
|
362
367
|
if token_acquired:
|
363
368
|
await self._safe_submit_scheduled_task_run(task_run)
|
364
369
|
|
365
|
-
async def __aenter__(self):
|
370
|
+
async def __aenter__(self) -> Self:
|
366
371
|
logger.debug("Starting task worker...")
|
367
372
|
|
368
373
|
if self._client._closed: # pyright: ignore[reportPrivateUsage]
|