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.
Files changed (102) hide show
  1. prefect/_experimental/lineage.py +7 -8
  2. prefect/_internal/_logging.py +15 -3
  3. prefect/_internal/compatibility/async_dispatch.py +22 -16
  4. prefect/_internal/compatibility/deprecated.py +42 -18
  5. prefect/_internal/compatibility/migration.py +2 -2
  6. prefect/_internal/concurrency/inspection.py +12 -14
  7. prefect/_internal/concurrency/primitives.py +2 -2
  8. prefect/_internal/concurrency/services.py +154 -80
  9. prefect/_internal/concurrency/waiters.py +13 -9
  10. prefect/_internal/pydantic/annotations/pendulum.py +7 -7
  11. prefect/_internal/pytz.py +4 -3
  12. prefect/_internal/retries.py +10 -5
  13. prefect/_internal/schemas/bases.py +19 -10
  14. prefect/_internal/schemas/validators.py +227 -388
  15. prefect/_version.py +3 -3
  16. prefect/blocks/core.py +3 -3
  17. prefect/client/{orchestration.py → orchestration/__init__.py} +38 -701
  18. prefect/client/orchestration/_artifacts/__init__.py +0 -0
  19. prefect/client/orchestration/_artifacts/client.py +239 -0
  20. prefect/client/orchestration/_concurrency_limits/__init__.py +0 -0
  21. prefect/client/orchestration/_concurrency_limits/client.py +762 -0
  22. prefect/client/orchestration/_logs/__init__.py +0 -0
  23. prefect/client/orchestration/_logs/client.py +95 -0
  24. prefect/client/orchestration/_variables/__init__.py +0 -0
  25. prefect/client/orchestration/_variables/client.py +157 -0
  26. prefect/client/orchestration/base.py +46 -0
  27. prefect/client/orchestration/routes.py +145 -0
  28. prefect/client/schemas/actions.py +2 -2
  29. prefect/client/schemas/filters.py +5 -0
  30. prefect/client/schemas/objects.py +3 -10
  31. prefect/client/schemas/schedules.py +22 -10
  32. prefect/concurrency/_asyncio.py +87 -0
  33. prefect/concurrency/{events.py → _events.py} +10 -10
  34. prefect/concurrency/asyncio.py +20 -104
  35. prefect/concurrency/context.py +6 -4
  36. prefect/concurrency/services.py +26 -74
  37. prefect/concurrency/sync.py +23 -44
  38. prefect/concurrency/v1/_asyncio.py +63 -0
  39. prefect/concurrency/v1/{events.py → _events.py} +13 -15
  40. prefect/concurrency/v1/asyncio.py +27 -80
  41. prefect/concurrency/v1/context.py +6 -4
  42. prefect/concurrency/v1/services.py +33 -79
  43. prefect/concurrency/v1/sync.py +18 -37
  44. prefect/context.py +51 -44
  45. prefect/deployments/base.py +4 -144
  46. prefect/deployments/flow_runs.py +12 -2
  47. prefect/deployments/runner.py +11 -3
  48. prefect/deployments/steps/pull.py +13 -0
  49. prefect/events/clients.py +7 -1
  50. prefect/events/schemas/events.py +3 -2
  51. prefect/flow_engine.py +54 -47
  52. prefect/input/run_input.py +2 -1
  53. prefect/main.py +1 -3
  54. prefect/results.py +2 -307
  55. prefect/runner/storage.py +87 -21
  56. prefect/serializers.py +32 -25
  57. prefect/settings/legacy.py +4 -4
  58. prefect/settings/models/api.py +3 -3
  59. prefect/settings/models/cli.py +3 -3
  60. prefect/settings/models/client.py +5 -3
  61. prefect/settings/models/cloud.py +3 -3
  62. prefect/settings/models/deployments.py +3 -3
  63. prefect/settings/models/experiments.py +4 -2
  64. prefect/settings/models/flows.py +3 -3
  65. prefect/settings/models/internal.py +4 -2
  66. prefect/settings/models/logging.py +4 -3
  67. prefect/settings/models/results.py +3 -3
  68. prefect/settings/models/root.py +3 -2
  69. prefect/settings/models/runner.py +4 -4
  70. prefect/settings/models/server/api.py +3 -3
  71. prefect/settings/models/server/database.py +11 -4
  72. prefect/settings/models/server/deployments.py +6 -2
  73. prefect/settings/models/server/ephemeral.py +4 -2
  74. prefect/settings/models/server/events.py +3 -2
  75. prefect/settings/models/server/flow_run_graph.py +6 -2
  76. prefect/settings/models/server/root.py +3 -3
  77. prefect/settings/models/server/services.py +26 -11
  78. prefect/settings/models/server/tasks.py +6 -3
  79. prefect/settings/models/server/ui.py +3 -3
  80. prefect/settings/models/tasks.py +5 -5
  81. prefect/settings/models/testing.py +3 -3
  82. prefect/settings/models/worker.py +5 -3
  83. prefect/settings/profiles.py +15 -2
  84. prefect/states.py +4 -7
  85. prefect/task_engine.py +54 -75
  86. prefect/tasks.py +84 -32
  87. prefect/telemetry/run_telemetry.py +13 -8
  88. prefect/transactions.py +4 -15
  89. prefect/utilities/_git.py +34 -0
  90. prefect/utilities/asyncutils.py +1 -1
  91. prefect/utilities/engine.py +3 -19
  92. prefect/utilities/generics.py +18 -0
  93. {prefect_client-3.1.10.dist-info → prefect_client-3.1.11.dist-info}/METADATA +1 -1
  94. {prefect_client-3.1.10.dist-info → prefect_client-3.1.11.dist-info}/RECORD +97 -88
  95. prefect/records/__init__.py +0 -1
  96. prefect/records/base.py +0 -235
  97. prefect/records/filesystem.py +0 -213
  98. prefect/records/memory.py +0 -184
  99. prefect/records/result_store.py +0 -70
  100. {prefect_client-3.1.10.dist-info → prefect_client-3.1.11.dist-info}/LICENSE +0 -0
  101. {prefect_client-3.1.10.dist-info → prefect_client-3.1.11.dist-info}/WHEEL +0 -0
  102. {prefect_client-3.1.10.dist-info → prefect_client-3.1.11.dist-info}/top_level.txt +0 -0
@@ -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.logging import get_logger
26
- from prefect.settings import PREFECT_DEBUG_MODE
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 = _get_git_remote_origin_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 = _get_git_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]
@@ -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 BaseResult, ResultRecordMetadata
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
@@ -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=Path(name).stem,
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 = Proxy.from_url(proxy_url) if proxy_url else None
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
 
@@ -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
- if isinstance(self._return_value, BaseResult):
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
- flow_run = client.create_flow_run(
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 trace.use_span(
709
- self._telemetry.span
710
- ) if self._telemetry.span else nullcontext():
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
- if isinstance(self._return_value, BaseResult):
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
- flow_run = await client.create_flow_run(
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 trace.use_span(
1271
- self._telemetry.span
1272
- ) if self._telemetry.span else nullcontext():
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():
@@ -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 BaseResult, ResultRecordMetadata
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)