prefect-client 3.1.11__py3-none-any.whl → 3.1.13__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 (133) hide show
  1. prefect/_experimental/sla/__init__.py +0 -0
  2. prefect/_experimental/sla/client.py +92 -0
  3. prefect/_experimental/sla/objects.py +61 -0
  4. prefect/_internal/concurrency/services.py +2 -2
  5. prefect/_internal/concurrency/threads.py +6 -0
  6. prefect/_internal/retries.py +6 -3
  7. prefect/_internal/schemas/validators.py +6 -4
  8. prefect/_version.py +3 -3
  9. prefect/artifacts.py +4 -1
  10. prefect/automations.py +236 -30
  11. prefect/blocks/__init__.py +3 -3
  12. prefect/blocks/abstract.py +57 -31
  13. prefect/blocks/core.py +181 -82
  14. prefect/blocks/notifications.py +134 -73
  15. prefect/blocks/redis.py +13 -9
  16. prefect/blocks/system.py +24 -11
  17. prefect/blocks/webhook.py +7 -5
  18. prefect/cache_policies.py +23 -22
  19. prefect/client/orchestration/__init__.py +103 -2006
  20. prefect/client/orchestration/_automations/__init__.py +0 -0
  21. prefect/client/orchestration/_automations/client.py +329 -0
  22. prefect/client/orchestration/_blocks_documents/__init__.py +0 -0
  23. prefect/client/orchestration/_blocks_documents/client.py +334 -0
  24. prefect/client/orchestration/_blocks_schemas/__init__.py +0 -0
  25. prefect/client/orchestration/_blocks_schemas/client.py +200 -0
  26. prefect/client/orchestration/_blocks_types/__init__.py +0 -0
  27. prefect/client/orchestration/_blocks_types/client.py +380 -0
  28. prefect/client/orchestration/_deployments/__init__.py +0 -0
  29. prefect/client/orchestration/_deployments/client.py +1128 -0
  30. prefect/client/orchestration/_flow_runs/__init__.py +0 -0
  31. prefect/client/orchestration/_flow_runs/client.py +903 -0
  32. prefect/client/orchestration/_flows/__init__.py +0 -0
  33. prefect/client/orchestration/_flows/client.py +343 -0
  34. prefect/client/orchestration/_logs/client.py +16 -14
  35. prefect/client/schemas/__init__.py +68 -28
  36. prefect/client/schemas/objects.py +5 -5
  37. prefect/client/utilities.py +3 -3
  38. prefect/context.py +15 -1
  39. prefect/deployments/base.py +13 -4
  40. prefect/deployments/flow_runs.py +5 -1
  41. prefect/deployments/runner.py +37 -1
  42. prefect/deployments/steps/core.py +1 -1
  43. prefect/deployments/steps/pull.py +8 -3
  44. prefect/deployments/steps/utility.py +2 -2
  45. prefect/docker/docker_image.py +13 -9
  46. prefect/engine.py +33 -11
  47. prefect/events/cli/automations.py +4 -4
  48. prefect/events/clients.py +17 -14
  49. prefect/events/schemas/automations.py +12 -8
  50. prefect/events/schemas/events.py +5 -1
  51. prefect/events/worker.py +1 -1
  52. prefect/filesystems.py +7 -3
  53. prefect/flow_engine.py +64 -47
  54. prefect/flows.py +128 -74
  55. prefect/futures.py +14 -7
  56. prefect/infrastructure/provisioners/__init__.py +2 -0
  57. prefect/infrastructure/provisioners/cloud_run.py +4 -4
  58. prefect/infrastructure/provisioners/coiled.py +249 -0
  59. prefect/infrastructure/provisioners/container_instance.py +4 -3
  60. prefect/infrastructure/provisioners/ecs.py +55 -43
  61. prefect/infrastructure/provisioners/modal.py +5 -4
  62. prefect/input/actions.py +5 -1
  63. prefect/input/run_input.py +157 -43
  64. prefect/logging/configuration.py +3 -3
  65. prefect/logging/filters.py +2 -2
  66. prefect/logging/formatters.py +15 -11
  67. prefect/logging/handlers.py +24 -14
  68. prefect/logging/highlighters.py +5 -5
  69. prefect/logging/loggers.py +28 -18
  70. prefect/logging/logging.yml +1 -1
  71. prefect/main.py +3 -1
  72. prefect/results.py +166 -86
  73. prefect/runner/runner.py +38 -29
  74. prefect/runner/server.py +3 -1
  75. prefect/runner/storage.py +18 -18
  76. prefect/runner/submit.py +19 -12
  77. prefect/runtime/deployment.py +15 -8
  78. prefect/runtime/flow_run.py +19 -6
  79. prefect/runtime/task_run.py +7 -3
  80. prefect/settings/base.py +17 -7
  81. prefect/settings/legacy.py +4 -4
  82. prefect/settings/models/api.py +4 -3
  83. prefect/settings/models/cli.py +4 -3
  84. prefect/settings/models/client.py +7 -4
  85. prefect/settings/models/cloud.py +9 -3
  86. prefect/settings/models/deployments.py +4 -3
  87. prefect/settings/models/experiments.py +4 -8
  88. prefect/settings/models/flows.py +4 -3
  89. prefect/settings/models/internal.py +4 -3
  90. prefect/settings/models/logging.py +8 -6
  91. prefect/settings/models/results.py +4 -3
  92. prefect/settings/models/root.py +11 -16
  93. prefect/settings/models/runner.py +8 -5
  94. prefect/settings/models/server/api.py +6 -3
  95. prefect/settings/models/server/database.py +120 -25
  96. prefect/settings/models/server/deployments.py +4 -3
  97. prefect/settings/models/server/ephemeral.py +7 -4
  98. prefect/settings/models/server/events.py +6 -3
  99. prefect/settings/models/server/flow_run_graph.py +4 -3
  100. prefect/settings/models/server/root.py +4 -3
  101. prefect/settings/models/server/services.py +15 -12
  102. prefect/settings/models/server/tasks.py +7 -4
  103. prefect/settings/models/server/ui.py +4 -3
  104. prefect/settings/models/tasks.py +10 -5
  105. prefect/settings/models/testing.py +4 -3
  106. prefect/settings/models/worker.py +7 -4
  107. prefect/settings/profiles.py +13 -12
  108. prefect/settings/sources.py +20 -19
  109. prefect/states.py +74 -51
  110. prefect/task_engine.py +43 -33
  111. prefect/task_runners.py +85 -72
  112. prefect/task_runs.py +20 -11
  113. prefect/task_worker.py +14 -9
  114. prefect/tasks.py +36 -28
  115. prefect/telemetry/bootstrap.py +13 -9
  116. prefect/telemetry/run_telemetry.py +15 -13
  117. prefect/telemetry/services.py +4 -0
  118. prefect/transactions.py +3 -3
  119. prefect/types/__init__.py +3 -1
  120. prefect/utilities/_deprecated.py +38 -0
  121. prefect/utilities/engine.py +11 -4
  122. prefect/utilities/filesystem.py +2 -2
  123. prefect/utilities/generics.py +1 -1
  124. prefect/utilities/pydantic.py +21 -36
  125. prefect/utilities/templating.py +25 -1
  126. prefect/workers/base.py +58 -33
  127. prefect/workers/process.py +20 -15
  128. prefect/workers/server.py +4 -5
  129. {prefect_client-3.1.11.dist-info → prefect_client-3.1.13.dist-info}/METADATA +3 -3
  130. {prefect_client-3.1.11.dist-info → prefect_client-3.1.13.dist-info}/RECORD +133 -114
  131. {prefect_client-3.1.11.dist-info → prefect_client-3.1.13.dist-info}/LICENSE +0 -0
  132. {prefect_client-3.1.11.dist-info → prefect_client-3.1.13.dist-info}/WHEEL +0 -0
  133. {prefect_client-3.1.11.dist-info → prefect_client-3.1.13.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())