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.
- prefect/_experimental/__init__.py +0 -0
- prefect/_experimental/lineage.py +181 -0
- prefect/_internal/compatibility/async_dispatch.py +38 -9
- prefect/_internal/pydantic/v2_validated_func.py +15 -10
- prefect/_internal/retries.py +15 -6
- prefect/_internal/schemas/bases.py +2 -1
- prefect/_internal/schemas/validators.py +5 -4
- prefect/_version.py +3 -3
- prefect/blocks/core.py +144 -17
- prefect/blocks/system.py +2 -1
- prefect/client/orchestration.py +88 -0
- prefect/client/schemas/actions.py +5 -5
- prefect/client/schemas/filters.py +1 -1
- prefect/client/schemas/objects.py +5 -5
- prefect/client/schemas/responses.py +1 -2
- prefect/client/schemas/schedules.py +1 -1
- prefect/client/subscriptions.py +2 -1
- prefect/client/utilities.py +15 -1
- prefect/context.py +1 -1
- prefect/deployments/flow_runs.py +3 -3
- prefect/deployments/runner.py +14 -14
- prefect/deployments/steps/core.py +3 -1
- prefect/deployments/steps/pull.py +60 -12
- prefect/events/clients.py +55 -4
- prefect/events/filters.py +1 -1
- prefect/events/related.py +2 -1
- prefect/events/schemas/events.py +1 -1
- prefect/events/utilities.py +2 -0
- prefect/events/worker.py +8 -0
- prefect/flow_engine.py +41 -81
- prefect/flow_runs.py +4 -2
- prefect/flows.py +4 -6
- prefect/results.py +43 -22
- prefect/runner/storage.py +3 -3
- prefect/serializers.py +28 -24
- prefect/settings/models/experiments.py +5 -0
- prefect/task_engine.py +34 -26
- prefect/task_worker.py +43 -25
- prefect/tasks.py +118 -125
- prefect/telemetry/instrumentation.py +1 -1
- prefect/telemetry/processors.py +10 -7
- prefect/telemetry/run_telemetry.py +157 -33
- prefect/types/__init__.py +4 -1
- prefect/variables.py +127 -19
- {prefect_client-3.1.6.dist-info → prefect_client-3.1.7.dist-info}/METADATA +2 -1
- {prefect_client-3.1.6.dist-info → prefect_client-3.1.7.dist-info}/RECORD +49 -47
- {prefect_client-3.1.6.dist-info → prefect_client-3.1.7.dist-info}/LICENSE +0 -0
- {prefect_client-3.1.6.dist-info → prefect_client-3.1.7.dist-info}/WHEEL +0 -0
- {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
|
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
|
-
|
42
|
-
|
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
|
-
|
173
|
+
_credentials = {"access_token": access_token} if access_token else credentials
|
124
174
|
|
125
175
|
storage = GitRepository(
|
126
176
|
url=repository,
|
127
|
-
credentials=
|
177
|
+
credentials=_credentials,
|
128
178
|
branch=branch,
|
129
179
|
include_submodules=include_submodules,
|
130
180
|
)
|
131
181
|
|
132
|
-
|
182
|
+
run_coro_as_sync(_pull_git_repository_with_retries(storage))
|
133
183
|
|
134
|
-
directory
|
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.
|
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 =
|
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 =
|
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 =
|
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
prefect/events/schemas/events.py
CHANGED
@@ -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
|
|
prefect/events/utilities.py
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
685
|
-
name=self.
|
686
|
-
|
687
|
-
|
688
|
-
|
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(
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
1259
|
-
name=self.
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
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(
|
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
|
-
|
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(
|
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():
|