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
@@ -6,10 +6,11 @@ import os
6
6
  from pathlib import Path
7
7
  from typing import TYPE_CHECKING, Any, Optional
8
8
 
9
+ from prefect._internal.compatibility.async_dispatch import async_dispatch
9
10
  from prefect._internal.retries import retry_async_fn
10
11
  from prefect.logging.loggers import get_logger
11
12
  from prefect.runner.storage import BlockStorageAdapter, GitRepository, RemoteStorage
12
- from prefect.utilities.asyncutils import sync_compatible
13
+ from prefect.utilities.asyncutils import run_coro_as_sync
13
14
 
14
15
  deployment_logger = get_logger("deployment")
15
16
 
@@ -17,7 +18,7 @@ if TYPE_CHECKING:
17
18
  from prefect.blocks.core import Block
18
19
 
19
20
 
20
- def set_working_directory(directory: str) -> dict:
21
+ def set_working_directory(directory: str) -> dict[str, str]:
21
22
  """
22
23
  Sets the working directory; works with both absolute and relative paths.
23
24
 
@@ -37,15 +38,64 @@ def set_working_directory(directory: str) -> dict:
37
38
  base_delay=1,
38
39
  max_delay=10,
39
40
  retry_on_exceptions=(RuntimeError,),
41
+ operation_name="git_clone",
40
42
  )
41
- @sync_compatible
42
- async def git_clone(
43
+ async def _pull_git_repository_with_retries(repo: GitRepository):
44
+ await repo.pull_code()
45
+
46
+
47
+ async def agit_clone(
48
+ repository: str,
49
+ branch: Optional[str] = None,
50
+ include_submodules: bool = False,
51
+ access_token: Optional[str] = None,
52
+ credentials: Optional["Block"] = None,
53
+ ) -> dict[str, str]:
54
+ """
55
+ Asynchronously clones a git repository into the current working directory.
56
+
57
+ Args:
58
+ repository: the URL of the repository to clone
59
+ branch: the branch to clone; if not provided, the default branch will be used
60
+ include_submodules (bool): whether to include git submodules when cloning the repository
61
+ access_token: an access token to use for cloning the repository; if not provided
62
+ the repository will be cloned using the default git credentials
63
+ credentials: a GitHubCredentials, GitLabCredentials, or BitBucketCredentials block can be used to specify the
64
+ credentials to use for cloning the repository.
65
+
66
+ Returns:
67
+ dict: a dictionary containing a `directory` key of the new directory that was created
68
+
69
+ Raises:
70
+ subprocess.CalledProcessError: if the git clone command fails for any reason
71
+ """
72
+ if access_token and credentials:
73
+ raise ValueError(
74
+ "Please provide either an access token or credentials but not both."
75
+ )
76
+
77
+ _credentials = {"access_token": access_token} if access_token else credentials
78
+
79
+ storage = GitRepository(
80
+ url=repository,
81
+ credentials=_credentials,
82
+ branch=branch,
83
+ include_submodules=include_submodules,
84
+ )
85
+
86
+ await _pull_git_repository_with_retries(storage)
87
+
88
+ return dict(directory=str(storage.destination.relative_to(Path.cwd())))
89
+
90
+
91
+ @async_dispatch(agit_clone)
92
+ def git_clone(
43
93
  repository: str,
44
94
  branch: Optional[str] = None,
45
95
  include_submodules: bool = False,
46
96
  access_token: Optional[str] = None,
47
97
  credentials: Optional["Block"] = None,
48
- ) -> dict:
98
+ ) -> dict[str, str]:
49
99
  """
50
100
  Clones a git repository into the current working directory.
51
101
 
@@ -120,20 +170,18 @@ async def git_clone(
120
170
  "Please provide either an access token or credentials but not both."
121
171
  )
122
172
 
123
- credentials = {"access_token": access_token} if access_token else credentials
173
+ _credentials = {"access_token": access_token} if access_token else credentials
124
174
 
125
175
  storage = GitRepository(
126
176
  url=repository,
127
- credentials=credentials,
177
+ credentials=_credentials,
128
178
  branch=branch,
129
179
  include_submodules=include_submodules,
130
180
  )
131
181
 
132
- await storage.pull_code()
182
+ run_coro_as_sync(_pull_git_repository_with_retries(storage))
133
183
 
