prefect-client 3.1.6__py3-none-any.whl → 3.1.7__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 (49) 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 +88 -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/storage.py +3 -3
  35. prefect/serializers.py +28 -24
  36. prefect/settings/models/experiments.py +5 -0
  37. prefect/task_engine.py +34 -26
  38. prefect/task_worker.py +43 -25
  39. prefect/tasks.py +118 -125
  40. prefect/telemetry/instrumentation.py +1 -1
  41. prefect/telemetry/processors.py +10 -7
  42. prefect/telemetry/run_telemetry.py +157 -33
  43. prefect/types/__init__.py +4 -1
  44. prefect/variables.py +127 -19
  45. {prefect_client-3.1.6.dist-info → prefect_client-3.1.7.dist-info}/METADATA +2 -1
  46. {prefect_client-3.1.6.dist-info → prefect_client-3.1.7.dist-info}/RECORD +49 -47
  47. {prefect_client-3.1.6.dist-info → prefect_client-3.1.7.dist-info}/LICENSE +0 -0
  48. {prefect_client-3.1.6.dist-info → prefect_client-3.1.7.dist-info}/WHEEL +0 -0
  49. {prefect_client-3.1.6.dist-info → prefect_client-3.1.7.dist-info}/top_level.txt +0 -0
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)