prefect-client 3.4.6.dev1__py3-none-any.whl → 3.4.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. prefect/AGENTS.md +28 -0
  2. prefect/_build_info.py +3 -3
  3. prefect/_internal/websockets.py +109 -0
  4. prefect/artifacts.py +51 -2
  5. prefect/assets/core.py +2 -2
  6. prefect/blocks/core.py +82 -11
  7. prefect/client/cloud.py +11 -1
  8. prefect/client/orchestration/__init__.py +21 -15
  9. prefect/client/orchestration/_deployments/client.py +139 -4
  10. prefect/client/orchestration/_flows/client.py +4 -4
  11. prefect/client/schemas/__init__.py +5 -2
  12. prefect/client/schemas/actions.py +1 -0
  13. prefect/client/schemas/filters.py +3 -0
  14. prefect/client/schemas/objects.py +27 -10
  15. prefect/context.py +16 -7
  16. prefect/events/clients.py +2 -76
  17. prefect/events/schemas/automations.py +4 -0
  18. prefect/events/schemas/labelling.py +2 -0
  19. prefect/flow_engine.py +6 -3
  20. prefect/flows.py +64 -45
  21. prefect/futures.py +25 -4
  22. prefect/locking/filesystem.py +1 -1
  23. prefect/logging/clients.py +347 -0
  24. prefect/runner/runner.py +1 -1
  25. prefect/runner/submit.py +10 -4
  26. prefect/serializers.py +8 -3
  27. prefect/server/api/logs.py +64 -9
  28. prefect/server/api/server.py +2 -0
  29. prefect/server/api/templates.py +8 -2
  30. prefect/settings/context.py +17 -14
  31. prefect/settings/models/server/logs.py +28 -0
  32. prefect/settings/models/server/root.py +5 -0
  33. prefect/settings/models/server/services.py +26 -0
  34. prefect/task_engine.py +73 -43
  35. prefect/task_runners.py +10 -10
  36. prefect/tasks.py +52 -9
  37. prefect/types/__init__.py +2 -0
  38. prefect/types/names.py +50 -0
  39. prefect/utilities/_ast.py +2 -2
  40. prefect/utilities/callables.py +1 -1
  41. prefect/utilities/collections.py +6 -6
  42. prefect/utilities/engine.py +67 -72
  43. prefect/utilities/pydantic.py +19 -1
  44. prefect/workers/base.py +2 -0
  45. {prefect_client-3.4.6.dev1.dist-info → prefect_client-3.4.7.dist-info}/METADATA +1 -1
  46. {prefect_client-3.4.6.dev1.dist-info → prefect_client-3.4.7.dist-info}/RECORD +48 -44
  47. {prefect_client-3.4.6.dev1.dist-info → prefect_client-3.4.7.dist-info}/WHEEL +0 -0
  48. {prefect_client-3.4.6.dev1.dist-info → prefect_client-3.4.7.dist-info}/licenses/LICENSE +0 -0
@@ -6,6 +6,7 @@ from uuid import UUID
6
6
 
7
7
  from httpx import HTTPStatusError, RequestError
8
8
 
9
+ from prefect._internal.compatibility.deprecated import deprecated_callable
9
10
  from prefect.client.orchestration.base import BaseAsyncClient, BaseClient
10
11
  from prefect.exceptions import ObjectNotFound
11
12
 
@@ -167,7 +168,7 @@ class DeploymentClient(BaseClient):
167
168
 
168
169
  return UUID(deployment_id)
169
170
 
170
- def set_deployment_paused_state(self, deployment_id: UUID, paused: bool) -> None:
171
+ def _set_deployment_paused_state(self, deployment_id: UUID, paused: bool) -> None:
171
172
  self.request(
172
173
  "PATCH",
173
174
  "/deployments/{id}",
@@ -175,6 +176,72 @@ class DeploymentClient(BaseClient):
175
176
  json={"paused": paused},
176
177
  )
177
178
 
