prefect-client 3.1.4__py3-none-any.whl → 3.1.6__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/__init__.py +3 -0
- prefect/_internal/compatibility/migration.py +1 -1
- prefect/_internal/concurrency/api.py +52 -52
- prefect/_internal/concurrency/calls.py +59 -35
- prefect/_internal/concurrency/cancellation.py +34 -18
- prefect/_internal/concurrency/event_loop.py +7 -6
- prefect/_internal/concurrency/threads.py +41 -33
- prefect/_internal/concurrency/waiters.py +28 -21
- prefect/_internal/pydantic/v1_schema.py +2 -2
- prefect/_internal/pydantic/v2_schema.py +10 -9
- prefect/_internal/schemas/bases.py +10 -11
- prefect/_internal/schemas/validators.py +2 -1
- prefect/_version.py +3 -3
- prefect/automations.py +53 -47
- prefect/blocks/abstract.py +12 -10
- prefect/blocks/core.py +4 -2
- prefect/cache_policies.py +11 -11
- prefect/client/__init__.py +3 -1
- prefect/client/base.py +36 -37
- prefect/client/cloud.py +26 -19
- prefect/client/collections.py +2 -2
- prefect/client/orchestration.py +366 -277
- prefect/client/schemas/__init__.py +24 -0
- prefect/client/schemas/actions.py +132 -120
- prefect/client/schemas/filters.py +5 -0
- prefect/client/schemas/objects.py +113 -85
- prefect/client/schemas/responses.py +21 -18
- prefect/client/schemas/schedules.py +136 -93
- prefect/client/subscriptions.py +28 -14
- prefect/client/utilities.py +32 -36
- prefect/concurrency/asyncio.py +6 -9
- prefect/concurrency/services.py +3 -0
- prefect/concurrency/sync.py +35 -5
- prefect/context.py +39 -31
- prefect/deployments/flow_runs.py +3 -5
- prefect/docker/__init__.py +1 -1
- prefect/events/schemas/events.py +25 -20
- prefect/events/utilities.py +1 -2
- prefect/filesystems.py +3 -3
- prefect/flow_engine.py +755 -138
- prefect/flow_runs.py +3 -3
- prefect/flows.py +214 -170
- prefect/logging/configuration.py +1 -1
- prefect/logging/highlighters.py +1 -2
- prefect/logging/loggers.py +30 -20
- prefect/main.py +17 -24
- prefect/runner/runner.py +43 -21
- prefect/runner/server.py +30 -32
- prefect/runner/submit.py +3 -6
- prefect/runner/utils.py +6 -6
- prefect/runtime/flow_run.py +7 -0
- prefect/settings/constants.py +2 -2
- prefect/settings/legacy.py +1 -1
- prefect/settings/models/server/events.py +10 -0
- prefect/settings/sources.py +9 -2
- prefect/task_engine.py +72 -19
- prefect/task_runners.py +2 -2
- prefect/tasks.py +46 -33
- prefect/telemetry/bootstrap.py +15 -2
- prefect/telemetry/run_telemetry.py +107 -0
- prefect/transactions.py +14 -14
- prefect/types/__init__.py +20 -3
- prefect/utilities/_engine.py +96 -0
- prefect/utilities/annotations.py +25 -18
- prefect/utilities/asyncutils.py +126 -140
- prefect/utilities/callables.py +87 -78
- prefect/utilities/collections.py +278 -117
- prefect/utilities/compat.py +13 -21
- prefect/utilities/context.py +6 -5
- prefect/utilities/dispatch.py +23 -12
- prefect/utilities/dockerutils.py +33 -32
- prefect/utilities/engine.py +126 -239
- prefect/utilities/filesystem.py +18 -15
- prefect/utilities/hashing.py +10 -11
- prefect/utilities/importtools.py +40 -27
- prefect/utilities/math.py +9 -5
- prefect/utilities/names.py +3 -3
- prefect/utilities/processutils.py +121 -57
- prefect/utilities/pydantic.py +41 -36
- prefect/utilities/render_swagger.py +22 -12
- prefect/utilities/schema_tools/__init__.py +2 -1
- prefect/utilities/schema_tools/hydration.py +50 -43
- prefect/utilities/schema_tools/validation.py +52 -42
- prefect/utilities/services.py +13 -12
- prefect/utilities/templating.py +45 -45
- prefect/utilities/text.py +2 -1
- prefect/utilities/timeout.py +4 -4
- prefect/utilities/urls.py +9 -4
- prefect/utilities/visualization.py +46 -24
- prefect/variables.py +9 -8
- prefect/workers/base.py +18 -10
- {prefect_client-3.1.4.dist-info → prefect_client-3.1.6.dist-info}/METADATA +5 -5
- {prefect_client-3.1.4.dist-info → prefect_client-3.1.6.dist-info}/RECORD +96 -94
- {prefect_client-3.1.4.dist-info → prefect_client-3.1.6.dist-info}/WHEEL +1 -1
- {prefect_client-3.1.4.dist-info → prefect_client-3.1.6.dist-info}/LICENSE +0 -0
- {prefect_client-3.1.4.dist-info → prefect_client-3.1.6.dist-info}/top_level.txt +0 -0
prefect/utilities/templating.py
CHANGED
@@ -1,17 +1,7 @@
|
|
1
1
|
import enum
|
2
2
|
import os
|
3
3
|
import re
|
4
|
-
from typing import
|
5
|
-
TYPE_CHECKING,
|
6
|
-
Any,
|
7
|
-
Dict,
|
8
|
-
NamedTuple,
|
9
|
-
Optional,
|
10
|
-
Set,
|
11
|
-
Type,
|
12
|
-
TypeVar,
|
13
|
-
Union,
|
14
|
-
)
|
4
|
+
from typing import TYPE_CHECKING, Any, NamedTuple, Optional, TypeVar, Union, cast
|
15
5
|
|
16
6
|
from prefect.client.utilities import inject_client
|
17
7
|
from prefect.utilities.annotations import NotSet
|
@@ -21,7 +11,7 @@ if TYPE_CHECKING:
|
|
21
11
|
from prefect.client.orchestration import PrefectClient
|
22
12
|
|
23
13
|
|
24
|
-
T = TypeVar("T", str, int, float, bool, dict, list, None)
|
14
|
+
T = TypeVar("T", str, int, float, bool, dict[Any, Any], list[Any], None)
|
25
15
|
|
26
16
|
PLACEHOLDER_CAPTURE_REGEX = re.compile(r"({{\s*([\w\.\-\[\]$]+)\s*}})")
|
27
17
|
BLOCK_DOCUMENT_PLACEHOLDER_PREFIX = "prefect.blocks."
|
@@ -62,7 +52,7 @@ def determine_placeholder_type(name: str) -> PlaceholderType:
|
|
62
52
|
return PlaceholderType.STANDARD
|
63
53
|
|
64
54
|
|
65
|
-
def find_placeholders(template: T) ->
|
55
|
+
def find_placeholders(template: T) -> set[Placeholder]:
|
66
56
|
"""
|
67
57
|
Finds all placeholders in a template.
|
68
58
|
|
@@ -72,8 +62,9 @@ def find_placeholders(template: T) -> Set[Placeholder]:
|
|
72
62
|
Returns:
|
73
63
|
A set of all placeholders in the template
|
74
64
|
"""
|
65
|
+
seed: set[Placeholder] = set()
|
75
66
|
if isinstance(template, (int, float, bool)):
|
76
|
-
return
|
67
|
+
return seed
|
77
68
|
if isinstance(template, str):
|
78
69
|
result = PLACEHOLDER_CAPTURE_REGEX.findall(template)
|
79
70
|
return {
|
@@ -81,18 +72,16 @@ def find_placeholders(template: T) -> Set[Placeholder]:
|
|
81
72
|
for full_match, name in result
|
82
73
|
}
|
83
74
|
elif isinstance(template, dict):
|
84
|
-
return
|
85
|
-
*[find_placeholders(value) for key, value in template.items()]
|
86
|
-
)
|
75
|
+
return seed.union(*[find_placeholders(value) for value in template.values()])
|
87
76
|
elif isinstance(template, list):
|
88
|
-
return
|
77
|
+
return seed.union(*[find_placeholders(item) for item in template])
|
89
78
|
else:
|
90
79
|
raise ValueError(f"Unexpected type: {type(template)}")
|
91
80
|
|
92
81
|
|
93
82
|
def apply_values(
|
94
|
-
template: T, values:
|
95
|
-
) -> Union[T,
|
83
|
+
template: T, values: dict[str, Any], remove_notset: bool = True
|
84
|
+
) -> Union[T, type[NotSet]]:
|
96
85
|
"""
|
97
86
|
Replaces placeholders in a template with values from a supplied dictionary.
|
98
87
|
|
@@ -120,7 +109,7 @@ def apply_values(
|
|
120
109
|
Returns:
|
121
110
|
The template with the values applied
|
122
111
|
"""
|
123
|
-
if
|
112
|
+
if template in (NotSet, None) or isinstance(template, (int, float)):
|
124
113
|
return template
|
125
114
|
if isinstance(template, str):
|
126
115
|
placeholders = find_placeholders(template)
|
@@ -155,7 +144,7 @@ def apply_values(
|
|
155
144
|
|
156
145
|
return template
|
157
146
|
elif isinstance(template, dict):
|
158
|
-
updated_template = {}
|
147
|
+
updated_template: dict[str, Any] = {}
|
159
148
|
for key, value in template.items():
|
160
149
|
updated_value = apply_values(value, values, remove_notset=remove_notset)
|
161
150
|
if updated_value is not NotSet:
|
@@ -163,22 +152,22 @@ def apply_values(
|
|
163
152
|
elif not remove_notset:
|
164
153
|
updated_template[key] = value
|
165
154
|
|
166
|
-
return updated_template
|
155
|
+
return cast(T, updated_template)
|
167
156
|
elif isinstance(template, list):
|
168
|
-
updated_list = []
|
157
|
+
updated_list: list[Any] = []
|
169
158
|
for value in template:
|
170
159
|
updated_value = apply_values(value, values, remove_notset=remove_notset)
|
171
160
|
if updated_value is not NotSet:
|
172
161
|
updated_list.append(updated_value)
|
173
|
-
return updated_list
|
162
|
+
return cast(T, updated_list)
|
174
163
|
else:
|
175
164
|
raise ValueError(f"Unexpected template type {type(template).__name__!r}")
|
176
165
|
|
177
166
|
|
178
167
|
@inject_client
|
179
168
|
async def resolve_block_document_references(
|
180
|
-
template: T, client: "PrefectClient" = None
|
181
|
-
) -> Union[T,
|
169
|
+
template: T, client: Optional["PrefectClient"] = None
|
170
|
+
) -> Union[T, dict[str, Any]]:
|
182
171
|
"""
|
183
172
|
Resolve block document references in a template by replacing each reference with
|
184
173
|
the data of the block document.
|
@@ -242,12 +231,17 @@ async def resolve_block_document_references(
|
|
242
231
|
Returns:
|
243
232
|
The template with block documents resolved
|
244
233
|
"""
|
234
|
+
if TYPE_CHECKING:
|
235
|
+
# The @inject_client decorator takes care of providing the client, but
|
236
|
+
# the function signature must mark it as optional to callers.
|
237
|
+
assert client is not None
|
238
|
+
|
245
239
|
if isinstance(template, dict):
|
246
240
|
block_document_id = template.get("$ref", {}).get("block_document_id")
|
247
241
|
if block_document_id:
|
248
242
|
block_document = await client.read_block_document(block_document_id)
|
249
243
|
return block_document.data
|
250
|
-
updated_template = {}
|
244
|
+
updated_template: dict[str, Any] = {}
|
251
245
|
for key, value in template.items():
|
252
246
|
updated_value = await resolve_block_document_references(
|
253
247
|
value, client=client
|
@@ -265,7 +259,7 @@ async def resolve_block_document_references(
|
|
265
259
|
placeholder.type is PlaceholderType.BLOCK_DOCUMENT
|
266
260
|
for placeholder in placeholders
|
267
261
|
)
|
268
|
-
if
|
262
|
+
if not (placeholders and has_block_document_placeholder):
|
269
263
|
return template
|
270
264
|
elif (
|
271
265
|
len(placeholders) == 1
|
@@ -274,31 +268,32 @@ async def resolve_block_document_references(
|
|
274
268
|
):
|
275
269
|
# value_keypath will be a list containing a dot path if additional
|
276
270
|
# attributes are accessed and an empty list otherwise.
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
271
|
+
[placeholder] = placeholders
|
272
|
+
parts = placeholder.name.replace(
|
273
|
+
BLOCK_DOCUMENT_PLACEHOLDER_PREFIX, ""
|
274
|
+
).split(".", 2)
|
275
|
+
block_type_slug, block_document_name, *value_keypath = parts
|
282
276
|
block_document = await client.read_block_document_by_name(
|
283
277
|
name=block_document_name, block_type_slug=block_type_slug
|
284
278
|
)
|
285
|
-
|
279
|
+
data = block_document.data
|
280
|
+
value: Union[T, dict[str, Any]] = data
|
286
281
|
|
287
282
|
# resolving system blocks to their data for backwards compatibility
|
288
|
-
if len(
|
283
|
+
if len(data) == 1 and "value" in data:
|
289
284
|
# only resolve the value if the keypath is not already pointing to "value"
|
290
|
-
if
|
291
|
-
value = value["value"]
|
285
|
+
if not (value_keypath and value_keypath[0].startswith("value")):
|
286
|
+
data = value = value["value"]
|
292
287
|
|
293
288
|
# resolving keypath/block attributes
|
294
|
-
if
|
295
|
-
|
296
|
-
|
297
|
-
if value is NotSet:
|
289
|
+
if value_keypath:
|
290
|
+
from_dict: Any = get_from_dict(data, value_keypath[0], default=NotSet)
|
291
|
+
if from_dict is NotSet:
|
298
292
|
raise ValueError(
|
299
293
|
f"Invalid template: {template!r}. Could not resolve the"
|
300
294
|
" keypath in the block document data."
|
301
295
|
)
|
296
|
+
value = from_dict
|
302
297
|
|
303
298
|
return value
|
304
299
|
else:
|
@@ -311,7 +306,7 @@ async def resolve_block_document_references(
|
|
311
306
|
|
312
307
|
|
313
308
|
@inject_client
|
314
|
-
async def resolve_variables(template: T, client: Optional["PrefectClient"] = None):
|
309
|
+
async def resolve_variables(template: T, client: Optional["PrefectClient"] = None) -> T:
|
315
310
|
"""
|
316
311
|
Resolve variables in a template by replacing each variable placeholder with the
|
317
312
|
value of the variable.
|
@@ -326,6 +321,11 @@ async def resolve_variables(template: T, client: Optional["PrefectClient"] = Non
|
|
326
321
|
Returns:
|
327
322
|
The template with variables resolved
|
328
323
|
"""
|
324
|
+
if TYPE_CHECKING:
|
325
|
+
# The @inject_client decorator takes care of providing the client, but
|
326
|
+
# the function signature must mark it as optional to callers.
|
327
|
+
assert client is not None
|
328
|
+
|
329
329
|
if isinstance(template, str):
|
330
330
|
placeholders = find_placeholders(template)
|
331
331
|
has_variable_placeholder = any(
|
@@ -346,7 +346,7 @@ async def resolve_variables(template: T, client: Optional["PrefectClient"] = Non
|
|
346
346
|
if variable is None:
|
347
347
|
return ""
|
348
348
|
else:
|
349
|
-
return variable.value
|
349
|
+
return cast(T, variable.value)
|
350
350
|
else:
|
351
351
|
for full_match, name, placeholder_type in placeholders:
|
352
352
|
if placeholder_type is PlaceholderType.VARIABLE:
|
@@ -355,7 +355,7 @@ async def resolve_variables(template: T, client: Optional["PrefectClient"] = Non
|
|
355
355
|
if variable is None:
|
356
356
|
template = template.replace(full_match, "")
|
357
357
|
else:
|
358
|
-
template = template.replace(full_match, variable.value)
|
358
|
+
template = template.replace(full_match, str(variable.value))
|
359
359
|
return template
|
360
360
|
elif isinstance(template, dict):
|
361
361
|
return {
|
prefect/utilities/text.py
CHANGED
prefect/utilities/timeout.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from asyncio import CancelledError
|
2
2
|
from contextlib import contextmanager
|
3
|
-
from typing import Optional
|
3
|
+
from typing import Optional
|
4
4
|
|
5
5
|
from prefect._internal.concurrency.cancellation import (
|
6
6
|
cancel_async_after,
|
@@ -8,7 +8,7 @@ from prefect._internal.concurrency.cancellation import (
|
|
8
8
|
)
|
9
9
|
|
10
10
|
|
11
|
-
def fail_if_not_timeout_error(timeout_exc_type:
|
11
|
+
def fail_if_not_timeout_error(timeout_exc_type: type[Exception]) -> None:
|
12
12
|
if not issubclass(timeout_exc_type, TimeoutError):
|
13
13
|
raise ValueError(
|
14
14
|
"The `timeout_exc_type` argument must be a subclass of `TimeoutError`."
|
@@ -17,7 +17,7 @@ def fail_if_not_timeout_error(timeout_exc_type: Type[Exception]) -> None:
|
|
17
17
|
|
18
18
|
@contextmanager
|
19
19
|
def timeout_async(
|
20
|
-
seconds: Optional[float] = None, timeout_exc_type:
|
20
|
+
seconds: Optional[float] = None, timeout_exc_type: type[TimeoutError] = TimeoutError
|
21
21
|
):
|
22
22
|
fail_if_not_timeout_error(timeout_exc_type)
|
23
23
|
|
@@ -34,7 +34,7 @@ def timeout_async(
|
|
34
34
|
|
35
35
|
@contextmanager
|
36
36
|
def timeout(
|
37
|
-
seconds: Optional[float] = None, timeout_exc_type:
|
37
|
+
seconds: Optional[float] = None, timeout_exc_type: type[TimeoutError] = TimeoutError
|
38
38
|
):
|
39
39
|
fail_if_not_timeout_error(timeout_exc_type)
|
40
40
|
|
prefect/utilities/urls.py
CHANGED
@@ -2,6 +2,7 @@ import inspect
|
|
2
2
|
import ipaddress
|
3
3
|
import socket
|
4
4
|
import urllib.parse
|
5
|
+
from logging import Logger
|
5
6
|
from string import Formatter
|
6
7
|
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
7
8
|
from urllib.parse import urlparse
|
@@ -19,7 +20,7 @@ if TYPE_CHECKING:
|
|
19
20
|
from prefect.futures import PrefectFuture
|
20
21
|
from prefect.variables import Variable
|
21
22
|
|
22
|
-
logger = get_logger("utilities.urls")
|
23
|
+
logger: Logger = get_logger("utilities.urls")
|
23
24
|
|
24
25
|
# The following objects are excluded from UI URL generation because we lack a
|
25
26
|
# directly-addressable URL:
|
@@ -64,7 +65,7 @@ URLType = Literal["ui", "api"]
|
|
64
65
|
RUN_TYPES = {"flow-run", "task-run"}
|
65
66
|
|
66
67
|
|
67
|
-
def validate_restricted_url(url: str):
|
68
|
+
def validate_restricted_url(url: str) -> None:
|
68
69
|
"""
|
69
70
|
Validate that the provided URL is safe for outbound requests. This prevents
|
70
71
|
attacks like SSRF (Server Side Request Forgery), where an attacker can make
|
@@ -123,7 +124,7 @@ def convert_class_to_name(obj: Any) -> str:
|
|
123
124
|
|
124
125
|
def url_for(
|
125
126
|
obj: Union[
|
126
|
-
"PrefectFuture",
|
127
|
+
"PrefectFuture[Any]",
|
127
128
|
"Block",
|
128
129
|
"Variable",
|
129
130
|
"Automation",
|
@@ -163,6 +164,7 @@ def url_for(
|
|
163
164
|
url_for("flow-run", obj_id="123e4567-e89b-12d3-a456-426614174000")
|
164
165
|
"""
|
165
166
|
from prefect.blocks.core import Block
|
167
|
+
from prefect.client.schemas.objects import WorkPool
|
166
168
|
from prefect.events.schemas.automations import Automation
|
167
169
|
from prefect.events.schemas.events import ReceivedEvent, Resource
|
168
170
|
from prefect.futures import PrefectFuture
|
@@ -228,8 +230,10 @@ def url_for(
|
|
228
230
|
elif name == "block":
|
229
231
|
# Blocks are client-side objects whose API representation is a
|
230
232
|
# BlockDocument.
|
231
|
-
obj_id = obj
|
233
|
+
obj_id = getattr(obj, "_block_document_id")
|
232
234
|
elif name in ("variable", "work-pool"):
|
235
|
+
if TYPE_CHECKING:
|
236
|
+
assert isinstance(obj, (Variable, WorkPool))
|
233
237
|
obj_id = obj.name
|
234
238
|
elif isinstance(obj, Resource):
|
235
239
|
obj_id = obj.id.rpartition(".")[2]
|
@@ -244,6 +248,7 @@ def url_for(
|
|
244
248
|
url_format = (
|
245
249
|
UI_URL_FORMATS.get(name) if url_type == "ui" else API_URL_FORMATS.get(name)
|
246
250
|
)
|
251
|
+
assert url_format is not None
|
247
252
|
|
248
253
|
if isinstance(obj, ReceivedEvent):
|
249
254
|
url = url_format.format(
|
@@ -2,10 +2,12 @@
|
|
2
2
|
Utilities for working with Flow.visualize()
|
3
3
|
"""
|
4
4
|
|
5
|
+
from collections.abc import Coroutine
|
5
6
|
from functools import partial
|
6
|
-
from typing import Any,
|
7
|
+
from typing import Any, Literal, Optional, Union, overload
|
7
8
|
|
8
|
-
import graphviz
|
9
|
+
import graphviz # type: ignore # no typing stubs available
|
10
|
+
from typing_extensions import Self
|
9
11
|
|
10
12
|
from prefect._internal.concurrency.api import from_async
|
11
13
|
|
@@ -19,7 +21,7 @@ class VisualizationUnsupportedError(Exception):
|
|
19
21
|
|
20
22
|
|
21
23
|
class TaskVizTrackerState:
|
22
|
-
current = None
|
24
|
+
current: Optional["TaskVizTracker"] = None
|
23
25
|
|
24
26
|
|
25
27
|
class GraphvizImportError(Exception):
|
@@ -30,16 +32,36 @@ class GraphvizExecutableNotFoundError(Exception):
|
|
30
32
|
pass
|
31
33
|
|
32
34
|
|
33
|
-
def get_task_viz_tracker():
|
35
|
+
def get_task_viz_tracker() -> Optional["TaskVizTracker"]:
|
34
36
|
return TaskVizTrackerState.current
|
35
37
|
|
36
38
|
|
39
|
+
@overload
|
40
|
+
def track_viz_task(
|
41
|
+
is_async: Literal[True],
|
42
|
+
task_name: str,
|
43
|
+
parameters: dict[str, Any],
|
44
|
+
viz_return_value: Optional[Any] = None,
|
45
|
+
) -> Coroutine[Any, Any, Any]:
|
46
|
+
...
|
47
|
+
|
48
|
+
|
49
|
+
@overload
|
50
|
+
def track_viz_task(
|
51
|
+
is_async: Literal[False],
|
52
|
+
task_name: str,
|
53
|
+
parameters: dict[str, Any],
|
54
|
+
viz_return_value: Optional[Any] = None,
|
55
|
+
) -> Any:
|
56
|
+
...
|
57
|
+
|
58
|
+
|
37
59
|
def track_viz_task(
|
38
60
|
is_async: bool,
|
39
61
|
task_name: str,
|
40
|
-
parameters: dict,
|
62
|
+
parameters: dict[str, Any],
|
41
63
|
viz_return_value: Optional[Any] = None,
|
42
|
-
):
|
64
|
+
) -> Union[Coroutine[Any, Any, Any], Any]:
|
43
65
|
"""Return a result if sync otherwise return a coroutine that returns the result"""
|
44
66
|
if is_async:
|
45
67
|
return from_async.wait_for_call_in_loop_thread(
|
@@ -50,14 +72,14 @@ def track_viz_task(
|
|
50
72
|
|
51
73
|
|
52
74
|
def _track_viz_task(
|
53
|
-
task_name,
|
54
|
-
parameters,
|
55
|
-
viz_return_value=None,
|
75
|
+
task_name: str,
|
76
|
+
parameters: dict[str, Any],
|
77
|
+
viz_return_value: Optional[Any] = None,
|
56
78
|
) -> Any:
|
57
79
|
task_run_tracker = get_task_viz_tracker()
|
58
80
|
if task_run_tracker:
|
59
|
-
upstream_tasks = []
|
60
|
-
for
|
81
|
+
upstream_tasks: list[VizTask] = []
|
82
|
+
for _, v in parameters.items():
|
61
83
|
if isinstance(v, VizTask):
|
62
84
|
upstream_tasks.append(v)
|
63
85
|
# if it's an object that we've already seen,
|
@@ -85,19 +107,19 @@ class VizTask:
|
|
85
107
|
def __init__(
|
86
108
|
self,
|
87
109
|
name: str,
|
88
|
-
upstream_tasks: Optional[
|
110
|
+
upstream_tasks: Optional[list["VizTask"]] = None,
|
89
111
|
):
|
90
112
|
self.name = name
|
91
|
-
self.upstream_tasks = upstream_tasks if upstream_tasks else []
|
113
|
+
self.upstream_tasks: list[VizTask] = upstream_tasks if upstream_tasks else []
|
92
114
|
|
93
115
|
|
94
116
|
class TaskVizTracker:
|
95
117
|
def __init__(self):
|
96
|
-
self.tasks = []
|
97
|
-
self.dynamic_task_counter = {}
|
98
|
-
self.object_id_to_task = {}
|
118
|
+
self.tasks: list[VizTask] = []
|
119
|
+
self.dynamic_task_counter: dict[str, int] = {}
|
120
|
+
self.object_id_to_task: dict[int, VizTask] = {}
|
99
121
|
|
100
|
-
def add_task(self, task: VizTask):
|
122
|
+
def add_task(self, task: VizTask) -> None:
|
101
123
|
if task.name not in self.dynamic_task_counter:
|
102
124
|
self.dynamic_task_counter[task.name] = 0
|
103
125
|
else:
|
@@ -106,11 +128,11 @@ class TaskVizTracker:
|
|
106
128
|
task.name = f"{task.name}-{self.dynamic_task_counter[task.name]}"
|
107
129
|
self.tasks.append(task)
|
108
130
|
|
109
|
-
def __enter__(self):
|
131
|
+
def __enter__(self) -> Self:
|
110
132
|
TaskVizTrackerState.current = self
|
111
133
|
return self
|
112
134
|
|
113
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
135
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
114
136
|
TaskVizTrackerState.current = None
|
115
137
|
|
116
138
|
def link_viz_return_value_to_viz_task(
|
@@ -129,7 +151,7 @@ class TaskVizTracker:
|
|
129
151
|
self.object_id_to_task[id(viz_return_value)] = viz_task
|
130
152
|
|
131
153
|
|
132
|
-
def build_task_dependencies(task_run_tracker: TaskVizTracker):
|
154
|
+
def build_task_dependencies(task_run_tracker: TaskVizTracker) -> graphviz.Digraph:
|
133
155
|
"""
|
134
156
|
Constructs a Graphviz directed graph object that represents the dependencies
|
135
157
|
between tasks in the given TaskVizTracker.
|
@@ -151,9 +173,9 @@ def build_task_dependencies(task_run_tracker: TaskVizTracker):
|
|
151
173
|
try:
|
152
174
|
g = graphviz.Digraph()
|
153
175
|
for task in task_run_tracker.tasks:
|
154
|
-
g.node(task.name)
|
176
|
+
g.node(task.name) # type: ignore[reportUnknownMemberType]
|
155
177
|
for upstream in task.upstream_tasks:
|
156
|
-
g.edge(upstream.name, task.name)
|
178
|
+
g.edge(upstream.name, task.name) # type: ignore[reportUnknownMemberType]
|
157
179
|
return g
|
158
180
|
except ImportError as exc:
|
159
181
|
raise GraphvizImportError from exc
|
@@ -166,7 +188,7 @@ def build_task_dependencies(task_run_tracker: TaskVizTracker):
|
|
166
188
|
)
|
167
189
|
|
168
190
|
|
169
|
-
def visualize_task_dependencies(graph: graphviz.Digraph, flow_run_name: str):
|
191
|
+
def visualize_task_dependencies(graph: graphviz.Digraph, flow_run_name: str) -> None:
|
170
192
|
"""
|
171
193
|
Renders and displays a Graphviz directed graph representing task dependencies.
|
172
194
|
|
@@ -184,7 +206,7 @@ def visualize_task_dependencies(graph: graphviz.Digraph, flow_run_name: str):
|
|
184
206
|
specifying a `viz_return_value`.
|
185
207
|
"""
|
186
208
|
try:
|
187
|
-
graph.render(filename=flow_run_name, view=True, format="png", cleanup=True)
|
209
|
+
graph.render(filename=flow_run_name, view=True, format="png", cleanup=True) # type: ignore[reportUnknownMemberType]
|
188
210
|
except graphviz.backend.ExecutableNotFound as exc:
|
189
211
|
msg = (
|
190
212
|
"It appears you do not have Graphviz installed, or it is not on your "
|
prefect/variables.py
CHANGED
@@ -73,17 +73,18 @@ class Variable(BaseModel):
|
|
73
73
|
raise ValueError(
|
74
74
|
f"Variable {name!r} already exists. Use `overwrite=True` to update it."
|
75
75
|
)
|
76
|
-
await client.update_variable(
|
76
|
+
await client.update_variable(
|
77
|
+
variable=VariableUpdate.model_validate(var_dict)
|
78
|
+
)
|
77
79
|
variable = await client.read_variable_by_name(name)
|
78
|
-
|
79
|
-
|
80
|
-
"value": variable.value,
|
81
|
-
"tags": variable.tags or [],
|
82
|
-
}
|
80
|
+
for key in var_dict.keys():
|
81
|
+
var_dict.update({key: getattr(variable, key)})
|
83
82
|
else:
|
84
|
-
await client.create_variable(
|
83
|
+
await client.create_variable(
|
84
|
+
variable=VariableCreate.model_validate(var_dict)
|
85
|
+
)
|
85
86
|
|
86
|
-
return cls(
|
87
|
+
return cls.model_validate(var_dict)
|
87
88
|
|
88
89
|
@classmethod
|
89
90
|
@sync_compatible
|
prefect/workers/base.py
CHANGED
@@ -53,6 +53,7 @@ from prefect.states import (
|
|
53
53
|
Pending,
|
54
54
|
exception_to_failed_state,
|
55
55
|
)
|
56
|
+
from prefect.types import KeyValueLabels
|
56
57
|
from prefect.utilities.dispatch import get_registry_for_type, register_base_type
|
57
58
|
from prefect.utilities.engine import propose_state
|
58
59
|
from prefect.utilities.services import critical_service_loop
|
@@ -637,8 +638,9 @@ class BaseWorker(abc.ABC):
|
|
637
638
|
await self._exit_stack.enter_async_context(self._runs_task_group)
|
638
639
|
|
639
640
|
if self._client.server_type == ServerType.CLOUD:
|
640
|
-
self._cloud_client =
|
641
|
-
|
641
|
+
self._cloud_client = await self._exit_stack.enter_async_context(
|
642
|
+
get_cloud_client()
|
643
|
+
)
|
642
644
|
|
643
645
|
self.is_setup = True
|
644
646
|
|
@@ -987,7 +989,6 @@ class BaseWorker(abc.ABC):
|
|
987
989
|
try:
|
988
990
|
configuration = await self._get_configuration(flow_run)
|
989
991
|
submitted_event = self._emit_flow_run_submitted_event(configuration)
|
990
|
-
await self._give_worker_labels_to_flow_run(flow_run.id)
|
991
992
|
result = await self.run(
|
992
993
|
flow_run=flow_run,
|
993
994
|
task_status=task_status,
|
@@ -1221,13 +1222,20 @@ class BaseWorker(abc.ABC):
|
|
1221
1222
|
Give this worker's identifying labels to the specified flow run.
|
1222
1223
|
"""
|
1223
1224
|
if self._cloud_client:
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1225
|
+
labels: KeyValueLabels = {
|
1226
|
+
"prefect.worker.name": self.name,
|
1227
|
+
"prefect.worker.type": self.type,
|
1228
|
+
}
|
1229
|
+
|
1230
|
+
if self._work_pool:
|
1231
|
+
labels.update(
|
1232
|
+
{
|
1233
|
+
"prefect.work-pool.name": self._work_pool.name,
|
1234
|
+
"prefect.work-pool.id": str(self._work_pool.id),
|
1235
|
+
}
|
1236
|
+
)
|
1237
|
+
|
1238
|
+
await self._cloud_client.update_flow_run_labels(flow_run_id, labels)
|
1231
1239
|
|
1232
1240
|
async def __aenter__(self):
|
1233
1241
|
self._logger.debug("Entering worker context...")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: prefect-client
|
3
|
-
Version: 3.1.
|
3
|
+
Version: 3.1.6
|
4
4
|
Summary: Workflow orchestration and management.
|
5
5
|
Home-page: https://www.prefect.io
|
6
6
|
Author: Prefect Technologies, Inc.
|
@@ -45,7 +45,7 @@ Requires-Dist: packaging<24.3,>=21.3
|
|
45
45
|
Requires-Dist: pathspec>=0.8.0
|
46
46
|
Requires-Dist: pendulum<4,>=3.0.0
|
47
47
|
Requires-Dist: prometheus-client>=0.20.0
|
48
|
-
Requires-Dist: pydantic
|
48
|
+
Requires-Dist: pydantic!=2.10.0,<3.0.0,>=2.7
|
49
49
|
Requires-Dist: pydantic-core<3.0.0,>=2.12.0
|
50
50
|
Requires-Dist: pydantic-extra-types<3.0.0,>=2.8.2
|
51
51
|
Requires-Dist: pydantic-settings>2.2.1
|
@@ -79,8 +79,6 @@ Requires-Dist: apprise<2.0.0,>=1.1.0; extra == "notifications"
|
|
79
79
|
<br>
|
80
80
|
<a href="https://prefect.io/slack" alt="Slack">
|
81
81
|
<img src="https://img.shields.io/badge/slack-join_community-red.svg?color=0052FF&labelColor=090422&logo=slack" /></a>
|
82
|
-
<a href="https://discourse.prefect.io/" alt="Discourse">
|
83
|
-
<img src="https://img.shields.io/badge/discourse-browse_forum-red.svg?color=0052FF&labelColor=090422&logo=discourse" /></a>
|
84
82
|
<a href="https://www.youtube.com/c/PrefectIO/" alt="YouTube">
|
85
83
|
<img src="https://img.shields.io/badge/youtube-watch_videos-red.svg?color=0052FF&labelColor=090422&logo=youtube" /></a>
|
86
84
|
</p>
|
@@ -167,7 +165,9 @@ Start with our [friendly tutorial](https://docs.prefect.io/tutorials) or explore
|
|
167
165
|
|
168
166
|
## Join the community
|
169
167
|
|
170
|
-
Prefect is made possible by the fastest growing community of thousands of friendly data engineers. Join us in building a new kind of workflow system.
|
168
|
+
Prefect is made possible by the fastest growing community of thousands of friendly data engineers. Join us in building a new kind of workflow system.
|
169
|
+
The [Prefect Slack community](https://prefect.io/slack) is a fantastic place to learn more about Prefect, ask questions, or get help with workflow design.
|
170
|
+
All community forums, including code contributions, issue discussions, and Slack messages are subject to our [Code of Conduct](https://github.com/PrefectHQ/prefect/blob/main/CODE_OF_CONDUCT.md).
|
171
171
|
|
172
172
|
## Contribute
|
173
173
|
|