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.
Files changed (98) hide show
  1. prefect/__main__.py +4 -0
  2. prefect/_experimental/lineage.py +40 -22
  3. prefect/_experimental/sla/objects.py +29 -1
  4. prefect/_internal/compatibility/deprecated.py +4 -4
  5. prefect/_internal/compatibility/migration.py +1 -1
  6. prefect/_internal/concurrency/calls.py +1 -2
  7. prefect/_internal/concurrency/cancellation.py +2 -4
  8. prefect/_internal/concurrency/services.py +1 -1
  9. prefect/_internal/concurrency/threads.py +3 -3
  10. prefect/_internal/schemas/bases.py +3 -11
  11. prefect/_internal/schemas/validators.py +36 -60
  12. prefect/_result_records.py +235 -0
  13. prefect/_version.py +3 -3
  14. prefect/agent.py +1 -0
  15. prefect/artifacts.py +408 -105
  16. prefect/automations.py +4 -8
  17. prefect/blocks/core.py +1 -1
  18. prefect/blocks/notifications.py +13 -8
  19. prefect/cache_policies.py +2 -0
  20. prefect/client/base.py +7 -8
  21. prefect/client/collections.py +3 -6
  22. prefect/client/orchestration/__init__.py +15 -263
  23. prefect/client/orchestration/_deployments/client.py +14 -6
  24. prefect/client/orchestration/_flow_runs/client.py +10 -6
  25. prefect/client/orchestration/_work_pools/__init__.py +0 -0
  26. prefect/client/orchestration/_work_pools/client.py +598 -0
  27. prefect/client/orchestration/base.py +9 -2
  28. prefect/client/schemas/actions.py +77 -3
  29. prefect/client/schemas/objects.py +22 -50
  30. prefect/client/schemas/schedules.py +11 -22
  31. prefect/client/types/flexible_schedule_list.py +2 -1
  32. prefect/context.py +2 -3
  33. prefect/deployments/base.py +13 -16
  34. prefect/deployments/flow_runs.py +1 -1
  35. prefect/deployments/runner.py +236 -47
  36. prefect/deployments/schedules.py +7 -1
  37. prefect/engine.py +4 -9
  38. prefect/events/clients.py +39 -0
  39. prefect/events/schemas/automations.py +4 -2
  40. prefect/events/utilities.py +15 -13
  41. prefect/exceptions.py +1 -1
  42. prefect/flow_engine.py +119 -0
  43. prefect/flow_runs.py +4 -8
  44. prefect/flows.py +282 -31
  45. prefect/infrastructure/__init__.py +1 -0
  46. prefect/infrastructure/base.py +1 -0
  47. prefect/infrastructure/provisioners/__init__.py +3 -6
  48. prefect/infrastructure/provisioners/coiled.py +3 -3
  49. prefect/infrastructure/provisioners/container_instance.py +1 -0
  50. prefect/infrastructure/provisioners/ecs.py +6 -6
  51. prefect/infrastructure/provisioners/modal.py +3 -3
  52. prefect/input/run_input.py +5 -7
  53. prefect/locking/filesystem.py +4 -3
  54. prefect/main.py +1 -1
  55. prefect/results.py +42 -249
  56. prefect/runner/runner.py +9 -4
  57. prefect/runner/server.py +5 -5
  58. prefect/runner/storage.py +12 -10
  59. prefect/runner/submit.py +2 -4
  60. prefect/runtime/task_run.py +37 -9
  61. prefect/schedules.py +231 -0
  62. prefect/serializers.py +5 -5
  63. prefect/settings/__init__.py +2 -1
  64. prefect/settings/base.py +3 -3
  65. prefect/settings/models/root.py +4 -0
  66. prefect/settings/models/server/services.py +50 -9
  67. prefect/settings/sources.py +4 -4
  68. prefect/states.py +42 -11
  69. prefect/task_engine.py +10 -10
  70. prefect/task_runners.py +11 -22
  71. prefect/task_worker.py +9 -9
  72. prefect/tasks.py +28 -45
  73. prefect/telemetry/bootstrap.py +4 -6
  74. prefect/telemetry/services.py +2 -4
  75. prefect/types/__init__.py +2 -1
  76. prefect/types/_datetime.py +28 -1
  77. prefect/utilities/_engine.py +0 -1
  78. prefect/utilities/asyncutils.py +4 -8
  79. prefect/utilities/collections.py +13 -22
  80. prefect/utilities/dispatch.py +2 -4
  81. prefect/utilities/dockerutils.py +6 -6
  82. prefect/utilities/importtools.py +1 -68
  83. prefect/utilities/names.py +1 -1
  84. prefect/utilities/processutils.py +3 -6
  85. prefect/utilities/pydantic.py +4 -6
  86. prefect/utilities/render_swagger.py +1 -1
  87. prefect/utilities/schema_tools/hydration.py +6 -5
  88. prefect/utilities/templating.py +21 -8
  89. prefect/utilities/visualization.py +2 -4
  90. prefect/workers/base.py +3 -3
  91. prefect/workers/block.py +1 -0
  92. prefect/workers/cloud.py +1 -0
  93. prefect/workers/process.py +1 -0
  94. {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/METADATA +1 -1
  95. {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/RECORD +98 -93
  96. {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/LICENSE +0 -0
  97. {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/WHEEL +0 -0
  98. {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-01-23T13:22:04-0800",
11
+ "date": "2025-02-07T18:02:21-0800",
12
12
  "dirty": true,
13
13
  "error": null,
14
- "full-revisionid": "5f1ebb57222bee1537b4d2c64b6dc9e3791ebafe",
15
- "version": "3.1.14"
14
+ "full-revisionid": "c8986edebb2dde3e2a931adbe24d2eaefcb799cb",
15
+ "version": "3.2.0"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
prefect/agent.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """
2
2
  2024-06-27: This surfaces an actionable error message for moved or removed objects in Prefect 3.0 upgrade.
3
3
  """
4
+
4
5
  from typing import Any, Callable
5
6
 
6
7
  from prefect._internal.compatibility.migration import getattr_migration