prefect-client 3.2.2__py3-none-any.whl → 3.2.4__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 (50) hide show
  1. prefect/__init__.py +15 -8
  2. prefect/_build_info.py +5 -0
  3. prefect/client/orchestration/__init__.py +16 -5
  4. prefect/main.py +0 -2
  5. prefect/server/api/__init__.py +34 -0
  6. prefect/server/api/admin.py +85 -0
  7. prefect/server/api/artifacts.py +224 -0
  8. prefect/server/api/automations.py +239 -0
  9. prefect/server/api/block_capabilities.py +25 -0
  10. prefect/server/api/block_documents.py +164 -0
  11. prefect/server/api/block_schemas.py +153 -0
  12. prefect/server/api/block_types.py +211 -0
  13. prefect/server/api/clients.py +246 -0
  14. prefect/server/api/collections.py +75 -0
  15. prefect/server/api/concurrency_limits.py +286 -0
  16. prefect/server/api/concurrency_limits_v2.py +269 -0
  17. prefect/server/api/csrf_token.py +38 -0
  18. prefect/server/api/dependencies.py +196 -0
  19. prefect/server/api/deployments.py +941 -0
  20. prefect/server/api/events.py +300 -0
  21. prefect/server/api/flow_run_notification_policies.py +120 -0
  22. prefect/server/api/flow_run_states.py +52 -0
  23. prefect/server/api/flow_runs.py +867 -0
  24. prefect/server/api/flows.py +210 -0
  25. prefect/server/api/logs.py +43 -0
  26. prefect/server/api/middleware.py +73 -0
  27. prefect/server/api/root.py +35 -0
  28. prefect/server/api/run_history.py +170 -0
  29. prefect/server/api/saved_searches.py +99 -0
  30. prefect/server/api/server.py +891 -0
  31. prefect/server/api/task_run_states.py +52 -0
  32. prefect/server/api/task_runs.py +342 -0
  33. prefect/server/api/task_workers.py +31 -0
  34. prefect/server/api/templates.py +35 -0
  35. prefect/server/api/ui/__init__.py +3 -0
  36. prefect/server/api/ui/flow_runs.py +128 -0
  37. prefect/server/api/ui/flows.py +173 -0
  38. prefect/server/api/ui/schemas.py +63 -0
  39. prefect/server/api/ui/task_runs.py +175 -0
  40. prefect/server/api/validation.py +382 -0
  41. prefect/server/api/variables.py +181 -0
  42. prefect/server/api/work_queues.py +230 -0
  43. prefect/server/api/workers.py +656 -0
  44. prefect/settings/sources.py +18 -5
  45. {prefect_client-3.2.2.dist-info → prefect_client-3.2.4.dist-info}/METADATA +10 -15
  46. {prefect_client-3.2.2.dist-info → prefect_client-3.2.4.dist-info}/RECORD +48 -10
  47. {prefect_client-3.2.2.dist-info → prefect_client-3.2.4.dist-info}/WHEEL +1 -2
  48. prefect/_version.py +0 -21
  49. prefect_client-3.2.2.dist-info/top_level.txt +0 -1
  50. {prefect_client-3.2.2.dist-info → prefect_client-3.2.4.dist-info/licenses}/LICENSE +0 -0
prefect/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  # Setup version and path constants
4
4
 
5
5
  import sys
6
- from . import _version
6
+ from . import _build_info
7
7
  import importlib
8
8
  import pathlib
9
9
  from typing import TYPE_CHECKING, Any, Optional, TypedDict, cast
@@ -24,16 +24,14 @@ if TYPE_CHECKING:
24
24
  unmapped,
25
25
  serve,
26
26
  aserve,
27
- deploy,
28
27
  pause_flow_run,
29
28
  resume_flow_run,
30
29
  suspend_flow_run,
31
30
  )
31
+ from prefect.deployments.runner import deploy
32
32
 
33
33
  __spec__: ModuleSpec
34
34
 
35
- # Versioneer provides version information as dictionaries
36
- # with these keys
37
35
  class VersionInfo(TypedDict("_FullRevisionId", {"full-revisionid": str})):
38
36
  version: str
39
37
  dirty: Optional[bool]
