Skip to content

Commit

Permalink
Merge pull request #1901 from DSD-DBS/training-sessions
Browse files Browse the repository at this point in the history
feat: Add support for provisioning of tool models
  • Loading branch information
MoritzWeber0 authored Nov 19, 2024
2 parents 5de7d25 + a58bff8 commit ee3864f
Show file tree
Hide file tree
Showing 93 changed files with 5,734 additions and 205 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0

"""Add provisioning feature
Revision ID: 014438261702
Revises: 320c5b39c509
Create Date: 2024-10-11 17:34:05.210906
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "014438261702"
down_revision = "320c5b39c509"
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
"model_provisioning",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("user_id", sa.Integer(), nullable=False),
sa.Column("tool_model_id", sa.Integer(), nullable=False),
sa.Column("revision", sa.String(), nullable=False),
sa.Column("commit_hash", sa.String(), nullable=False),
sa.Column("provisioned_at", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(
["tool_model_id"],
["models.id"],
),
sa.ForeignKeyConstraint(
["user_id"],
["users.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(
op.f("ix_model_provisioning_id"),
"model_provisioning",
["id"],
unique=False,
)
op.add_column(
"sessions", sa.Column("provisioning_id", sa.Integer(), nullable=True)
)
op.create_foreign_key(
None, "sessions", "model_provisioning", ["provisioning_id"], ["id"]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0

"""Add project tools table
Revision ID: 2f8449c217fa
Revises: 014438261702
Create Date: 2024-10-29 14:11:47.774679
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "2f8449c217fa"
down_revision = "014438261702"
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
"project_tool_association",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("project_id", sa.Integer(), nullable=False),
sa.Column("tool_version_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
["project_id"],
["projects.id"],
),
sa.ForeignKeyConstraint(
["tool_version_id"],
["versions.id"],
),
sa.PrimaryKeyConstraint("id", "project_id", "tool_version_id"),
)
2 changes: 2 additions & 0 deletions backend/capellacollab/core/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
import capellacollab.projects.toolmodels.models
import capellacollab.projects.toolmodels.modelsources.git.models
import capellacollab.projects.toolmodels.modelsources.t4c.models
import capellacollab.projects.toolmodels.provisioning.models
import capellacollab.projects.toolmodels.restrictions.models
import capellacollab.projects.tools.models
import capellacollab.projects.users.models
import capellacollab.sessions.models
import capellacollab.settings.configuration.models
Expand Down
30 changes: 30 additions & 0 deletions backend/capellacollab/core/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,33 @@ class ZIPFileResponse(fastapi.responses.StreamingResponse):
}
}
}


class MarkdownResponse(fastapi.responses.Response):
"""Custom error class for Markdown responses.
To use the class as response class, pass the following parameters
to the fastapi route definition.
```python
response_class=fastapi.responses.Response
responses=responses.MarkdownResponse.responses
```
Don't use Markdown as response_class as this will also change the
media type for all error responses, see:
https://github.com/tiangolo/fastapi/discussions/6799
To return an Markdown response in the route, use:
```python
return responses.MarkdownResponse(
content=b"# Hello World",
)
```
"""

media_type = "text/markdown"
responses: dict[int | str, dict[str, t.Any]] | None = {
200: {"content": {"text/markdown": {"schema": {"type": "string"}}}}
}
6 changes: 6 additions & 0 deletions backend/capellacollab/projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

if t.TYPE_CHECKING:
from capellacollab.projects.toolmodels.models import DatabaseToolModel
from capellacollab.projects.tools.models import (
DatabaseProjectToolAssociation,
)
from capellacollab.projects.users.models import ProjectUserAssociation


Expand Down Expand Up @@ -134,5 +137,8 @@ class DatabaseProject(database.Base):
models: orm.Mapped[list[DatabaseToolModel]] = orm.relationship(
default_factory=list, back_populates="project"
)
tools: orm.Mapped[list[DatabaseProjectToolAssociation]] = orm.relationship(
default_factory=list, back_populates="project"
)

is_archived: orm.Mapped[bool] = orm.mapped_column(default=False)
6 changes: 6 additions & 0 deletions backend/capellacollab/projects/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from capellacollab.projects.toolmodels.backups import core as backups_core
from capellacollab.projects.toolmodels.backups import crud as backups_crud
from capellacollab.projects.toolmodels.backups import models as backups_models
from capellacollab.projects.tools import routes as projects_tools_routes
from capellacollab.projects.users import crud as projects_users_crud
from capellacollab.projects.users import models as projects_users_models
from capellacollab.projects.users import routes as projects_users_routes
Expand Down Expand Up @@ -206,3 +207,8 @@ def _delete_all_pipelines_for_project(
prefix="/{project_slug}/events",
tags=["Projects - Events"],
)
router.include_router(
projects_tools_routes.router,
prefix="/{project_slug}/tools",
tags=["Projects - Tools"],
)
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ def _job_is_finished(status: models.PipelineRunStatus):


def _refresh_and_trigger_pipeline_jobs():
log.debug("Starting to refresh and trigger pipeline jobs...")
_schedule_pending_jobs()
with database.SessionLocal() as db:
for run in crud.get_scheduled_or_running_pipelines(db):
Expand Down Expand Up @@ -301,3 +302,4 @@ def _refresh_and_trigger_pipeline_jobs():
_terminate_job(run)

db.commit()
log.debug("Finished refreshing and triggering of pipeline jobs.")
8 changes: 4 additions & 4 deletions backend/capellacollab/projects/toolmodels/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import sqlalchemy as sa
from sqlalchemy import orm