179
+ @deprecated_callable(
180
+ start_date="Jun 2025",
181
+ help="Use pause_deployment or resume_deployment instead.",
182
+ )
183
+ def set_deployment_paused_state(self, deployment_id: UUID, paused: bool) -> None:
184
+ """
185
+ DEPRECATED: Use pause_deployment or resume_deployment instead.
186
+
187
+ Set the paused state of a deployment.
188
+
189
+ Args:
190
+ deployment_id: the deployment ID to update
191
+ paused: whether the deployment should be paused
192
+ """
193
+ self._set_deployment_paused_state(deployment_id, paused)
194
+
195
+ def pause_deployment(self, deployment_id: Union[UUID, str]) -> None:
196
+ """
197
+ Pause a deployment by ID.
198
+
199
+ Args:
200
+ deployment_id: The deployment ID of interest (can be a UUID or a string).
201
+
202
+ Raises:
203
+ ObjectNotFound: If request returns 404
204
+ RequestError: If request fails
205
+ """
206
+ if not isinstance(deployment_id, UUID):
207
+ try:
208
+ deployment_id = UUID(deployment_id)
209
+ except ValueError:
210
+ raise ValueError(f"Invalid deployment ID: {deployment_id}")
211
+
212
+ try:
213
+ self._set_deployment_paused_state(deployment_id, paused=True)
214
+ except HTTPStatusError as e:
215
+ if e.response.status_code == 404:
216
+ raise ObjectNotFound(http_exc=e) from e
217
+ else:
218
+ raise
219
+
220
+ def resume_deployment(self, deployment_id: Union[UUID, str]) -> None:
221
+ """
222
+ Resume (unpause) a deployment by ID.
223
+
224
+ Args:
225
+ deployment_id: The deployment ID of interest (can be a UUID or a string).
226
+
227
+ Raises:
228
+ ObjectNotFound: If request returns 404
229
+ RequestError: If request fails
230
+ """
231
+ if not isinstance(deployment_id, UUID):
232
+ try:
233
+ deployment_id = UUID(deployment_id)
234
+ except ValueError:
235
+ raise ValueError(f"Invalid deployment ID: {deployment_id}")
236
+
237
+ try:
238
+ self._set_deployment_paused_state(deployment_id, paused=False)
239
+ except HTTPStatusError as e:
240
+ if e.response.status_code == 404:
241
+ raise ObjectNotFound(http_exc=e) from e
242
+ else:
243
+ raise
244
+
178
245
  def update_deployment(
179
246
  self,
180
247
  deployment_id: UUID,
@@ -230,7 +297,7 @@ class DeploymentClient(BaseClient):
230
297
  deployment_id: the deployment ID of interest
231
298
 
232
299
  Returns:
233
- a [Deployment model][prefect.client.schemas.objects.Deployment] representation of the deployment
300
+ a Deployment model representation of the deployment
234
301
  """
235
302
 
236
303
  from prefect.client.schemas.responses import DeploymentResponse
@@ -760,7 +827,7 @@ class DeploymentAsyncClient(BaseAsyncClient):
760
827
 
761
828
  return UUID(deployment_id)
762
829
 
763
- async def set_deployment_paused_state(
830
+ async def _set_deployment_paused_state(
764
831
  self, deployment_id: UUID, paused: bool
765
832
  ) -> None:
766
833
  await self.request(
@@ -770,6 +837,74 @@ class DeploymentAsyncClient(BaseAsyncClient):
770
837
  json={"paused": paused},
771
838
  )
772
839
 
840
+ @deprecated_callable(
841
+ start_date="Jun 2025",
842
+ help="Use pause_deployment or resume_deployment instead.",
843
+ )
844
+ async def set_deployment_paused_state(
845
+ self, deployment_id: UUID, paused: bool
846
+ ) -> None:
847
+ """
848
+ DEPRECATED: Use pause_deployment or resume_deployment instead.
849
+
850
+ Set the paused state of a deployment.
851
+
852
+ Args:
853
+ deployment_id: the deployment ID to update
854
+ paused: whether the deployment should be paused
855
+ """
856
+ await self._set_deployment_paused_state(deployment_id, paused)
857
+
858
+ async def pause_deployment(self, deployment_id: Union[UUID, str]) -> None:
859
+ """
860
+ Pause a deployment by ID.
861
+
862
+ Args:
863
+ deployment_id: The deployment ID of interest (can be a UUID or a string).
864
+
865
+ Raises:
866
+ ObjectNotFound: If request returns 404
867
+ RequestError: If request fails
868
+ """
869
+ if not isinstance(deployment_id, UUID):
870
+ try:
871
+ deployment_id = UUID(deployment_id)
872
+ except ValueError:
873
+ raise ValueError(f"Invalid deployment ID: {deployment_id}")
874
+
875
+ try:
876
+ await self._set_deployment_paused_state(deployment_id, paused=True)
877
+ except HTTPStatusError as e:
878
+ if e.response.status_code == 404:
879
+ raise ObjectNotFound(http_exc=e) from e
880
+ else:
881
+ raise
882
+
883
+ async def resume_deployment(self, deployment_id: Union[UUID, str]) -> None:
884
+ """
885
+ Resume (unpause) a deployment by ID.
886
+
887
+ Args:
888
+ deployment_id: The deployment ID of interest (can be a UUID or a string).
889
+
890
+ Raises:
891
+ ObjectNotFound: If request returns 404
892
+ RequestError: If request fails
893
+ """
894
+ if not isinstance(deployment_id, UUID):
895
+ try:
896
+ deployment_id = UUID(deployment_id)
897
+ except ValueError:
898
+ raise ValueError(f"Invalid deployment ID: {deployment_id}")
899
+
900
+ try:
901
+ await self._set_deployment_paused_state(deployment_id, paused=False)
902
+ except HTTPStatusError as e:
903
+ if e.response.status_code == 404:
904
+ raise ObjectNotFound(http_exc=e) from e
905
+ else:
906
+ raise
907
+
773
908
  async def update_deployment(
774
909
  self,
775
910
  deployment_id: UUID,
@@ -826,7 +961,7 @@ class DeploymentAsyncClient(BaseAsyncClient):
826
961
  deployment_id: the deployment ID of interest
827
962
 
828
963
  Returns:
829
- a [Deployment model][prefect.client.schemas.objects.Deployment] representation of the deployment
964
+ a Deployment model representation of the deployment
830
965
  """
831
966
 
832
967
  from prefect.client.schemas.responses import DeploymentResponse
@@ -31,7 +31,7 @@ class FlowClient(BaseClient):
31
31
  Create a flow in the Prefect API.
32
32
 
33
33
  Args:
34
- flow: a [Flow][prefect.flows.Flow] object
34
+ flow: a `Flow` object
35
35
 
36
36
  Raises:
37
37
  httpx.RequestError: if a flow was not created for any reason
@@ -78,7 +78,7 @@ class FlowClient(BaseClient):
78
78
  flow_id: the flow ID of interest
79
79
 
80
80
  Returns:
81
- a [Flow model][prefect.client.schemas.objects.Flow] representation of the flow
81
+ a Flow model representation of the flow
82
82
  """
83
83
  response = self.request("GET", "/flows/{id}", path_params={"id": flow_id})
84
84
  from prefect.client.schemas.objects import Flow
@@ -190,7 +190,7 @@ class FlowAsyncClient(BaseAsyncClient):
190
190
  Create a flow in the Prefect API.
191
191
 
192
192
  Args:
193
- flow: a [Flow][prefect.flows.Flow] object
193
+ flow: a `Flow` object
194
194
 
195
195
  Raises:
196
196
  httpx.RequestError: if a flow was not created for any reason
@@ -237,7 +237,7 @@ class FlowAsyncClient(BaseAsyncClient):
237
237
  flow_id: the flow ID of interest
238
238
 
239
239
  Returns:
240
- a [Flow model][prefect.client.schemas.objects.Flow] representation of the flow
240
+ a Flow model representation of the flow
241
241
  """
242
242
  response = await self.request("GET", "/flows/{id}", path_params={"id": flow_id})
243
243
  from prefect.client.schemas.objects import Flow
@@ -11,11 +11,12 @@ if TYPE_CHECKING:
11
11
  BlockType,
12
12
  FlowRun,
13
13
  FlowRunPolicy,
14
+ FlowRunResult,
14
15
  State,
15
16
  StateDetails,
16
17
  StateType,
17
18
  TaskRun,
18
- TaskRunInput,
19
+ RunInput,
19
20
  TaskRunPolicy,
20
21
  TaskRunResult,
21
22
  Workspace,
@@ -36,6 +37,7 @@ _public_api = {
36
37
  "DEFAULT_BLOCK_SCHEMA_VERSION": (__package__, ".objects"),
37
38
  "FlowRun": (__package__, ".objects"),
38
39
  "FlowRunPolicy": (__package__, ".objects"),
40
+ "FlowRunResult": (__package__, ".objects"),
39
41
  "OrchestrationResult": (__package__, ".responses"),
40
42
  "SetStateStatus": (__package__, ".responses"),
41
43
  "State": (__package__, ".objects"),
@@ -60,6 +62,7 @@ __all__ = [
60
62
  "DEFAULT_BLOCK_SCHEMA_VERSION",
61
63
  "FlowRun",
62
64
  "FlowRunPolicy",
65
+ "FlowRunResult",
63
66
  "OrchestrationResult",
64
67
  "SetStateStatus",
65
68
  "State",
@@ -70,7 +73,7 @@ __all__ = [
70
73
  "StateRejectDetails",
71
74
  "StateType",
72
75
  "TaskRun",
73
- "TaskRunInput",
76
+ "RunInput",
74
77
  "TaskRunPolicy",
75
78
  "TaskRunResult",
76
79
  "Workspace",
@@ -443,6 +443,7 @@ class TaskRunCreate(ActionBaseModel):
443
443
  list[
444
444
  Union[
445
445
  objects.TaskRunResult,
446
+ objects.FlowRunResult,
446
447
  objects.Parameter,
447
448
  objects.Constant,
448
449
  ]
@@ -462,6 +462,9 @@ class DeploymentFilterId(PrefectBaseModel):
462
462
  any_: Optional[List[UUID]] = Field(
463
463
  default=None, description="A list of deployment ids to include"
464
464
  )
465
+ not_any_: Optional[List[UUID]] = Field(
466
+ default=None, description="A list of deployment ids to exclude"
467
+ )
465
468
 
466
469
 
467
470
  class DeploymentFilterName(PrefectBaseModel):
@@ -81,6 +81,11 @@ DEFAULT_BLOCK_SCHEMA_VERSION: Literal["non-versioned"] = "non-versioned"
81
81
  DEFAULT_AGENT_WORK_POOL_NAME: Literal["default-agent-pool"] = "default-agent-pool"
82
82
 
83
83
 
84
+ class RunType(AutoEnum):
85
+ FLOW_RUN = "flow_run"
86
+ TASK_RUN = "task_run"
87
+
88
+
84
89
  class StateType(AutoEnum):
85
90
  """Enumeration of state types."""
86
91
 
@@ -164,7 +169,6 @@ class ConcurrencyLimitConfig(PrefectBaseModel):
164
169
  class StateDetails(PrefectBaseModel):
165
170
  flow_run_id: Optional[UUID] = None
166
171
  task_run_id: Optional[UUID] = None
167
- # for task runs that represent subflows, the subflow's run ID
168
172
  child_flow_run_id: Optional[UUID] = None
169
173
  scheduled_time: Optional[DateTime] = None
170
174
  cache_key: Optional[str] = None
@@ -182,6 +186,16 @@ class StateDetails(PrefectBaseModel):
182
186
  # Captures the trace_id and span_id of the span where this state was created
183
187
  traceparent: Optional[str] = None
184
188
 
189
+ def to_run_result(
190
+ self, run_type: RunType
191
+ ) -> Optional[Union[FlowRunResult, TaskRunResult]]:
192
+ if run_type == run_type.FLOW_RUN and self.flow_run_id:
193
+ return FlowRunResult(id=self.flow_run_id)
194
+ elif run_type == run_type.TASK_RUN and self.task_run_id:
195
+ return TaskRunResult(id=self.task_run_id)
196
+ else:
197
+ return None
198
+
185
199
 
186
200
  def data_discriminator(x: Any) -> str:
187
201
  if isinstance(x, dict) and "storage_key" in x:
@@ -279,10 +293,6 @@ class State(TimeSeriesBaseModel, ObjectBaseModel, Generic[R]):
279
293
  if the state is of type `FAILED` and the underlying data is an exception. When flow
280
294
  was run in a different memory space (using `run_deployment`), this will only raise
281
295
  if `fetch` is `True`.
282
- fetch: a boolean specifying whether to resolve references to persisted
283
- results into data. For synchronous users, this defaults to `True`.
284
- For asynchronous users, this defaults to `False` for backwards
285
- compatibility.
286
296
  retry_result_failure: a boolean specifying whether to retry on failures to
287
297
  load the result from result storage
288
298
 
@@ -738,7 +748,7 @@ class TaskRunPolicy(PrefectBaseModel):
738
748
  return validate_not_negative(v)
739
749
 
740
750
 
741
- class TaskRunInput(PrefectBaseModel):
751
+ class RunInput(PrefectBaseModel):
742
752
  """
743
753
  Base class for classes that represent inputs to task runs, which
744
754
  could include, constants, parameters, or other task runs.
@@ -751,21 +761,26 @@ class TaskRunInput(PrefectBaseModel):
751
761
  input_type: str
752
762
 
753
763
 
754
- class TaskRunResult(TaskRunInput):
764
+ class TaskRunResult(RunInput):
755
765
  """Represents a task run result input to another task run."""
756
766
 
757
767
  input_type: Literal["task_run"] = "task_run"
758
768
  id: UUID
759
769
 
760
770
 
761
- class Parameter(TaskRunInput):
771
+ class FlowRunResult(RunInput):
772
+ input_type: Literal["flow_run"] = "flow_run"
773
+ id: UUID
774
+
775
+
776
+ class Parameter(RunInput):
762
777
  """Represents a parameter input to a task run."""
763
778
 
764
779
  input_type: Literal["parameter"] = "parameter"
765
780
  name: str
766
781
 
767
782
 
768
- class Constant(TaskRunInput):
783
+ class Constant(RunInput):
769
784
  """Represents constant input value to a task run."""
770
785
 
771
786
  input_type: Literal["constant"] = "constant"
@@ -815,7 +830,9 @@ class TaskRun(TimeSeriesBaseModel, ObjectBaseModel):
815
830
  state_id: Optional[UUID] = Field(
816
831
  default=None, description="The id of the current task run state."
817
832
  )
818
- task_inputs: dict[str, list[Union[TaskRunResult, Parameter, Constant]]] = Field(
833
+ task_inputs: dict[
834
+ str, list[Union[TaskRunResult, FlowRunResult, Parameter, Constant]]
835
+ ] = Field(
819
836
  default_factory=dict,
820
837
  description=(
821
838
  "Tracks the source of inputs to a task run. Used for internal bookkeeping. "
prefect/context.py CHANGED
@@ -34,6 +34,7 @@ from prefect._internal.compatibility.migration import getattr_migration
34
34
  from prefect.assets import Asset
35
35
  from prefect.client.orchestration import PrefectClient, SyncPrefectClient, get_client
36
36
  from prefect.client.schemas import FlowRun, TaskRun
37
+ from prefect.client.schemas.objects import RunType
37
38
  from prefect.events.worker import EventsWorker
38
39
  from prefect.exceptions import MissingContextError
39
40
  from prefect.results import (
@@ -363,7 +364,7 @@ class EngineContext(RunContext):
363
364
  flow: The flow instance associated with the run
364
365
  flow_run: The API metadata for the flow run
365
366
  task_runner: The task runner instance being used for the flow run
366
- task_run_results: A mapping of result ids to task run states for this flow run
367
+ run_results: A mapping of result ids to run states for this flow run
367
368
  log_prints: Whether to log print statements from the flow run
368
369
  parameters: The parameters passed to the flow run
369
370
  detached: Flag indicating if context has been serialized and sent to remote infrastructure
@@ -394,9 +395,10 @@ class EngineContext(RunContext):
394
395
  # Counter for flow pauses
395
396
  observed_flow_pauses: dict[str, int] = Field(default_factory=dict)
396
397
 
397
- # Tracking for result from task runs in this flow run for dependency tracking
398
- # Holds the ID of the object returned by the task run and task run state
399
- task_run_results: dict[int, State] = Field(default_factory=dict)
398
+ # Tracking for result from task runs and sub flows in this flow run for
399
+ # dependency tracking. Holds the ID of the object returned by
400
+ # the run and state
401
+ run_results: dict[int, tuple[State, RunType]] = Field(default_factory=dict)
400
402
 
401
403
  # Tracking information needed to track asset linage between
402
404
  # tasks and materialization
@@ -492,6 +494,7 @@ class AssetContext(ContextModel):
492
494
  materialized_by: Optional[str] = None
493
495
  task_run_id: Optional[UUID] = None
494
496
  materialization_metadata: dict[str, dict[str, Any]] = Field(default_factory=dict)
497
+ copy_to_child_ctx: bool = False
495
498
 
496
499
  __var__: ClassVar[ContextVar[Self]] = ContextVar("asset_context")
497
500
 
@@ -501,6 +504,7 @@ class AssetContext(ContextModel):
501
504
  task: "Task[Any, Any]",
502
505
  task_run_id: UUID,
503
506
  task_inputs: Optional[dict[str, set[Any]]] = None,
507
+ copy_to_child_ctx: bool = False,
504
508
  ) -> "AssetContext":
505
509
  """
506
510
  Create an AssetContext from a task and its resolved inputs.
@@ -509,6 +513,7 @@ class AssetContext(ContextModel):
509
513
  task: The task instance
510
514
  task_run_id: The task run ID
511
515
  task_inputs: The resolved task inputs (TaskRunResult objects)
516
+ copy_to_child_ctx: Whether this context should be copied on a child AssetContext
512
517
 
513
518
  Returns:
514
519
  Configured AssetContext
@@ -518,13 +523,16 @@ class AssetContext(ContextModel):
518
523
 
519
524
  upstream_assets: set[Asset] = set()
520
525
 
521
- # Get upstream assets from engine context instead of TaskRunResult.assets
522
526
  flow_ctx = FlowRunContext.get()
523
527
  if task_inputs and flow_ctx:
524
- for inputs in task_inputs.values():
528
+ for name, inputs in task_inputs.items():
529
+ # Parent task runs are not dependencies
530
+ # that we want to track
531
+ if name == "__parents__":
532
+ continue
533
+
525
534
  for task_input in inputs:
526
535
  if isinstance(task_input, TaskRunResult):
527
- # Look up assets in the engine context
528
536
  task_assets = flow_ctx.task_run_assets.get(task_input.id)
529
537
  if task_assets:
530
538
  upstream_assets.update(task_assets)
@@ -541,6 +549,7 @@ class AssetContext(ContextModel):
541
549
  if isinstance(task, MaterializingTask)
542
550
  else None,
543
551
  task_run_id=task_run_id,
552
+ copy_to_child_ctx=copy_to_child_ctx,
544
553
  )
545
554
  ctx.update_tracked_assets()
546
555
 
prefect/events/clients.py CHANGED
@@ -1,7 +1,5 @@
1
1
  import abc
2
2
  import asyncio
3
- import os
4
- import ssl
5
3
  from datetime import timedelta
6
4
  from types import TracebackType
7
5
  from typing import (
@@ -9,7 +7,6 @@ from typing import (
9
7
  Any,
10
8
  ClassVar,
11
9
  Dict,
12
- Generator,
13
10
  List,
14
11
  MutableMapping,
15
12
  Optional,
@@ -17,18 +14,14 @@ from typing import (
17
14
  Type,
18
15
  cast,
19
16
  )
20
- from urllib.parse import urlparse
21
- from urllib.request import proxy_bypass
22
17
  from uuid import UUID
23
18
 
24
- import certifi
25
19
  import orjson
26
20
  from cachetools import TTLCache
27
21
  from prometheus_client import Counter
28
- from python_socks.async_.asyncio import Proxy
29
22
  from typing_extensions import Self
30
23
  from websockets import Subprotocol
31
- from websockets.asyncio.client import ClientConnection, connect
24
+ from websockets.asyncio.client import ClientConnection
32
25
  from websockets.exceptions import (
33
26
  ConnectionClosed,
34
27
  ConnectionClosedError,
@@ -36,13 +29,12 @@ from websockets.exceptions import (
36
29
  )
37
30
 
38
31
  import prefect.types._datetime
32
+ from prefect._internal.websockets import websocket_connect
39
33
  from prefect.events import Event
40
34
  from prefect.logging import get_logger
41
35
  from prefect.settings import (
42
36
  PREFECT_API_AUTH_STRING,
43
37
  PREFECT_API_KEY,
44
- PREFECT_API_SSL_CERT_FILE,
45
- PREFECT_API_TLS_INSECURE_SKIP_VERIFY,
46
38
  PREFECT_API_URL,
47
39
  PREFECT_CLOUD_API_URL,
48
40
  PREFECT_DEBUG_MODE,
@@ -94,72 +86,6 @@ def events_out_socket_from_api_url(url: str) -> str:
94
86
  return http_to_ws(url) + "/events/out"
95
87
 
96
88
 
97
- class WebsocketProxyConnect(connect):
98
- def __init__(self: Self, uri: str, **kwargs: Any):
99
- # super() is intentionally deferred to the _proxy_connect method
100
- # to allow for the socket to be established first
101
-
102
- self.uri = uri
103
- self._kwargs = kwargs
104
-
105
- u = urlparse(uri)
106
- host = u.hostname
107
-
108
- if not host:
109
- raise ValueError(f"Invalid URI {uri}, no hostname found")
110
-
111
- if u.scheme == "ws":
112
- port = u.port or 80
113
- proxy_url = os.environ.get("HTTP_PROXY")
114
- elif u.scheme == "wss":
115
- port = u.port or 443
116
- proxy_url = os.environ.get("HTTPS_PROXY")
117
- kwargs["server_hostname"] = host
118
- else:
119
- raise ValueError(
120
- "Unsupported scheme %s. Expected 'ws' or 'wss'. " % u.scheme
121
- )
122
-
123
- self._proxy = (
124
- Proxy.from_url(proxy_url) if proxy_url and not proxy_bypass(host) else None
125
- )
126
- self._host = host
127
- self._port = port
128
-
129
- if PREFECT_API_TLS_INSECURE_SKIP_VERIFY and u.scheme == "wss":
130
- # Create an unverified context for insecure connections
131
- ctx = ssl.create_default_context()
132
- ctx.check_hostname = False
133
- ctx.verify_mode = ssl.CERT_NONE
134
- self._kwargs.setdefault("ssl", ctx)
135
- elif u.scheme == "wss":
136
- cert_file = PREFECT_API_SSL_CERT_FILE.value()
137
- if not cert_file:
138
- cert_file = certifi.where()
139
- # Create a verified context with the certificate file
140
- ctx = ssl.create_default_context(cafile=cert_file)
141
- self._kwargs.setdefault("ssl", ctx)
142
-
143
- async def _proxy_connect(self: Self) -> ClientConnection:
144
- if self._proxy:
145
- sock = await self._proxy.connect(
146
- dest_host=self._host,
147
- dest_port=self._port,
148
- )
149
- self._kwargs["sock"] = sock
150
-
151
- super().__init__(self.uri, **self._kwargs)
152
- proto = await self.__await_impl__()
153
- return proto
154
-
155
- def __await__(self: Self) -> Generator[Any, None, ClientConnection]:
156
- return self._proxy_connect().__await__()
157
-
158
-
159
- def websocket_connect(uri: str, **kwargs: Any) -> WebsocketProxyConnect:
160
- return WebsocketProxyConnect(uri, **kwargs)
161
-
162
-
163
89
  def get_events_client(
164
90
  reconnection_attempts: int = 10,
165
91
  checkpoint_every: int = 700,
@@ -416,6 +416,10 @@ class AutomationCore(PrefectBaseModel, extra="ignore"): # type: ignore[call-arg
416
416
  enabled: bool = Field(
417
417
  default=True, description="Whether this automation will be evaluated"
418
418
  )
419
+ tags: List[str] = Field(
420
+ default_factory=list,
421
+ description="A list of tags associated with this automation",
422
+ )
419
423
 
420
424
  trigger: TriggerTypes = Field(
421
425
  default=...,
@@ -8,6 +8,7 @@ class LabelDiver:
8
8
  presenting the labels as a graph of objects that may be accessed by attribute. For
9
9
  example:
10
10
 
11
+ ```python
11
12
  diver = LabelDiver({
12
13
  'hello.world': 'foo',
13
14
  'hello.world.again': 'bar'
@@ -15,6 +16,7 @@ class LabelDiver:
15
16
 
16
17
  assert str(diver.hello.world) == 'foo'
17
18
  assert str(diver.hello.world.again) == 'bar'
19
+ ```
18
20
 
19
21
  """
20
22
 
prefect/flow_engine.py CHANGED
@@ -105,7 +105,7 @@ from prefect.utilities.callables import (
105
105
  from prefect.utilities.collections import visit_collection
106
106
  from prefect.utilities.engine import (
107
107
  capture_sigterm,
108
- link_state_to_result,
108
+ link_state_to_flow_run_result,
109
109
  propose_state,
110
110
  propose_state_sync,
111
111
  resolve_to_final_result,
@@ -338,6 +338,7 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
338
338
  self._return_value, State
339
339
  ):
340
340
  _result = self._return_value
341
+ link_state_to_flow_run_result(self.state, _result)
341
342
 
342
343
  if asyncio.iscoroutine(_result):
343
344
  # getting the value for a BaseResult may return an awaitable
@@ -373,6 +374,7 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
373
374
  self.set_state(terminal_state)
374
375
  self._return_value = resolved_result
375
376
 
377
+ link_state_to_flow_run_result(terminal_state, resolved_result)
376
378
  self._telemetry.end_span_on_success()
377
379
 
378
380
  return result
@@ -903,6 +905,7 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
903
905
  self._return_value, State
904
906
  ):
905
907
  _result = self._return_value
908
+ link_state_to_flow_run_result(self.state, _result)
906
909
 
907
910
  if asyncio.iscoroutine(_result):
908
911
  # getting the value for a BaseResult may return an awaitable
@@ -1426,7 +1429,7 @@ def run_generator_flow_sync(
1426
1429
  while True:
1427
1430
  gen_result = next(gen)
1428
1431
  # link the current state to the result for dependency tracking
1429
- link_state_to_result(engine.state, gen_result)
1432
+ link_state_to_flow_run_result(engine.state, gen_result)
1430
1433
  yield gen_result
1431
1434
  except StopIteration as exc:
1432
1435
  engine.handle_success(exc.value)
@@ -1468,7 +1471,7 @@ async def run_generator_flow_async(
1468
1471
  # can't use anext in Python < 3.10
1469
1472
  gen_result = await gen.__anext__()
1470
1473
  # link the current state to the result for dependency tracking
1471
- link_state_to_result(engine.state, gen_result)
1474
+ link_state_to_flow_run_result(engine.state, gen_result)
1472
1475
  yield gen_result
1473
1476
  except (StopAsyncIteration, GeneratorExit) as exc:
1474
1477
  await engine.handle_success(None)