prefect-client 3.1.10__py3-none-any.whl → 3.1.11__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/lineage.py +7 -8
- prefect/_internal/_logging.py +15 -3
- prefect/_internal/compatibility/async_dispatch.py +22 -16
- prefect/_internal/compatibility/deprecated.py +42 -18
- prefect/_internal/compatibility/migration.py +2 -2
- prefect/_internal/concurrency/inspection.py +12 -14
- prefect/_internal/concurrency/primitives.py +2 -2
- prefect/_internal/concurrency/services.py +154 -80
- prefect/_internal/concurrency/waiters.py +13 -9
- prefect/_internal/pydantic/annotations/pendulum.py +7 -7
- prefect/_internal/pytz.py +4 -3
- prefect/_internal/retries.py +10 -5
- prefect/_internal/schemas/bases.py +19 -10
- prefect/_internal/schemas/validators.py +227 -388
- prefect/_version.py +3 -3
- prefect/blocks/core.py +3 -3
- prefect/client/{orchestration.py → orchestration/__init__.py} +38 -701
- prefect/client/orchestration/_artifacts/__init__.py +0 -0
- prefect/client/orchestration/_artifacts/client.py +239 -0
- prefect/client/orchestration/_concurrency_limits/__init__.py +0 -0
- prefect/client/orchestration/_concurrency_limits/client.py +762 -0
- prefect/client/orchestration/_logs/__init__.py +0 -0
- prefect/client/orchestration/_logs/client.py +95 -0
- prefect/client/orchestration/_variables/__init__.py +0 -0
- prefect/client/orchestration/_variables/client.py +157 -0
- prefect/client/orchestration/base.py +46 -0
- prefect/client/orchestration/routes.py +145 -0
- prefect/client/schemas/actions.py +2 -2
- prefect/client/schemas/filters.py +5 -0
- prefect/client/schemas/objects.py +3 -10
- prefect/client/schemas/schedules.py +22 -10
- prefect/concurrency/_asyncio.py +87 -0
- prefect/concurrency/{events.py → _events.py} +10 -10
- prefect/concurrency/asyncio.py +20 -104
- prefect/concurrency/context.py +6 -4
- prefect/concurrency/services.py +26 -74
- prefect/concurrency/sync.py +23 -44
- prefect/concurrency/v1/_asyncio.py +63 -0
- prefect/concurrency/v1/{events.py → _events.py} +13 -15
- prefect/concurrency/v1/asyncio.py +27 -80
- prefect/concurrency/v1/context.py +6 -4
- prefect/concurrency/v1/services.py +33 -79
- prefect/concurrency/v1/sync.py +18 -37
- prefect/context.py +51 -44
- prefect/deployments/base.py +4 -144
- prefect/deployments/flow_runs.py +12 -2
- prefect/deployments/runner.py +11 -3
- prefect/deployments/steps/pull.py +13 -0
- prefect/events/clients.py +7 -1
- prefect/events/schemas/events.py +3 -2
- prefect/flow_engine.py +54 -47
- prefect/input/run_input.py +2 -1
- prefect/main.py +1 -3
- prefect/results.py +2 -307
- prefect/runner/storage.py +87 -21
- prefect/serializers.py +32 -25
- prefect/settings/legacy.py +4 -4
- prefect/settings/models/api.py +3 -3
- prefect/settings/models/cli.py +3 -3
- prefect/settings/models/client.py +5 -3
- prefect/settings/models/cloud.py +3 -3
- prefect/settings/models/deployments.py +3 -3
- prefect/settings/models/experiments.py +4 -2
- prefect/settings/models/flows.py +3 -3
- prefect/settings/models/internal.py +4 -2
- prefect/settings/models/logging.py +4 -3
- prefect/settings/models/results.py +3 -3
- prefect/settings/models/root.py +3 -2
- prefect/settings/models/runner.py +4 -4
- prefect/settings/models/server/api.py +3 -3
- prefect/settings/models/server/database.py +11 -4
- prefect/settings/models/server/deployments.py +6 -2
- prefect/settings/models/server/ephemeral.py +4 -2
- prefect/settings/models/server/events.py +3 -2
- prefect/settings/models/server/flow_run_graph.py +6 -2
- prefect/settings/models/server/root.py +3 -3
- prefect/settings/models/server/services.py +26 -11
- prefect/settings/models/server/tasks.py +6 -3
- prefect/settings/models/server/ui.py +3 -3
- prefect/settings/models/tasks.py +5 -5
- prefect/settings/models/testing.py +3 -3
- prefect/settings/models/worker.py +5 -3
- prefect/settings/profiles.py +15 -2
- prefect/states.py +4 -7
- prefect/task_engine.py +54 -75
- prefect/tasks.py +84 -32
- prefect/telemetry/run_telemetry.py +13 -8
- prefect/transactions.py +4 -15
- prefect/utilities/_git.py +34 -0
- prefect/utilities/asyncutils.py +1 -1
- prefect/utilities/engine.py +3 -19
- prefect/utilities/generics.py +18 -0
- {prefect_client-3.1.10.dist-info → prefect_client-3.1.11.dist-info}/METADATA +1 -1
- {prefect_client-3.1.10.dist-info → prefect_client-3.1.11.dist-info}/RECORD +97 -88
- prefect/records/__init__.py +0 -1
- prefect/records/base.py +0 -235
- prefect/records/filesystem.py +0 -213
- prefect/records/memory.py +0 -184
- prefect/records/result_store.py +0 -70
- {prefect_client-3.1.10.dist-info → prefect_client-3.1.11.dist-info}/LICENSE +0 -0
- {prefect_client-3.1.10.dist-info → prefect_client-3.1.11.dist-info}/WHEEL +0 -0
- {prefect_client-3.1.10.dist-info → prefect_client-3.1.11.dist-info}/top_level.txt +0 -0
prefect/deployments/base.py
CHANGED
@@ -5,27 +5,19 @@ build system for managing flows and deployments.
|
|
5
5
|
To get started, follow along with [the deloyments tutorial](/tutorials/deployments/).
|
6
6
|
"""
|
7
7
|
|
8
|
-
import ast
|
9
|
-
import asyncio
|
10
|
-
import math
|
11
8
|
import os
|
12
|
-
import subprocess
|
13
|
-
import sys
|
14
9
|
from copy import deepcopy
|
15
10
|
from pathlib import Path
|
16
11
|
from typing import Any, Dict, List, Optional, cast
|
17
12
|
|
18
|
-
import anyio
|
19
13
|
import yaml
|
20
14
|
from ruamel.yaml import YAML
|
21
15
|
|
22
16
|
from prefect.client.schemas.actions import DeploymentScheduleCreate
|
23
17
|
from prefect.client.schemas.objects import ConcurrencyLimitStrategy
|
24
18
|
from prefect.client.schemas.schedules import IntervalSchedule
|
25
|
-
from prefect.
|
26
|
-
from prefect.
|
27
|
-
from prefect.utilities.asyncutils import LazySemaphore
|
28
|
-
from prefect.utilities.filesystem import create_default_ignore_file, get_open_file_limit
|
19
|
+
from prefect.utilities._git import get_git_branch, get_git_remote_origin_url
|
20
|
+
from prefect.utilities.filesystem import create_default_ignore_file
|
29
21
|
from prefect.utilities.templating import apply_values
|
30
22
|
|
31
23
|
|
@@ -146,36 +138,6 @@ def configure_project_by_recipe(recipe: str, **formatting_kwargs) -> dict:
|
|
146
138
|
return config
|
147
139
|
|
148
140
|
|
149
|
-
def _get_git_remote_origin_url() -> Optional[str]:
|
150
|
-
"""
|
151
|
-
Returns the git remote origin URL for the current directory.
|
152
|
-
"""
|
153
|
-
try:
|
154
|
-
origin_url = subprocess.check_output(
|
155
|
-
["git", "config", "--get", "remote.origin.url"],
|
156
|
-
shell=sys.platform == "win32",
|
157
|
-
stderr=subprocess.DEVNULL,
|
158
|
-
)
|
159
|
-
origin_url = origin_url.decode().strip()
|
160
|
-
except subprocess.CalledProcessError:
|
161
|
-
return None
|
162
|
-
|
163
|
-
return origin_url
|
164
|
-
|
165
|
-
|
166
|
-
def _get_git_branch() -> Optional[str]:
|
167
|
-
"""
|
168
|
-
Returns the git branch for the current directory.
|
169
|
-
"""
|
170
|
-
try:
|
171
|
-
branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
|
172
|
-
branch = branch.decode().strip()
|
173
|
-
except subprocess.CalledProcessError:
|
174
|
-
return None
|
175
|
-
|
176
|
-
return branch
|
177
|
-
|
178
|
-
|
179
141
|
def initialize_project(
|
180
142
|
name: Optional[str] = None,
|
181
143
|
recipe: Optional[str] = None,
|
@@ -198,11 +160,11 @@ def initialize_project(
|
|
198
160
|
formatting_kwargs = {"directory": str(Path(".").absolute().resolve())}
|
199
161
|
dir_name = os.path.basename(os.getcwd())
|
200
162
|
|
201
|
-
remote_url =
|
163
|
+
remote_url = get_git_remote_origin_url()
|
202
164
|
if remote_url:
|
203
165
|
formatting_kwargs["repository"] = remote_url
|
204
166
|
is_git_based = True
|
205
|
-
branch =
|
167
|
+
branch = get_git_branch()
|
206
168
|
formatting_kwargs["branch"] = branch or "main"
|
207
169
|
|
208
170
|
formatting_kwargs["name"] = dir_name
|
@@ -373,105 +335,3 @@ def _save_deployment_to_prefect_file(
|
|
373
335
|
|
374
336
|
with prefect_file.open(mode="w") as f:
|
375
337
|
ryaml.dump(parsed_prefect_file_contents, f)
|
376
|
-
|
377
|
-
|
378
|
-
# Only allow half of the open file limit to be open at once to allow for other
|
379
|
-
# actors to open files.
|
380
|
-
OPEN_FILE_SEMAPHORE = LazySemaphore(lambda: math.floor(get_open_file_limit() * 0.5))
|
381
|
-
|
382
|
-
|
383
|
-
async def _find_flow_functions_in_file(filename: str) -> List[Dict]:
|
384
|
-
decorator_name = "flow"
|
385
|
-
decorator_module = "prefect"
|
386
|
-
decorated_functions = []
|
387
|
-
async with OPEN_FILE_SEMAPHORE:
|
388
|
-
try:
|
389
|
-
async with await anyio.open_file(filename) as f:
|
390
|
-
try:
|
391
|
-
tree = ast.parse(await f.read())
|
392
|
-
except SyntaxError:
|
393
|
-
if PREFECT_DEBUG_MODE:
|
394
|
-
get_logger().debug(
|
395
|
-
f"Could not parse {filename} as a Python file. Skipping."
|
396
|
-
)
|
397
|
-
return decorated_functions
|
398
|
-
except Exception as exc:
|
399
|
-
if PREFECT_DEBUG_MODE:
|
400
|
-
get_logger().debug(f"Could not open {filename}: {exc}. Skipping.")
|
401
|
-
return decorated_functions
|
402
|
-
|
403
|
-
for node in ast.walk(tree):
|
404
|
-
if isinstance(
|
405
|
-
node,
|
406
|
-
(
|
407
|
-
ast.FunctionDef,
|
408
|
-
ast.AsyncFunctionDef,
|
409
|
-
),
|
410
|
-
):
|
411
|
-
for decorator in node.decorator_list:
|
412
|
-
# handles @flow
|
413
|
-
is_name_match = (
|
414
|
-
isinstance(decorator, ast.Name) and decorator.id == decorator_name
|
415
|
-
)
|
416
|
-
# handles @flow()
|
417
|
-
is_func_name_match = (
|
418
|
-
isinstance(decorator, ast.Call)
|
419
|
-
and isinstance(decorator.func, ast.Name)
|
420
|
-
and decorator.func.id == decorator_name
|
421
|
-
)
|
422
|
-
# handles @prefect.flow
|
423
|
-
is_module_attribute_match = (
|
424
|
-
isinstance(decorator, ast.Attribute)
|
425
|
-
and isinstance(decorator.value, ast.Name)
|
426
|
-
and decorator.value.id == decorator_module
|
427
|
-
and decorator.attr == decorator_name
|
428
|
-
)
|
429
|
-
# handles @prefect.flow()
|
430
|
-
is_module_attribute_func_match = (
|
431
|
-
isinstance(decorator, ast.Call)
|
432
|
-
and isinstance(decorator.func, ast.Attribute)
|
433
|
-
and decorator.func.attr == decorator_name
|
434
|
-
and isinstance(decorator.func.value, ast.Name)
|
435
|
-
and decorator.func.value.id == decorator_module
|
436
|
-
)
|
437
|
-
if is_name_match or is_module_attribute_match:
|
438
|
-
decorated_functions.append(
|
439
|
-
{
|
440
|
-
"flow_name": node.name,
|
441
|
-
"function_name": node.name,
|
442
|
-
"filepath": str(filename),
|
443
|
-
}
|
444
|
-
)
|
445
|
-
if is_func_name_match or is_module_attribute_func_match:
|
446
|
-
name_kwarg_node = next(
|
447
|
-
(kw for kw in decorator.keywords if kw.arg == "name"), None
|
448
|
-
)
|
449
|
-
flow_name = (
|
450
|
-
name_kwarg_node.value.value
|
451
|
-
if isinstance(name_kwarg_node, ast.Constant)
|
452
|
-
else node.name
|
453
|
-
)
|
454
|
-
decorated_functions.append(
|
455
|
-
{
|
456
|
-
"flow_name": flow_name,
|
457
|
-
"function_name": node.name,
|
458
|
-
"filepath": str(filename),
|
459
|
-
}
|
460
|
-
)
|
461
|
-
return decorated_functions
|
462
|
-
|
463
|
-
|
464
|
-
async def _search_for_flow_functions(directory: str = ".") -> List[Dict]:
|
465
|
-
"""
|
466
|
-
Search for flow functions in the provided directory. If no directory is provided,
|
467
|
-
the current working directory is used.
|
468
|
-
|
469
|
-
Returns:
|
470
|
-
List[Dict]: the flow name, function name, and filepath of all flow functions found
|
471
|
-
"""
|
472
|
-
path = anyio.Path(directory)
|
473
|
-
coros = []
|
474
|
-
async for file in path.rglob("*.py"):
|
475
|
-
coros.append(_find_flow_functions_in_file(file))
|
476
|
-
|
477
|
-
return [fn for file_fns in await asyncio.gather(*coros) for fn in file_fns]
|
prefect/deployments/flow_runs.py
CHANGED
@@ -10,9 +10,12 @@ from prefect.client.schemas import FlowRun
|
|
10
10
|
from prefect.client.utilities import inject_client
|
11
11
|
from prefect.context import FlowRunContext, TaskRunContext
|
12
12
|
from prefect.logging import get_logger
|
13
|
-
from prefect.results import
|
13
|
+
from prefect.results import ResultRecordMetadata
|
14
14
|
from prefect.states import Pending, Scheduled
|
15
15
|
from prefect.tasks import Task
|
16
|
+
from prefect.telemetry.run_telemetry import (
|
17
|
+
LABELS_TRACEPARENT_KEY,
|
18
|
+
)
|
16
19
|
from prefect.utilities.asyncutils import sync_compatible
|
17
20
|
from prefect.utilities.slugify import slugify
|
18
21
|
|
@@ -22,7 +25,6 @@ if TYPE_CHECKING:
|
|
22
25
|
|
23
26
|
prefect.client.schemas.StateCreate.model_rebuild(
|
24
27
|
_types_namespace={
|
25
|
-
"BaseResult": BaseResult,
|
26
28
|
"ResultRecordMetadata": ResultRecordMetadata,
|
27
29
|
}
|
28
30
|
)
|
@@ -156,6 +158,13 @@ async def run_deployment(
|
|
156
158
|
else:
|
157
159
|
parent_task_run_id = None
|
158
160
|
|
161
|
+
if flow_run_ctx and flow_run_ctx.flow_run:
|
162
|
+
traceparent = flow_run_ctx.flow_run.labels.get(LABELS_TRACEPARENT_KEY)
|
163
|
+
else:
|
164
|
+
traceparent = None
|
165
|
+
|
166
|
+
trace_labels = {LABELS_TRACEPARENT_KEY: traceparent} if traceparent else {}
|
167
|
+
|
159
168
|
flow_run = await client.create_flow_run_from_deployment(
|
160
169
|
deployment.id,
|
161
170
|
parameters=parameters,
|
@@ -166,6 +175,7 @@ async def run_deployment(
|
|
166
175
|
parent_task_run_id=parent_task_run_id,
|
167
176
|
work_queue_name=work_queue_name,
|
168
177
|
job_variables=job_variables,
|
178
|
+
labels=trace_labels,
|
169
179
|
)
|
170
180
|
|
171
181
|
flow_run_id = flow_run.id
|
prefect/deployments/runner.py
CHANGED
@@ -33,7 +33,7 @@ import importlib
|
|
33
33
|
import tempfile
|
34
34
|
from datetime import datetime, timedelta
|
35
35
|
from pathlib import Path
|
36
|
-
from typing import TYPE_CHECKING, Any, Iterable, List, Optional, Union
|
36
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Iterable, List, Optional, Union
|
37
37
|
from uuid import UUID
|
38
38
|
|
39
39
|
from pydantic import (
|
@@ -41,6 +41,7 @@ from pydantic import (
|
|
41
41
|
ConfigDict,
|
42
42
|
Field,
|
43
43
|
PrivateAttr,
|
44
|
+
field_validator,
|
44
45
|
model_validator,
|
45
46
|
)
|
46
47
|
from rich.console import Console
|
@@ -129,7 +130,7 @@ class RunnerDeployment(BaseModel):
|
|
129
130
|
available settings.
|
130
131
|
"""
|
131
132
|
|
132
|
-
model_config = ConfigDict(arbitrary_types_allowed=True)
|
133
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True)
|
133
134
|
|
134
135
|
name: str = Field(..., description="The name of the deployment.")
|
135
136
|
flow_name: Optional[str] = Field(
|
@@ -220,6 +221,13 @@ class RunnerDeployment(BaseModel):
|
|
220
221
|
def entrypoint_type(self) -> EntrypointType:
|
221
222
|
return self._entrypoint_type
|
222
223
|
|
224
|
+
@field_validator("name", mode="before")
|
225
|
+
@classmethod
|
226
|
+
def validate_name(cls, value: str) -> str:
|
227
|
+
if value.endswith(".py"):
|
228
|
+
return Path(value).stem
|
229
|
+
return value
|
230
|
+
|
223
231
|
@model_validator(mode="after")
|
224
232
|
def validate_automation_names(self):
|
225
233
|
"""Ensure that each trigger has a name for its automation if none is provided."""
|
@@ -508,7 +516,7 @@ class RunnerDeployment(BaseModel):
|
|
508
516
|
concurrency_options = None
|
509
517
|
|
510
518
|
deployment = cls(
|
511
|
-
name=
|
519
|
+
name=name,
|
512
520
|
flow_name=flow.name,
|
513
521
|
schedules=constructed_schedules,
|
514
522
|
concurrency_limit=concurrency_limit,
|
@@ -50,6 +50,7 @@ async def agit_clone(
|
|
50
50
|
include_submodules: bool = False,
|
51
51
|
access_token: Optional[str] = None,
|
52
52
|
credentials: Optional["Block"] = None,
|
53
|
+
directories: Optional[list[str]] = None,
|
53
54
|
) -> dict[str, str]:
|
54
55
|
"""
|
55
56
|
Asynchronously clones a git repository into the current working directory.
|
@@ -81,6 +82,7 @@ async def agit_clone(
|
|
81
82
|
credentials=_credentials,
|
82
83
|
branch=branch,
|
83
84
|
include_submodules=include_submodules,
|
85
|
+
directories=directories,
|
84
86
|
)
|
85
87
|
|
86
88
|
await _pull_git_repository_with_retries(storage)
|
@@ -95,6 +97,7 @@ def git_clone(
|
|
95
97
|
include_submodules: bool = False,
|
96
98
|
access_token: Optional[str] = None,
|
97
99
|
credentials: Optional["Block"] = None,
|
100
|
+
directories: Optional[list[str]] = None,
|
98
101
|
) -> dict[str, str]:
|
99
102
|
"""
|
100
103
|
Clones a git repository into the current working directory.
|
@@ -107,6 +110,7 @@ def git_clone(
|
|
107
110
|
the repository will be cloned using the default git credentials
|
108
111
|
credentials: a GitHubCredentials, GitLabCredentials, or BitBucketCredentials block can be used to specify the
|
109
112
|
credentials to use for cloning the repository.
|
113
|
+
directories: Specify directories you want to be included (uses git sparse-checkout)
|
110
114
|
|
111
115
|
Returns:
|
112
116
|
dict: a dictionary containing a `directory` key of the new directory that was created
|
@@ -164,6 +168,14 @@ def git_clone(
|
|
164
168
|
- prefect.deployments.steps.git_clone:
|
165
169
|
repository: git@github.com:org/repo.git
|
166
170
|
```
|
171
|
+
|
172
|
+
Clone a repository using sparse-checkout (allows specific folders of the repository to be checked out)
|
173
|
+
```yaml
|
174
|
+
pull:
|
175
|
+
- prefect.deployments.steps.git_clone:
|
176
|
+
repository: https://github.com/org/repo.git
|
177
|
+
directories: ["dir_1", "dir_2", "prefect"]
|
178
|
+
```
|
167
179
|
"""
|
168
180
|
if access_token and credentials:
|
169
181
|
raise ValueError(
|
@@ -177,6 +189,7 @@ def git_clone(
|
|
177
189
|
credentials=_credentials,
|
178
190
|
branch=branch,
|
179
191
|
include_submodules=include_submodules,
|
192
|
+
directories=directories,
|
180
193
|
)
|
181
194
|
|
182
195
|
run_coro_as_sync(_pull_git_repository_with_retries(storage))
|
prefect/events/clients.py
CHANGED
@@ -16,6 +16,7 @@ from typing import (
|
|
16
16
|
cast,
|
17
17
|
)
|
18
18
|
from urllib.parse import urlparse
|
19
|
+
from urllib.request import proxy_bypass
|
19
20
|
from uuid import UUID
|
20
21
|
|
21
22
|
import orjson
|
@@ -95,6 +96,9 @@ class WebsocketProxyConnect(Connect):
|
|
95
96
|
u = urlparse(uri)
|
96
97
|
host = u.hostname
|
97
98
|
|
99
|
+
if not host:
|
100
|
+
raise ValueError(f"Invalid URI {uri}, no hostname found")
|
101
|
+
|
98
102
|
if u.scheme == "ws":
|
99
103
|
port = u.port or 80
|
100
104
|
proxy_url = os.environ.get("HTTP_PROXY")
|
@@ -107,7 +111,9 @@ class WebsocketProxyConnect(Connect):
|
|
107
111
|
"Unsupported scheme %s. Expected 'ws' or 'wss'. " % u.scheme
|
108
112
|
)
|
109
113
|
|
110
|
-
self._proxy =
|
114
|
+
self._proxy = (
|
115
|
+
Proxy.from_url(proxy_url) if proxy_url and not proxy_bypass(host) else None
|
116
|
+
)
|
111
117
|
self._host = host
|
112
118
|
self._port = port
|
113
119
|
|
prefect/events/schemas/events.py
CHANGED
@@ -2,6 +2,7 @@ import copy
|
|
2
2
|
from collections import defaultdict
|
3
3
|
from typing import (
|
4
4
|
Any,
|
5
|
+
ClassVar,
|
5
6
|
Dict,
|
6
7
|
Iterable,
|
7
8
|
List,
|
@@ -108,7 +109,7 @@ def _validate_related_resources(value) -> List:
|
|
108
109
|
class Event(PrefectBaseModel):
|
109
110
|
"""The client-side view of an event that has happened to a Resource"""
|
110
111
|
|
111
|
-
model_config = ConfigDict(extra="ignore")
|
112
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(extra="ignore")
|
112
113
|
|
113
114
|
occurred: DateTime = Field(
|
114
115
|
default_factory=lambda: DateTime.now("UTC"),
|
@@ -177,7 +178,7 @@ class ReceivedEvent(Event):
|
|
177
178
|
"""The server-side view of an event that has happened to a Resource after it has
|
178
179
|
been received by the server"""
|
179
180
|
|
180
|
-
model_config = ConfigDict(from_attributes=True)
|
181
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(from_attributes=True)
|
181
182
|
|
182
183
|
received: DateTime = Field(
|
183
184
|
...,
|
prefect/flow_engine.py
CHANGED
@@ -55,7 +55,6 @@ from prefect.logging.loggers import (
|
|
55
55
|
patch_print,
|
56
56
|
)
|
57
57
|
from prefect.results import (
|
58
|
-
BaseResult,
|
59
58
|
ResultStore,
|
60
59
|
get_result_store,
|
61
60
|
should_persist_result,
|
@@ -307,10 +306,7 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
307
306
|
if self._return_value is not NotSet and not isinstance(
|
308
307
|
self._return_value, State
|
309
308
|
):
|
310
|
-
|
311
|
-
_result = self._return_value.get()
|
312
|
-
else:
|
313
|
-
_result = self._return_value
|
309
|
+
_result = self._return_value
|
314
310
|
|
315
311
|
if asyncio.iscoroutine(_result):
|
316
312
|
# getting the value for a BaseResult may return an awaitable
|
@@ -490,24 +486,13 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
490
486
|
):
|
491
487
|
return subflow_run
|
492
488
|
|
493
|
-
|
489
|
+
return client.create_flow_run(
|
494
490
|
flow=self.flow,
|
495
491
|
parameters=self.flow.serialize_parameters(parameters),
|
496
492
|
state=Pending(),
|
497
493
|
parent_task_run_id=getattr(parent_task_run, "id", None),
|
498
494
|
tags=TagsContext.get().current_tags,
|
499
495
|
)
|
500
|
-
if flow_run_ctx:
|
501
|
-
parent_logger = get_run_logger(flow_run_ctx)
|
502
|
-
parent_logger.info(
|
503
|
-
f"Created subflow run {flow_run.name!r} for flow {self.flow.name!r}"
|
504
|
-
)
|
505
|
-
else:
|
506
|
-
self.logger.info(
|
507
|
-
f"Created flow run {flow_run.name!r} for flow {self.flow.name!r}"
|
508
|
-
)
|
509
|
-
|
510
|
-
return flow_run
|
511
496
|
|
512
497
|
def call_hooks(self, state: Optional[State] = None):
|
513
498
|
if state is None:
|
@@ -606,6 +591,7 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
606
591
|
stack.enter_context(ConcurrencyContext())
|
607
592
|
|
608
593
|
# set the logger to the flow run logger
|
594
|
+
|
609
595
|
self.logger = flow_run_logger(flow_run=self.flow_run, flow=self.flow)
|
610
596
|
|
611
597
|
# update the flow run name if necessary
|
@@ -616,12 +602,32 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
616
602
|
self.client.set_flow_run_name(
|
617
603
|
flow_run_id=self.flow_run.id, name=flow_run_name
|
618
604
|
)
|
605
|
+
|
619
606
|
self.logger.extra["flow_run_name"] = flow_run_name
|
620
607
|
self.logger.debug(
|
621
608
|
f"Renamed flow run {self.flow_run.name!r} to {flow_run_name!r}"
|
622
609
|
)
|
623
610
|
self.flow_run.name = flow_run_name
|
624
611
|
self._flow_run_name_set = True
|
612
|
+
|
613
|
+
self._telemetry.update_run_name(name=flow_run_name)
|
614
|
+
|
615
|
+
if self.flow_run.parent_task_run_id:
|
616
|
+
_logger = get_run_logger(FlowRunContext.get())
|
617
|
+
run_type = "subflow"
|
618
|
+
else:
|
619
|
+
_logger = self.logger
|
620
|
+
run_type = "flow"
|
621
|
+
|
622
|
+
_logger.info(
|
623
|
+
f"Beginning {run_type} run {self.flow_run.name!r} for flow {self.flow.name!r}"
|
624
|
+
)
|
625
|
+
|
626
|
+
if flow_run_url := url_for(self.flow_run):
|
627
|
+
self.logger.info(
|
628
|
+
f"View at {flow_run_url}", extra={"send_to_api": False}
|
629
|
+
)
|
630
|
+
|
625
631
|
yield
|
626
632
|
|
627
633
|
@contextmanager
|
@@ -635,12 +641,6 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
635
641
|
|
636
642
|
if not self.flow_run:
|
637
643
|
self.flow_run = self.create_flow_run(self.client)
|
638
|
-
flow_run_url = url_for(self.flow_run)
|
639
|
-
|
640
|
-
if flow_run_url:
|
641
|
-
self.logger.info(
|
642
|
-
f"View at {flow_run_url}", extra={"send_to_api": False}
|
643
|
-
)
|
644
644
|
else:
|
645
645
|
# Update the empirical policy to match the flow if it is not set
|
646
646
|
if self.flow_run.empirical_policy.retry_delay is None:
|
@@ -658,7 +658,6 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
658
658
|
)
|
659
659
|
|
660
660
|
self._telemetry.start_span(
|
661
|
-
name=self.flow.name,
|
662
661
|
run=self.flow_run,
|
663
662
|
client=self.client,
|
664
663
|
parameters=self.parameters,
|
@@ -705,9 +704,11 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
705
704
|
@contextmanager
|
706
705
|
def start(self) -> Generator[None, None, None]:
|
707
706
|
with self.initialize_run():
|
708
|
-
with
|
709
|
-
self._telemetry.span
|
710
|
-
|
707
|
+
with (
|
708
|
+
trace.use_span(self._telemetry.span)
|
709
|
+
if self._telemetry.span
|
710
|
+
else nullcontext()
|
711
|
+
):
|
711
712
|
self.begin_run()
|
712
713
|
|
713
714
|
if self.state.is_running():
|
@@ -870,10 +871,7 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
870
871
|
if self._return_value is not NotSet and not isinstance(
|
871
872
|
self._return_value, State
|
872
873
|
):
|
873
|
-
|
874
|
-
_result = self._return_value.get()
|
875
|
-
else:
|
876
|
-
_result = self._return_value
|
874
|
+
_result = self._return_value
|
877
875
|
|
878
876
|
if asyncio.iscoroutine(_result):
|
879
877
|
# getting the value for a BaseResult may return an awaitable
|
@@ -1052,24 +1050,13 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
1052
1050
|
):
|
1053
1051
|
return subflow_run
|
1054
1052
|
|
1055
|
-
|
1053
|
+
return await client.create_flow_run(
|
1056
1054
|
flow=self.flow,
|
1057
1055
|
parameters=self.flow.serialize_parameters(parameters),
|
1058
1056
|
state=Pending(),
|
1059
1057
|
parent_task_run_id=getattr(parent_task_run, "id", None),
|
1060
1058
|
tags=TagsContext.get().current_tags,
|
1061
1059
|
)
|
1062
|
-
if flow_run_ctx:
|
1063
|
-
parent_logger = get_run_logger(flow_run_ctx)
|
1064
|
-
parent_logger.info(
|
1065
|
-
f"Created subflow run {flow_run.name!r} for flow {self.flow.name!r}"
|
1066
|
-
)
|
1067
|
-
else:
|
1068
|
-
self.logger.info(
|
1069
|
-
f"Created flow run {flow_run.name!r} for flow {self.flow.name!r}"
|
1070
|
-
)
|
1071
|
-
|
1072
|
-
return flow_run
|
1073
1060
|
|
1074
1061
|
async def call_hooks(self, state: Optional[State] = None):
|
1075
1062
|
if state is None:
|
@@ -1171,6 +1158,7 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
1171
1158
|
self.logger = flow_run_logger(flow_run=self.flow_run, flow=self.flow)
|
1172
1159
|
|
1173
1160
|
# update the flow run name if necessary
|
1161
|
+
|
1174
1162
|
if not self._flow_run_name_set and self.flow.flow_run_name:
|
1175
1163
|
flow_run_name = resolve_custom_flow_run_name(
|
1176
1164
|
flow=self.flow, parameters=self.parameters
|
@@ -1184,6 +1172,24 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
1184
1172
|
)
|
1185
1173
|
self.flow_run.name = flow_run_name
|
1186
1174
|
self._flow_run_name_set = True
|
1175
|
+
|
1176
|
+
self._telemetry.update_run_name(name=flow_run_name)
|
1177
|
+
if self.flow_run.parent_task_run_id:
|
1178
|
+
_logger = get_run_logger(FlowRunContext.get())
|
1179
|
+
run_type = "subflow"
|
1180
|
+
else:
|
1181
|
+
_logger = self.logger
|
1182
|
+
run_type = "flow"
|
1183
|
+
|
1184
|
+
_logger.info(
|
1185
|
+
f"Beginning {run_type} run {self.flow_run.name!r} for flow {self.flow.name!r}"
|
1186
|
+
)
|
1187
|
+
|
1188
|
+
if flow_run_url := url_for(self.flow_run):
|
1189
|
+
self.logger.info(
|
1190
|
+
f"View at {flow_run_url}", extra={"send_to_api": False}
|
1191
|
+
)
|
1192
|
+
|
1187
1193
|
yield
|
1188
1194
|
|
1189
1195
|
@asynccontextmanager
|
@@ -1220,7 +1226,6 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
1220
1226
|
)
|
1221
1227
|
|
1222
1228
|
await self._telemetry.async_start_span(
|
1223
|
-
name=self.flow.name,
|
1224
1229
|
run=self.flow_run,
|
1225
1230
|
client=self.client,
|
1226
1231
|
parameters=self.parameters,
|
@@ -1267,9 +1272,11 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
|
|
1267
1272
|
@asynccontextmanager
|
1268
1273
|
async def start(self) -> AsyncGenerator[None, None]:
|
1269
1274
|
async with self.initialize_run():
|
1270
|
-
with
|
1271
|
-
self._telemetry.span
|
1272
|
-
|
1275
|
+
with (
|
1276
|
+
trace.use_span(self._telemetry.span)
|
1277
|
+
if self._telemetry.span
|
1278
|
+
else nullcontext()
|
1279
|
+
):
|
1273
1280
|
await self.begin_run()
|
1274
1281
|
|
1275
1282
|
if self.state.is_running():
|
prefect/input/run_input.py
CHANGED
@@ -64,6 +64,7 @@ from inspect import isclass
|
|
64
64
|
from typing import (
|
65
65
|
TYPE_CHECKING,
|
66
66
|
Any,
|
67
|
+
ClassVar,
|
67
68
|
Dict,
|
68
69
|
Generic,
|
69
70
|
Literal,
|
@@ -144,7 +145,7 @@ class RunInputMetadata(pydantic.BaseModel):
|
|
144
145
|
|
145
146
|
|
146
147
|
class RunInput(pydantic.BaseModel):
|
147
|
-
model_config = ConfigDict(extra="forbid")
|
148
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid")
|
148
149
|
|
149
150
|
_description: Optional[str] = pydantic.PrivateAttr(default=None)
|
150
151
|
_metadata: RunInputMetadata = pydantic.PrivateAttr()
|
prefect/main.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# Import user-facing API
|
2
2
|
from typing import Any
|
3
|
-
|
4
3
|
from prefect.deployments import deploy
|
5
4
|
from prefect.states import State
|
6
5
|
from prefect.logging import get_run_logger
|
@@ -9,7 +8,7 @@ from prefect.transactions import Transaction
|
|
9
8
|
from prefect.tasks import task, Task
|
10
9
|
from prefect.context import tags
|
11
10
|
from prefect.utilities.annotations import unmapped, allow_failure
|
12
|
-
from prefect.results import
|
11
|
+
from prefect.results import ResultRecordMetadata
|
13
12
|
from prefect.flow_runs import pause_flow_run, resume_flow_run, suspend_flow_run
|
14
13
|
from prefect.client.orchestration import get_client
|
15
14
|
from prefect.client.cloud import get_cloud_client
|
@@ -30,7 +29,6 @@ import prefect.client.schemas
|
|
30
29
|
_types: dict[str, Any] = dict(
|
31
30
|
Task=Task,
|
32
31
|
Flow=Flow,
|
33
|
-
BaseResult=BaseResult,
|
34
32
|
ResultRecordMetadata=ResultRecordMetadata,
|
35
33
|
)
|
36
34
|
prefect.context.FlowRunContext.model_rebuild(_types_namespace=_types)
|