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.
- prefect/AGENTS.md +28 -0
- prefect/_build_info.py +3 -3
- prefect/_internal/websockets.py +109 -0
- prefect/artifacts.py +51 -2
- prefect/assets/core.py +2 -2
- prefect/blocks/core.py +82 -11
- prefect/client/cloud.py +11 -1
- prefect/client/orchestration/__init__.py +21 -15
- prefect/client/orchestration/_deployments/client.py +139 -4
- prefect/client/orchestration/_flows/client.py +4 -4
- prefect/client/schemas/__init__.py +5 -2
- prefect/client/schemas/actions.py +1 -0
- prefect/client/schemas/filters.py +3 -0
- prefect/client/schemas/objects.py +27 -10
- prefect/context.py +16 -7
- prefect/events/clients.py +2 -76
- prefect/events/schemas/automations.py +4 -0
- prefect/events/schemas/labelling.py +2 -0
- prefect/flow_engine.py +6 -3
- prefect/flows.py +64 -45
- prefect/futures.py +25 -4
- prefect/locking/filesystem.py +1 -1
- prefect/logging/clients.py +347 -0
- prefect/runner/runner.py +1 -1
- prefect/runner/submit.py +10 -4
- prefect/serializers.py +8 -3
- prefect/server/api/logs.py +64 -9
- prefect/server/api/server.py +2 -0
- prefect/server/api/templates.py +8 -2
- prefect/settings/context.py +17 -14
- prefect/settings/models/server/logs.py +28 -0
- prefect/settings/models/server/root.py +5 -0
- prefect/settings/models/server/services.py +26 -0
- prefect/task_engine.py +73 -43
- prefect/task_runners.py +10 -10
- prefect/tasks.py +52 -9
- prefect/types/__init__.py +2 -0
- prefect/types/names.py +50 -0
- prefect/utilities/_ast.py +2 -2
- prefect/utilities/callables.py +1 -1
- prefect/utilities/collections.py +6 -6
- prefect/utilities/engine.py +67 -72
- prefect/utilities/pydantic.py +19 -1
- prefect/workers/base.py +2 -0
- {prefect_client-3.4.6.dev1.dist-info → prefect_client-3.4.7.dist-info}/METADATA +1 -1
- {prefect_client-3.4.6.dev1.dist-info → prefect_client-3.4.7.dist-info}/RECORD +48 -44
- {prefect_client-3.4.6.dev1.dist-info → prefect_client-3.4.7.dist-info}/WHEEL +0 -0
- {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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
-
"
|
76
|
+
"RunInput",
|
74
77
|
"TaskRunPolicy",
|
75
78
|
"TaskRunResult",
|
76
79
|
"Workspace",
|
@@ -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
|
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(
|
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
|
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(
|
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[
|
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
|
-
|
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
|
398
|
-
# Holds the ID of the object returned by
|
399
|
-
|
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.
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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)
|