prefect-client 3.1.6__py3-none-any.whl → 3.1.8__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.
Files changed (55) hide show
  1. prefect/_experimental/__init__.py +0 -0
  2. prefect/_experimental/lineage.py +181 -0
  3. prefect/_internal/compatibility/async_dispatch.py +38 -9
  4. prefect/_internal/pydantic/v2_validated_func.py +15 -10
  5. prefect/_internal/retries.py +15 -6
  6. prefect/_internal/schemas/bases.py +2 -1
  7. prefect/_internal/schemas/validators.py +5 -4
  8. prefect/_version.py +3 -3
  9. prefect/blocks/core.py +144 -17
  10. prefect/blocks/system.py +2 -1
  11. prefect/client/orchestration.py +106 -0
  12. prefect/client/schemas/actions.py +5 -5
  13. prefect/client/schemas/filters.py +1 -1
  14. prefect/client/schemas/objects.py +5 -5
  15. prefect/client/schemas/responses.py +1 -2
  16. prefect/client/schemas/schedules.py +1 -1
  17. prefect/client/subscriptions.py +2 -1
  18. prefect/client/utilities.py +15 -1
  19. prefect/context.py +1 -1
  20. prefect/deployments/flow_runs.py +3 -3
  21. prefect/deployments/runner.py +14 -14
  22. prefect/deployments/steps/core.py +3 -1
  23. prefect/deployments/steps/pull.py +60 -12
  24. prefect/events/clients.py +55 -4
  25. prefect/events/filters.py +1 -1
  26. prefect/events/related.py +2 -1
  27. prefect/events/schemas/events.py +1 -1
  28. prefect/events/utilities.py +2 -0
  29. prefect/events/worker.py +8 -0
  30. prefect/flow_engine.py +41 -81
  31. prefect/flow_runs.py +4 -2
  32. prefect/flows.py +4 -6
  33. prefect/results.py +43 -22
  34. prefect/runner/runner.py +129 -18
  35. prefect/runner/storage.py +3 -3
  36. prefect/serializers.py +28 -24
  37. prefect/settings/__init__.py +1 -0
  38. prefect/settings/base.py +3 -2
  39. prefect/settings/models/api.py +4 -0
  40. prefect/settings/models/experiments.py +5 -0
  41. prefect/settings/models/runner.py +8 -0
  42. prefect/settings/models/server/api.py +7 -1
  43. prefect/task_engine.py +34 -26
  44. prefect/task_worker.py +43 -25
  45. prefect/tasks.py +118 -125
  46. prefect/telemetry/instrumentation.py +1 -1
  47. prefect/telemetry/processors.py +10 -7
  48. prefect/telemetry/run_telemetry.py +157 -33
  49. prefect/types/__init__.py +4 -1
  50. prefect/variables.py +127 -19
  51. {prefect_client-3.1.6.dist-info → prefect_client-3.1.8.dist-info}/METADATA +2 -1
  52. {prefect_client-3.1.6.dist-info → prefect_client-3.1.8.dist-info}/RECORD +55 -53
  53. {prefect_client-3.1.6.dist-info → prefect_client-3.1.8.dist-info}/LICENSE +0 -0
  54. {prefect_client-3.1.6.dist-info → prefect_client-3.1.8.dist-info}/WHEEL +0 -0
  55. {prefect_client-3.1.6.dist-info → prefect_client-3.1.8.dist-info}/top_level.txt +0 -0
prefect/task_engine.py CHANGED
@@ -4,7 +4,7 @@ import logging
4
4
  import threading
5
5
  import time
6
6
  from asyncio import CancelledError
7
- from contextlib import ExitStack, asynccontextmanager, contextmanager
7
+ from contextlib import ExitStack, asynccontextmanager, contextmanager, nullcontext
8
8
  from dataclasses import dataclass, field
9
9
  from functools import partial
10
10
  from textwrap import dedent
@@ -523,7 +523,7 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
523
523
  self.set_state(terminal_state)
524
524
  self._return_value = result
525
525
 
526
- self._telemetry.end_span_on_success(terminal_state.message)
526
+ self._telemetry.end_span_on_success()
527
527
  return result
528
528
 
529
529
  def handle_retry(self, exc: Exception) -> bool:
