prefect-client 3.2.1__py3-none-any.whl → 3.2.3__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 (72) hide show
  1. prefect/__init__.py +15 -8
  2. prefect/_build_info.py +5 -0
  3. prefect/_internal/schemas/bases.py +4 -7
  4. prefect/_internal/schemas/validators.py +5 -6
  5. prefect/_result_records.py +6 -1
  6. prefect/client/orchestration/__init__.py +18 -6
  7. prefect/client/schemas/schedules.py +2 -2
  8. prefect/concurrency/asyncio.py +4 -3
  9. prefect/concurrency/sync.py +3 -3
  10. prefect/concurrency/v1/asyncio.py +3 -3
  11. prefect/concurrency/v1/sync.py +3 -3
  12. prefect/deployments/flow_runs.py +2 -2
  13. prefect/docker/docker_image.py +2 -3
  14. prefect/engine.py +1 -1
  15. prefect/events/clients.py +4 -3
  16. prefect/events/related.py +3 -5
  17. prefect/flows.py +11 -5
  18. prefect/locking/filesystem.py +8 -8
  19. prefect/logging/handlers.py +7 -11
  20. prefect/main.py +0 -2
  21. prefect/runtime/flow_run.py +10 -17
  22. prefect/server/api/__init__.py +34 -0
  23. prefect/server/api/admin.py +85 -0
  24. prefect/server/api/artifacts.py +224 -0
  25. prefect/server/api/automations.py +239 -0
  26. prefect/server/api/block_capabilities.py +25 -0
  27. prefect/server/api/block_documents.py +164 -0
  28. prefect/server/api/block_schemas.py +153 -0
  29. prefect/server/api/block_types.py +211 -0
  30. prefect/server/api/clients.py +246 -0
  31. prefect/server/api/collections.py +75 -0
  32. prefect/server/api/concurrency_limits.py +286 -0
  33. prefect/server/api/concurrency_limits_v2.py +269 -0
  34. prefect/server/api/csrf_token.py +38 -0
  35. prefect/server/api/dependencies.py +196 -0
  36. prefect/server/api/deployments.py +941 -0
  37. prefect/server/api/events.py +300 -0
  38. prefect/server/api/flow_run_notification_policies.py +120 -0
  39. prefect/server/api/flow_run_states.py +52 -0
  40. prefect/server/api/flow_runs.py +867 -0
  41. prefect/server/api/flows.py +210 -0
  42. prefect/server/api/logs.py +43 -0
  43. prefect/server/api/middleware.py +73 -0
  44. prefect/server/api/root.py +35 -0
  45. prefect/server/api/run_history.py +170 -0
  46. prefect/server/api/saved_searches.py +99 -0
  47. prefect/server/api/server.py +891 -0
  48. prefect/server/api/task_run_states.py +52 -0
  49. prefect/server/api/task_runs.py +342 -0
  50. prefect/server/api/task_workers.py +31 -0
  51. prefect/server/api/templates.py +35 -0
  52. prefect/server/api/ui/__init__.py +3 -0
  53. prefect/server/api/ui/flow_runs.py +128 -0
  54. prefect/server/api/ui/flows.py +173 -0
  55. prefect/server/api/ui/schemas.py +63 -0
  56. prefect/server/api/ui/task_runs.py +175 -0
  57. prefect/server/api/validation.py +382 -0
  58. prefect/server/api/variables.py +181 -0
  59. prefect/server/api/work_queues.py +230 -0
  60. prefect/server/api/workers.py +656 -0
  61. prefect/settings/sources.py +18 -5
  62. prefect/states.py +3 -3
  63. prefect/task_engine.py +3 -3
  64. prefect/types/_datetime.py +82 -3
  65. prefect/utilities/dockerutils.py +2 -2
  66. prefect/workers/base.py +5 -5
  67. {prefect_client-3.2.1.dist-info → prefect_client-3.2.3.dist-info}/METADATA +10 -15
  68. {prefect_client-3.2.1.dist-info → prefect_client-3.2.3.dist-info}/RECORD +70 -32
  69. {prefect_client-3.2.1.dist-info → prefect_client-3.2.3.dist-info}/WHEEL +1 -2
  70. prefect/_version.py +0 -21
  71. prefect_client-3.2.1.dist-info/top_level.txt +0 -1
  72. {prefect_client-3.2.1.dist-info → prefect_client-3.2.3.dist-info/licenses}/LICENSE +0 -0