@@ -41,8 +39,17 @@ if TYPE_CHECKING:
41
39
  date: Optional[str]
42
40
 
43
41
 
44
- __version_info__: "VersionInfo" = cast("VersionInfo", _version.get_versions())
45
- __version__ = __version_info__["version"]
42
+ __version__ = _build_info.__version__
43
+ __version_info__: "VersionInfo" = cast(
44
+ "VersionInfo",
45
+ {
46
+ "version": __version__,
47
+ "date": _build_info.__build_date__,
48
+ "full-revisionid": _build_info.__git_commit__,
49
+ "error": None,
50
+ "dirty": _build_info.__dirty__,
51
+ },
52
+ )
46
53
 
47
54
  # The absolute path to this module
48
55
  __module_path__: pathlib.Path = pathlib.Path(__file__).parent
@@ -56,12 +63,12 @@ __ui_static_subpath__: pathlib.Path = __module_path__ / "server" / "ui_build"
56
63
  # The absolute path to the built UI within the Python module
57
64
  __ui_static_path__: pathlib.Path = __module_path__ / "server" / "ui"
58
65
 
59
- del _version, pathlib
66
+ del _build_info, pathlib
60
67
 
61
68
  _public_api: dict[str, tuple[Optional[str], str]] = {
62
69
  "allow_failure": (__spec__.parent, ".main"),
63
70
  "aserve": (__spec__.parent, ".main"),
64
- "deploy": (__spec__.parent, ".main"),
71
+ "deploy": (__spec__.parent, ".deployments.runner"),
65
72
  "flow": (__spec__.parent, ".main"),
66
73
  "Flow": (__spec__.parent, ".main"),
67
74
  "get_client": (__spec__.parent, ".main"),
prefect/_build_info.py ADDED
@@ -0,0 +1,5 @@
1
+ # Generated by versioningit
2
+ __version__ = "3.2.4"
3
+ __build_date__ = "2025-02-18 21:30:33.140267+00:00"
4
+ __git_commit__ = "701e7f4891ec22af2cc202c9747be8763a5153f1"
5
+ __dirty__ = False
@@ -149,19 +149,30 @@ T = TypeVar("T")
149
149
 
150
150
 
151
151
  @overload
152
- def get_client( # type: ignore # TODO
153
- *,
154
- httpx_settings: Optional[dict[str, Any]] = ...,
155
- sync_client: Literal[False] = False,
152
+ def get_client(
153
+ httpx_settings: Optional[dict[str, Any]],
154
+ sync_client: Literal[False],
156
155
  ) -> "PrefectClient": ...
157
156
 
158
157
 
158
+ @overload
159
+ def get_client(*, httpx_settings: Optional[dict[str, Any]]) -> "PrefectClient": ...
160
+
161
+
162
+ @overload
163
+ def get_client(*, sync_client: Literal[False] = False) -> "PrefectClient": ...
164
+
165
+
159
166
  @overload
160
167
  def get_client(
161
- *, httpx_settings: Optional[dict[str, Any]] = ..., sync_client: Literal[True] = ...
168
+ httpx_settings: Optional[dict[str, Any]], sync_client: Literal[True]
162
169
  ) -> "SyncPrefectClient": ...
163
170
 
164
171
 
172
+ @overload
173
+ def get_client(*, sync_client: Literal[True]) -> "SyncPrefectClient": ...
174
+
175
+
165
176
  def get_client(
166
177
  httpx_settings: Optional[dict[str, Any]] = None, sync_client: bool = False
167
178
  ) -> Union["SyncPrefectClient", "PrefectClient"]:
prefect/main.py CHANGED
@@ -1,6 +1,5 @@
1
1
  # Import user-facing API
2
2
  from typing import Any
3
- from prefect.deployments import deploy
4
3
  from prefect.states import State
5
4
  from prefect.logging import get_run_logger
6
5
  from prefect.flows import FlowDecorator, flow, Flow, serve, aserve
@@ -77,7 +76,6 @@ __all__ = [
77
76
  "unmapped",
78
77
  "serve",
79
78
  "aserve",
80
- "deploy",
81
79
  "pause_flow_run",
82
80
  "resume_flow_run",
83
81
  "suspend_flow_run",
@@ -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
+ )