Skip to content

Commit 39d1d6b

Browse files
committed
Exports API
1 parent a357347 commit 39d1d6b

10 files changed

Lines changed: 1415 additions & 18 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import uuid
2+
3+
from dstack._internal.core.models.common import CoreModel
4+
5+
6+
class ExportImport(CoreModel):
7+
project_name: str
8+
9+
10+
class ExportedFleet(CoreModel):
11+
id: uuid.UUID
12+
name: str
13+
14+
15+
class Export(CoreModel):
16+
id: uuid.UUID
17+
name: str
18+
imports: list[ExportImport]
19+
exported_fleets: list[ExportedFleet]

src/dstack/_internal/server/app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
auth,
3232
backends,
3333
events,
34+
exports,
3435
files,
3536
fleets,
3637
gateways,
@@ -253,6 +254,7 @@ def register_routes(app: FastAPI, ui: bool = True):
253254
app.include_router(files.router)
254255
app.include_router(events.root_router)
255256
app.include_router(templates.router)
257+
app.include_router(exports.project_router)
256258

257259
@app.exception_handler(ForbiddenError)
258260
async def forbidden_error_handler(request: Request, exc: ForbiddenError):

src/dstack/_internal/server/models.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@
4949
from dstack._internal.utils.logging import get_logger
5050

5151
logger = get_logger(__name__)
52+
# Default options (save-update, merge) + delete-orphan + delete (required by delete-orphan)
53+
# delete-orphan allows to automatically delete entities removed from the relationship
54+
CASCADE_DEFAULT_WITH_DELETE_ORPHAN = "save-update, merge, delete-orphan, delete"
5255

5356

5457
class NaiveDateTime(TypeDecorator):
@@ -760,10 +763,7 @@ class InstanceModel(PipelineModelMixin, BaseModel):
760763

761764
volume_attachments: Mapped[List["VolumeAttachmentModel"]] = relationship(
762765
back_populates="instance",
763-
# Add delete-orphan option so that removing entries from volume_attachments
764-
# automatically marks them for deletion.
765-
# SQLAlchemy requires delete when using delete-orphan.
766-
cascade="save-update, merge, delete-orphan, delete",
766+
cascade=CASCADE_DEFAULT_WITH_DELETE_ORPHAN,
767767
)
768768

769769
__table_args__ = (
@@ -1043,8 +1043,14 @@ class ExportModel(BaseModel):
10431043
)
10441044
project: Mapped["ProjectModel"] = relationship()
10451045
created_at: Mapped[datetime] = mapped_column(NaiveDateTime, default=get_current_datetime)
1046-
imports: Mapped[List["ImportModel"]] = relationship(back_populates="export")
1047-
exported_fleets: Mapped[List["ExportedFleetModel"]] = relationship(back_populates="export")
1046+
imports: Mapped[List["ImportModel"]] = relationship(
1047+
back_populates="export",
1048+
cascade=CASCADE_DEFAULT_WITH_DELETE_ORPHAN,
1049+
)
1050+
exported_fleets: Mapped[List["ExportedFleetModel"]] = relationship(
1051+
back_populates="export",
1052+
cascade=CASCADE_DEFAULT_WITH_DELETE_ORPHAN,
1053+
)
10481054

10491055

10501056
class ImportModel(BaseModel):
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from typing import Annotated
2+
3+
from fastapi import APIRouter, Depends
4+
from sqlalchemy.ext.asyncio import AsyncSession
5+
6+
from dstack._internal.core.models.exports import Export
7+
from dstack._internal.server.db import get_session
8+
from dstack._internal.server.models import ProjectModel, UserModel
9+
from dstack._internal.server.schemas.exports import (
10+
CreateExportRequest,
11+
DeleteExportRequest,
12+
UpdateExportRequest,
13+
)
14+
from dstack._internal.server.security.permissions import ProjectAdmin, ProjectMember
15+
from dstack._internal.server.services import exports as exports_services
16+
from dstack._internal.server.utils.routers import get_base_api_additional_responses
17+
18+
project_router = APIRouter(
19+
prefix="/api/project/{project_name}/exports",
20+
tags=["exports"],
21+
responses=get_base_api_additional_responses(),
22+
)
23+
24+
25+
@project_router.post("/create", response_model=Export)
26+
async def create_export(
27+
body: CreateExportRequest,
28+
session: Annotated[AsyncSession, Depends(get_session)],
29+
user_project: Annotated[tuple[UserModel, ProjectModel], Depends(ProjectAdmin())],
30+
):
31+
user, project = user_project
32+
return await exports_services.create_export(
33+
session=session,
34+
project=project,
35+
user=user,
36+
name=body.name,
37+
importer_project_names=body.importer_projects,
38+
exported_fleet_names=body.exported_fleets,
39+
)
40+
41+
42+
@project_router.post("/update", response_model=Export)
43+
async def update_export(
44+
body: UpdateExportRequest,
45+
session: Annotated[AsyncSession, Depends(get_session)],
46+
user_project: Annotated[tuple[UserModel, ProjectModel], Depends(ProjectAdmin())],
47+
):
48+
user, project = user_project
49+
return await exports_services.update_export(
50+
session=session,
51+
project=project,
52+
user=user,
53+
name=body.name,
54+
add_importer_project_names=body.add_importer_projects,
55+
remove_importer_project_names=body.remove_importer_projects,
56+
add_exported_fleet_names=body.add_exported_fleets,
57+
remove_exported_fleet_names=body.remove_exported_fleets,
58+
)
59+
60+
61+
@project_router.post("/delete")
62+
async def delete_export(
63+
body: DeleteExportRequest,
64+
session: Annotated[AsyncSession, Depends(get_session)],
65+
user_project: Annotated[tuple[UserModel, ProjectModel], Depends(ProjectAdmin())],
66+
):
67+
_, project = user_project
68+
await exports_services.delete_export(
69+
session=session,
70+
project=project,
71+
name=body.name,
72+
)
73+
74+
75+
@project_router.post("/list", response_model=list[Export])
76+
async def list_exports(
77+
session: Annotated[AsyncSession, Depends(get_session)],
78+
user_project: Annotated[tuple[UserModel, ProjectModel], Depends(ProjectMember())],
79+
):
80+
_, project = user_project
81+
return await exports_services.list_exports(
82+
session=session,
83+
project=project,
84+
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from dstack._internal.core.models.common import CoreModel
2+
3+
4+
class CreateExportRequest(CoreModel):
5+
name: str
6+
importer_projects: list[str] = []
7+
exported_fleets: list[str] = []
8+
9+
10+
class UpdateExportRequest(CoreModel):
11+
name: str
12+
add_importer_projects: list[str] = []
13+
remove_importer_projects: list[str] = []
14+
add_exported_fleets: list[str] = []
15+
remove_exported_fleets: list[str] = []
16+
17+
18+
class DeleteExportRequest(CoreModel):
19+
name: str

0 commit comments

Comments
 (0)