@@ -586,7 +586,7 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
586
586
  self.record_terminal_state_timing(state)
587
587
  self.set_state(state)
588
588
  self._raised = exc
589
- self._telemetry.end_span_on_failure(state.message)
589
+ self._telemetry.end_span_on_failure(state.message if state else None)
590
590
 
591
591
  def handle_timeout(self, exc: TimeoutError) -> None:
592
592
  if not self.handle_retry(exc):
@@ -600,6 +600,7 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
600
600
  message=message,
601
601
  name="TimedOut",
602
602
  )
603
+ self.record_terminal_state_timing(state)
603
604
  self.set_state(state)
604
605
  self._raised = exc
605
606
 
@@ -611,7 +612,7 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
611
612
  self.set_state(state, force=True)
612
613
  self._raised = exc
613
614
  self._telemetry.record_exception(exc)
614
- self._telemetry.end_span_on_failure(state.message)
615
+ self._telemetry.end_span_on_failure(state.message if state else None)
615
616
 
616
617
  @contextmanager
617
618
  def setup_run_context(self, client: Optional[SyncPrefectClient] = None):
@@ -669,7 +670,7 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
669
670
  with SyncClientContext.get_or_create() as client_ctx:
670
671
  self._client = client_ctx.client
671
672
  self._is_started = True
672
- flow_run_context = FlowRunContext.get()
673
+ parent_flow_run_context = FlowRunContext.get()
673
674
  parent_task_run_context = TaskRunContext.get()
674
675
 
675
676
  try:
@@ -678,7 +679,7 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
678
679
  self.task.create_local_run(
679
680
  id=task_run_id,
680
681
  parameters=self.parameters,
681
- flow_run_context=flow_run_context,
682
+ flow_run_context=parent_flow_run_context,
682
683
  parent_task_run_context=parent_task_run_context,
683
684
  wait_for=self.wait_for,
684
685
  extra_task_inputs=dependencies,
@@ -696,11 +697,12 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
696
697
  self.logger.debug(
697
698
  f"Created task run {self.task_run.name!r} for task {self.task.name!r}"
698
699
  )
699
- labels = (
700
- flow_run_context.flow_run.labels if flow_run_context else {}
701
- )
700
+
702
701
  self._telemetry.start_span(
703
- self.task_run, self.parameters, labels
702
+ run=self.task_run,
703
+ name=self.task.name,
704
+ client=self.client,
705
+ parameters=self.parameters,
704
706
  )
705
707
 
706
708
  yield self
@@ -754,7 +756,9 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
754
756
  dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
755
757
  ) -> Generator[None, None, None]:
756
758
  with self.initialize_run(task_run_id=task_run_id, dependencies=dependencies):
757
- with trace.use_span(self._telemetry._span):
759
+ with trace.use_span(
760
+ self._telemetry.span
761
+ ) if self._telemetry.span else nullcontext():
758
762
  self.begin_run()
759
763
  try:
760
764
  yield
@@ -1057,7 +1061,7 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1057
1061
  await self.set_state(terminal_state)
1058
1062
  self._return_value = result
1059
1063
 
1060
- self._telemetry.end_span_on_success(terminal_state.message)
1064
+ self._telemetry.end_span_on_success()
1061
1065
 
1062
1066
  return result
1063
1067
 
@@ -1134,6 +1138,7 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1134
1138
  message=message,
1135
1139
  name="TimedOut",
1136
1140
  )
1141
+ self.record_terminal_state_timing(state)
1137
1142
  await self.set_state(state)
1138
1143
  self._raised = exc
1139
1144
  self._telemetry.end_span_on_failure(state.message)
@@ -1204,15 +1209,16 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1204
1209
  async with AsyncClientContext.get_or_create():
1205
1210
  self._client = get_client()
1206
1211
  self._is_started = True
1207
- flow_run_context = FlowRunContext.get()
1212
+ parent_flow_run_context = FlowRunContext.get()
1213
+ parent_task_run_context = TaskRunContext.get()
1208
1214
 
1209
1215
  try:
1210
1216
  if not self.task_run:
1211
1217
  self.task_run = await self.task.create_local_run(
1212
1218
  id=task_run_id,
1213
1219
  parameters=self.parameters,
1214
- flow_run_context=flow_run_context,
1215
- parent_task_run_context=TaskRunContext.get(),
1220
+ flow_run_context=parent_flow_run_context,
1221
+ parent_task_run_context=parent_task_run_context,
1216
1222
  wait_for=self.wait_for,
1217
1223
  extra_task_inputs=dependencies,
1218
1224
  )
@@ -1229,11 +1235,11 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1229
1235
  f"Created task run {self.task_run.name!r} for task {self.task.name!r}"
1230
1236
  )
1231
1237
 
1232
- labels = (
1233
- flow_run_context.flow_run.labels if flow_run_context else {}
1234
- )
1235
- self._telemetry.start_span(
1236
- self.task_run, self.parameters, labels
1238
+ await self._telemetry.async_start_span(
1239
+ run=self.task_run,
1240
+ name=self.task.name,
1241
+ client=self.client,
1242
+ parameters=self.parameters,
1237
1243
  )
1238
1244
 
1239
1245
  yield self
@@ -1289,7 +1295,9 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1289
1295
  async with self.initialize_run(
1290
1296
  task_run_id=task_run_id, dependencies=dependencies
1291
1297
  ):
1292
- with trace.use_span(self._telemetry._span):
1298
+ with trace.use_span(
1299
+ self._telemetry.span
1300
+ ) if self._telemetry.span else nullcontext():
1293
1301
  await self.begin_run()
1294
1302
  try:
1295
1303
  yield
@@ -1370,7 +1378,7 @@ def run_task_sync(
1370
1378
  task_run_id: Optional[UUID] = None,
1371
1379
  task_run: Optional[TaskRun] = None,
1372
1380
  parameters: Optional[Dict[str, Any]] = None,
1373
- wait_for: Optional[Iterable[PrefectFuture]] = None,
1381
+ wait_for: Optional[Iterable[PrefectFuture[R]]] = None,
1374
1382
  return_type: Literal["state", "result"] = "result",
1375
1383
  dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
1376
1384
  context: Optional[Dict[str, Any]] = None,
@@ -1397,7 +1405,7 @@ async def run_task_async(
1397
1405
  task_run_id: Optional[UUID] = None,
1398
1406
  task_run: Optional[TaskRun] = None,
1399
1407
  parameters: Optional[Dict[str, Any]] = None,
1400
- wait_for: Optional[Iterable[PrefectFuture]] = None,
1408
+ wait_for: Optional[Iterable[PrefectFuture[R]]] = None,
1401
1409
  return_type: Literal["state", "result"] = "result",
1402
1410
  dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
1403
1411
  context: Optional[Dict[str, Any]] = None,
@@ -1424,7 +1432,7 @@ def run_generator_task_sync(
1424
1432
  task_run_id: Optional[UUID] = None,
1425
1433
  task_run: Optional[TaskRun] = None,
1426
1434
  parameters: Optional[Dict[str, Any]] = None,
1427
- wait_for: Optional[Iterable[PrefectFuture]] = None,
1435
+ wait_for: Optional[Iterable[PrefectFuture[R]]] = None,
1428
1436
  return_type: Literal["state", "result"] = "result",
1429
1437
  dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
1430
1438
  context: Optional[Dict[str, Any]] = None,
@@ -1479,7 +1487,7 @@ async def run_generator_task_async(
1479
1487
  task_run_id: Optional[UUID] = None,
1480
1488
  task_run: Optional[TaskRun] = None,
1481
1489
  parameters: Optional[Dict[str, Any]] = None,
1482
- wait_for: Optional[Iterable[PrefectFuture]] = None,
1490
+ wait_for: Optional[Iterable[PrefectFuture[R]]] = None,
1483
1491
  return_type: Literal["state", "result"] = "result",
1484
1492
  dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
1485
1493
  context: Optional[Dict[str, Any]] = None,
@@ -1535,7 +1543,7 @@ def run_task(
1535
1543
  task_run_id: Optional[UUID] = None,
1536
1544
  task_run: Optional[TaskRun] = None,
1537
1545
  parameters: Optional[Dict[str, Any]] = None,
1538
- wait_for: Optional[Iterable[PrefectFuture]] = None,
1546
+ wait_for: Optional[Iterable[PrefectFuture[R]]] = None,
1539
1547
  return_type: Literal["state", "result"] = "result",
1540
1548
  dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
1541
1549
  context: Optional[Dict[str, Any]] = None,
prefect/task_worker.py CHANGED
@@ -7,7 +7,7 @@ import sys
7
7
  from concurrent.futures import ThreadPoolExecutor
8
8
  from contextlib import AsyncExitStack
9
9
  from contextvars import copy_context
10
- from typing import Optional
10
+ from typing import TYPE_CHECKING, Any, Optional
11
11
  from uuid import UUID
12
12
 
13
13
  import anyio
@@ -16,6 +16,7 @@ import pendulum
16
16
  import uvicorn
17
17
  from exceptiongroup import BaseExceptionGroup # novermin
18
18
  from fastapi import FastAPI
19
+ from typing_extensions import ParamSpec, TypeVar
19
20
  from websockets.exceptions import InvalidStatusCode
20
21
 
21
22
  from prefect import Task
@@ -35,12 +36,17 @@ from prefect.task_engine import run_task_async, run_task_sync
35
36
  from prefect.utilities.annotations import NotSet
36
37
  from prefect.utilities.asyncutils import asyncnullcontext, sync_compatible
37
38
  from prefect.utilities.engine import emit_task_run_state_change_event
38
- from prefect.utilities.processutils import _register_signal
39
+ from prefect.utilities.processutils import (
40
+ _register_signal, # pyright: ignore[reportPrivateUsage]
41
+ )
39
42
  from prefect.utilities.services import start_client_metrics_server
40
43
  from prefect.utilities.urls import url_for
41
44
 
42
45
  logger = get_logger("task_worker")
43
46
 
47
+ P = ParamSpec("P")
48
+ R = TypeVar("R", infer_variance=True)
49
+
44
50
 
45
51
  class StopTaskWorker(Exception):
46
52
  """Raised when the task worker is stopped."""
@@ -48,8 +54,10 @@ class StopTaskWorker(Exception):
48
54
  pass
49
55
 
50
56
 
51
- def should_try_to_read_parameters(task: Task, task_run: TaskRun) -> bool:
57
+ def should_try_to_read_parameters(task: Task[P, R], task_run: TaskRun) -> bool:
52
58
  """Determines whether a task run should read parameters from the result store."""
59
+ if TYPE_CHECKING:
60
+ assert task_run.state is not None
53
61
  new_enough_state_details = hasattr(
54
62
  task_run.state.state_details, "task_parameters_id"
55
63
  )
@@ -76,20 +84,23 @@ class TaskWorker:
76
84
 
77
85
  def __init__(
78
86
  self,
79
- *tasks: Task,
87
+ *tasks: Task[P, R],
80
88
  limit: Optional[int] = 10,
81
89
  ):
82
- self.tasks = []
90
+ self.tasks: list["Task[..., Any]"] = []
83
91
  for t in tasks:
84
- if isinstance(t, Task):
85
- if t.cache_policy in [None, NONE, NotSet]:
86
- self.tasks.append(
87
- t.with_options(persist_result=True, cache_policy=DEFAULT)
88
- )
89
- else:
90
- self.tasks.append(t.with_options(persist_result=True))
92
+ if not TYPE_CHECKING:
93
+ if not isinstance(t, Task):
94
+ continue
91
95
 
92
- self.task_keys = set(t.task_key for t in tasks if isinstance(t, Task))
96
+ if t.cache_policy in [None, NONE, NotSet]:
97
+ self.tasks.append(
98
+ t.with_options(persist_result=True, cache_policy=DEFAULT)
99
+ )
100
+ else:
101
+ self.tasks.append(t.with_options(persist_result=True))
102
+
103
+ self.task_keys = set(t.task_key for t in tasks if isinstance(t, Task)) # pyright: ignore[reportUnnecessaryIsInstance]
93
104
 
94
105
  self._started_at: Optional[pendulum.DateTime] = None
95
106
  self.stopping: bool = False
@@ -97,7 +108,9 @@ class TaskWorker:
97
108
  self._client = get_client()
98
109
  self._exit_stack = AsyncExitStack()
99
110
 
100
- if not asyncio.get_event_loop().is_running():
111
+ try:
112
+ asyncio.get_running_loop()
113
+ except RuntimeError:
101
114
  raise RuntimeError(
102
115
  "TaskWorker must be initialized within an async context."
103
116
  )
@@ -141,7 +154,7 @@ class TaskWorker:
141
154
  def available_tasks(self) -> Optional[int]:
142
155
  return int(self._limiter.available_tokens) if self._limiter else None
143
156
 
144
- def handle_sigterm(self, signum, frame):
157
+ def handle_sigterm(self, signum: int, frame: object):
145
158
  """
146
159
  Shuts down the task worker when a SIGTERM is received.
147
160
  """
@@ -252,6 +265,8 @@ class TaskWorker:
252
265
  self._release_token(task_run.id)
253
266
 
254
267
  async def _submit_scheduled_task_run(self, task_run: TaskRun):
268
+ if TYPE_CHECKING:
269
+ assert task_run.state is not None
255
270
  logger.debug(
256
271
  f"Found task run: {task_run.name!r} in state: {task_run.state.name!r}"
257
272
  )
@@ -280,7 +295,7 @@ class TaskWorker:
280
295
  result_storage=await get_or_create_default_task_scheduling_storage()
281
296
  ).update_for_task(task)
282
297
  try:
283
- run_data = await store.read_parameters(parameters_id)
298
+ run_data: dict[str, Any] = await store.read_parameters(parameters_id)
284
299
  parameters = run_data.get("parameters", {})
285
300
  wait_for = run_data.get("wait_for", [])
286
301
  run_context = run_data.get("context", None)
@@ -350,7 +365,7 @@ class TaskWorker:
350
365
  async def __aenter__(self):
351
366
  logger.debug("Starting task worker...")
352
367
 
353
- if self._client._closed:
368
+ if self._client._closed: # pyright: ignore[reportPrivateUsage]
354
369
  self._client = get_client()
355
370
  self._runs_task_group = anyio.create_task_group()
356
371
 
@@ -362,7 +377,7 @@ class TaskWorker:
362
377
  self._started_at = pendulum.now()
363
378
  return self
364
379
 
365
- async def __aexit__(self, *exc_info):
380
+ async def __aexit__(self, *exc_info: Any) -> None:
366
381
  logger.debug("Stopping task worker...")
367
382
  self._started_at = None
368
383
  await self._exit_stack.__aexit__(*exc_info)
@@ -372,7 +387,9 @@ def create_status_server(task_worker: TaskWorker) -> FastAPI:
372
387
  status_app = FastAPI()
373
388
 
374
389
  @status_app.get("/status")
375
- def status():
390
+ def status(): # pyright: ignore[reportUnusedFunction]
391
+ if TYPE_CHECKING:
392
+ assert task_worker.started_at is not None
376
393
  return {
377
394
  "client_id": task_worker.client_id,
378
395
  "started_at": task_worker.started_at.isoformat(),
@@ -393,11 +410,13 @@ def create_status_server(task_worker: TaskWorker) -> FastAPI:
393
410
 
394
411
  @sync_compatible
395
412
  async def serve(
396
- *tasks: Task, limit: Optional[int] = 10, status_server_port: Optional[int] = None
413
+ *tasks: Task[P, R],
414
+ limit: Optional[int] = 10,
415
+ status_server_port: Optional[int] = None,
397
416
  ):
398
- """Serve the provided tasks so that their runs may be submitted to and executed.
399
- in the engine. Tasks do not need to be within a flow run context to be submitted.
400
- You must `.submit` the same task object that you pass to `serve`.
417
+ """Serve the provided tasks so that their runs may be submitted to
418
+ and executed in the engine. Tasks do not need to be within a flow run context to be
419
+ submitted. You must `.submit` the same task object that you pass to `serve`.
401
420
 
402
421
  Args:
403
422
  - tasks: A list of tasks to serve. When a scheduled task run is found for a
@@ -422,8 +441,7 @@ async def serve(
422
441
  print(message.upper())
423
442
 
424
443
  # starts a long-lived process that listens for scheduled runs of these tasks
425
- if __name__ == "__main__":
426
- serve(say, yell)
444
+ serve(say, yell)
427
445
  ```
428
446
  """
429
447
  task_worker = TaskWorker(*tasks, limit=limit)