prefect-client 3.1.5__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 (114) hide show
  1. prefect/__init__.py +3 -0
  2. prefect/_experimental/__init__.py +0 -0
  3. prefect/_experimental/lineage.py +181 -0
  4. prefect/_internal/compatibility/async_dispatch.py +38 -9
  5. prefect/_internal/compatibility/migration.py +1 -1
  6. prefect/_internal/concurrency/api.py +52 -52
  7. prefect/_internal/concurrency/calls.py +59 -35
  8. prefect/_internal/concurrency/cancellation.py +34 -18
  9. prefect/_internal/concurrency/event_loop.py +7 -6
  10. prefect/_internal/concurrency/threads.py +41 -33
  11. prefect/_internal/concurrency/waiters.py +28 -21
  12. prefect/_internal/pydantic/v1_schema.py +2 -2
  13. prefect/_internal/pydantic/v2_schema.py +10 -9
  14. prefect/_internal/pydantic/v2_validated_func.py +15 -10
  15. prefect/_internal/retries.py +15 -6
  16. prefect/_internal/schemas/bases.py +11 -8
  17. prefect/_internal/schemas/validators.py +7 -5
  18. prefect/_version.py +3 -3
  19. prefect/automations.py +53 -47
  20. prefect/blocks/abstract.py +12 -10
  21. prefect/blocks/core.py +148 -19
  22. prefect/blocks/system.py +2 -1
  23. prefect/cache_policies.py +11 -11
  24. prefect/client/__init__.py +3 -1
  25. prefect/client/base.py +36 -37
  26. prefect/client/cloud.py +26 -19
  27. prefect/client/collections.py +2 -2
  28. prefect/client/orchestration.py +430 -273
  29. prefect/client/schemas/__init__.py +24 -0
  30. prefect/client/schemas/actions.py +128 -121
  31. prefect/client/schemas/filters.py +1 -1
  32. prefect/client/schemas/objects.py +114 -85
  33. prefect/client/schemas/responses.py +19 -20
  34. prefect/client/schemas/schedules.py +136 -93
  35. prefect/client/subscriptions.py +30 -15
  36. prefect/client/utilities.py +46 -36
  37. prefect/concurrency/asyncio.py +6 -9
  38. prefect/concurrency/sync.py +35 -5
  39. prefect/context.py +40 -32
  40. prefect/deployments/flow_runs.py +6 -8
  41. prefect/deployments/runner.py +14 -14
  42. prefect/deployments/steps/core.py +3 -1
  43. prefect/deployments/steps/pull.py +60 -12
  44. prefect/docker/__init__.py +1 -1
  45. prefect/events/clients.py +55 -4
  46. prefect/events/filters.py +1 -1
  47. prefect/events/related.py +2 -1
  48. prefect/events/schemas/events.py +26 -21
  49. prefect/events/utilities.py +3 -2
  50. prefect/events/worker.py +8 -0
  51. prefect/filesystems.py +3 -3
  52. prefect/flow_engine.py +87 -87
  53. prefect/flow_runs.py +7 -5
  54. prefect/flows.py +218 -176
  55. prefect/logging/configuration.py +1 -1
  56. prefect/logging/highlighters.py +1 -2
  57. prefect/logging/loggers.py +30 -20
  58. prefect/main.py +17 -24
  59. prefect/results.py +43 -22
  60. prefect/runner/runner.py +43 -21
  61. prefect/runner/server.py +30 -32
  62. prefect/runner/storage.py +3 -3
  63. prefect/runner/submit.py +3 -6
  64. prefect/runner/utils.py +6 -6
  65. prefect/runtime/flow_run.py +7 -0
  66. prefect/serializers.py +28 -24
  67. prefect/settings/constants.py +2 -2
  68. prefect/settings/legacy.py +1 -1
  69. prefect/settings/models/experiments.py +5 -0
  70. prefect/settings/models/server/events.py +10 -0
  71. prefect/task_engine.py +87 -26
  72. prefect/task_runners.py +2 -2
  73. prefect/task_worker.py +43 -25
  74. prefect/tasks.py +148 -142
  75. prefect/telemetry/bootstrap.py +15 -2
  76. prefect/telemetry/instrumentation.py +1 -1
  77. prefect/telemetry/processors.py +10 -7
  78. prefect/telemetry/run_telemetry.py +231 -0
  79. prefect/transactions.py +14 -14
  80. prefect/types/__init__.py +5 -5
  81. prefect/utilities/_engine.py +96 -0
  82. prefect/utilities/annotations.py +25 -18
  83. prefect/utilities/asyncutils.py +126 -140
  84. prefect/utilities/callables.py +87 -78
  85. prefect/utilities/collections.py +278 -117
  86. prefect/utilities/compat.py +13 -21
  87. prefect/utilities/context.py +6 -5
  88. prefect/utilities/dispatch.py +23 -12
  89. prefect/utilities/dockerutils.py +33 -32
  90. prefect/utilities/engine.py +126 -239
  91. prefect/utilities/filesystem.py +18 -15
  92. prefect/utilities/hashing.py +10 -11
  93. prefect/utilities/importtools.py +40 -27
  94. prefect/utilities/math.py +9 -5
  95. prefect/utilities/names.py +3 -3
  96. prefect/utilities/processutils.py +121 -57
  97. prefect/utilities/pydantic.py +41 -36
  98. prefect/utilities/render_swagger.py +22 -12
  99. prefect/utilities/schema_tools/__init__.py +2 -1
  100. prefect/utilities/schema_tools/hydration.py +50 -43
  101. prefect/utilities/schema_tools/validation.py +52 -42
  102. prefect/utilities/services.py +13 -12
  103. prefect/utilities/templating.py +45 -45
  104. prefect/utilities/text.py +2 -1
  105. prefect/utilities/timeout.py +4 -4
  106. prefect/utilities/urls.py +9 -4
  107. prefect/utilities/visualization.py +46 -24
  108. prefect/variables.py +136 -27
  109. prefect/workers/base.py +15 -8
  110. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/METADATA +5 -2
  111. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/RECORD +114 -110
  112. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/LICENSE +0 -0
  113. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/WHEEL +0 -0
  114. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/top_level.txt +0 -0