@@ -23,14 +23,14 @@ Available attributes:
23
23
  from __future__ import annotations
24
24
 
25
25
  import os
26
+ from datetime import datetime
26
27
  from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional
27
28
 
28
- import pendulum
29
-
30
29
  from prefect._internal.concurrency.api import create_call, from_sync
31
30
  from prefect.client.orchestration import get_client
32
31
  from prefect.context import FlowRunContext, TaskRunContext
33
32
  from prefect.settings import PREFECT_API_URL, PREFECT_UI_URL
33
+ from prefect.types._datetime import DateTime, Timezone, now, parse_datetime
34
34
 
35
35
  if TYPE_CHECKING:
36
36
  from prefect.client.schemas.objects import Flow, FlowRun, TaskRun
@@ -53,28 +53,21 @@ __all__ = [
53
53
  ]
54
54
 
55
55
 
56
- def _pendulum_parse(dt: str) -> pendulum.DateTime:
57
- """
58
- Use pendulum to cast different format date strings to pendulum.DateTime --
59
- tzinfo is ignored (UTC forced)
60
- """
61
- return pendulum.parse(dt, tz=None, strict=False).set(tz="UTC")
56
+ def _parse_datetime_UTC(dt: str) -> DateTime:
57
+ pendulum_dt = parse_datetime(dt, tz=Timezone("UTC"), strict=False)
58
+ assert isinstance(pendulum_dt, datetime)
59
+ return DateTime.instance(pendulum_dt)
62
60
 
63
61
 
64
62
  type_cast: dict[
65
- type[bool]
66
- | type[int]
67
- | type[float]
68
- | type[str]
69
- | type[None]
70
- | type[pendulum.DateTime],
63
+ type[bool] | type[int] | type[float] | type[str] | type[None] | type[DateTime],
71
64
  Callable[[Any], Any],
72
65
  ] = {
73
66
  bool: lambda x: x.lower() == "true",
74
67
  int: int,
75
68
  float: float,
76
69
  str: str,
77
- pendulum.DateTime: _pendulum_parse,
70
+ DateTime: _parse_datetime_UTC,
78
71
  # for optional defined attributes, when real value is NoneType, use str
79
72
  type(None): str,
80
73
  }
@@ -221,11 +214,11 @@ def get_flow_version() -> Optional[str]:
221
214
  return flow_run_ctx.flow.version
222
215
 
223
216
 
224
- def get_scheduled_start_time() -> pendulum.DateTime:
217
+ def get_scheduled_start_time() -> DateTime:
225
218
  flow_run_ctx = FlowRunContext.get()
226
219
  run_id = get_id()
227
220
  if flow_run_ctx is None and run_id is None:
228
- return pendulum.now("utc")
221
+ return now("UTC")
229
222
  elif flow_run_ctx is None:
