prefect-client 3.1.10__py3-none-any.whl → 3.1.12__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/_experimental/lineage.py +7 -8
- prefect/_experimental/sla/__init__.py +0 -0
- prefect/_experimental/sla/client.py +66 -0
- prefect/_experimental/sla/objects.py +53 -0
- prefect/_internal/_logging.py +15 -3
- prefect/_internal/compatibility/async_dispatch.py +22 -16
- prefect/_internal/compatibility/deprecated.py +42 -18
- prefect/_internal/compatibility/migration.py +2 -2
- prefect/_internal/concurrency/inspection.py +12 -14
- prefect/_internal/concurrency/primitives.py +2 -2
- prefect/_internal/concurrency/services.py +154 -80
- prefect/_internal/concurrency/waiters.py +13 -9
- prefect/_internal/pydantic/annotations/pendulum.py +7 -7
- prefect/_internal/pytz.py +4 -3
- prefect/_internal/retries.py +10 -5
- prefect/_internal/schemas/bases.py +19 -10
- prefect/_internal/schemas/validators.py +227 -388
- prefect/_version.py +3 -3
- prefect/automations.py +236 -30
- prefect/blocks/__init__.py +3 -3
- prefect/blocks/abstract.py +53 -30
- prefect/blocks/core.py +183 -84
- prefect/blocks/notifications.py +133 -73
- prefect/blocks/redis.py +13 -9
- prefect/blocks/system.py +24 -11
- prefect/blocks/webhook.py +7 -5
- prefect/cache_policies.py +3 -2
- prefect/client/orchestration/__init__.py +1957 -0
- prefect/client/orchestration/_artifacts/__init__.py +0 -0
- prefect/client/orchestration/_artifacts/client.py +239 -0
- prefect/client/orchestration/_automations/__init__.py +0 -0
- prefect/client/orchestration/_automations/client.py +329 -0
- prefect/client/orchestration/_blocks_documents/__init__.py +0 -0
- prefect/client/orchestration/_blocks_documents/client.py +334 -0
- prefect/client/orchestration/_blocks_schemas/__init__.py +0 -0
- prefect/client/orchestration/_blocks_schemas/client.py +200 -0
- prefect/client/orchestration/_blocks_types/__init__.py +0 -0
- prefect/client/orchestration/_blocks_types/client.py +380 -0
- prefect/client/orchestration/_concurrency_limits/__init__.py +0 -0
- prefect/client/orchestration/_concurrency_limits/client.py +762 -0
- prefect/client/orchestration/_deployments/__init__.py +0 -0
- prefect/client/orchestration/_deployments/client.py +1128 -0
- prefect/client/orchestration/_flow_runs/__init__.py +0 -0
- prefect/client/orchestration/_flow_runs/client.py +903 -0
- prefect/client/orchestration/_flows/__init__.py +0 -0
- prefect/client/orchestration/_flows/client.py +343 -0
- prefect/client/orchestration/_logs/__init__.py +0 -0
- prefect/client/orchestration/_logs/client.py +97 -0
- prefect/client/orchestration/_variables/__init__.py +0 -0
- prefect/client/orchestration/_variables/client.py +157 -0
- prefect/client/orchestration/base.py +46 -0
- prefect/client/orchestration/routes.py +145 -0
- prefect/client/schemas/__init__.py +68 -28
- prefect/client/schemas/actions.py +2 -2
- prefect/client/schemas/filters.py +5 -0
- prefect/client/schemas/objects.py +8 -15
- prefect/client/schemas/schedules.py +22 -10
- prefect/concurrency/_asyncio.py +87 -0
- prefect/concurrency/{events.py → _events.py} +10 -10
- prefect/concurrency/asyncio.py +20 -104
- prefect/concurrency/context.py +6 -4
- prefect/concurrency/services.py +26 -74
- prefect/concurrency/sync.py +23 -44
- prefect/concurrency/v1/_asyncio.py +63 -0
- prefect/concurrency/v1/{events.py → _events.py} +13 -15
- prefect/concurrency/v1/asyncio.py +27 -80
- prefect/concurrency/v1/context.py +6 -4
- prefect/concurrency/v1/services.py +33 -79
- prefect/concurrency/v1/sync.py +18 -37
- prefect/context.py +66 -45
- prefect/deployments/base.py +10 -144
- prefect/deployments/flow_runs.py +12 -2
- prefect/deployments/runner.py +53 -4
- prefect/deployments/steps/pull.py +13 -0
- prefect/engine.py +17 -4
- prefect/events/clients.py +7 -1
- prefect/events/schemas/events.py +3 -2
- prefect/filesystems.py +6 -2
- prefect/flow_engine.py +101 -85
- prefect/flows.py +10 -1
- prefect/input/run_input.py +2 -1
- prefect/logging/logging.yml +1 -1
- prefect/main.py +1 -3
- prefect/results.py +2 -307
- prefect/runner/runner.py +4 -2
- prefect/runner/storage.py +87 -21
- prefect/serializers.py +32 -25
- prefect/settings/legacy.py +4 -4
- prefect/settings/models/api.py +3 -3
- prefect/settings/models/cli.py +3 -3
- prefect/settings/models/client.py +5 -3
- prefect/settings/models/cloud.py +8 -3
- prefect/settings/models/deployments.py +3 -3
- prefect/settings/models/experiments.py +4 -7
- prefect/settings/models/flows.py +3 -3
- prefect/settings/models/internal.py +4 -2
- prefect/settings/models/logging.py +4 -3
- prefect/settings/models/results.py +3 -3
- prefect/settings/models/root.py +3 -2
- prefect/settings/models/runner.py +4 -4
- prefect/settings/models/server/api.py +3 -3
- prefect/settings/models/server/database.py +11 -4
- prefect/settings/models/server/deployments.py +6 -2
- prefect/settings/models/server/ephemeral.py +4 -2
- prefect/settings/models/server/events.py +3 -2
- prefect/settings/models/server/flow_run_graph.py +6 -2
- prefect/settings/models/server/root.py +3 -3
- prefect/settings/models/server/services.py +26 -11
- prefect/settings/models/server/tasks.py +6 -3
- prefect/settings/models/server/ui.py +3 -3
- prefect/settings/models/tasks.py +5 -5
- prefect/settings/models/testing.py +3 -3
- prefect/settings/models/worker.py +5 -3
- prefect/settings/profiles.py +15 -2
- prefect/states.py +61 -45
- prefect/task_engine.py +54 -75
- prefect/task_runners.py +56 -55
- prefect/task_worker.py +2 -2
- prefect/tasks.py +90 -36
- prefect/telemetry/bootstrap.py +10 -9
- prefect/telemetry/run_telemetry.py +13 -8
- prefect/telemetry/services.py +4 -0
- prefect/transactions.py +4 -15
- prefect/utilities/_git.py +34 -0
- prefect/utilities/asyncutils.py +1 -1
- prefect/utilities/engine.py +3 -19
- prefect/utilities/generics.py +18 -0
- prefect/utilities/templating.py +25 -1
- prefect/workers/base.py +6 -3
- prefect/workers/process.py +1 -1
- {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/METADATA +2 -2
- {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/RECORD +135 -109
- prefect/client/orchestration.py +0 -4523
- prefect/records/__init__.py +0 -1
- prefect/records/base.py +0 -235
- prefect/records/filesystem.py +0 -213
- prefect/records/memory.py +0 -184
- prefect/records/result_store.py +0 -70
- {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/LICENSE +0 -0
- {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/WHEEL +0 -0
- {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1128 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from collections.abc import Iterable
|
4
|
+
from typing import TYPE_CHECKING, Any, Union
|
5
|
+
|
6
|
+
from httpx import HTTPStatusError, RequestError
|
7
|
+
|
8
|
+
from prefect.client.orchestration.base import BaseAsyncClient, BaseClient
|
9
|
+
from prefect.exceptions import ObjectNotFound
|
10
|
+
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
import datetime
|
13
|
+
from uuid import UUID
|
14
|
+
|
15
|
+
from prefect.client.schemas import FlowRun
|
16
|
+
from prefect.client.schemas.actions import (
|
17
|
+
DeploymentCreate,
|
18
|
+
DeploymentScheduleCreate,
|
19
|
+
DeploymentUpdate,
|
20
|
+
)
|
21
|
+
from prefect.client.schemas.filters import (
|
22
|
+
DeploymentFilter,
|
23
|
+
FlowFilter,
|
24
|
+
FlowRunFilter,
|
25
|
+
TaskRunFilter,
|
26
|
+
WorkPoolFilter,
|
27
|
+
WorkQueueFilter,
|
28
|
+
)
|
29
|
+
from prefect.client.schemas.objects import (
|
30
|
+
ConcurrencyOptions,
|
31
|
+
DeploymentSchedule,
|
32
|
+
)
|
33
|
+
from prefect.client.schemas.responses import (
|
34
|
+
DeploymentResponse,
|
35
|
+
FlowRunResponse,
|
36
|
+
)
|
37
|
+
from prefect.client.schemas.schedules import SCHEDULE_TYPES
|
38
|
+
from prefect.client.schemas.sorting import (
|
39
|
+
DeploymentSort,
|
40
|
+
)
|
41
|
+
from prefect.states import State
|
42
|
+
from prefect.types import KeyValueLabelsField
|
43
|
+
|
44
|
+
|
45
|
+
class DeploymentClient(BaseClient):
|
46
|
+
def create_deployment(
|
47
|
+
self,
|
48
|
+
flow_id: "UUID",
|
49
|
+
name: str,
|
50
|
+
version: str | None = None,
|
51
|
+
schedules: list["DeploymentScheduleCreate"] | None = None,
|
52
|
+
concurrency_limit: int | None = None,
|
53
|
+
concurrency_options: "ConcurrencyOptions | None" = None,
|
54
|
+
parameters: dict[str, Any] | None = None,
|
55
|
+
description: str | None = None,
|
56
|
+
work_queue_name: str | None = None,
|
57
|
+
work_pool_name: str | None = None,
|
58
|
+
tags: list[str] | None = None,
|
59
|
+
storage_document_id: "UUID | None" = None,
|
60
|
+
path: str | None = None,
|
61
|
+
entrypoint: str | None = None,
|
62
|
+
infrastructure_document_id: "UUID | None" = None,
|
63
|
+
parameter_openapi_schema: dict[str, Any] | None = None,
|
64
|
+
paused: bool | None = None,
|
65
|
+
pull_steps: list[dict[str, Any]] | None = None,
|
66
|
+
enforce_parameter_schema: bool | None = None,
|
67
|
+
job_variables: dict[str, Any] | None = None,
|
68
|
+
) -> "UUID":
|
69
|
+
"""
|
70
|
+
Create a deployment.
|
71
|
+
|
72
|
+
Args:
|
73
|
+
flow_id: the flow ID to create a deployment for
|
74
|
+
name: the name of the deployment
|
75
|
+
version: an optional version string for the deployment
|
76
|
+
tags: an optional list of tags to apply to the deployment
|
77
|
+
storage_document_id: an reference to the storage block document
|
78
|
+
used for the deployed flow
|
79
|
+
infrastructure_document_id: an reference to the infrastructure block document
|
80
|
+
to use for this deployment
|
81
|
+
job_variables: A dictionary of dot delimited infrastructure overrides that
|
82
|
+
will be applied at runtime; for example `env.CONFIG_KEY=config_value` or
|
83
|
+
`namespace='prefect'`. This argument was previously named `infra_overrides`.
|
84
|
+
Both arguments are supported for backwards compatibility.
|
85
|
+
|
86
|
+
Raises:
|
87
|
+
RequestError: if the deployment was not created for any reason
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
the ID of the deployment in the backend
|
91
|
+
"""
|
92
|
+
from uuid import UUID
|
93
|
+
|
94
|
+
from prefect.client.schemas.actions import DeploymentCreate
|
95
|
+
|
96
|
+
if parameter_openapi_schema is None:
|
97
|
+
parameter_openapi_schema = {}
|
98
|
+
deployment_create = DeploymentCreate(
|
99
|
+
flow_id=flow_id,
|
100
|
+
name=name,
|
101
|
+
version=version,
|
102
|
+
parameters=dict(parameters or {}),
|
103
|
+
tags=list(tags or []),
|
104
|
+
work_queue_name=work_queue_name,
|
105
|
+
description=description,
|
106
|
+
storage_document_id=storage_document_id,
|
107
|
+
path=path,
|
108
|
+
entrypoint=entrypoint,
|
109
|
+
infrastructure_document_id=infrastructure_document_id,
|
110
|
+
job_variables=dict(job_variables or {}),
|
111
|
+
parameter_openapi_schema=parameter_openapi_schema,
|
112
|
+
paused=paused,
|
113
|
+
schedules=schedules or [],
|
114
|
+
concurrency_limit=concurrency_limit,
|
115
|
+
concurrency_options=concurrency_options,
|
116
|
+
pull_steps=pull_steps,
|
117
|
+
enforce_parameter_schema=enforce_parameter_schema,
|
118
|
+
)
|
119
|
+
|
120
|
+
if work_pool_name is not None:
|
121
|
+
deployment_create.work_pool_name = work_pool_name
|
122
|
+
|
123
|
+
# Exclude newer fields that are not set to avoid compatibility issues
|
124
|
+
exclude = {
|
125
|
+
field
|
126
|
+
for field in ["work_pool_name", "work_queue_name"]
|
127
|
+
if field not in deployment_create.model_fields_set
|
128
|
+
}
|
129
|
+
|
130
|
+
if deployment_create.paused is None:
|
131
|
+
exclude.add("paused")
|
132
|
+
|
133
|
+
if deployment_create.pull_steps is None:
|
134
|
+
exclude.add("pull_steps")
|
135
|
+
|
136
|
+
if deployment_create.enforce_parameter_schema is None:
|
137
|
+
exclude.add("enforce_parameter_schema")
|
138
|
+
|
139
|
+
json = deployment_create.model_dump(mode="json", exclude=exclude)
|
140
|
+
response = self.request(
|
141
|
+
"POST",
|
142
|
+
"/deployments/",
|
143
|
+
json=json,
|
144
|
+
)
|
145
|
+
deployment_id = response.json().get("id")
|
146
|
+
if not deployment_id:
|
147
|
+
raise RequestError(f"Malformed response: {response}")
|
148
|
+
|
149
|
+
return UUID(deployment_id)
|
150
|
+
|
151
|
+
def set_deployment_paused_state(self, deployment_id: "UUID", paused: bool) -> None:
|
152
|
+
self.request(
|
153
|
+
"PATCH",
|
154
|
+
"/deployments/{id}",
|
155
|
+
path_params={"id": deployment_id},
|
156
|
+
json={"paused": paused},
|
157
|
+
)
|
158
|
+
|
159
|
+
def update_deployment(
|
160
|
+
self,
|
161
|
+
deployment_id: "UUID",
|
162
|
+
deployment: "DeploymentUpdate",
|
163
|
+
) -> None:
|
164
|
+
self.request(
|
165
|
+
"PATCH",
|
166
|
+
"/deployments/{id}",
|
167
|
+
path_params={"id": deployment_id},
|
168
|
+
json=deployment.model_dump(mode="json", exclude_unset=True),
|
169
|
+
)
|
170
|
+
|
171
|
+
def _create_deployment_from_schema(self, schema: "DeploymentCreate") -> "UUID":
|
172
|
+
"""
|
173
|
+
Create a deployment from a prepared `DeploymentCreate` schema.
|
174
|
+
"""
|
175
|
+
from uuid import UUID
|
176
|
+
|
177
|
+
# TODO: We are likely to remove this method once we have considered the
|
178
|
+
# packaging interface for deployments further.
|
179
|
+
response = self.request(
|
180
|
+
"POST", "/deployments/", json=schema.model_dump(mode="json")
|
181
|
+
)
|
182
|
+
deployment_id = response.json().get("id")
|
183
|
+
if not deployment_id:
|
184
|
+
raise RequestError(f"Malformed response: {response}")
|
185
|
+
|
186
|
+
return UUID(deployment_id)
|
187
|
+
|
188
|
+
def read_deployment(
|
189
|
+
self,
|
190
|
+
deployment_id: Union["UUID", str],
|
191
|
+
) -> "DeploymentResponse":
|
192
|
+
"""
|
193
|
+
Query the Prefect API for a deployment by id.
|
194
|
+
|
195
|
+
Args:
|
196
|
+
deployment_id: the deployment ID of interest
|
197
|
+
|
198
|
+
Returns:
|
199
|
+
a [Deployment model][prefect.client.schemas.objects.Deployment] representation of the deployment
|
200
|
+
"""
|
201
|
+
from uuid import UUID
|
202
|
+
|
203
|
+
from prefect.client.schemas.responses import DeploymentResponse
|
204
|
+
|
205
|
+
if not isinstance(deployment_id, UUID):
|
206
|
+
try:
|
207
|
+
deployment_id = UUID(deployment_id)
|
208
|
+
except ValueError:
|
209
|
+
raise ValueError(f"Invalid deployment ID: {deployment_id}")
|
210
|
+
|
211
|
+
try:
|
212
|
+
response = self.request(
|
213
|
+
"GET",
|
214
|
+
"/deployments/{id}",
|
215
|
+
path_params={"id": deployment_id},
|
216
|
+
)
|
217
|
+
except HTTPStatusError as e:
|
218
|
+
if e.response.status_code == 404:
|
219
|
+
raise ObjectNotFound(http_exc=e) from e
|
220
|
+
else:
|
221
|
+
raise
|
222
|
+
return DeploymentResponse.model_validate(response.json())
|
223
|
+
|
224
|
+
def read_deployment_by_name(
|
225
|
+
self,
|
226
|
+
name: str,
|
227
|
+
) -> "DeploymentResponse":
|
228
|
+
"""
|
229
|
+
Query the Prefect API for a deployment by name.
|
230
|
+
|
231
|
+
Args:
|
232
|
+
name: A deployed flow's name: <FLOW_NAME>/<DEPLOYMENT_NAME>
|
233
|
+
|
234
|
+
Raises:
|
235
|
+
ObjectNotFound: If request returns 404
|
236
|
+
RequestError: If request fails
|
237
|
+
|
238
|
+
Returns:
|
239
|
+
a Deployment model representation of the deployment
|
240
|
+
"""
|
241
|
+
from prefect.client.schemas.responses import DeploymentResponse
|
242
|
+
|
243
|
+
try:
|
244
|
+
flow_name, deployment_name = name.split("/")
|
245
|
+
response = self.request(
|
246
|
+
"GET",
|
247
|
+
"/deployments/name/{flow_name}/{deployment_name}",
|
248
|
+
path_params={
|
249
|
+
"flow_name": flow_name,
|
250
|
+
"deployment_name": deployment_name,
|
251
|
+
},
|
252
|
+
)
|
253
|
+
except (HTTPStatusError, ValueError) as e:
|
254
|
+
if isinstance(e, HTTPStatusError) and e.response.status_code == 404:
|
255
|
+
raise ObjectNotFound(http_exc=e) from e
|
256
|
+
elif isinstance(e, ValueError):
|
257
|
+
raise ValueError(
|
258
|
+
f"Invalid deployment name format: {name}. Expected format: <FLOW_NAME>/<DEPLOYMENT_NAME>"
|
259
|
+
) from e
|
260
|
+
else:
|
261
|
+
raise
|
262
|
+
|
263
|
+
return DeploymentResponse.model_validate(response.json())
|
264
|
+
|
265
|
+
def read_deployments(
|
266
|
+
self,
|
267
|
+
*,
|
268
|
+
flow_filter: "FlowFilter | None" = None,
|
269
|
+
flow_run_filter: "FlowRunFilter | None" = None,
|
270
|
+
task_run_filter: "TaskRunFilter | None" = None,
|
271
|
+
deployment_filter: "DeploymentFilter | None" = None,
|
272
|
+
work_pool_filter: "WorkPoolFilter | None" = None,
|
273
|
+
work_queue_filter: "WorkQueueFilter | None" = None,
|
274
|
+
limit: int | None = None,
|
275
|
+
sort: "DeploymentSort | None" = None,
|
276
|
+
offset: int = 0,
|
277
|
+
) -> list["DeploymentResponse"]:
|
278
|
+
"""
|
279
|
+
Query the Prefect API for deployments. Only deployments matching all
|
280
|
+
the provided criteria will be returned.
|
281
|
+
|
282
|
+
Args:
|
283
|
+
flow_filter: filter criteria for flows
|
284
|
+
flow_run_filter: filter criteria for flow runs
|
285
|
+
task_run_filter: filter criteria for task runs
|
286
|
+
deployment_filter: filter criteria for deployments
|
287
|
+
work_pool_filter: filter criteria for work pools
|
288
|
+
work_queue_filter: filter criteria for work pool queues
|
289
|
+
limit: a limit for the deployment query
|
290
|
+
offset: an offset for the deployment query
|
291
|
+
|
292
|
+
Returns:
|
293
|
+
a list of Deployment model representations
|
294
|
+
of the deployments
|
295
|
+
"""
|
296
|
+
from prefect.client.schemas.responses import DeploymentResponse
|
297
|
+
|
298
|
+
body: dict[str, Any] = {
|
299
|
+
"flows": flow_filter.model_dump(mode="json") if flow_filter else None,
|
300
|
+
"flow_runs": (
|
301
|
+
flow_run_filter.model_dump(mode="json", exclude_unset=True)
|
302
|
+
if flow_run_filter
|
303
|
+
else None
|
304
|
+
),
|
305
|
+
"task_runs": (
|
306
|
+
task_run_filter.model_dump(mode="json") if task_run_filter else None
|
307
|
+
),
|
308
|
+
"deployments": (
|
309
|
+
deployment_filter.model_dump(mode="json") if deployment_filter else None
|
310
|
+
),
|
311
|
+
"work_pools": (
|
312
|
+
work_pool_filter.model_dump(mode="json") if work_pool_filter else None
|
313
|
+
),
|
314
|
+
"work_pool_queues": (
|
315
|
+
work_queue_filter.model_dump(mode="json") if work_queue_filter else None
|
316
|
+
),
|
317
|
+
"limit": limit,
|
318
|
+
"offset": offset,
|
319
|
+
"sort": sort,
|
320
|
+
}
|
321
|
+
|
322
|
+
response = self.request("POST", "/deployments/filter", json=body)
|
323
|
+
return DeploymentResponse.model_validate_list(response.json())
|
324
|
+
|
325
|
+
def delete_deployment(
|
326
|
+
self,
|
327
|
+
deployment_id: "UUID",
|
328
|
+
) -> None:
|
329
|
+
"""
|
330
|
+
Delete deployment by id.
|
331
|
+
|
332
|
+
Args:
|
333
|
+
deployment_id: The deployment id of interest.
|
334
|
+
Raises:
|
335
|
+
ObjectNotFound: If request returns 404
|
336
|
+
RequestError: If requests fails
|
337
|
+
"""
|
338
|
+
try:
|
339
|
+
self.request(
|
340
|
+
"DELETE",
|
341
|
+
"/deployments/{id}",
|
342
|
+
path_params={"id": deployment_id},
|
343
|
+
)
|
344
|
+
except HTTPStatusError as e:
|
345
|
+
if e.response.status_code == 404:
|
346
|
+
raise ObjectNotFound(http_exc=e) from e
|
347
|
+
else:
|
348
|
+
raise
|
349
|
+
|
350
|
+
def create_deployment_schedules(
|
351
|
+
self,
|
352
|
+
deployment_id: "UUID",
|
353
|
+
schedules: list[tuple["SCHEDULE_TYPES", bool]],
|
354
|
+
) -> list["DeploymentSchedule"]:
|
355
|
+
"""
|
356
|
+
Create deployment schedules.
|
357
|
+
|
358
|
+
Args:
|
359
|
+
deployment_id: the deployment ID
|
360
|
+
schedules: a list of tuples containing the schedule to create
|
361
|
+
and whether or not it should be active.
|
362
|
+
|
363
|
+
Raises:
|
364
|
+
RequestError: if the schedules were not created for any reason
|
365
|
+
|
366
|
+
Returns:
|
367
|
+
the list of schedules created in the backend
|
368
|
+
"""
|
369
|
+
from prefect.client.schemas.actions import DeploymentScheduleCreate
|
370
|
+
from prefect.client.schemas.objects import DeploymentSchedule
|
371
|
+
|
372
|
+
deployment_schedule_create = [
|
373
|
+
DeploymentScheduleCreate(schedule=schedule[0], active=schedule[1])
|
374
|
+
for schedule in schedules
|
375
|
+
]
|
376
|
+
|
377
|
+
json = [
|
378
|
+
deployment_schedule_create.model_dump(mode="json")
|
379
|
+
for deployment_schedule_create in deployment_schedule_create
|
380
|
+
]
|
381
|
+
response = self.request(
|
382
|
+
"POST",
|
383
|
+
"/deployments/{id}/schedules",
|
384
|
+
path_params={"id": deployment_id},
|
385
|
+
json=json,
|
386
|
+
)
|
387
|
+
return DeploymentSchedule.model_validate_list(response.json())
|
388
|
+
|
389
|
+
def read_deployment_schedules(
|
390
|
+
self,
|
391
|
+
deployment_id: "UUID",
|
392
|
+
) -> list["DeploymentSchedule"]:
|
393
|
+
"""
|
394
|
+
Query the Prefect API for a deployment's schedules.
|
395
|
+
|
396
|
+
Args:
|
397
|
+
deployment_id: the deployment ID
|
398
|
+
|
399
|
+
Returns:
|
400
|
+
a list of DeploymentSchedule model representations of the deployment schedules
|
401
|
+
"""
|
402
|
+
from prefect.client.schemas.objects import DeploymentSchedule
|
403
|
+
|
404
|
+
try:
|
405
|
+
response = self.request(
|
406
|
+
"GET",
|
407
|
+
"/deployments/{id}/schedules",
|
408
|
+
path_params={"id": deployment_id},
|
409
|
+
)
|
410
|
+
except HTTPStatusError as e:
|
411
|
+
if e.response.status_code == 404:
|
412
|
+
raise ObjectNotFound(http_exc=e) from e
|
413
|
+
else:
|
414
|
+
raise
|
415
|
+
return DeploymentSchedule.model_validate_list(response.json())
|
416
|
+
|
417
|
+
def update_deployment_schedule(
|
418
|
+
self,
|
419
|
+
deployment_id: "UUID",
|
420
|
+
schedule_id: "UUID",
|
421
|
+
active: bool | None = None,
|
422
|
+
schedule: "SCHEDULE_TYPES | None" = None,
|
423
|
+
) -> None:
|
424
|
+
"""
|
425
|
+
Update a deployment schedule by ID.
|
426
|
+
|
427
|
+
Args:
|
428
|
+
deployment_id: the deployment ID
|
429
|
+
schedule_id: the deployment schedule ID of interest
|
430
|
+
active: whether or not the schedule should be active
|
431
|
+
schedule: the cron, rrule, or interval schedule this deployment schedule should use
|
432
|
+
"""
|
433
|
+
from prefect.client.schemas.actions import DeploymentScheduleUpdate
|
434
|
+
|
435
|
+
kwargs: dict[str, Any] = {}
|
436
|
+
if active is not None:
|
437
|
+
kwargs["active"] = active
|
438
|
+
if schedule is not None:
|
439
|
+
kwargs["schedule"] = schedule
|
440
|
+
|
441
|
+
deployment_schedule_update = DeploymentScheduleUpdate(**kwargs)
|
442
|
+
json = deployment_schedule_update.model_dump(mode="json", exclude_unset=True)
|
443
|
+
|
444
|
+
try:
|
445
|
+
self.request(
|
446
|
+
"PATCH",
|
447
|
+
"/deployments/{id}/schedules/{schedule_id}",
|
448
|
+
path_params={"id": deployment_id, "schedule_id": schedule_id},
|
449
|
+
json=json,
|
450
|
+
)
|
451
|
+
except HTTPStatusError as e:
|
452
|
+
if e.response.status_code == 404:
|
453
|
+
raise ObjectNotFound(http_exc=e) from e
|
454
|
+
else:
|
455
|
+
raise
|
456
|
+
|
457
|
+
def delete_deployment_schedule(
|
458
|
+
self,
|
459
|
+
deployment_id: "UUID",
|
460
|
+
schedule_id: "UUID",
|
461
|
+
) -> None:
|
462
|
+
"""
|
463
|
+
Delete a deployment schedule.
|
464
|
+
|
465
|
+
Args:
|
466
|
+
deployment_id: the deployment ID
|
467
|
+
schedule_id: the ID of the deployment schedule to delete.
|
468
|
+
|
469
|
+
Raises:
|
470
|
+
RequestError: if the schedules were not deleted for any reason
|
471
|
+
"""
|
472
|
+
try:
|
473
|
+
self.request(
|
474
|
+
"DELETE",
|
475
|
+
"/deployments/{id}/schedules/{schedule_id}",
|
476
|
+
path_params={"id": deployment_id, "schedule_id": schedule_id},
|
477
|
+
)
|
478
|
+
except HTTPStatusError as e:
|
479
|
+
if e.response.status_code == 404:
|
480
|
+
raise ObjectNotFound(http_exc=e) from e
|
481
|
+
else:
|
482
|
+
raise
|
483
|
+
|
484
|
+
def get_scheduled_flow_runs_for_deployments(
|
485
|
+
self,
|
486
|
+
deployment_ids: list["UUID"],
|
487
|
+
scheduled_before: "datetime.datetime | None" = None,
|
488
|
+
limit: int | None = None,
|
489
|
+
) -> list["FlowRunResponse"]:
|
490
|
+
from prefect.client.schemas.responses import FlowRunResponse
|
491
|
+
|
492
|
+
body: dict[str, Any] = dict(deployment_ids=[str(id) for id in deployment_ids])
|
493
|
+
if scheduled_before:
|
494
|
+
body["scheduled_before"] = str(scheduled_before)
|
495
|
+
if limit:
|
496
|
+
body["limit"] = limit
|
497
|
+
|
498
|
+
response = self.request(
|
499
|
+
"POST",
|
500
|
+
"/deployments/get_scheduled_flow_runs",
|
501
|
+
json=body,
|
502
|
+
)
|
503
|
+
|
504
|
+
return FlowRunResponse.model_validate_list(response.json())
|
505
|
+
|
506
|
+
def create_flow_run_from_deployment(
|
507
|
+
self,
|
508
|
+
deployment_id: "UUID",
|
509
|
+
*,
|
510
|
+
parameters: dict[str, Any] | None = None,
|
511
|
+
context: dict[str, Any] | None = None,
|
512
|
+
state: State[Any] | None = None,
|
513
|
+
name: str | None = None,
|
514
|
+
tags: Iterable[str] | None = None,
|
515
|
+
idempotency_key: str | None = None,
|
516
|
+
parent_task_run_id: "UUID | None" = None,
|
517
|
+
work_queue_name: str | None = None,
|
518
|
+
job_variables: dict[str, Any] | None = None,
|
519
|
+
labels: "KeyValueLabelsField | None" = None,
|
520
|
+
) -> "FlowRun":
|
521
|
+
"""
|
522
|
+
Create a flow run for a deployment.
|
523
|
+
|
524
|
+
Args:
|
525
|
+
deployment_id: The deployment ID to create the flow run from
|
526
|
+
parameters: Parameter overrides for this flow run. Merged with the
|
527
|
+
deployment defaults
|
528
|
+
context: Optional run context data
|
529
|
+
state: The initial state for the run. If not provided, defaults to
|
530
|
+
`Scheduled` for now. Should always be a `Scheduled` type.
|
531
|
+
name: An optional name for the flow run. If not provided, the server will
|
532
|
+
generate a name.
|
533
|
+
tags: An optional iterable of tags to apply to the flow run; these tags
|
534
|
+
are merged with the deployment's tags.
|
535
|
+
idempotency_key: Optional idempotency key for creation of the flow run.
|
536
|
+
If the key matches the key of an existing flow run, the existing run will
|
537
|
+
be returned instead of creating a new one.
|
538
|
+
parent_task_run_id: if a subflow run is being created, the placeholder task
|
539
|
+
run identifier in the parent flow
|
540
|
+
work_queue_name: An optional work queue name to add this run to. If not provided,
|
541
|
+
will default to the deployment's set work queue. If one is provided that does not
|
542
|
+
exist, a new work queue will be created within the deployment's work pool.
|
543
|
+
job_variables: Optional variables that will be supplied to the flow run job.
|
544
|
+
|
545
|
+
Raises:
|
546
|
+
RequestError: if the Prefect API does not successfully create a run for any reason
|
547
|
+
|
548
|
+
Returns:
|
549
|
+
The flow run model
|
550
|
+
"""
|
551
|
+
from prefect.client.schemas.actions import DeploymentFlowRunCreate
|
552
|
+
from prefect.client.schemas.objects import FlowRun
|
553
|
+
from prefect.states import Scheduled
|
554
|
+
|
555
|
+
parameters = parameters or {}
|
556
|
+
context = context or {}
|
557
|
+
state = state or Scheduled()
|
558
|
+
tags = tags or []
|
559
|
+
labels = labels or {}
|
560
|
+
|
561
|
+
flow_run_create = DeploymentFlowRunCreate(
|
562
|
+
parameters=parameters,
|
563
|
+
context=context,
|
564
|
+
state=state.to_state_create(),
|
565
|
+
tags=list(tags),
|
566
|
+
name=name,
|
567
|
+
idempotency_key=idempotency_key,
|
568
|
+
parent_task_run_id=parent_task_run_id,
|
569
|
+
job_variables=job_variables,
|
570
|
+
labels=labels,
|
571
|
+
)
|
572
|
+
|
573
|
+
# done separately to avoid including this field in payloads sent to older API versions
|
574
|
+
if work_queue_name:
|
575
|
+
flow_run_create.work_queue_name = work_queue_name
|
576
|
+
|
577
|
+
response = self.request(
|
578
|
+
"POST",
|
579
|
+
"/deployments/{id}/create_flow_run",
|
580
|
+
path_params={"id": deployment_id},
|
581
|
+
json=flow_run_create.model_dump(mode="json", exclude_unset=True),
|
582
|
+
)
|
583
|
+
return FlowRun.model_validate(response.json())
|
584
|
+
|
585
|
+
|
586
|
+
class DeploymentAsyncClient(BaseAsyncClient):
|
587
|
+
async def create_deployment(
|
588
|
+
self,
|
589
|
+
flow_id: "UUID",
|
590
|
+
name: str,
|
591
|
+
version: str | None = None,
|
592
|
+
schedules: list["DeploymentScheduleCreate"] | None = None,
|
593
|
+
concurrency_limit: int | None = None,
|
594
|
+
concurrency_options: "ConcurrencyOptions | None" = None,
|
595
|
+
parameters: dict[str, Any] | None = None,
|
596
|
+
description: str | None = None,
|
597
|
+
work_queue_name: str | None = None,
|
598
|
+
work_pool_name: str | None = None,
|
599
|
+
tags: list[str] | None = None,
|
600
|
+
storage_document_id: "UUID | None" = None,
|
601
|
+
path: str | None = None,
|
602
|
+
entrypoint: str | None = None,
|
603
|
+
infrastructure_document_id: "UUID | None" = None,
|
604
|
+
parameter_openapi_schema: dict[str, Any] | None = None,
|
605
|
+
paused: bool | None = None,
|
606
|
+
pull_steps: list[dict[str, Any]] | None = None,
|
607
|
+
enforce_parameter_schema: bool | None = None,
|
608
|
+
job_variables: dict[str, Any] | None = None,
|
609
|
+
) -> "UUID":
|
610
|
+
"""
|
611
|
+
Create a deployment.
|
612
|
+
|
613
|
+
Args:
|
614
|
+
flow_id: the flow ID to create a deployment for
|
615
|
+
name: the name of the deployment
|
616
|
+
version: an optional version string for the deployment
|
617
|
+
tags: an optional list of tags to apply to the deployment
|
618
|
+
storage_document_id: an reference to the storage block document
|
619
|
+
used for the deployed flow
|
620
|
+
infrastructure_document_id: an reference to the infrastructure block document
|
621
|
+
to use for this deployment
|
622
|
+
job_variables: A dictionary of dot delimited infrastructure overrides that
|
623
|
+
will be applied at runtime; for example `env.CONFIG_KEY=config_value` or
|
624
|
+
`namespace='prefect'`. This argument was previously named `infra_overrides`.
|
625
|
+
Both arguments are supported for backwards compatibility.
|
626
|
+
|
627
|
+
Raises:
|
628
|
+
RequestError: if the deployment was not created for any reason
|
629
|
+
|
630
|
+
Returns:
|
631
|
+
the ID of the deployment in the backend
|
632
|
+
"""
|
633
|
+
from uuid import UUID
|
634
|
+
|
635
|
+
from prefect.client.schemas.actions import DeploymentCreate
|
636
|
+
|
637
|
+
if parameter_openapi_schema is None:
|
638
|
+
parameter_openapi_schema = {}
|
639
|
+
deployment_create = DeploymentCreate(
|
640
|
+
flow_id=flow_id,
|
641
|
+
name=name,
|
642
|
+
version=version,
|
643
|
+
parameters=dict(parameters or {}),
|
644
|
+
tags=list(tags or []),
|
645
|
+
work_queue_name=work_queue_name,
|
646
|
+
description=description,
|
647
|
+
storage_document_id=storage_document_id,
|
648
|
+
path=path,
|
649
|
+
entrypoint=entrypoint,
|
650
|
+
infrastructure_document_id=infrastructure_document_id,
|
651
|
+
job_variables=dict(job_variables or {}),
|
652
|
+
parameter_openapi_schema=parameter_openapi_schema,
|
653
|
+
paused=paused,
|
654
|
+
schedules=schedules or [],
|
655
|
+
concurrency_limit=concurrency_limit,
|
656
|
+
concurrency_options=concurrency_options,
|
657
|
+
pull_steps=pull_steps,
|
658
|
+
enforce_parameter_schema=enforce_parameter_schema,
|
659
|
+
)
|
660
|
+
|
661
|
+
if work_pool_name is not None:
|
662
|
+
deployment_create.work_pool_name = work_pool_name
|
663
|
+
|
664
|
+
# Exclude newer fields that are not set to avoid compatibility issues
|
665
|
+
exclude = {
|
666
|
+
field
|
667
|
+
for field in ["work_pool_name", "work_queue_name"]
|
668
|
+
if field not in deployment_create.model_fields_set
|
669
|
+
}
|
670
|
+
|
671
|
+
if deployment_create.paused is None:
|
672
|
+
exclude.add("paused")
|
673
|
+
|
674
|
+
if deployment_create.pull_steps is None:
|
675
|
+
exclude.add("pull_steps")
|
676
|
+
|
677
|
+
if deployment_create.enforce_parameter_schema is None:
|
678
|
+
exclude.add("enforce_parameter_schema")
|
679
|
+
|
680
|
+
json = deployment_create.model_dump(mode="json", exclude=exclude)
|
681
|
+
response = await self.request(
|
682
|
+
"POST",
|
683
|
+
"/deployments/",
|
684
|
+
json=json,
|
685
|
+
)
|
686
|
+
deployment_id = response.json().get("id")
|
687
|
+
if not deployment_id:
|
688
|
+
raise RequestError(f"Malformed response: {response}")
|
689
|
+
|
690
|
+
return UUID(deployment_id)
|
691
|
+
|
692
|
+
async def set_deployment_paused_state(
|
693
|
+
self, deployment_id: "UUID", paused: bool
|
694
|
+
) -> None:
|
695
|
+
await self.request(
|
696
|
+
"PATCH",
|
697
|
+
"/deployments/{id}",
|
698
|
+
path_params={"id": deployment_id},
|
699
|
+
json={"paused": paused},
|
700
|
+
)
|
701
|
+
|
702
|
+
async def update_deployment(
|
703
|
+
self,
|
704
|
+
deployment_id: "UUID",
|
705
|
+
deployment: "DeploymentUpdate",
|
706
|
+
) -> None:
|
707
|
+
await self.request(
|
708
|
+
"PATCH",
|
709
|
+
"/deployments/{id}",
|
710
|
+
path_params={"id": deployment_id},
|
711
|
+
json=deployment.model_dump(mode="json", exclude_unset=True),
|
712
|
+
)
|
713
|
+
|
714
|
+
async def _create_deployment_from_schema(
|
715
|
+
self, schema: "DeploymentCreate"
|
716
|
+
) -> "UUID":
|
717
|
+
"""
|
718
|
+
Create a deployment from a prepared `DeploymentCreate` schema.
|
719
|
+
"""
|
720
|
+
from uuid import UUID
|
721
|
+
|
722
|
+
# TODO: We are likely to remove this method once we have considered the
|
723
|
+
# packaging interface for deployments further.
|
724
|
+
response = await self.request(
|
725
|
+
"POST", "/deployments/", json=schema.model_dump(mode="json")
|
726
|
+
)
|
727
|
+
deployment_id = response.json().get("id")
|
728
|
+
if not deployment_id:
|
729
|
+
raise RequestError(f"Malformed response: {response}")
|
730
|
+
|
731
|
+
return UUID(deployment_id)
|
732
|
+
|
733
|
+
async def read_deployment(
|
734
|
+
self,
|
735
|
+
deployment_id: Union["UUID", str],
|
736
|
+
) -> "DeploymentResponse":
|
737
|
+
"""
|
738
|
+
Query the Prefect API for a deployment by id.
|
739
|
+
|
740
|
+
Args:
|
741
|
+
deployment_id: the deployment ID of interest
|
742
|
+
|
743
|
+
Returns:
|
744
|
+
a [Deployment model][prefect.client.schemas.objects.Deployment] representation of the deployment
|
745
|
+
"""
|
746
|
+
from uuid import UUID
|
747
|
+
|
748
|
+
from prefect.client.schemas.responses import DeploymentResponse
|
749
|
+
|
750
|
+
if not isinstance(deployment_id, UUID):
|
751
|
+
try:
|
752
|
+
deployment_id = UUID(deployment_id)
|
753
|
+
except ValueError:
|
754
|
+
raise ValueError(f"Invalid deployment ID: {deployment_id}")
|
755
|
+
|
756
|
+
try:
|
757
|
+
response = await self.request(
|
758
|
+
"GET",
|
759
|
+
"/deployments/{id}",
|
760
|
+
path_params={"id": deployment_id},
|
761
|
+
)
|
762
|
+
except HTTPStatusError as e:
|
763
|
+
if e.response.status_code == 404:
|
764
|
+
raise ObjectNotFound(http_exc=e) from e
|
765
|
+
else:
|
766
|
+
raise
|
767
|
+
return DeploymentResponse.model_validate(response.json())
|
768
|
+
|
769
|
+
async def read_deployment_by_name(
|
770
|
+
self,
|
771
|
+
name: str,
|
772
|
+
) -> "DeploymentResponse":
|
773
|
+
"""
|
774
|
+
Query the Prefect API for a deployment by name.
|
775
|
+
|
776
|
+
Args:
|
777
|
+
name: A deployed flow's name: <FLOW_NAME>/<DEPLOYMENT_NAME>
|
778
|
+
|
779
|
+
Raises:
|
780
|
+
ObjectNotFound: If request returns 404
|
781
|
+
RequestError: If request fails
|
782
|
+
|
783
|
+
Returns:
|
784
|
+
a Deployment model representation of the deployment
|
785
|
+
"""
|
786
|
+
from prefect.client.schemas.responses import DeploymentResponse
|
787
|
+
|
788
|
+
try:
|
789
|
+
flow_name, deployment_name = name.split("/")
|
790
|
+
response = await self.request(
|
791
|
+
"GET",
|
792
|
+
"/deployments/name/{flow_name}/{deployment_name}",
|
793
|
+
path_params={
|
794
|
+
"flow_name": flow_name,
|
795
|
+
"deployment_name": deployment_name,
|
796
|
+
},
|
797
|
+
)
|
798
|
+
except (HTTPStatusError, ValueError) as e:
|
799
|
+
if isinstance(e, HTTPStatusError) and e.response.status_code == 404:
|
800
|
+
raise ObjectNotFound(http_exc=e) from e
|
801
|
+
elif isinstance(e, ValueError):
|
802
|
+
raise ValueError(
|
803
|
+
f"Invalid deployment name format: {name}. Expected format: <FLOW_NAME>/<DEPLOYMENT_NAME>"
|
804
|
+
) from e
|
805
|
+
else:
|
806
|
+
raise
|
807
|
+
|
808
|
+
return DeploymentResponse.model_validate(response.json())
|
809
|
+
|
810
|
+
async def read_deployments(
|
811
|
+
self,
|
812
|
+
*,
|
813
|
+
flow_filter: "FlowFilter | None" = None,
|
814
|
+
flow_run_filter: "FlowRunFilter | None" = None,
|
815
|
+
task_run_filter: "TaskRunFilter | None" = None,
|
816
|
+
deployment_filter: "DeploymentFilter | None" = None,
|
817
|
+
work_pool_filter: "WorkPoolFilter | None" = None,
|
818
|
+
work_queue_filter: "WorkQueueFilter | None" = None,
|
819
|
+
limit: int | None = None,
|
820
|
+
sort: "DeploymentSort | None" = None,
|
821
|
+
offset: int = 0,
|
822
|
+
) -> list["DeploymentResponse"]:
|
823
|
+
"""
|
824
|
+
Query the Prefect API for deployments. Only deployments matching all
|
825
|
+
the provided criteria will be returned.
|
826
|
+
|
827
|
+
Args:
|
828
|
+
flow_filter: filter criteria for flows
|
829
|
+
flow_run_filter: filter criteria for flow runs
|
830
|
+
task_run_filter: filter criteria for task runs
|
831
|
+
deployment_filter: filter criteria for deployments
|
832
|
+
work_pool_filter: filter criteria for work pools
|
833
|
+
work_queue_filter: filter criteria for work pool queues
|
834
|
+
limit: a limit for the deployment query
|
835
|
+
offset: an offset for the deployment query
|
836
|
+
|
837
|
+
Returns:
|
838
|
+
a list of Deployment model representations
|
839
|
+
of the deployments
|
840
|
+
"""
|
841
|
+
from prefect.client.schemas.responses import DeploymentResponse
|
842
|
+
|
843
|
+
body: dict[str, Any] = {
|
844
|
+
"flows": flow_filter.model_dump(mode="json") if flow_filter else None,
|
845
|
+
"flow_runs": (
|
846
|
+
flow_run_filter.model_dump(mode="json", exclude_unset=True)
|
847
|
+
if flow_run_filter
|
848
|
+
else None
|
849
|
+
),
|
850
|
+
"task_runs": (
|
851
|
+
task_run_filter.model_dump(mode="json") if task_run_filter else None
|
852
|
+
),
|
853
|
+
"deployments": (
|
854
|
+
deployment_filter.model_dump(mode="json") if deployment_filter else None
|
855
|
+
),
|
856
|
+
"work_pools": (
|
857
|
+
work_pool_filter.model_dump(mode="json") if work_pool_filter else None
|
858
|
+
),
|
859
|
+
"work_pool_queues": (
|
860
|
+
work_queue_filter.model_dump(mode="json") if work_queue_filter else None
|
861
|
+
),
|
862
|
+
"limit": limit,
|
863
|
+
"offset": offset,
|
864
|
+
"sort": sort,
|
865
|
+
}
|
866
|
+
|
867
|
+
response = await self.request("POST", "/deployments/filter", json=body)
|
868
|
+
return DeploymentResponse.model_validate_list(response.json())
|
869
|
+
|
870
|
+
async def delete_deployment(
|
871
|
+
self,
|
872
|
+
deployment_id: "UUID",
|
873
|
+
) -> None:
|
874
|
+
"""
|
875
|
+
Delete deployment by id.
|
876
|
+
|
877
|
+
Args:
|
878
|
+
deployment_id: The deployment id of interest.
|
879
|
+
Raises:
|
880
|
+
ObjectNotFound: If request returns 404
|
881
|
+
RequestError: If requests fails
|
882
|
+
"""
|
883
|
+
try:
|
884
|
+
await self.request(
|
885
|
+
"DELETE",
|
886
|
+
"/deployments/{id}",
|
887
|
+
path_params={"id": deployment_id},
|
888
|
+
)
|
889
|
+
except HTTPStatusError as e:
|
890
|
+
if e.response.status_code == 404:
|
891
|
+
raise ObjectNotFound(http_exc=e) from e
|
892
|
+
else:
|
893
|
+
raise
|
894
|
+
|
895
|
+
async def create_deployment_schedules(
|
896
|
+
self,
|
897
|
+
deployment_id: "UUID",
|
898
|
+
schedules: list[tuple["SCHEDULE_TYPES", bool]],
|
899
|
+
) -> list["DeploymentSchedule"]:
|
900
|
+
"""
|
901
|
+
Create deployment schedules.
|
902
|
+
|
903
|
+
Args:
|
904
|
+
deployment_id: the deployment ID
|
905
|
+
schedules: a list of tuples containing the schedule to create
|
906
|
+
and whether or not it should be active.
|
907
|
+
|
908
|
+
Raises:
|
909
|
+
RequestError: if the schedules were not created for any reason
|
910
|
+
|
911
|
+
Returns:
|
912
|
+
the list of schedules created in the backend
|
913
|
+
"""
|
914
|
+
from prefect.client.schemas.actions import DeploymentScheduleCreate
|
915
|
+
from prefect.client.schemas.objects import DeploymentSchedule
|
916
|
+
|
917
|
+
deployment_schedule_create = [
|
918
|
+
DeploymentScheduleCreate(schedule=schedule[0], active=schedule[1])
|
919
|
+
for schedule in schedules
|
920
|
+
]
|
921
|
+
|
922
|
+
json = [
|
923
|
+
deployment_schedule_create.model_dump(mode="json")
|
924
|
+
for deployment_schedule_create in deployment_schedule_create
|
925
|
+
]
|
926
|
+
response = await self.request(
|
927
|
+
"POST",
|
928
|
+
"/deployments/{id}/schedules",
|
929
|
+
path_params={"id": deployment_id},
|
930
|
+
json=json,
|
931
|
+
)
|
932
|
+
return DeploymentSchedule.model_validate_list(response.json())
|
933
|
+
|
934
|
+
async def read_deployment_schedules(
|
935
|
+
self,
|
936
|
+
deployment_id: "UUID",
|
937
|
+
) -> list["DeploymentSchedule"]:
|
938
|
+
"""
|
939
|
+
Query the Prefect API for a deployment's schedules.
|
940
|
+
|
941
|
+
Args:
|
942
|
+
deployment_id: the deployment ID
|
943
|
+
|
944
|
+
Returns:
|
945
|
+
a list of DeploymentSchedule model representations of the deployment schedules
|
946
|
+
"""
|
947
|
+
from prefect.client.schemas.objects import DeploymentSchedule
|
948
|
+
|
949
|
+
try:
|
950
|
+
response = await self.request(
|
951
|
+
"GET",
|
952
|
+
"/deployments/{id}/schedules",
|
953
|
+
path_params={"id": deployment_id},
|
954
|
+
)
|
955
|
+
except HTTPStatusError as e:
|
956
|
+
if e.response.status_code == 404:
|
957
|
+
raise ObjectNotFound(http_exc=e) from e
|
958
|
+
else:
|
959
|
+
raise
|
960
|
+
return DeploymentSchedule.model_validate_list(response.json())
|
961
|
+
|
962
|
+
async def update_deployment_schedule(
|
963
|
+
self,
|
964
|
+
deployment_id: "UUID",
|
965
|
+
schedule_id: "UUID",
|
966
|
+
active: bool | None = None,
|
967
|
+
schedule: "SCHEDULE_TYPES | None" = None,
|
968
|
+
) -> None:
|
969
|
+
"""
|
970
|
+
Update a deployment schedule by ID.
|
971
|
+
|
972
|
+
Args:
|
973
|
+
deployment_id: the deployment ID
|
974
|
+
schedule_id: the deployment schedule ID of interest
|
975
|
+
active: whether or not the schedule should be active
|
976
|
+
schedule: the cron, rrule, or interval schedule this deployment schedule should use
|
977
|
+
"""
|
978
|
+
from prefect.client.schemas.actions import DeploymentScheduleUpdate
|
979
|
+
|
980
|
+
kwargs: dict[str, Any] = {}
|
981
|
+
if active is not None:
|
982
|
+
kwargs["active"] = active
|
983
|
+
if schedule is not None:
|
984
|
+
kwargs["schedule"] = schedule
|
985
|
+
|
986
|
+
deployment_schedule_update = DeploymentScheduleUpdate(**kwargs)
|
987
|
+
json = deployment_schedule_update.model_dump(mode="json", exclude_unset=True)
|
988
|
+
|
989
|
+
try:
|
990
|
+
await self.request(
|
991
|
+
"PATCH",
|
992
|
+
"/deployments/{id}/schedules/{schedule_id}",
|
993
|
+
path_params={"id": deployment_id, "schedule_id": schedule_id},
|
994
|
+
json=json,
|
995
|
+
)
|
996
|
+
except HTTPStatusError as e:
|
997
|
+
if e.response.status_code == 404:
|
998
|
+
raise ObjectNotFound(http_exc=e) from e
|
999
|
+
else:
|
1000
|
+
raise
|
1001
|
+
|
1002
|
+
async def delete_deployment_schedule(
|
1003
|
+
self,
|
1004
|
+
deployment_id: "UUID",
|
1005
|
+
schedule_id: "UUID",
|
1006
|
+
) -> None:
|
1007
|
+
"""
|
1008
|
+
Delete a deployment schedule.
|
1009
|
+
|
1010
|
+
Args:
|
1011
|
+
deployment_id: the deployment ID
|
1012
|
+
schedule_id: the ID of the deployment schedule to delete.
|
1013
|
+
|
1014
|
+
Raises:
|
1015
|
+
RequestError: if the schedules were not deleted for any reason
|
1016
|
+
"""
|
1017
|
+
try:
|
1018
|
+
await self.request(
|
1019
|
+
"DELETE",
|
1020
|
+
"/deployments/{id}/schedules/{schedule_id}",
|
1021
|
+
path_params={"id": deployment_id, "schedule_id": schedule_id},
|
1022
|
+
)
|
1023
|
+
except HTTPStatusError as e:
|
1024
|
+
if e.response.status_code == 404:
|
1025
|
+
raise ObjectNotFound(http_exc=e) from e
|
1026
|
+
else:
|
1027
|
+
raise
|
1028
|
+
|
1029
|
+
async def get_scheduled_flow_runs_for_deployments(
|
1030
|
+
self,
|
1031
|
+
deployment_ids: list["UUID"],
|
1032
|
+
scheduled_before: "datetime.datetime | None" = None,
|
1033
|
+
limit: int | None = None,
|
1034
|
+
) -> list["FlowRunResponse"]:
|
1035
|
+
from prefect.client.schemas.responses import FlowRunResponse
|
1036
|
+
|
1037
|
+
body: dict[str, Any] = dict(deployment_ids=[str(id) for id in deployment_ids])
|
1038
|
+
if scheduled_before:
|
1039
|
+
body["scheduled_before"] = str(scheduled_before)
|
1040
|
+
if limit:
|
1041
|
+
body["limit"] = limit
|
1042
|
+
|
1043
|
+
response = await self.request(
|
1044
|
+
"POST",
|
1045
|
+
"/deployments/get_scheduled_flow_runs",
|
1046
|
+
json=body,
|
1047
|
+
)
|
1048
|
+
|
1049
|
+
return FlowRunResponse.model_validate_list(response.json())
|
1050
|
+
|
1051
|
+
async def create_flow_run_from_deployment(
|
1052
|
+
self,
|
1053
|
+
deployment_id: "UUID",
|
1054
|
+
*,
|
1055
|
+
parameters: dict[str, Any] | None = None,
|
1056
|
+
context: dict[str, Any] | None = None,
|
1057
|
+
state: State[Any] | None = None,
|
1058
|
+
name: str | None = None,
|
1059
|
+
tags: Iterable[str] | None = None,
|
1060
|
+
idempotency_key: str | None = None,
|
1061
|
+
parent_task_run_id: "UUID | None" = None,
|
1062
|
+
work_queue_name: str | None = None,
|
1063
|
+
job_variables: dict[str, Any] | None = None,
|
1064
|
+
labels: "KeyValueLabelsField | None" = None,
|
1065
|
+
) -> "FlowRun":
|
1066
|
+
"""
|
1067
|
+
Create a flow run for a deployment.
|
1068
|
+
|
1069
|
+
Args:
|
1070
|
+
deployment_id: The deployment ID to create the flow run from
|
1071
|
+
parameters: Parameter overrides for this flow run. Merged with the
|
1072
|
+
deployment defaults
|
1073
|
+
context: Optional run context data
|
1074
|
+
state: The initial state for the run. If not provided, defaults to
|
1075
|
+
`Scheduled` for now. Should always be a `Scheduled` type.
|
1076
|
+
name: An optional name for the flow run. If not provided, the server will
|
1077
|
+
generate a name.
|
1078
|
+
tags: An optional iterable of tags to apply to the flow run; these tags
|
1079
|
+
are merged with the deployment's tags.
|
1080
|
+
idempotency_key: Optional idempotency key for creation of the flow run.
|
1081
|
+
If the key matches the key of an existing flow run, the existing run will
|
1082
|
+
be returned instead of creating a new one.
|
1083
|
+
parent_task_run_id: if a subflow run is being created, the placeholder task
|
1084
|
+
run identifier in the parent flow
|
1085
|
+
work_queue_name: An optional work queue name to add this run to. If not provided,
|
1086
|
+
will default to the deployment's set work queue. If one is provided that does not
|
1087
|
+
exist, a new work queue will be created within the deployment's work pool.
|
1088
|
+
job_variables: Optional variables that will be supplied to the flow run job.
|
1089
|
+
|
1090
|
+
Raises:
|
1091
|
+
RequestError: if the Prefect API does not successfully create a run for any reason
|
1092
|
+
|
1093
|
+
Returns:
|
1094
|
+
The flow run model
|
1095
|
+
"""
|
1096
|
+
from prefect.client.schemas.actions import DeploymentFlowRunCreate
|
1097
|
+
from prefect.client.schemas.objects import FlowRun
|
1098
|
+
from prefect.states import Scheduled
|
1099
|
+
|
1100
|
+
parameters = parameters or {}
|
1101
|
+
context = context or {}
|
1102
|
+
state = state or Scheduled()
|
1103
|
+
tags = tags or []
|
1104
|
+
labels = labels or {}
|
1105
|
+
|
1106
|
+
flow_run_create = DeploymentFlowRunCreate(
|
1107
|
+
parameters=parameters,
|
1108
|
+
context=context,
|
1109
|
+
state=state.to_state_create(),
|
1110
|
+
tags=list(tags),
|
1111
|
+
name=name,
|
1112
|
+
idempotency_key=idempotency_key,
|
1113
|
+
parent_task_run_id=parent_task_run_id,
|
1114
|
+
job_variables=job_variables,
|
1115
|
+
labels=labels,
|
1116
|
+
)
|
1117
|
+
|
1118
|
+
# done separately to avoid including this field in payloads sent to older API versions
|
1119
|
+
if work_queue_name:
|
1120
|
+
flow_run_create.work_queue_name = work_queue_name
|
1121
|
+
|
1122
|
+
response = await self.request(
|
1123
|
+
"POST",
|
1124
|
+
"/deployments/{id}/create_flow_run",
|
1125
|
+
path_params={"id": deployment_id},
|
1126
|
+
json=flow_run_create.model_dump(mode="json", exclude_unset=True),
|
1127
|
+
)
|
1128
|
+
return FlowRun.model_validate(response.json())
|