from capellacollab.projects import models as projects_model
from capellacollab.projects import models as projects_models
from capellacollab.tools import models as tools_models

from . import models
Expand Down Expand Up @@ -68,7 +68,7 @@ def get_model_by_slugs(
.options(orm.joinedload(models.DatabaseToolModel.project))
.where(
models.DatabaseToolModel.project.has(
projects_model.DatabaseProject.slug == project_slug
projects_models.DatabaseProject.slug == project_slug
)
)
.where(models.DatabaseToolModel.slug == model_slug)
Expand All @@ -77,7 +77,7 @@ def get_model_by_slugs(

def create_model(
db: orm.Session,
project: projects_model.DatabaseProject,
project: projects_models.DatabaseProject,
post_model: models.PostToolModel,
tool: tools_models.DatabaseTool,
version: tools_models.DatabaseVersion | None = None,
Expand Down Expand Up @@ -135,7 +135,7 @@ def update_model(
name: str | None,
version: tools_models.DatabaseVersion | None,
nature: tools_models.DatabaseNature | None,
project: projects_model.DatabaseProject,
project: projects_models.DatabaseProject,
display_order: int | None,
) -> models.DatabaseToolModel:
model.version = version
Expand Down
16 changes: 16 additions & 0 deletions backend/capellacollab/projects/toolmodels/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
DatabaseVersion,
)

from .provisioning.models import DatabaseModelProvisioning
from .restrictions.models import DatabaseToolModelRestrictions


Expand Down Expand Up @@ -124,6 +125,14 @@ class DatabaseToolModel(database.Base):
)
)

provisioning: orm.Mapped[list[DatabaseModelProvisioning]] = (
orm.relationship(
back_populates="tool_model",
cascade="delete",
default_factory=list,
)
)


class ToolModel(core_pydantic.BaseModel):
id: int
Expand All @@ -144,3 +153,10 @@ class SimpleToolModel(core_pydantic.BaseModel):
slug: str
name: str
project: projects_models.SimpleProject


class SimpleToolModelWithoutProject(core_pydantic.BaseModel):
id: int
slug: str
name: str
git_models: list[GitModel] | None = None
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from capellacollab.projects.toolmodels import models as toolmodels_models
from capellacollab.projects.toolmodels.backups import crud as backups_crud
from capellacollab.projects.users import models as projects_users_models
from capellacollab.settings.modelsources.git import core as git_core
from capellacollab.settings.modelsources.git import core as instances_git_core
from capellacollab.settings.modelsources.git import models as git_models
from capellacollab.settings.modelsources.git import util as git_util

Expand Down Expand Up @@ -69,7 +69,7 @@ async def get_revisions_of_primary_git_model(
injectables.get_existing_primary_git_model
),
) -> git_models.GetRevisionsResponseModel:
return await git_core.get_remote_refs(
return await instances_git_core.get_remote_refs(
primary_git_model.path,
primary_git_model.username,
primary_git_model.password,
Expand All @@ -94,7 +94,7 @@ async def get_revisions_with_model_credentials(
injectables.get_existing_git_model
),
):
return await git_core.get_remote_refs(
return await instances_git_core.get_remote_refs(
url, git_model.username, git_model.password
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0
37 changes: 37 additions & 0 deletions backend/capellacollab/projects/toolmodels/provisioning/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0

import sqlalchemy as sa
from sqlalchemy import orm

from capellacollab.projects.toolmodels import models as toolmodels_models
from capellacollab.users import models as users_models

from . import models


def create_model_provisioning(
db: orm.Session, model: models.DatabaseModelProvisioning
) -> models.DatabaseModelProvisioning:
db.add(model)
db.commit()
return model


def get_model_provisioning(
db: orm.Session,
tool_model: toolmodels_models.DatabaseToolModel,
user: users_models.DatabaseUser,
) -> models.DatabaseModelProvisioning | None:
return db.execute(
sa.select(models.DatabaseModelProvisioning)
.where(models.DatabaseModelProvisioning.tool_model == tool_model)
.where(models.DatabaseModelProvisioning.user == user)
).scalar_one_or_none()


def delete_model_provisioning(
db: orm.Session, provisioning: models.DatabaseModelProvisioning
):
db.delete(provisioning)
db.commit()
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0

from fastapi import status

from capellacollab.core import exceptions as core_exceptions


class ProvisioningNotFoundError(core_exceptions.BaseError):
def __init__(self, project_slug: str, model_slug: str):
super().__init__(
status_code=status.HTTP_404_NOT_FOUND,
title="Provisioning not found",
reason=f"Couldn't find a provisioning for the model '{model_slug}' in the project '{project_slug}'.",
err_code="PROVISIONING_NOT_FOUND",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0


import fastapi
from sqlalchemy import orm

from capellacollab.core import database
from capellacollab.users import injectables as users_injectables
from capellacollab.users import models as users_models

from .. import injectables as toolmodels_injectables
from .. import models as toolmodels_models
from . import crud, models


def get_model_provisioning(
model: toolmodels_models.DatabaseToolModel = fastapi.Depends(
toolmodels_injectables.get_existing_capella_model
),
current_user: users_models.DatabaseUser = fastapi.Depends(
users_injectables.get_own_user
),
db: orm.Session = fastapi.Depends(database.get_db),
) -> models.DatabaseModelProvisioning | None:
return crud.get_model_provisioning(db, tool_model=model, user=current_user)
Loading

0 comments on commit ee3864f

Please sign in to comment.