134
- directory = str(storage.destination.relative_to(Path.cwd()))
135
- deployment_logger.info(f"Cloned repository {repository!r} into {directory!r}")
136
- return {"directory": directory}
184
+ return dict(directory=str(storage.destination.relative_to(Path.cwd())))
137
185
 
138
186
 
139
187
  async def pull_from_remote_storage(url: str, **settings: Any):
@@ -190,7 +238,7 @@ async def pull_with_block(block_document_name: str, block_type_slug: str):
190
238
 
191
239
  full_slug = f"{block_type_slug}/{block_document_name}"
192
240
  try:
193
- block = await Block.load(full_slug)
241
+ block = await Block.aload(full_slug)
194
242
  except Exception:
195
243
  deployment_logger.exception("Unable to load block '%s'", full_slug)
196
244
  raise
prefect/events/clients.py CHANGED
@@ -1,11 +1,13 @@
1
1
  import abc
2
2
  import asyncio
3
+ import os
3
4
  from types import TracebackType
4
5
  from typing import (
5
6
  TYPE_CHECKING,
6
7
  Any,
7
8
  ClassVar,
8
9
  Dict,
10
+ Generator,
9
11
  List,
10
12
  MutableMapping,
11
13
  Optional,
@@ -13,20 +15,22 @@ from typing import (
13
15
  Type,
14
16
  cast,
15
17
  )
18
+ from urllib.parse import urlparse
16
19
  from uuid import UUID
17
20
 
18
21
  import orjson
19
22
  import pendulum
20
23
  from cachetools import TTLCache
21
24
  from prometheus_client import Counter
25
+ from python_socks.async_.asyncio import Proxy
22
26
  from typing_extensions import Self
23
27
  from websockets import Subprotocol
24
- from websockets.client import WebSocketClientProtocol, connect
25
28
  from websockets.exceptions import (
26
29
  ConnectionClosed,
27
30
  ConnectionClosedError,
28
31
  ConnectionClosedOK,
29
32
  )
33
+ from websockets.legacy.client import Connect, WebSocketClientProtocol
30
34
 
31
35
  from prefect.events import Event
32
36
  from prefect.logging import get_logger
@@ -80,6 +84,53 @@ def events_out_socket_from_api_url(url: str):
80
84
  return http_to_ws(url) + "/events/out"
81
85
 
82
86
 
87
+ class WebsocketProxyConnect(Connect):
88
+ def __init__(self: Self, uri: str, **kwargs: Any):
89
+ # super() is intentionally deferred to the _proxy_connect method
90
+ # to allow for the socket to be established first
91
+
92
+ self.uri = uri
93
+ self._kwargs = kwargs
94
+
95
+ u = urlparse(uri)
96
+ host = u.hostname
97
+
98
+ if u.scheme == "ws":
99
+ port = u.port or 80
100
+ proxy_url = os.environ.get("HTTP_PROXY")
101
+ elif u.scheme == "wss":
102
+ port = u.port or 443
103
+ proxy_url = os.environ.get("HTTPS_PROXY")
104
+ kwargs["server_hostname"] = host
105
+ else:
106
+ raise ValueError(
107
+ "Unsupported scheme %s. Expected 'ws' or 'wss'. " % u.scheme
108
+ )
109
+
110
+ self._proxy = Proxy.from_url(proxy_url) if proxy_url else None
111
+ self._host = host
112
+ self._port = port
113
+
114
+ async def _proxy_connect(self: Self) -> WebSocketClientProtocol:
115
+ if self._proxy:
116
+ sock = await self._proxy.connect(
117
+ dest_host=self._host,
118
+ dest_port=self._port,
119
+ )
120
+ self._kwargs["sock"] = sock
121
+
122
+ super().__init__(self.uri, **self._kwargs)
123
+ proto = await self.__await_impl__()
124
+ return proto
125
+
126
+ def __await__(self: Self) -> Generator[Any, None, WebSocketClientProtocol]:
127
+ return self._proxy_connect().__await__()
128
+
129
+
130
+ def websocket_connect(uri: str, **kwargs: Any) -> WebsocketProxyConnect:
131
+ return WebsocketProxyConnect(uri, **kwargs)
132
+
133
+
83
134
  def get_events_client(
84
135
  reconnection_attempts: int = 10,
85
136
  checkpoint_every: int = 700,
@@ -265,7 +316,7 @@ class PrefectEventsClient(EventsClient):
265
316
  )
266
317
 
267
318
  self._events_socket_url = events_in_socket_from_api_url(api_url)
268
- self._connect = connect(self._events_socket_url)
319
+ self._connect = websocket_connect(self._events_socket_url)
269
320
  self._websocket = None
270
321
  self._reconnection_attempts = reconnection_attempts
271
322
  self._unconfirmed_events = []
@@ -435,7 +486,7 @@ class PrefectCloudEventsClient(PrefectEventsClient):
435
486
  reconnection_attempts=reconnection_attempts,
436
487
  checkpoint_every=checkpoint_every,
437
488
  )