230
223
  flow_run = from_sync.call_soon_in_loop_thread(
231
224
  create_call(_get_flow_run, run_id)
@@ -0,0 +1,34 @@
1
+ from . import (
2
+ artifacts,
3
+ admin,
4
+ automations,
5
+ block_capabilities,
6
+ block_documents,
7
+ block_schemas,
8
+ block_types,
9
+ collections,
10
+ concurrency_limits,
11
+ concurrency_limits_v2,
12
+ csrf_token,
13
+ dependencies,
14
+ deployments,
15
+ events,
16
+ flow_run_notification_policies,
17
+ flow_run_states,
18
+ flow_runs,
19
+ flows,
20
+ logs,
21
+ middleware,
22
+ root,
23
+ run_history,
24
+ saved_searches,
25
+ task_run_states,
26
+ task_runs,
27
+ task_workers,
28
+ templates,
29
+ ui,
30
+ variables,
31
+ work_queues,
32
+ workers,
33
+ )
34
+ from . import server # Server relies on all of the above routes
@@ -0,0 +1,85 @@
1
+ """
2
+ Routes for admin-level interactions with the Prefect REST API.
3
+ """
4
+
5
+ from fastapi import Body, Depends, Response, status
6
+
7
+ import prefect
8
+ import prefect.settings
9
+ from prefect.server.database import PrefectDBInterface, provide_database_interface
10
+ from prefect.server.utilities.server import PrefectRouter
11
+
12
+ router: PrefectRouter = PrefectRouter(prefix="/admin", tags=["Admin"])
13
+
14
+
15
+ @router.get("/settings")
16
+ async def read_settings() -> prefect.settings.Settings:
17
+ """
18
+ Get the current Prefect REST API settings.
19
+
20
+ Secret setting values will be obfuscated.
21
+ """
22
+ return prefect.settings.get_current_settings()
23
+
24
+
25
+ @router.get("/version")
26
+ async def read_version() -> str:
27
+ """Returns the Prefect version number"""
28
+ return prefect.__version__
29
+
30
+
31
+ @router.post("/database/clear", status_code=status.HTTP_204_NO_CONTENT)
32
+ async def clear_database(
33
+ db: PrefectDBInterface = Depends(provide_database_interface),
34
+ confirm: bool = Body(
35
+ False,
36
+ embed=True,
37
+ description="Pass confirm=True to confirm you want to modify the database.",
38
+ ),
39
+ response: Response = None, # type: ignore
40
+ ) -> None:
41
+ """Clear all database tables without dropping them."""
42
+ if not confirm:
43
+ response.status_code = status.HTTP_400_BAD_REQUEST
44
+ return
45
+ async with db.session_context(begin_transaction=True) as session:
46
+ # work pool has a circular dependency on pool queue; delete it first
47
+ await session.execute(db.WorkPool.__table__.delete())
48
+ for table in reversed(db.Base.metadata.sorted_tables):
49
+ await session.execute(table.delete())
50
+
51
+
52
+ @router.post("/database/drop", status_code=status.HTTP_204_NO_CONTENT)
53
+ async def drop_database(
54
+ db: PrefectDBInterface = Depends(provide_database_interface),
55
+ confirm: bool = Body(
56
+ False,
57
+ embed=True,
58
+ description="Pass confirm=True to confirm you want to modify the database.",
59
+ ),
60
+ response: Response = None,
61
+ ) -> None:
62
+ """Drop all database objects."""
63
+ if not confirm:
64
+ response.status_code = status.HTTP_400_BAD_REQUEST
65
+ return
66
+
67
+ await db.drop_db()
68
+
69
+
70
+ @router.post("/database/create", status_code=status.HTTP_204_NO_CONTENT)
71
+ async def create_database(
72
+ db: PrefectDBInterface = Depends(provide_database_interface),
73
+ confirm: bool = Body(
74
+ False,
75
+ embed=True,
76
+ description="Pass confirm=True to confirm you want to modify the database.",
77
+ ),
78
+ response: Response = None,
79
+ ) -> None:
80
+ """Create all database objects."""
81
+ if not confirm:
82
+ response.status_code = status.HTTP_400_BAD_REQUEST
83
+ return
84
+
85
+ await db.create_db()
@@ -0,0 +1,224 @@
1
+ """
2
+ Routes for interacting with artifact objects.
3
+ """
4
+
5
+ from typing import List
6
+ from uuid import UUID
7
+
8
+ from fastapi import Body, Depends, HTTPException, Path, Response, status
9
+
10
+ import prefect.server.api.dependencies as dependencies
11
+ from prefect.server import models
12
+ from prefect.server.database import PrefectDBInterface, provide_database_interface
13
+ from prefect.server.schemas import actions, core, filters, sorting
14
+ from prefect.server.utilities.server import PrefectRouter
15
+ from prefect.types._datetime import now
16
+
17
+ router: PrefectRouter = PrefectRouter(
18
+ prefix="/artifacts",
19
+ tags=["Artifacts"],
20
+ )
21
+
22
+
23
+ @router.post("/")
24
+ async def create_artifact(
25
+ artifact: actions.ArtifactCreate,
26
+ response: Response,
27
+ db: PrefectDBInterface = Depends(provide_database_interface),
28
+ ) -> core.Artifact:
29
+ artifact = core.Artifact(**artifact.model_dump())
30
+
31
+ right_now = now("UTC")
32
+
33
+ async with db.session_context(begin_transaction=True) as session:
34
+ model = await models.artifacts.create_artifact(
35
+ session=session,
36
+ artifact=artifact,
37
+ )
38
+
39
+ if model.created >= right_now:
40
+ response.status_code = status.HTTP_201_CREATED
41
+ return model
42
+
43
+
44
+ @router.get("/{id}")
45
+ async def read_artifact(
46
+ artifact_id: UUID = Path(
47
+ ..., description="The ID of the artifact to retrieve.", alias="id"
48
+ ),
49
+ db: PrefectDBInterface = Depends(provide_database_interface),
50
+ ) -> core.Artifact:
51
+ """
52
+ Retrieve an artifact from the database.
53
+ """
54
+ async with db.session_context() as session:
55
+ artifact = await models.artifacts.read_artifact(
56
+ session=session, artifact_id=artifact_id
57
+ )
58
+
59
+ if artifact is None:
60
+ raise HTTPException(status_code=404, detail="Artifact not found.")
61
+ return artifact
62
+
63
+
64
+ @router.get("/{key}/latest")
65
+ async def read_latest_artifact(
66
+ key: str = Path(
67
+ ...,
68
+ description="The key of the artifact to retrieve.",
69
+ ),
70
+ db: PrefectDBInterface = Depends(provide_database_interface),
71
+ ) -> core.Artifact:
72
+ """
73
+ Retrieve the latest artifact from the artifact table.
74
+ """
75
+ async with db.session_context() as session:
76
+ artifact = await models.artifacts.read_latest_artifact(session=session, key=key)
77
+
78
+ if artifact is None:
79
+ raise HTTPException(status_code=404, detail="Artifact not found.")
80
+ return artifact
81
+
82
+
83
+ @router.post("/filter")
84
+ async def read_artifacts(
85
+ sort: sorting.ArtifactSort = Body(sorting.ArtifactSort.ID_DESC),
86
+ limit: int = dependencies.LimitBody(),
87
+ offset: int = Body(0, ge=0),
88
+ artifacts: filters.ArtifactFilter = None,
89
+ flow_runs: filters.FlowRunFilter = None,
90
+ task_runs: filters.TaskRunFilter = None,
91
+ flows: filters.FlowFilter = None,
92
+ deployments: filters.DeploymentFilter = None,
93
+ db: PrefectDBInterface = Depends(provide_database_interface),
94
+ ) -> List[core.Artifact]:
95
+ """
96
+ Retrieve artifacts from the database.
97
+ """
98
+ async with db.session_context() as session:
99
+ return await models.artifacts.read_artifacts(
100
+ session=session,
101
+ artifact_filter=artifacts,
102
+ flow_run_filter=flow_runs,
103
+ task_run_filter=task_runs,
104
+ flow_filter=flows,
105
+ deployment_filter=deployments,
106
+ offset=offset,
107
+ limit=limit,
108
+ sort=sort,
109
+ )
110
+
111
+
112
+ @router.post("/latest/filter")
113
+ async def read_latest_artifacts(
114
+ sort: sorting.ArtifactCollectionSort = Body(sorting.ArtifactCollectionSort.ID_DESC),
115
+ limit: int = dependencies.LimitBody(),
116
+ offset: int = Body(0, ge=0),
117
+ artifacts: filters.ArtifactCollectionFilter = None,
118
+ flow_runs: filters.FlowRunFilter = None,
119
+ task_runs: filters.TaskRunFilter = None,
120
+ flows: filters.FlowFilter = None,
121
+ deployments: filters.DeploymentFilter = None,
122
+ db: PrefectDBInterface = Depends(provide_database_interface),
123
+ ) -> List[core.ArtifactCollection]:
124
+ """
125
+ Retrieve artifacts from the database.
126
+ """
127
+ async with db.session_context() as session:
128
+ return await models.artifacts.read_latest_artifacts(
129
+ session=session,
130
+ artifact_filter=artifacts,
131
+ flow_run_filter=flow_runs,
132
+ task_run_filter=task_runs,
133
+ flow_filter=flows,
134
+ deployment_filter=deployments,
135
+ offset=offset,
136
+ limit=limit,
137
+ sort=sort,
138
+ )
139
+
140
+
141
+ @router.post("/count")
142
+ async def count_artifacts(
143
+ artifacts: filters.ArtifactFilter = None,
144
+ flow_runs: filters.FlowRunFilter = None,
145
+ task_runs: filters.TaskRunFilter = None,
146
+ flows: filters.FlowFilter = None,
147
+ deployments: filters.DeploymentFilter = None,
148
+ db: PrefectDBInterface = Depends(provide_database_interface),
149
+ ) -> int:
150
+ """
151
+ Count artifacts from the database.
152
+ """
153
+ async with db.session_context() as session:
154
+ return await models.artifacts.count_artifacts(
155
+ session=session,
156
+ artifact_filter=artifacts,
157
+ flow_run_filter=flow_runs,
158
+ task_run_filter=task_runs,
159
+ flow_filter=flows,
160
+ deployment_filter=deployments,
161
+ )
162
+
163
+
164
+ @router.post("/latest/count")
165
+ async def count_latest_artifacts(
166
+ artifacts: filters.ArtifactCollectionFilter = None,
167
+ flow_runs: filters.FlowRunFilter = None,
168
+ task_runs: filters.TaskRunFilter = None,
169
+ flows: filters.FlowFilter = None,
170
+ deployments: filters.DeploymentFilter = None,
171
+ db: PrefectDBInterface = Depends(provide_database_interface),
172
+ ) -> int:
173
+ """
174
+ Count artifacts from the database.
175
+ """
176
+ async with db.session_context() as session:
177
+ return await models.artifacts.count_latest_artifacts(
178
+ session=session,
179
+ artifact_filter=artifacts,
180
+ flow_run_filter=flow_runs,
181
+ task_run_filter=task_runs,
182
+ flow_filter=flows,
183
+ deployment_filter=deployments,
184
+ )
185
+
186
+
187
+ @router.patch("/{id}", status_code=204)
188
+ async def update_artifact(
189
+ artifact: actions.ArtifactUpdate,
190
+ artifact_id: UUID = Path(
191
+ ..., description="The ID of the artifact to update.", alias="id"
192
+ ),
193
+ db: PrefectDBInterface = Depends(provide_database_interface),
194
+ ) -> None:
195
+ """
196
+ Update an artifact in the database.
197
+ """
198
+ async with db.session_context(begin_transaction=True) as session:
199
+ result = await models.artifacts.update_artifact(
200
+ session=session,
201
+ artifact_id=artifact_id,
202
+ artifact=artifact,
203
+ )
204
+ if not result:
205
+ raise HTTPException(status_code=404, detail="Artifact not found.")
206
+
207
+
208
+ @router.delete("/{id}", status_code=204)
209
+ async def delete_artifact(
210
+ artifact_id: UUID = Path(
211
+ ..., description="The ID of the artifact to delete.", alias="id"
212
+ ),
213
+ db: PrefectDBInterface = Depends(provide_database_interface),
214
+ ) -> None:
215
+ """
216
+ Delete an artifact from the database.
217
+ """
218
+ async with db.session_context(begin_transaction=True) as session:
219
+ result = await models.artifacts.delete_artifact(
220
+ session=session,
221
+ artifact_id=artifact_id,
222
+ )
223
+ if not result:
224
+ raise HTTPException(status_code=404, detail="Artifact not found.")
@@ -0,0 +1,239 @@
1
+ from typing import Optional, Sequence
2
+ from uuid import UUID
3
+
4
+ from fastapi import Body, Depends, HTTPException, Path, status
5
+ from fastapi.exceptions import RequestValidationError
6
+ from pydantic import ValidationError
7
+
8
+ from prefect.server.api.dependencies import LimitBody
9
+ from prefect.server.api.validation import (
10
+ validate_job_variables_for_run_deployment_action,
11
+ )
12
+ from prefect.server.database import PrefectDBInterface, provide_database_interface
13
+ from prefect.server.events import actions
14
+ from prefect.server.events.filters import AutomationFilter, AutomationFilterCreated
15
+ from prefect.server.events.models import automations as automations_models
16
+ from prefect.server.events.schemas.automations import (
17
+ Automation,
18
+ AutomationCreate,
19
+ AutomationPartialUpdate,
20
+ AutomationSort,
21
+ AutomationUpdate,
22
+ )
23
+ from prefect.server.exceptions import ObjectNotFoundError
24
+ from prefect.server.utilities.server import PrefectRouter
25
+ from prefect.types._datetime import now
26
+ from prefect.utilities.schema_tools.validation import (
27
+ ValidationError as JSONSchemaValidationError,
28
+ )
29
+
30
+ router: PrefectRouter = PrefectRouter(
31
+ prefix="/automations",
32
+ tags=["Automations"],
33
+ dependencies=[],
34
+ )
35
+
36
+
37
+ @router.post("/", status_code=status.HTTP_201_CREATED)
38
+ async def create_automation(
39
+ automation: AutomationCreate,
40
+ db: PrefectDBInterface = Depends(provide_database_interface),
41
+ ) -> Automation:
42
+ # reset any client-provided IDs on the provided triggers
43
+ automation.trigger.reset_ids()
44
+
45
+ errors = []
46
+ for action in automation.actions:
47
+ if (
48
+ isinstance(action, actions.RunDeployment)
49
+ and action.deployment_id is not None
50
+ and action.job_variables is not None
51
+ and action.job_variables != {}
52
+ ):
53
+ async with db.session_context() as session:
54
+ try:
55
+ await validate_job_variables_for_run_deployment_action(
56
+ session, action
57
+ )
58
+ except JSONSchemaValidationError as exc:
59
+ errors.append(str(exc))
60
+
61
+ if errors:
62
+ raise HTTPException(
63
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
64
+ detail=f"Error creating automation: {' '.join(errors)}",
65
+ )
66
+
67
+ automation_dict = automation.model_dump()
68
+ owner_resource = automation_dict.pop("owner_resource", None)
69
+
70
+ async with db.session_context(begin_transaction=True) as session:
71
+ created_automation = await automations_models.create_automation(
72
+ session=session,
73
+ automation=Automation(
74
+ **automation_dict,
75
+ ),
76
+ )
77
+
78
+ if owner_resource:
79
+ await automations_models.relate_automation_to_resource(
80
+ session,
81
+ automation_id=created_automation.id,
82
+ resource_id=owner_resource,
83
+ owned_by_resource=True,
84
+ )
85
+
86
+ return created_automation
87
+
88
+
89
+ @router.put("/{id:uuid}", status_code=status.HTTP_204_NO_CONTENT)
90
+ async def update_automation(
91
+ automation: AutomationUpdate,
92
+ automation_id: UUID = Path(..., alias="id"),
93
+ db: PrefectDBInterface = Depends(provide_database_interface),
94
+ ) -> None:
95
+ # reset any client-provided IDs on the provided triggers
96
+ automation.trigger.reset_ids()
97
+
98
+ errors = []
99
+ for action in automation.actions:
100
+ if (
101
+ isinstance(action, actions.RunDeployment)
102
+ and action.deployment_id is not None
103
+ and action.job_variables is not None
104
+ ):
105
+ async with db.session_context() as session:
106
+ try:
107
+ await validate_job_variables_for_run_deployment_action(
108
+ session, action
109
+ )
110
+ except JSONSchemaValidationError as exc:
111
+ errors.append(str(exc))
112
+ if errors:
113
+ raise HTTPException(
114
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
115
+ detail=f"Error creating automation: {' '.join(errors)}",
116
+ )
117
+
118
+ async with db.session_context(begin_transaction=True) as session:
119
+ updated = await automations_models.update_automation(
120
+ session=session,
121
+ automation_update=automation,
122
+ automation_id=automation_id,
123
+ )
124
+
125
+ if not updated:
126
+ raise ObjectNotFoundError("Automation not found")
127
+
128
+
129
+ @router.patch(
130
+ "/{id:uuid}",
131
+ status_code=status.HTTP_204_NO_CONTENT,
132
+ )
133
+ async def patch_automation(
134
+ automation: AutomationPartialUpdate,
135
+ automation_id: UUID = Path(..., alias="id"),
136
+ db: PrefectDBInterface = Depends(provide_database_interface),
137
+ ) -> None:
138
+ try:
139
+ async with db.session_context(begin_transaction=True) as session:
140
+ updated = await automations_models.update_automation(
141
+ session=session,
142
+ automation_update=automation,
143
+ automation_id=automation_id,
144
+ )
145
+ except ValidationError as e:
146
+ raise RequestValidationError(
147
+ errors=e.errors(),
148
+ body=automation.model_dump(mode="json"),
149
+ )
150
+
151
+ if not updated:
152
+ raise ObjectNotFoundError("Automation not found")
153
+
154
+
155
+ @router.delete(
156
+ "/{id:uuid}",
157
+ status_code=status.HTTP_204_NO_CONTENT,
158
+ )
159
+ async def delete_automation(
160
+ automation_id: UUID = Path(..., alias="id"),
161
+ db: PrefectDBInterface = Depends(provide_database_interface),
162
+ ) -> None:
163
+ async with db.session_context(begin_transaction=True) as session:
164
+ deleted = await automations_models.delete_automation(
165
+ session=session,
166
+ automation_id=automation_id,
167
+ )
168
+
169
+ if not deleted:
170
+ raise ObjectNotFoundError("Automation not found")
171
+
172
+
173
+ @router.post("/filter")
174
+ async def read_automations(
175
+ sort: AutomationSort = Body(AutomationSort.NAME_ASC),
176
+ limit: int = LimitBody(),
177
+ offset: int = Body(0, ge=0),
178
+ automations: Optional[AutomationFilter] = None,
179
+ db: PrefectDBInterface = Depends(provide_database_interface),
180
+ ) -> Sequence[Automation]:
181
+ async with db.session_context() as session:
182
+ return await automations_models.read_automations_for_workspace(
183
+ session=session,
184
+ sort=sort,
185
+ limit=limit,
186
+ offset=offset,
187
+ automation_filter=automations,
188
+ )
189
+
190
+
191
+ @router.post("/count")
192
+ async def count_automations(
193
+ db: PrefectDBInterface = Depends(provide_database_interface),
194
+ ) -> int:
195
+ async with db.session_context() as session:
196
+ return await automations_models.count_automations_for_workspace(session=session)
197
+
198
+
199
+ @router.get("/{id:uuid}")
200
+ async def read_automation(
201
+ automation_id: UUID = Path(..., alias="id"),
202
+ db: PrefectDBInterface = Depends(provide_database_interface),
203
+ ) -> Automation:
204
+ async with db.session_context() as session:
205
+ automation = await automations_models.read_automation(
206
+ session=session,
207
+ automation_id=automation_id,
208
+ )
209
+
210
+ if not automation:
211
+ raise ObjectNotFoundError("Automation not found")
212
+ return automation
213
+
214
+
215
+ @router.get("/related-to/{resource_id:str}")
216
+ async def read_automations_related_to_resource(
217
+ resource_id: str = Path(..., alias="resource_id"),
218
+ db: PrefectDBInterface = Depends(provide_database_interface),
219
+ ) -> Sequence[Automation]:
220
+ async with db.session_context() as session:
221
+ return await automations_models.read_automations_related_to_resource(
222
+ session=session,
223
+ resource_id=resource_id,
224
+ )
225
+
226
+
227
+ @router.delete("/owned-by/{resource_id:str}", status_code=status.HTTP_202_ACCEPTED)
228
+ async def delete_automations_owned_by_resource(
229
+ resource_id: str = Path(..., alias="resource_id"),
230
+ db: PrefectDBInterface = Depends(provide_database_interface),
231
+ ) -> None:
232
+ async with db.session_context(begin_transaction=True) as session:
233
+ await automations_models.delete_automations_owned_by_resource(
234
+ session,
235
+ resource_id=resource_id,
236
+ automation_filter=AutomationFilter(
237
+ created=AutomationFilterCreated(before_=now("UTC"))
238
+ ),
239
+ )
@@ -0,0 +1,25 @@
1
+ """
2
+ Routes for interacting with block capabilities.
3
+ """
4
+
5
+ from typing import List
6
+
7
+ from fastapi import Depends
8
+
9
+ from prefect.server import models
10
+ from prefect.server.database import PrefectDBInterface, provide_database_interface
11
+ from prefect.server.utilities.server import PrefectRouter
12
+
13
+ router: PrefectRouter = PrefectRouter(
14
+ prefix="/block_capabilities", tags=["Block capabilities"]
15
+ )
16
+
17
+
18
+ @router.get("/")
19
+ async def read_available_block_capabilities(
20
+ db: PrefectDBInterface = Depends(provide_database_interface),
21
+ ) -> List[str]:
22
+ async with db.session_context() as session:
23
+ return await models.block_schemas.read_available_block_capabilities(
24
+ session=session
25
+ )