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.
- prefect/__init__.py +15 -8
- prefect/_build_info.py +5 -0
- prefect/_internal/schemas/bases.py +4 -7
- prefect/_internal/schemas/validators.py +5 -6
- prefect/_result_records.py +6 -1
- prefect/client/orchestration/__init__.py +18 -6
- prefect/client/schemas/schedules.py +2 -2
- prefect/concurrency/asyncio.py +4 -3
- prefect/concurrency/sync.py +3 -3
- prefect/concurrency/v1/asyncio.py +3 -3
- prefect/concurrency/v1/sync.py +3 -3
- prefect/deployments/flow_runs.py +2 -2
- prefect/docker/docker_image.py +2 -3
- prefect/engine.py +1 -1
- prefect/events/clients.py +4 -3
- prefect/events/related.py +3 -5
- prefect/flows.py +11 -5
- prefect/locking/filesystem.py +8 -8
- prefect/logging/handlers.py +7 -11
- prefect/main.py +0 -2
- prefect/runtime/flow_run.py +10 -17
- 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/states.py +3 -3
- prefect/task_engine.py +3 -3
- prefect/types/_datetime.py +82 -3
- prefect/utilities/dockerutils.py +2 -2
- prefect/workers/base.py +5 -5
- {prefect_client-3.2.1.dist-info → prefect_client-3.2.3.dist-info}/METADATA +10 -15
- {prefect_client-3.2.1.dist-info → prefect_client-3.2.3.dist-info}/RECORD +70 -32
- {prefect_client-3.2.1.dist-info → prefect_client-3.2.3.dist-info}/WHEEL +1 -2
- prefect/_version.py +0 -21
- prefect_client-3.2.1.dist-info/top_level.txt +0 -1
- {prefect_client-3.2.1.dist-info → prefect_client-3.2.3.dist-info/licenses}/LICENSE +0 -0
prefect/runtime/flow_run.py
CHANGED
@@ -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
|
57
|
-
""
|
58
|
-
|
59
|
-
|
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
|
-
|
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() ->
|
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
|
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
|
+
)
|