prefect-client 3.1.14__py3-none-any.whl → 3.2.0__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/__main__.py +4 -0
- prefect/_experimental/lineage.py +40 -22
- prefect/_experimental/sla/objects.py +29 -1
- prefect/_internal/compatibility/deprecated.py +4 -4
- prefect/_internal/compatibility/migration.py +1 -1
- prefect/_internal/concurrency/calls.py +1 -2
- prefect/_internal/concurrency/cancellation.py +2 -4
- prefect/_internal/concurrency/services.py +1 -1
- prefect/_internal/concurrency/threads.py +3 -3
- prefect/_internal/schemas/bases.py +3 -11
- prefect/_internal/schemas/validators.py +36 -60
- prefect/_result_records.py +235 -0
- prefect/_version.py +3 -3
- prefect/agent.py +1 -0
- prefect/artifacts.py +408 -105
- prefect/automations.py +4 -8
- prefect/blocks/core.py +1 -1
- prefect/blocks/notifications.py +13 -8
- prefect/cache_policies.py +2 -0
- prefect/client/base.py +7 -8
- prefect/client/collections.py +3 -6
- prefect/client/orchestration/__init__.py +15 -263
- prefect/client/orchestration/_deployments/client.py +14 -6
- prefect/client/orchestration/_flow_runs/client.py +10 -6
- prefect/client/orchestration/_work_pools/__init__.py +0 -0
- prefect/client/orchestration/_work_pools/client.py +598 -0
- prefect/client/orchestration/base.py +9 -2
- prefect/client/schemas/actions.py +77 -3
- prefect/client/schemas/objects.py +22 -50
- prefect/client/schemas/schedules.py +11 -22
- prefect/client/types/flexible_schedule_list.py +2 -1
- prefect/context.py +2 -3
- prefect/deployments/base.py +13 -16
- prefect/deployments/flow_runs.py +1 -1
- prefect/deployments/runner.py +236 -47
- prefect/deployments/schedules.py +7 -1
- prefect/engine.py +4 -9
- prefect/events/clients.py +39 -0
- prefect/events/schemas/automations.py +4 -2
- prefect/events/utilities.py +15 -13
- prefect/exceptions.py +1 -1
- prefect/flow_engine.py +119 -0
- prefect/flow_runs.py +4 -8
- prefect/flows.py +282 -31
- prefect/infrastructure/__init__.py +1 -0
- prefect/infrastructure/base.py +1 -0
- prefect/infrastructure/provisioners/__init__.py +3 -6
- prefect/infrastructure/provisioners/coiled.py +3 -3
- prefect/infrastructure/provisioners/container_instance.py +1 -0
- prefect/infrastructure/provisioners/ecs.py +6 -6
- prefect/infrastructure/provisioners/modal.py +3 -3
- prefect/input/run_input.py +5 -7
- prefect/locking/filesystem.py +4 -3
- prefect/main.py +1 -1
- prefect/results.py +42 -249
- prefect/runner/runner.py +9 -4
- prefect/runner/server.py +5 -5
- prefect/runner/storage.py +12 -10
- prefect/runner/submit.py +2 -4
- prefect/runtime/task_run.py +37 -9
- prefect/schedules.py +231 -0
- prefect/serializers.py +5 -5
- prefect/settings/__init__.py +2 -1
- prefect/settings/base.py +3 -3
- prefect/settings/models/root.py +4 -0
- prefect/settings/models/server/services.py +50 -9
- prefect/settings/sources.py +4 -4
- prefect/states.py +42 -11
- prefect/task_engine.py +10 -10
- prefect/task_runners.py +11 -22
- prefect/task_worker.py +9 -9
- prefect/tasks.py +28 -45
- prefect/telemetry/bootstrap.py +4 -6
- prefect/telemetry/services.py +2 -4
- prefect/types/__init__.py +2 -1
- prefect/types/_datetime.py +28 -1
- prefect/utilities/_engine.py +0 -1
- prefect/utilities/asyncutils.py +4 -8
- prefect/utilities/collections.py +13 -22
- prefect/utilities/dispatch.py +2 -4
- prefect/utilities/dockerutils.py +6 -6
- prefect/utilities/importtools.py +1 -68
- prefect/utilities/names.py +1 -1
- prefect/utilities/processutils.py +3 -6
- prefect/utilities/pydantic.py +4 -6
- prefect/utilities/render_swagger.py +1 -1
- prefect/utilities/schema_tools/hydration.py +6 -5
- prefect/utilities/templating.py +21 -8
- prefect/utilities/visualization.py +2 -4
- prefect/workers/base.py +3 -3
- prefect/workers/block.py +1 -0
- prefect/workers/cloud.py +1 -0
- prefect/workers/process.py +1 -0
- {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/METADATA +1 -1
- {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/RECORD +98 -93
- {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/LICENSE +0 -0
- {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/WHEEL +0 -0
- {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,235 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import inspect
|
4
|
+
import uuid
|
5
|
+
from typing import (
|
6
|
+
TYPE_CHECKING,
|
7
|
+
Any,
|
8
|
+
Generic,
|
9
|
+
Optional,
|
10
|
+
TypeVar,
|
11
|
+
Union,
|
12
|
+
)
|
13
|
+
from uuid import UUID
|
14
|
+
|
15
|
+
from pydantic import (
|
16
|
+
BaseModel,
|
17
|
+
Field,
|
18
|
+
ValidationError,
|
19
|
+
model_validator,
|
20
|
+
)
|
21
|
+
|
22
|
+
import prefect
|
23
|
+
from prefect.exceptions import (
|
24
|
+
SerializationError,
|
25
|
+
)
|
26
|
+
from prefect.serializers import PickleSerializer, Serializer
|
27
|
+
from prefect.types import DateTime
|
28
|
+
|
29
|
+
if TYPE_CHECKING:
|
30
|
+
pass
|
31
|
+
|
32
|
+
|
33
|
+
ResultSerializer = Union[Serializer, str]
|
34
|
+
LITERAL_TYPES: set[type] = {type(None), bool, UUID}
|
35
|
+
R = TypeVar("R")
|
36
|
+
|
37
|
+
|
38
|
+
class ResultRecordMetadata(BaseModel):
|
39
|
+
"""
|
40
|
+
Metadata for a result record.
|
41
|
+
"""
|
42
|
+
|
43
|
+
storage_key: Optional[str] = Field(
|
44
|
+
default=None
|
45
|
+
) # optional for backwards compatibility
|
46
|
+
expiration: Optional[DateTime] = Field(default=None)
|
47
|
+
serializer: Serializer = Field(default_factory=PickleSerializer)
|
48
|
+
prefect_version: str = Field(default=prefect.__version__)
|
49
|
+
storage_block_id: Optional[uuid.UUID] = Field(default=None)
|
50
|
+
|
51
|
+
def dump_bytes(self) -> bytes:
|
52
|
+
"""
|
53
|
+
Serialize the metadata to bytes.
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
bytes: the serialized metadata
|
57
|
+
"""
|
58
|
+
return self.model_dump_json(serialize_as_any=True).encode()
|
59
|
+
|
60
|
+
@classmethod
|
61
|
+
def load_bytes(cls, data: bytes) -> "ResultRecordMetadata":
|
62
|
+
"""
|
63
|
+
Deserialize metadata from bytes.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
data: the serialized metadata
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
ResultRecordMetadata: the deserialized metadata
|
70
|
+
"""
|
71
|
+
return cls.model_validate_json(data)
|
72
|
+
|
73
|
+
def __eq__(self, other: Any) -> bool:
|
74
|
+
if not isinstance(other, ResultRecordMetadata):
|
75
|
+
return False
|
76
|
+
return (
|
77
|
+
self.storage_key == other.storage_key
|
78
|
+
and self.expiration == other.expiration
|
79
|
+
and self.serializer == other.serializer
|
80
|
+
and self.prefect_version == other.prefect_version
|
81
|
+
and self.storage_block_id == other.storage_block_id
|
82
|
+
)
|
83
|
+
|
84
|
+
|
85
|
+
class ResultRecord(BaseModel, Generic[R]):
|
86
|
+
"""
|
87
|
+
A record of a result.
|
88
|
+
"""
|
89
|
+
|
90
|
+
metadata: ResultRecordMetadata
|
91
|
+
result: R
|
92
|
+
|
93
|
+
@property
|
94
|
+
def expiration(self) -> DateTime | None:
|
95
|
+
return self.metadata.expiration
|
96
|
+
|
97
|
+
@property
|
98
|
+
def serializer(self) -> Serializer:
|
99
|
+
return self.metadata.serializer
|
100
|
+
|
101
|
+
def serialize_result(self) -> bytes:
|
102
|
+
try:
|
103
|
+
data = self.serializer.dumps(self.result)
|
104
|
+
except Exception as exc:
|
105
|
+
extra_info = (
|
106
|
+
'You can try a different serializer (e.g. result_serializer="json") '
|
107
|
+
"or disabling persistence (persist_result=False) for this flow or task."
|
108
|
+
)
|
109
|
+
# check if this is a known issue with cloudpickle and pydantic
|
110
|
+
# and add extra information to help the user recover
|
111
|
+
|
112
|
+
if (
|
113
|
+
isinstance(exc, TypeError)
|
114
|
+
and isinstance(self.result, BaseModel)
|
115
|
+
and str(exc).startswith("cannot pickle")
|
116
|
+
):
|
117
|
+
try:
|
118
|
+
from IPython.core.getipython import get_ipython
|
119
|
+
|
120
|
+
if get_ipython() is not None:
|
121
|
+
extra_info = inspect.cleandoc(
|
122
|
+
"""
|
123
|
+
This is a known issue in Pydantic that prevents
|
124
|
+
locally-defined (non-imported) models from being
|
125
|
+
serialized by cloudpickle in IPython/Jupyter
|
126
|
+
environments. Please see
|
127
|
+
https://github.com/pydantic/pydantic/issues/8232 for
|
128
|
+
more information. To fix the issue, either: (1) move
|
129
|
+
your Pydantic class definition to an importable
|
130
|
+
location, (2) use the JSON serializer for your flow
|
131
|
+
or task (`result_serializer="json"`), or (3)
|
132
|
+
disable result persistence for your flow or task
|
133
|
+
(`persist_result=False`).
|
134
|
+
"""
|
135
|
+
).replace("\n", " ")
|
136
|
+
except ImportError:
|
137
|
+
pass
|
138
|
+
raise SerializationError(
|
139
|
+
f"Failed to serialize object of type {type(self.result).__name__!r} with "
|
140
|
+
f"serializer {self.serializer.type!r}. {extra_info}"
|
141
|
+
) from exc
|
142
|
+
|
143
|
+
return data
|
144
|
+
|
145
|
+
@model_validator(mode="before")
|
146
|
+
@classmethod
|
147
|
+
def coerce_old_format(cls, value: dict[str, Any] | Any) -> dict[str, Any]:
|
148
|
+
if isinstance(value, dict):
|
149
|
+
if "data" in value:
|
150
|
+
value["result"] = value.pop("data")
|
151
|
+
if "metadata" not in value:
|
152
|
+
value["metadata"] = {}
|
153
|
+
if "expiration" in value:
|
154
|
+
value["metadata"]["expiration"] = value.pop("expiration")
|
155
|
+
if "serializer" in value:
|
156
|
+
value["metadata"]["serializer"] = value.pop("serializer")
|
157
|
+
if "prefect_version" in value:
|
158
|
+
value["metadata"]["prefect_version"] = value.pop("prefect_version")
|
159
|
+
return value
|
160
|
+
|
161
|
+
def serialize_metadata(self) -> bytes:
|
162
|
+
return self.metadata.dump_bytes()
|
163
|
+
|
164
|
+
def serialize(
|
165
|
+
self,
|
166
|
+
) -> bytes:
|
167
|
+
"""
|
168
|
+
Serialize the record to bytes.
|
169
|
+
|
170
|
+
Returns:
|
171
|
+
bytes: the serialized record
|
172
|
+
|
173
|
+
"""
|
174
|
+
return (
|
175
|
+
self.model_copy(update={"result": self.serialize_result()})
|
176
|
+
.model_dump_json(serialize_as_any=True)
|
177
|
+
.encode()
|
178
|
+
)
|
179
|
+
|
180
|
+
@classmethod
|
181
|
+
def deserialize(
|
182
|
+
cls, data: bytes, backup_serializer: Serializer | None = None
|
183
|
+
) -> "ResultRecord[R]":
|
184
|
+
"""
|
185
|
+
Deserialize a record from bytes.
|
186
|
+
|
187
|
+
Args:
|
188
|
+
data: the serialized record
|
189
|
+
backup_serializer: The serializer to use to deserialize the result record. Only
|
190
|
+
necessary if the provided data does not specify a serializer.
|
191
|
+
|
192
|
+
Returns:
|
193
|
+
ResultRecord: the deserialized record
|
194
|
+
"""
|
195
|
+
try:
|
196
|
+
instance = cls.model_validate_json(data)
|
197
|
+
except ValidationError:
|
198
|
+
if backup_serializer is None:
|
199
|
+
raise
|
200
|
+
else:
|
201
|
+
result = backup_serializer.loads(data)
|
202
|
+
return cls(
|
203
|
+
metadata=ResultRecordMetadata(serializer=backup_serializer),
|
204
|
+
result=result,
|
205
|
+
)
|
206
|
+
if isinstance(instance.result, bytes):
|
207
|
+
instance.result = instance.serializer.loads(instance.result)
|
208
|
+
elif isinstance(instance.result, str):
|
209
|
+
instance.result = instance.serializer.loads(instance.result.encode())
|
210
|
+
return instance
|
211
|
+
|
212
|
+
@classmethod
|
213
|
+
def deserialize_from_result_and_metadata(
|
214
|
+
cls, result: bytes, metadata: bytes
|
215
|
+
) -> "ResultRecord[R]":
|
216
|
+
"""
|
217
|
+
Deserialize a record from separate result and metadata bytes.
|
218
|
+
|
219
|
+
Args:
|
220
|
+
result: the result
|
221
|
+
metadata: the serialized metadata
|
222
|
+
|
223
|
+
Returns:
|
224
|
+
ResultRecord: the deserialized record
|
225
|
+
"""
|
226
|
+
result_record_metadata = ResultRecordMetadata.load_bytes(metadata)
|
227
|
+
return cls(
|
228
|
+
metadata=result_record_metadata,
|
229
|
+
result=result_record_metadata.serializer.loads(result),
|
230
|
+
)
|
231
|
+
|
232
|
+
def __eq__(self, other: Any | "ResultRecord[Any]") -> bool:
|
233
|
+
if not isinstance(other, ResultRecord):
|
234
|
+
return False
|
235
|
+
return self.metadata == other.metadata and self.result == other.result
|
prefect/_version.py
CHANGED
@@ -8,11 +8,11 @@ import json
|
|
8
8
|
|
9
9
|
version_json = '''
|
10
10
|
{
|
11
|
-
"date": "2025-
|
11
|
+
"date": "2025-02-07T18:02:21-0800",
|
12
12
|
"dirty": true,
|
13
13
|
"error": null,
|
14
|
-
"full-revisionid": "
|
15
|
-
"version": "3.
|
14
|
+
"full-revisionid": "c8986edebb2dde3e2a931adbe24d2eaefcb799cb",
|
15
|
+
"version": "3.2.0"
|
16
16
|
}
|
17
17
|
''' # END VERSION_JSON
|
18
18
|
|