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.
- prefect/__init__.py +15 -8
- prefect/_build_info.py +5 -0
- prefect/client/orchestration/__init__.py +16 -5
- prefect/main.py +0 -2
- prefect/server/api/__init__.py +34 -0
- prefect/server/api/admin.py +85 -0
- prefect/server/api/artifacts.py +224 -0
- prefect/server/api/automations.py +239 -0
- prefect/server/api/block_capabilities.py +25 -0
- prefect/server/api/block_documents.py +164 -0
- prefect/server/api/block_schemas.py +153 -0
- prefect/server/api/block_types.py +211 -0
- prefect/server/api/clients.py +246 -0
- prefect/server/api/collections.py +75 -0
- prefect/server/api/concurrency_limits.py +286 -0
- prefect/server/api/concurrency_limits_v2.py +269 -0
- prefect/server/api/csrf_token.py +38 -0
- prefect/server/api/dependencies.py +196 -0
- prefect/server/api/deployments.py +941 -0
- prefect/server/api/events.py +300 -0
- prefect/server/api/flow_run_notification_policies.py +120 -0
- prefect/server/api/flow_run_states.py +52 -0
- prefect/server/api/flow_runs.py +867 -0
- prefect/server/api/flows.py +210 -0
- prefect/server/api/logs.py +43 -0
- prefect/server/api/middleware.py +73 -0
- prefect/server/api/root.py +35 -0
- prefect/server/api/run_history.py +170 -0
- prefect/server/api/saved_searches.py +99 -0
- prefect/server/api/server.py +891 -0
- prefect/server/api/task_run_states.py +52 -0
- prefect/server/api/task_runs.py +342 -0
- prefect/server/api/task_workers.py +31 -0
- prefect/server/api/templates.py +35 -0
- prefect/server/api/ui/__init__.py +3 -0
- prefect/server/api/ui/flow_runs.py +128 -0
- prefect/server/api/ui/flows.py +173 -0
- prefect/server/api/ui/schemas.py +63 -0
- prefect/server/api/ui/task_runs.py +175 -0
- prefect/server/api/validation.py +382 -0
- prefect/server/api/variables.py +181 -0
- prefect/server/api/work_queues.py +230 -0
- prefect/server/api/workers.py +656 -0
- prefect/settings/sources.py +18 -5
- {prefect_client-3.2.2.dist-info → prefect_client-3.2.4.dist-info}/METADATA +10 -15
- {prefect_client-3.2.2.dist-info → prefect_client-3.2.4.dist-info}/RECORD +48 -10
- {prefect_client-3.2.2.dist-info → prefect_client-3.2.4.dist-info}/WHEEL +1 -2
- prefect/_version.py +0 -21
- prefect_client-3.2.2.dist-info/top_level.txt +0 -1
- {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
|
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
|
-
|
45
|
-
|
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
|
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, ".
|
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
@@ -149,19 +149,30 @@ T = TypeVar("T")
|
|
149
149
|
|
150
150
|
|
151
151
|
@overload
|
152
|
-
def get_client(
|
153
|
-
|
154
|
-
|
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
|
-
|
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
|
+
)
|