@@ -1,40 +1,39 @@
1
1
  import asyncio
2
2
  import contextlib
3
- import inspect
4
3
  import os
5
4
  import signal
6
5
  import time
6
+ from collections.abc import Awaitable, Callable, Generator
7
7
  from functools import partial
8
+ from logging import Logger
8
9
  from typing import (
9
10
  TYPE_CHECKING,
10
11
  Any,
11
- Callable,
12
- Dict,
13
- Iterable,
12
+ NoReturn,
14
13
  Optional,
15
- Set,
16
14
  TypeVar,
17
15
  Union,
16
+ cast,
18
17
  )
19
- from uuid import UUID, uuid4
18
+ from uuid import UUID
20
19
 
21
20
  import anyio
22
- from typing_extensions import Literal
21
+ from typing_extensions import TypeIs
23
22
 
24
23
  import prefect
25
24
  import prefect.context
25
+ import prefect.exceptions
26
26
  import prefect.plugins
27
27
  from prefect._internal.concurrency.cancellation import get_deadline
28
28
  from prefect.client.schemas import OrchestrationResult, TaskRun
29
- from prefect.client.schemas.objects import (
30
- StateType,
31
- TaskRunInput,
32
- TaskRunResult,
33
- )
34
- from prefect.client.schemas.responses import SetStateStatus
35
- from prefect.context import (
36
- FlowRunContext,
29
+ from prefect.client.schemas.objects import TaskRunInput, TaskRunResult
30
+ from prefect.client.schemas.responses import (
31
+ SetStateStatus,
32
+ StateAbortDetails,
33
+ StateRejectDetails,
34
+ StateWaitDetails,
37
35
  )
36
+ from prefect.context import FlowRunContext
38
37
  from prefect.events import Event, emit_event
39
38
  from prefect.exceptions import (
40
39
  Pause,
@@ -44,37 +43,26 @@ from prefect.exceptions import (
44
43
  )
45
44
  from prefect.flows import Flow
46
45
  from prefect.futures import PrefectFuture
47
- from prefect.logging.loggers import (
48
- get_logger,
49
- task_run_logger,
50
- )
46
+ from prefect.logging.loggers import get_logger
51
47
  from prefect.results import BaseResult, ResultRecord, should_persist_result
52
- from prefect.settings import (
53
- PREFECT_LOGGING_LOG_PRINTS,
54
- )
55
- from prefect.states import (
56
- State,
57
- get_state_exception,
58
- )
48
+ from prefect.settings import PREFECT_LOGGING_LOG_PRINTS
49
+ from prefect.states import State
59
50
  from prefect.tasks import Task
60
51
  from prefect.utilities.annotations import allow_failure, quote
61
- from prefect.utilities.asyncutils import (
62
- gather,
63
- run_coro_as_sync,
64
- )
52
+ from prefect.utilities.asyncutils import run_coro_as_sync
65
53
  from prefect.utilities.collections import StopVisiting, visit_collection
66
54
  from prefect.utilities.text import truncated_to
67
55
 
68
56
  if TYPE_CHECKING:
69
57
  from prefect.client.orchestration import PrefectClient, SyncPrefectClient
70
58
 
71
- API_HEALTHCHECKS = {}
72
- UNTRACKABLE_TYPES = {bool, type(None), type(...), type(NotImplemented)}
73
- engine_logger = get_logger("engine")
59
+ API_HEALTHCHECKS: dict[str, float] = {}
60
+ UNTRACKABLE_TYPES: set[type[Any]] = {bool, type(None), type(...), type(NotImplemented)}
61
+ engine_logger: Logger = get_logger("engine")
74
62
  T = TypeVar("T")
75
63
 
76
64
 
77
- async def collect_task_run_inputs(expr: Any, max_depth: int = -1) -> Set[TaskRunInput]:
65
+ async def collect_task_run_inputs(expr: Any, max_depth: int = -1) -> set[TaskRunInput]:
78
66
  """
79
67
  This function recurses through an expression to generate a set of any discernible
80
68
  task run inputs it finds in the data structure. It produces a set of all inputs
@@ -87,14 +75,11 @@ async def collect_task_run_inputs(expr: Any, max_depth: int = -1) -> Set[TaskRun
87
75
  """
88
76
  # TODO: This function needs to be updated to detect parameters and constants
89
77
 
90
- inputs = set()
91
- futures = set()
78
+ inputs: set[TaskRunInput] = set()
92
79
 
93
- def add_futures_and_states_to_inputs(obj):
80
+ def add_futures_and_states_to_inputs(obj: Any) -> None:
94
81
  if isinstance(obj, PrefectFuture):
95
- # We need to wait for futures to be submitted before we can get the task
96
- # run id but we want to do so asynchronously
97
- futures.add(obj)
82
+ inputs.add(TaskRunResult(id=obj.task_run_id))
98
83
  elif isinstance(obj, State):
99
84
  if obj.state_details.task_run_id:
100
85
  inputs.add(TaskRunResult(id=obj.state_details.task_run_id))
@@ -113,16 +98,12 @@ async def collect_task_run_inputs(expr: Any, max_depth: int = -1) -> Set[TaskRun
113
98
  max_depth=max_depth,
114
99
  )
115
100
 
116
- await asyncio.gather(*[future._wait_for_submission() for future in futures])
117
- for future in futures:
118
- inputs.add(TaskRunResult(id=future.task_run.id))
119
-
120
101
  return inputs
121
102
 
122
103
 
123
104
  def collect_task_run_inputs_sync(
124
105
  expr: Any, future_cls: Any = PrefectFuture, max_depth: int = -1
125
- ) -> Set[TaskRunInput]:
106
+ ) -> set[TaskRunInput]:
126
107
  """
127
108
  This function recurses through an expression to generate a set of any discernible
128
109
  task run inputs it finds in the data structure. It produces a set of all inputs
@@ -135,9 +116,9 @@ def collect_task_run_inputs_sync(
135
116
  """
136
117
  # TODO: This function needs to be updated to detect parameters and constants
137
118
 
138
- inputs = set()
119
+ inputs: set[TaskRunInput] = set()
139
120
 
140
- def add_futures_and_states_to_inputs(obj):
121
+ def add_futures_and_states_to_inputs(obj: Any) -> None:
141
122
  if isinstance(obj, future_cls) and hasattr(obj, "task_run_id"):
142
123
  inputs.add(TaskRunResult(id=obj.task_run_id))
143
124
  elif isinstance(obj, State):
@@ -161,58 +142,9 @@ def collect_task_run_inputs_sync(
161
142
  return inputs
162
143
 
163
144
 
164
- async def wait_for_task_runs_and_report_crashes(
165
- task_run_futures: Iterable[PrefectFuture], client: "PrefectClient"
166
- ) -> Literal[True]:
167
- crash_exceptions = []
168
-
169
- # Gather states concurrently first
170
- states = await gather(*(future._wait for future in task_run_futures))
171
-
172
- for future, state in zip(task_run_futures, states):
173
- logger = task_run_logger(future.task_run)
174
-
175
- if not state.type == StateType.CRASHED:
176
- continue
177
-
178
- # We use this utility instead of `state.result` for type checking
179
- exception = await get_state_exception(state)
180
-
181
- task_run = await client.read_task_run(future.task_run.id)
182
- if not task_run.state.is_crashed():
183
- logger.info(f"Crash detected! {state.message}")
184
- logger.debug("Crash details:", exc_info=exception)
185
-
186
- # Update the state of the task run
187
- result = await client.set_task_run_state(
188
- task_run_id=future.task_run.id, state=state, force=True
189
- )
190
- if result.status == SetStateStatus.ACCEPT:
191
- engine_logger.debug(
192
- f"Reported crashed task run {future.name!r} successfully."
193
- )
194
- else:
195
- engine_logger.warning(
196
- f"Failed to report crashed task run {future.name!r}. "
197
- f"Orchestrator did not accept state: {result!r}"
198
- )
199
- else:
200
- # Populate the state details on the local state
201
- future._final_state.state_details = task_run.state.state_details
202
-
203
- crash_exceptions.append(exception)
204
-
205
- # Now that we've finished reporting crashed tasks, reraise any exit exceptions
206
- for exception in crash_exceptions:
207
- if isinstance(exception, (KeyboardInterrupt, SystemExit)):
208
- raise exception
209
-
210
- return True
211
-
212
-
213
145
  @contextlib.contextmanager
214
- def capture_sigterm():
215
- def cancel_flow_run(*args):
146
+ def capture_sigterm() -> Generator[None, Any, None]:
147
+ def cancel_flow_run(*args: object) -> NoReturn:
216
148
  raise TerminationSignal(signal=signal.SIGTERM)
217
149
 
218
150
  original_term_handler = None
@@ -241,8 +173,8 @@ def capture_sigterm():
241
173
 
242
174
 
243
175
  async def resolve_inputs(
244
- parameters: Dict[str, Any], return_data: bool = True, max_depth: int = -1
245
- ) -> Dict[str, Any]:
176
+ parameters: dict[str, Any], return_data: bool = True, max_depth: int = -1
177
+ ) -> dict[str, Any]:
246
178
  """
247
179
  Resolve any `Quote`, `PrefectFuture`, or `State` types nested in parameters into
248
180
  data.
@@ -254,24 +186,26 @@ async def resolve_inputs(
254
186
  UpstreamTaskError: If any of the upstream states are not `COMPLETED`
255
187
  """
256
188
 
257
- futures = set()
258
- states = set()
259
- result_by_state = {}
189
+ futures: set[PrefectFuture[Any]] = set()
190
+ states: set[State[Any]] = set()
191
+ result_by_state: dict[State[Any], Any] = {}
260
192
 
261
193
  if not parameters:
262
194
  return {}
263
195
 
264
- def collect_futures_and_states(expr, context):
196
+ def collect_futures_and_states(expr: Any, context: dict[str, Any]) -> Any:
265
197
  # Expressions inside quotes should not be traversed
266
198
  if isinstance(context.get("annotation"), quote):
267
199
  raise StopVisiting()
268
200
 
269
201
  if isinstance(expr, PrefectFuture):
270
- futures.add(expr)
202
+ fut: PrefectFuture[Any] = expr
203
+ futures.add(fut)
271
204
  if isinstance(expr, State):
272
- states.add(expr)
205
+ state: State[Any] = expr
206
+ states.add(state)
273
207
 
274
- return expr
208
+ return cast(Any, expr)
275
209
 
276
210
  visit_collection(
277
211
  parameters,
@@ -281,32 +215,27 @@ async def resolve_inputs(
281
215
  context={},
282
216
  )
283
217
 
284
- # Wait for all futures so we do not block when we retrieve the state in `resolve_input`
285
- states.update(await asyncio.gather(*[future._wait() for future in futures]))
286
-
287
218
  # Only retrieve the result if requested as it may be expensive
288
219
  if return_data:
289
220
  finished_states = [state for state in states if state.is_final()]
290
221
 
291
- state_results = await asyncio.gather(
292
- *[
293
- state.result(raise_on_failure=False, fetch=True)
294
- for state in finished_states
295
- ]
296
- )
222
+ state_results = [
223
+ state.result(raise_on_failure=False, fetch=True)
224
+ for state in finished_states
225
+ ]
297
226
 
298
227
  for state, result in zip(finished_states, state_results):
299
228
  result_by_state[state] = result
300
229
 
301
- def resolve_input(expr, context):
302
- state = None
230
+ def resolve_input(expr: Any, context: dict[str, Any]) -> Any:
231
+ state: Optional[State[Any]] = None
303
232
 
304
233
  # Expressions inside quotes should not be modified
305
234
  if isinstance(context.get("annotation"), quote):
306
235
  raise StopVisiting()
307
236
 
308
237
  if isinstance(expr, PrefectFuture):
309
- state = expr._final_state
238
+ state = expr.state
310
239
  elif isinstance(expr, State):
311
240
  state = expr
312
241
  else:
@@ -329,7 +258,7 @@ async def resolve_inputs(
329
258
 
330
259
  return result_by_state.get(state)
331
260
 
332
- resolved_parameters = {}
261
+ resolved_parameters: dict[str, Any] = {}
333
262
  for parameter, value in parameters.items():
334
263
  try:
335
264
  resolved_parameters[parameter] = visit_collection(
@@ -353,13 +282,21 @@ async def resolve_inputs(
353
282
  return resolved_parameters
354
283
 
355
284
 
285
+ def _is_base_result(data: Any) -> TypeIs[BaseResult[Any]]:
286
+ return isinstance(data, BaseResult)
287
+
288
+
289
+ def _is_result_record(data: Any) -> TypeIs[ResultRecord[Any]]:
290
+ return isinstance(data, ResultRecord)
291
+
292
+
356
293
  async def propose_state(
357
294
  client: "PrefectClient",
358
- state: State[object],
295
+ state: State[Any],
359
296
  force: bool = False,
360
297
  task_run_id: Optional[UUID] = None,
361
298
  flow_run_id: Optional[UUID] = None,
362
- ) -> State[object]:
299
+ ) -> State[Any]:
363
300
  """
364
301
  Propose a new state for a flow run or task run, invoking Prefect orchestration logic.
365
302
 
@@ -396,11 +333,12 @@ async def propose_state(
396
333
 
397
334
  # Handle task and sub-flow tracing
398
335
  if state.is_final():
399
- if isinstance(state.data, BaseResult) and state.data.has_cached_object():
336
+ result: Any
337
+ if _is_base_result(state.data) and state.data.has_cached_object():
400
338
  # Avoid fetching the result unless it is cached, otherwise we defeat
401
339
  # the purpose of disabling `cache_result_in_memory`
402
- result = await state.result(raise_on_failure=False, fetch=True)
403
- elif isinstance(state.data, ResultRecord):
340
+ result = state.result(raise_on_failure=False, fetch=True)
341
+ elif _is_result_record(state.data):
404
342
  result = state.data.result
405
343
  else:
406
344
  result = state.data
@@ -409,9 +347,13 @@ async def propose_state(
409
347
 
410
348
  # Handle repeated WAITs in a loop instead of recursively, to avoid
411
349
  # reaching max recursion depth in extreme cases.
412
- async def set_state_and_handle_waits(set_state_func) -> OrchestrationResult:
350
+ async def set_state_and_handle_waits(
351
+ set_state_func: Callable[[], Awaitable[OrchestrationResult[Any]]],
352
+ ) -> OrchestrationResult[Any]:
413
353
  response = await set_state_func()
414
354
  while response.status == SetStateStatus.WAIT:
355
+ if TYPE_CHECKING:
356
+ assert isinstance(response.details, StateWaitDetails)
415
357
  engine_logger.debug(
416
358
  f"Received wait instruction for {response.details.delay_seconds}s: "
417
359
  f"{response.details.reason}"
@@ -436,6 +378,8 @@ async def propose_state(
436
378
  # Parse the response to return the new state
437
379
  if response.status == SetStateStatus.ACCEPT:
438
380
  # Update the state with the details if provided
381
+ if TYPE_CHECKING:
382
+ assert response.state is not None
439
383
  state.id = response.state.id
440
384
  state.timestamp = response.state.timestamp
441
385
  if response.state.state_details:
@@ -443,9 +387,16 @@ async def propose_state(
443
387
  return state
444
388
 
445
389
  elif response.status == SetStateStatus.ABORT:
390
+ if TYPE_CHECKING:
391
+ assert isinstance(response.details, StateAbortDetails)
392
+
446
393
  raise prefect.exceptions.Abort(response.details.reason)
447
394
 
448
395
  elif response.status == SetStateStatus.REJECT:
396
+ if TYPE_CHECKING:
397
+ assert response.state is not None
398
+ assert isinstance(response.details, StateRejectDetails)
399
+
449
400
  if response.state.is_paused():
450
401
  raise Pause(response.details.reason, state=response.state)
451
402
  return response.state
@@ -458,11 +409,11 @@ async def propose_state(
458
409
 
459
410
  def propose_state_sync(
460
411
  client: "SyncPrefectClient",
461
- state: State[object],
412
+ state: State[Any],
462
413
  force: bool = False,
463
414
  task_run_id: Optional[UUID] = None,
464
415
  flow_run_id: Optional[UUID] = None,
465
- ) -> State[object]:
416
+ ) -> State[Any]:
466
417
  """
467
418
  Propose a new state for a flow run or task run, invoking Prefect orchestration logic.
468
419
 
@@ -499,13 +450,13 @@ def propose_state_sync(
499
450
 
500
451
  # Handle task and sub-flow tracing
501
452
  if state.is_final():
502
- if isinstance(state.data, BaseResult) and state.data.has_cached_object():
453
+ if _is_base_result(state.data) and state.data.has_cached_object():
503
454
  # Avoid fetching the result unless it is cached, otherwise we defeat
504
455
  # the purpose of disabling `cache_result_in_memory`
505
456
  result = state.result(raise_on_failure=False, fetch=True)
506
457
  if asyncio.iscoroutine(result):
507
458
  result = run_coro_as_sync(result)
508
- elif isinstance(state.data, ResultRecord):
459
+ elif _is_result_record(state.data):
509
460
  result = state.data.result
510
461
  else:
511
462
  result = state.data
@@ -514,9 +465,13 @@ def propose_state_sync(
514
465
 
515
466
  # Handle repeated WAITs in a loop instead of recursively, to avoid
516
467
  # reaching max recursion depth in extreme cases.
517
- def set_state_and_handle_waits(set_state_func) -> OrchestrationResult:
468
+ def set_state_and_handle_waits(
469
+ set_state_func: Callable[[], OrchestrationResult[Any]],
470
+ ) -> OrchestrationResult[Any]:
518
471
  response = set_state_func()
519
472
  while response.status == SetStateStatus.WAIT:
473
+ if TYPE_CHECKING:
474
+ assert isinstance(response.details, StateWaitDetails)
520
475
  engine_logger.debug(
521
476
  f"Received wait instruction for {response.details.delay_seconds}s: "
522
477
  f"{response.details.reason}"
@@ -540,6 +495,8 @@ def propose_state_sync(
540
495
 
541
496
  # Parse the response to return the new state
542
497
  if response.status == SetStateStatus.ACCEPT:
498
+ if TYPE_CHECKING:
499
+ assert response.state is not None
543
500
  # Update the state with the details if provided
544
501
  state.id = response.state.id
545
502
  state.timestamp = response.state.timestamp
@@ -548,9 +505,14 @@ def propose_state_sync(
548
505
  return state
549
506
 
550
507
  elif response.status == SetStateStatus.ABORT:
508
+ if TYPE_CHECKING:
509
+ assert isinstance(response.details, StateAbortDetails)
551
510
  raise prefect.exceptions.Abort(response.details.reason)
552
511
 
553
512
  elif response.status == SetStateStatus.REJECT:
513
+ if TYPE_CHECKING:
514
+ assert response.state is not None
515
+ assert isinstance(response.details, StateRejectDetails)
554
516
  if response.state.is_paused():
555
517
  raise Pause(response.details.reason, state=response.state)
556
518
  return response.state
@@ -561,26 +523,6 @@ def propose_state_sync(
561
523
  )
562
524
 
563
525
 
564
- def _dynamic_key_for_task_run(
565
- context: FlowRunContext, task: Task, stable: bool = True
566
- ) -> Union[int, str]:
567
- if (
568
- stable is False or context.detached
569
- ): # this task is running on remote infrastructure
570
- return str(uuid4())
571
- elif context.flow_run is None: # this is an autonomous task run
572
- context.task_run_dynamic_keys[task.task_key] = getattr(
573
- task, "dynamic_key", str(uuid4())
574
- )
575
-
576
- elif task.task_key not in context.task_run_dynamic_keys:
577
- context.task_run_dynamic_keys[task.task_key] = 0
578
- else:
579
- context.task_run_dynamic_keys[task.task_key] += 1
580
-
581
- return context.task_run_dynamic_keys[task.task_key]
582
-
583
-
584
526
  def get_state_for_result(obj: Any) -> Optional[State]:
585
527
  """
586
528
  Get the state related to a result object.
@@ -631,28 +573,29 @@ def link_state_to_result(state: State, result: Any) -> None:
631
573
  # Holding large user objects in memory can cause memory bloat
632
574
  linked_state = state.model_copy(update={"data": None})
633
575
 
634
- def link_if_trackable(obj: Any) -> None:
635
- """Track connection between a task run result and its associated state if it has a unique ID.
576
+ if flow_run_context:
636
577
 
637
- We cannot track booleans, Ellipsis, None, NotImplemented, or the integers from -5 to 256
638
- because they are singletons.
578
+ def link_if_trackable(obj: Any) -> None:
579
+ """Track connection between a task run result and its associated state if it has a unique ID.
639
580
 
640
- This function will mutate the State if the object is an untrackable type by setting the value
641
- for `State.state_details.untrackable_result` to `True`.
581
+ We cannot track booleans, Ellipsis, None, NotImplemented, or the integers from -5 to 256
582
+ because they are singletons.
642
583
 
643
- """
644
- if (type(obj) in UNTRACKABLE_TYPES) or (
645
- isinstance(obj, int) and (-5 <= obj <= 256)
646
- ):
647
- state.state_details.untrackable_result = True
648
- return
649
- flow_run_context.task_run_results[id(obj)] = linked_state
584
+ This function will mutate the State if the object is an untrackable type by setting the value
585
+ for `State.state_details.untrackable_result` to `True`.
586
+
587
+ """
588
+ if (type(obj) in UNTRACKABLE_TYPES) or (
589
+ isinstance(obj, int) and (-5 <= obj <= 256)
590
+ ):
591
+ state.state_details.untrackable_result = True
592
+ return
593
+ flow_run_context.task_run_results[id(obj)] = linked_state
650
594
 
651
- if flow_run_context:
652
595
  visit_collection(expr=result, visit_fn=link_if_trackable, max_depth=1)
653
596
 
654
597
 
655
- def should_log_prints(flow_or_task: Union[Flow, Task]) -> bool:
598
+ def should_log_prints(flow_or_task: Union["Flow[..., Any]", "Task[..., Any]"]) -> bool:
656
599
  flow_run_context = FlowRunContext.get()
657
600
 
658
601
  if flow_or_task.log_prints is None:
@@ -664,63 +607,7 @@ def should_log_prints(flow_or_task: Union[Flow, Task]) -> bool:
664
607
  return flow_or_task.log_prints
665
608
 
666
609
 
667
- def _resolve_custom_flow_run_name(flow: Flow, parameters: Dict[str, Any]) -> str:
668
- if callable(flow.flow_run_name):
669
- flow_run_name = flow.flow_run_name()
670
- if not isinstance(flow_run_name, str):
671
- raise TypeError(
672
- f"Callable {flow.flow_run_name} for 'flow_run_name' returned type"
673
- f" {type(flow_run_name).__name__} but a string is required."
674
- )
675
- elif isinstance(flow.flow_run_name, str):
676
- flow_run_name = flow.flow_run_name.format(**parameters)
677
- else:
678
- raise TypeError(
679
- "Expected string or callable for 'flow_run_name'; got"
680
- f" {type(flow.flow_run_name).__name__} instead."
681
- )
682
-
683
- return flow_run_name
684
-
685
-
686
- def _resolve_custom_task_run_name(task: Task, parameters: Dict[str, Any]) -> str:
687
- if callable(task.task_run_name):
688
- sig = inspect.signature(task.task_run_name)
689
-
690
- # If the callable accepts a 'parameters' kwarg, pass the entire parameters dict
691
- if "parameters" in sig.parameters:
692
- task_run_name = task.task_run_name(parameters=parameters)
693
- else:
694
- # If it doesn't expect parameters, call it without arguments
695
- task_run_name = task.task_run_name()
696
-
697
- if not isinstance(task_run_name, str):
698
- raise TypeError(
699
- f"Callable {task.task_run_name} for 'task_run_name' returned type"
700
- f" {type(task_run_name).__name__} but a string is required."
701
- )
702
- elif isinstance(task.task_run_name, str):
703
- task_run_name = task.task_run_name.format(**parameters)
704
- else:
705
- raise TypeError(
706
- "Expected string or callable for 'task_run_name'; got"
707
- f" {type(task.task_run_name).__name__} instead."
708
- )
709
-
710
- return task_run_name
711
-
712
-
713
- def _get_hook_name(hook: Callable) -> str:
714
- return (
715
- hook.__name__
716
- if hasattr(hook, "__name__")
717
- else (
718
- hook.func.__name__ if isinstance(hook, partial) else hook.__class__.__name__
719
- )
720
- )
721
-
722
-
723
- async def check_api_reachable(client: "PrefectClient", fail_message: str):
610
+ async def check_api_reachable(client: "PrefectClient", fail_message: str) -> None:
724
611
  # Do not perform a healthcheck if it exists and is not expired
725
612
  api_url = str(client.api_url)
726
613
  if api_url in API_HEALTHCHECKS:
@@ -740,15 +627,15 @@ async def check_api_reachable(client: "PrefectClient", fail_message: str):
740
627
 
741
628
  def emit_task_run_state_change_event(
742
629
  task_run: TaskRun,
743
- initial_state: Optional[State],
744
- validated_state: State,
630
+ initial_state: Optional[State[Any]],
631
+ validated_state: State[Any],
745
632
  follows: Optional[Event] = None,
746
- ) -> Event:
633
+ ) -> Optional[Event]:
747
634
  state_message_truncation_length = 100_000
748
635
 
749
- if isinstance(validated_state.data, ResultRecord) and should_persist_result():
636
+ if _is_result_record(validated_state.data) and should_persist_result():
750
637
  data = validated_state.data.metadata.model_dump(mode="json")
751
- elif isinstance(validated_state.data, BaseResult):
638
+ elif _is_base_result(validated_state.data):
752
639
  data = validated_state.data.model_dump(mode="json")
753
640
  else:
754
641
  data = None
@@ -830,20 +717,20 @@ def emit_task_run_state_change_event(
830
717
  )
831
718
 
832
719
 
833
- def resolve_to_final_result(expr, context):
720
+ def resolve_to_final_result(expr: Any, context: dict[str, Any]) -> Any:
834
721
  """
835
722
  Resolve any `PrefectFuture`, or `State` types nested in parameters into
836
723
  data. Designed to be use with `visit_collection`.
837
724
  """
838
- state = None
725
+ state: Optional[State[Any]] = None
839
726
 
840
727
  # Expressions inside quotes should not be modified
841
728
  if isinstance(context.get("annotation"), quote):
842
729
  raise StopVisiting()
843
730
 
844
731
  if isinstance(expr, PrefectFuture):
845
- upstream_task_run = context.get("current_task_run")
846
- upstream_task = context.get("current_task")
732
+ upstream_task_run: Optional[TaskRun] = context.get("current_task_run")
733
+ upstream_task: Optional["Task[..., Any]"] = context.get("current_task")
847
734
  if (
848
735
  upstream_task
849
736
  and upstream_task_run
@@ -877,15 +764,15 @@ def resolve_to_final_result(expr, context):
877
764
  " 'COMPLETED' state."
878
765
  )
879
766
 
880
- _result = state.result(raise_on_failure=False, fetch=True)
881
- if asyncio.iscoroutine(_result):
882
- _result = run_coro_as_sync(_result)
883
- return _result
767
+ result = state.result(raise_on_failure=False, fetch=True)
768
+ if asyncio.iscoroutine(result):
769
+ result = run_coro_as_sync(result)
770
+ return result
884
771
 
885
772
 
886
773
  def resolve_inputs_sync(
887
- parameters: Dict[str, Any], return_data: bool = True, max_depth: int = -1
888
- ) -> Dict[str, Any]:
774
+ parameters: dict[str, Any], return_data: bool = True, max_depth: int = -1
775
+ ) -> dict[str, Any]:
889
776
  """
890
777
  Resolve any `Quote`, `PrefectFuture`, or `State` types nested in parameters into
891
778
  data.
@@ -900,7 +787,7 @@ def resolve_inputs_sync(
900
787
  if not parameters:
901
788
  return {}
902
789
 
903
- resolved_parameters = {}
790
+ resolved_parameters: dict[str, Any] = {}
904
791
  for parameter, value in parameters.items():
905
792
  try:
906
793
  resolved_parameters[parameter] = visit_collection(