438
- self._connect = connect(
489
+ self._connect = websocket_connect(
439
490
  self._events_socket_url,
440
491
  extra_headers={"Authorization": f"bearer {api_key}"},
441
492
  )
@@ -494,7 +545,7 @@ class PrefectEventSubscriber:
494
545
 
495
546
  logger.debug("Connecting to %s", socket_url)
496
547
 
497
- self._connect = connect(
548
+ self._connect = websocket_connect(
498
549
  socket_url,
499
550
  subprotocols=[Subprotocol("prefect")],
500
551
  )
prefect/events/filters.py CHANGED
@@ -3,9 +3,9 @@ from uuid import UUID
3
3
 
4
4
  import pendulum
5
5
  from pydantic import Field, PrivateAttr
6
- from pydantic_extra_types.pendulum_dt import DateTime
7
6
 
8
7
  from prefect._internal.schemas.bases import PrefectBaseModel
8
+ from prefect.types import DateTime
9
9
  from prefect.utilities.collections import AutoEnum
10
10
 
11
11
  from .schemas.events import Event, Resource, ResourceSpecification
prefect/events/related.py CHANGED
@@ -15,7 +15,8 @@ from typing import (
15
15
  from uuid import UUID
16
16
 
17
17
  import pendulum
18
- from pendulum.datetime import DateTime
18
+
19
+ from prefect.types import DateTime
19
20
 
20
21
  from .schemas.events import RelatedResource
21
22
 
@@ -20,7 +20,6 @@ from pydantic import (
20
20
  RootModel,
21
21
  model_validator,
22
22
  )
23
- from pydantic_extra_types.pendulum_dt import DateTime
24
23
  from typing_extensions import Annotated, Self
25
24
 
26
25
  from prefect._internal.schemas.bases import PrefectBaseModel
@@ -28,6 +27,7 @@ from prefect.logging import get_logger
28
27
  from prefect.settings import (
29
28
  PREFECT_EVENTS_MAXIMUM_LABELS_PER_RESOURCE,
30
29
  )
30
+ from prefect.types import DateTime
31
31
 
32
32
  from .labelling import Labelled
33
33
 
@@ -24,6 +24,7 @@ def emit_event(
24
24
  payload: Optional[Dict[str, Any]] = None,
25
25
  id: Optional[UUID] = None,
26
26
  follows: Optional[Event] = None,
27
+ **kwargs: Optional[Dict[str, Any]],
27
28
  ) -> Optional[Event]:
28
29
  """
29
30
  Send an event to Prefect Cloud.
@@ -62,6 +63,7 @@ def emit_event(
62
63
  event_kwargs: Dict[str, Any] = {
63
64
  "event": event,
64
65
  "resource": resource,
66
+ **kwargs,
65
67
  }
66
68
 
67
69
  if occurred is None:
prefect/events/worker.py CHANGED
@@ -83,6 +83,14 @@ class EventsWorker(QueueService[Event]):
83
83
  await self._client.emit(event)
84
84
 
85
85
  async def attach_related_resources_from_context(self, event: Event):
86
+ if "prefect.resource.lineage-group" in event.resource:
87
+ # We attach related resources to lineage events in `emit_lineage_event`,
88
+ # instead of the worker, because not all run-related resources are
89
+ # upstream from every lineage event (they might be downstream).
90
+ # The "related" field in the event schema tracks upstream resources
91
+ # only.
92
+ return
93
+
86
94
  exclude = {resource.id for resource in event.involved_resources}
87
95
  event.related += await related_resources_from_run_context(
88
96
  client=self._orchestration_client, exclude=exclude
prefect/flow_engine.py CHANGED
@@ -24,10 +24,8 @@ from uuid import UUID
24
24
 
25
25
  from anyio import CancelScope
26
26
  from opentelemetry import propagate, trace
27
- from opentelemetry.trace import Tracer, get_tracer
28
27
  from typing_extensions import ParamSpec
29
28
 
30
- import prefect
31
29
  from prefect import Task
32
30
  from prefect.client.orchestration import PrefectClient, SyncPrefectClient, get_client
33
31
  from prefect.client.schemas import FlowRun, TaskRun
@@ -72,7 +70,12 @@ from prefect.states import (
72
70
  exception_to_failed_state,
73
71
  return_value_to_state,
74
72
  )
75
- from prefect.telemetry.run_telemetry import OTELSetter
73
+ from prefect.telemetry.run_telemetry import (
74
+ LABELS_TRACEPARENT_KEY,
75
+ TRACEPARENT_KEY,
76
+ OTELSetter,
77
+ RunTelemetry,
78
+ )
76
79
  from prefect.types import KeyValueLabels
77
80
  from prefect.utilities._engine import get_hook_name, resolve_custom_flow_run_name
78
81
  from prefect.utilities.annotations import NotSet
@@ -95,8 +98,6 @@ from prefect.utilities.urls import url_for
95
98
 
96
99
  P = ParamSpec("P")
97
100
  R = TypeVar("R")
98
- LABELS_TRACEPARENT_KEY = "__OTEL_TRACEPARENT"
99
- TRACEPARENT_KEY = "traceparent"
100
101
 
101
102
 
102
103
  class FlowRunTimeoutError(TimeoutError):
@@ -136,10 +137,7 @@ class BaseFlowRunEngine(Generic[P, R]):
136
137
  _is_started: bool = False
137
138
  short_circuit: bool = False
138
139
  _flow_run_name_set: bool = False
139
- _tracer: Tracer = field(
140
- default_factory=lambda: get_tracer("prefect", prefect.__version__)
141
- )
142
- _span: Optional[trace.Span] = None
140
+ _telemetry: RunTelemetry = field(default_factory=RunTelemetry)
143
141
 
144
142
  def __post_init__(self):
145
143
  if self.flow is None and self.flow_run_id is None:
@@ -152,21 +150,6 @@ class BaseFlowRunEngine(Generic[P, R]):
152
150
  def state(self) -> State:
153
151
  return self.flow_run.state # type: ignore
154
152
 
155
- def _end_span_on_success(self):
156
- if not self._span:
157
- return
158
- self._span.set_status(trace.Status(trace.StatusCode.OK))
159
- self._span.end(time.time_ns())
160
- self._span = None
161
-
162
- def _end_span_on_error(self, exc: BaseException, description: Optional[str]):
163
- if not self._span:
164
- return
165
- self._span.record_exception(exc)
166
- self._span.set_status(trace.Status(trace.StatusCode.ERROR, description))
167
- self._span.end(time.time_ns())
168
- self._span = None
169
-
170
153
  def is_running(self) -> bool:
171
154
  if getattr(self, "flow_run", None) is None:
172
155
  return False
@@ -185,6 +168,7 @@ class BaseFlowRunEngine(Generic[P, R]):
185
168
  self, span: trace.Span, client: Union[SyncPrefectClient, PrefectClient]
186
169
  ):
187
170
  parent_flow_run_ctx = FlowRunContext.get()
171
+
188
172
  if parent_flow_run_ctx and parent_flow_run_ctx.flow_run:
189
173
  if traceparent := parent_flow_run_ctx.flow_run.labels.get(
190
174
  LABELS_TRACEPARENT_KEY
@@ -194,6 +178,7 @@ class BaseFlowRunEngine(Generic[P, R]):
194
178
  carrier={TRACEPARENT_KEY: traceparent},
195
179
  setter=OTELSetter(),
196
180
  )
181
+
197
182
  else:
198
183
  carrier: KeyValueLabels = {}
199
184
  propagate.get_global_textmap().inject(
@@ -315,16 +300,7 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
315
300
  self.flow_run.state_name = state.name # type: ignore
316
301
  self.flow_run.state_type = state.type # type: ignore
317
302
 
318
- if self._span:
319
- self._span.add_event(
320
- state.name or state.type,
321
- {
322
- "prefect.state.message": state.message or "",
323
- "prefect.state.type": state.type,
324
- "prefect.state.name": state.name or state.type,
325
- "prefect.state.id": str(state.id),
326
- },
327
- )
303
+ self._telemetry.update_state(state)
328
304
  return state
329
305
 
330
306
  def result(self, raise_on_failure: bool = True) -> "Union[R, State, None]":
@@ -374,7 +350,7 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
374
350
  self.set_state(terminal_state)
375
351
  self._return_value = resolved_result
376
352
 
377
- self._end_span_on_success()
353
+ self._telemetry.end_span_on_success()
378
354
 
379
355
  return result
380
356
 
@@ -406,8 +382,8 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
406
382
  )
407
383
  state = self.set_state(Running())
408
384
  self._raised = exc
409
-
410
- self._end_span_on_error(exc, state.message)
385
+ self._telemetry.record_exception(exc)
386
+ self._telemetry.end_span_on_failure(state.message)
411
387
 
412
388
  return state
413
389
 
@@ -426,8 +402,8 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
426
402
  )
427
403
  self.set_state(state)
428
404
  self._raised = exc
429
-
430
- self._end_span_on_error(exc, message)
405
+ self._telemetry.record_exception(exc)
406
+ self._telemetry.end_span_on_failure(message)
431
407
 
432
408
  def handle_crash(self, exc: BaseException) -> None:
433
409
  state = run_coro_as_sync(exception_to_crashed_state(exc))
@@ -435,8 +411,8 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
435
411
  self.logger.debug("Crash details:", exc_info=exc)
436
412
  self.set_state(state, force=True)
437
413
  self._raised = exc
438
-
439
- self._end_span_on_error(exc, state.message if state else "")
414
+ self._telemetry.record_exception(exc)
415
+ self._telemetry.end_span_on_failure(state.message if state else None)
440
416
 
441
417
  def load_subflow_run(
442
418
  self,
@@ -681,19 +657,12 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
681
657
  empirical_policy=self.flow_run.empirical_policy,
682
658
  )
683
659
 
684
- span = self._tracer.start_span(
685
- name=self.flow_run.name,
686
- attributes={
687
- **self.flow_run.labels,
688
- "prefect.run.type": "flow",
689
- "prefect.run.id": str(self.flow_run.id),
690
- "prefect.tags": self.flow_run.tags,
691
- "prefect.flow.name": self.flow.name,
692
- },
660
+ self._telemetry.start_span(
661
+ name=self.flow.name,
662
+ run=self.flow_run,
663
+ client=self.client,
664
+ parameters=self.parameters,
693
665
  )
694
- self._update_otel_labels(span, self.client)
695
-
696
- self._span = span
697
666
 
698
667
  try:
699
668
  yield self
@@ -736,7 +705,9 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
736
705
  @contextmanager
737
706
  def start(self) -> Generator[None, None, None]:
738
707
  with self.initialize_run():
739
- with trace.use_span(self._span) if self._span else nullcontext():
708
+ with trace.use_span(
709
+ self._telemetry.span
710
+ ) if self._telemetry.span else nullcontext():
740
711
  self.begin_run()
741
712
 
742
713
  if self.state.is_running():
@@ -892,16 +863,7 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
892
863
  self.flow_run.state_name = state.name # type: ignore
893
864
  self.flow_run.state_type = state.type # type: ignore
894
865
 
895
- if self._span:
896
- self._span.add_event(
897
- state.name or state.type,
898
- {
899
- "prefect.state.message": state.message or "",
900
- "prefect.state.type": state.type,
901
- "prefect.state.name": state.name or state.type,
902
- "prefect.state.id": str(state.id),
903
- },
904
- )
866
+ self._telemetry.update_state(state)
905
867
  return state
906
868
 
907
869
  async def result(self, raise_on_failure: bool = True) -> "Union[R, State, None]":
@@ -949,7 +911,7 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
949
911
  await self.set_state(terminal_state)
950
912
  self._return_value = resolved_result
951
913
 
952
- self._end_span_on_success()
914
+ self._telemetry.end_span_on_success()
953
915
 
954
916
  return result
955
917
 
@@ -979,8 +941,8 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
979
941
  )
980
942
  state = await self.set_state(Running())
981
943
  self._raised = exc
982
-
983
- self._end_span_on_error(exc, state.message)
944
+ self._telemetry.record_exception(exc)
945
+ self._telemetry.end_span_on_failure(state.message)
984
946
 
985
947
  return state
986
948
 
@@ -1000,7 +962,8 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
1000
962
  await self.set_state(state)
1001
963
  self._raised = exc
1002
964
 
1003
- self._end_span_on_error(exc, message)
965
+ self._telemetry.record_exception(exc)
966
+ self._telemetry.end_span_on_failure(message)
1004
967
 
1005
968
  async def handle_crash(self, exc: BaseException) -> None:
1006
969
  # need to shield from asyncio cancellation to ensure we update the state
@@ -1012,7 +975,8 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
1012
975
  await self.set_state(state, force=True)
1013
976
  self._raised = exc
1014
977
 
1015
- self._end_span_on_error(exc, state.message)
978
+ self._telemetry.record_exception(exc)
979
+ self._telemetry.end_span_on_failure(state.message)
1016
980
 
1017
981
  async def load_subflow_run(
1018
982
  self,
@@ -1255,18 +1219,12 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
1255
1219
  empirical_policy=self.flow_run.empirical_policy,
1256
1220
  )
1257
1221
 
1258
- span = self._tracer.start_span(
1259
- name=self.flow_run.name,
1260
- attributes={
1261
- **self.flow_run.labels,
1262
- "prefect.run.type": "flow",
1263
- "prefect.run.id": str(self.flow_run.id),
1264
- "prefect.tags": self.flow_run.tags,
1265
- "prefect.flow.name": self.flow.name,
1266
- },
1222
+ await self._telemetry.async_start_span(
1223
+ name=self.flow.name,
1224
+ run=self.flow_run,
1225
+ client=self.client,
1226
+ parameters=self.parameters,
1267
1227
  )
1268
- self._update_otel_labels(span, self.client)
1269
- self._span = span
1270
1228
 
1271
1229
  try:
1272
1230
  yield self
@@ -1309,7 +1267,9 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
1309
1267
  @asynccontextmanager
1310
1268
  async def start(self) -> AsyncGenerator[None, None]:
1311
1269
  async with self.initialize_run():
1312
- with trace.use_span(self._span) if self._span else nullcontext():
1270
+ with trace.use_span(
1271
+ self._telemetry.span
1272
+ ) if self._telemetry.span else nullcontext():
1313
1273
  await self.begin_run()
1314
1274
 
1315
1275
  if self.state.is_running():
prefect/flow_runs.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from typing import (
2
2
  TYPE_CHECKING,
3
- Dict,
3
+ Any,
4
4
  Optional,
5
5
  Type,
6
6
  TypeVar,
@@ -430,7 +430,9 @@ async def suspend_flow_run(
430
430
 
431
431
 
432
432
  @sync_compatible
433
- async def resume_flow_run(flow_run_id, run_input: Optional[Dict] = None):
433
+ async def resume_flow_run(
434
+ flow_run_id: UUID, run_input: Optional[dict[str, Any]] = None
435
+ ) -> None:
434
436
  """
435
437
  Resumes a paused flow.
436
438
 
prefect/flows.py CHANGED
@@ -564,14 +564,12 @@ class Flow(Generic[P, R]):
564
564
  "Cannot mix Pydantic v1 and v2 types as arguments to a flow."
565
565
  )
566
566
 
567
+ validated_fn_kwargs = dict(arbitrary_types_allowed=True)
568
+
567
569
  if has_v1_models:
568
- validated_fn = V1ValidatedFunction(
569
- self.fn, config={"arbitrary_types_allowed": True}
570
- )
570
+ validated_fn = V1ValidatedFunction(self.fn, config=validated_fn_kwargs)
571
571
  else:
572
- validated_fn = V2ValidatedFunction(
573
- self.fn, config=pydantic.ConfigDict(arbitrary_types_allowed=True)
574
- )
572
+ validated_fn = V2ValidatedFunction(self.fn, config=validated_fn_kwargs)
575
573
 
576
574
  try:
577
575
  with warnings.catch_warnings():