Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow users to save acmg criteria on the server (#98) #237

Merged
merged 7 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions backend/alembic/versions/d10fec1c88fc_init_acmgseqvar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""init acmgseqvar
Revision ID: d10fec1c88fc
Revises: 4f3b20f156c1
Create Date: 2023-11-29 11:16:25.636296+01:00

"""
import fastapi_users_db_sqlalchemy.generics # noqa
import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision = "d10fec1c88fc"
down_revision = "4f3b20f156c1"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"acmgseqvar",
sa.Column("id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),
sa.Column("user", sa.Uuid(), nullable=False),
sa.Column("seqvar_name", sa.String(length=255), nullable=False),
sa.Column("acmg_rank", sa.JSON(), nullable=True),
sa.ForeignKeyConstraint(["user"], ["user.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("user", "seqvar_name", name="uq_acmgseqvar"),
)
op.create_index(op.f("ix_acmgseqvar_id"), "acmgseqvar", ["id"], unique=False)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_acmgseqvar_id"), table_name="acmgseqvar")
op.drop_table("acmgseqvar")
# ### end Alembic commands ###
3 changes: 2 additions & 1 deletion backend/app/api/api_v1/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
from httpx_oauth.clients.openid import OpenID
from httpx_oauth.errors import GetIdEmailError

from app.api.api_v1.endpoints import adminmsgs, auth, bookmarks, caseinfo, utils
from app.api.api_v1.endpoints import acmgseqvar, adminmsgs, auth, bookmarks, caseinfo, utils
from app.core.auth import auth_backend_bearer, auth_backend_cookie, fastapi_users
from app.core.config import settings
from app.schemas.user import UserRead, UserUpdate

api_router = APIRouter()
api_router.include_router(acmgseqvar.router, prefix="/acmgseqvar", tags=["acmgseqvar"])
api_router.include_router(adminmsgs.router, prefix="/adminmsgs", tags=["adminmsgs"])
api_router.include_router(bookmarks.router, prefix="/bookmarks", tags=["bookmarks"])
api_router.include_router(utils.router, prefix="/utils", tags=["utils"])
Expand Down
173 changes: 173 additions & 0 deletions backend/app/api/api_v1/endpoints/acmgseqvar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession

from app import crud, schemas
from app.api import deps
from app.api.deps import current_active_superuser, current_active_user
from app.models.user import User

router = APIRouter()


@router.post("/create", response_model=schemas.AcmgSeqVarCreate)
async def create_acmgseqvar(
acmgseqvar: schemas.AcmgSeqVarCreate,
db: AsyncSession = Depends(deps.get_db),
user: User = Depends(current_active_user),
):
"""
Create a new ACMG Sequence Variant.

:param acmgseqvar: ACMG Sequence Variant to create
:type acmgseqvar: dict or :class:`.schemas.AcmgSeqVarCreate`
:return: ACMG Sequence Variant
:rtype: dict
"""
acmgseqvar.user = user.id
return await crud.acmgseqvar.create(db, obj_in=acmgseqvar)


@router.get(
"/list-all",
dependencies=[Depends(current_active_superuser)],
response_model=list[schemas.AcmgSeqVarRead],
)
async def list_acmgseqvars(
skip: int = 0, limit: int = 100, db: AsyncSession = Depends(deps.get_db)
):
"""
List all ACMG Sequence Variants. Available only for superusers.

:param skip: number of ACMG Sequence Variants to skip
:type skip: int
:param limit: maximum number of ACMG Sequence Variants to return
:type limit: int
:return: list of ACMG Sequence Variants
:rtype: list
"""
return await crud.acmgseqvar.get_multi(db, skip=skip, limit=limit)


@router.get(
"/get-by-id",
dependencies=[Depends(current_active_superuser)],
response_model=schemas.AcmgSeqVarRead,
)
async def get_acmgseqvar(id: str, db: AsyncSession = Depends(deps.get_db)):
"""
Get a ACMG Sequence Variant by id. Available only for superusers.

:param id: ACMG Sequence Variant id
:type id: uuid
:return: ACMG Sequence Variant
:rtype: dict
"""
response = await crud.acmgseqvar.get(db, id)
if not response:
raise HTTPException(status_code=404, detail="ACMG Sequence Variant not found")
else:
return response


@router.get("/list", response_model=list[schemas.AcmgSeqVarRead])
async def list_acmgseqvars_by_user(
skip: int = 0,
limit: int = 100,
db: AsyncSession = Depends(deps.get_db),
user: User = Depends(current_active_user),
):
"""
List ACMG Sequence Variants by user.

:return: list of ACMG Sequence Variants
:rtype: list
"""
return await crud.acmgseqvar.get_multi_by_user(db, user_id=user.id, skip=skip, limit=limit)


@router.get("/get", response_model=schemas.AcmgSeqVarRead)
async def get_acmgseqvar_by_user(
seqvar: str,
db: AsyncSession = Depends(deps.get_db),
user: User = Depends(current_active_user),
):
"""
Get a ACMG Sequence Variant by id.

:param id: ACMG Sequence Variant id
:type id: uuid
:return: ACMG Sequence Variant
:rtype: dict
"""
response = await crud.acmgseqvar.get_by_user(db, user_id=user.id, seqvar_name=seqvar)
if not response:
raise HTTPException(status_code=404, detail="ACMG Sequence Variant not found")
else:
return response


@router.put("/update", response_model=schemas.AcmgSeqVarRead)
@router.patch("/update", response_model=schemas.AcmgSeqVarRead)
async def update_acmgseqvar(
acmgseqvar: schemas.AcmgSeqVarUpdate,
db: AsyncSession = Depends(deps.get_db),
user: User = Depends(current_active_user),
):
"""
Update a ACMG Sequence Variant.

:param acmgseqvar: ACMG Sequence Variant to update
:type acmgseqvar: dict or :class:`.schemas.AcmgSeqVarUpdate`
:return: ACMG Sequence Variant
:rtype: dict
"""
acmgseqvar.user = user.id
response = await crud.acmgseqvar.get_by_user(
db, user_id=user.id, seqvar_name=acmgseqvar.seqvar_name
)
if not response:
raise HTTPException(status_code=404, detail="ACMG Sequence Variant not found")
else:
return await crud.acmgseqvar.update(db, db_obj=response, obj_in=acmgseqvar)


@router.delete(
"/delete-by-id",
dependencies=[Depends(current_active_superuser)],
response_model=schemas.AcmgSeqVarRead,
)
async def delete_acmgseqvar(id: str, db: AsyncSession = Depends(deps.get_db)):
"""
Delete a ACMG Sequence Variant by id. Available only for superusers.

:param id: ACMG Sequence Variant id
:type id: uuid
:return: ACMG Sequence Variant
:rtype: dict
"""
response = await crud.acmgseqvar.get(db, id)
if not response:
raise HTTPException(status_code=404, detail="ACMG Sequence Variant not found")
else:
return await crud.acmgseqvar.remove(db, id=id)


@router.delete("/delete", response_model=schemas.AcmgSeqVarRead)
async def delete_acmgseqvar_by_user(
seqvar: str,
db: AsyncSession = Depends(deps.get_db),
user: User = Depends(current_active_user),
):
"""
Delete a ACMG Sequence Variant by id.

:param id: ACMG Sequence Variant id
:type id: uuid
:return: ACMG Sequence Variant
:rtype: dict
"""
response = await crud.acmgseqvar.get_by_user(db, user_id=user.id, seqvar_name=seqvar)
if not response:
raise HTTPException(status_code=404, detail="ACMG Sequence Variant not found")
else:
return await crud.acmgseqvar.remove(db, id=response.id)
42 changes: 21 additions & 21 deletions backend/app/api/api_v1/endpoints/caseinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,27 +67,6 @@ async def get_caseinfo(id: str, db: AsyncSession = Depends(deps.get_db)):
return response


@router.delete(
"/delete-by-id",
dependencies=[Depends(current_active_superuser)],
response_model=schemas.CaseInfoRead,
)
async def delete_caseinfo(id: str, db: AsyncSession = Depends(deps.get_db)):
"""
Delete a Case Information by id. Available only for superusers.

:param id: Case Information id
:type id: uuid
:return: Case Information
:rtype: dict
"""
response = await crud.caseinfo.remove(db, id=id)
if not response:
raise HTTPException(status_code=404, detail="Case Information not found")
else:
return response


@router.get("/list", response_model=list[schemas.CaseInfoRead])
async def list_caseinfos_for_user(
db: AsyncSession = Depends(deps.get_db),
Expand Down Expand Up @@ -140,6 +119,27 @@ async def update_caseinfo_for_user(
return await crud.caseinfo.update(db, db_obj=caseinfo, obj_in=caseinfoupdate)


@router.delete(
"/delete-by-id",
dependencies=[Depends(current_active_superuser)],
response_model=schemas.CaseInfoRead,
)
async def delete_caseinfo(id: str, db: AsyncSession = Depends(deps.get_db)):
"""
Delete a Case Information by id. Available only for superusers.

:param id: Case Information id
:type id: uuid
:return: Case Information
:rtype: dict
"""
response = await crud.caseinfo.remove(db, id=id)
if not response:
raise HTTPException(status_code=404, detail="Case Information not found")
else:
return response


@router.delete("/delete", response_model=schemas.CaseInfoRead)
async def delete_caseinfo_for_user(
db: AsyncSession = Depends(deps.get_db), user: User = Depends(current_active_user)
Expand Down
3 changes: 3 additions & 0 deletions backend/app/crud/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from app.crud.acmgseqvar import CrudAcmgSeqVar
from app.crud.base import CrudBase
from app.crud.bookmarks import CrudBookmark
from app.crud.caseinfo import CrudCaseInfo
from app.models.acmgseqvar import AcmgSeqVar
from app.models.adminmsg import AdminMessage
from app.models.bookmark import Bookmark
from app.models.caseinfo import CaseInfo
Expand All @@ -9,3 +11,4 @@
adminmessage = CrudBase[AdminMessage, AdminMessageCreate, AdminMessageUpdate](AdminMessage)
bookmark = CrudBookmark(Bookmark)
caseinfo = CrudCaseInfo(CaseInfo)
acmgseqvar = CrudAcmgSeqVar(AcmgSeqVar)
27 changes: 27 additions & 0 deletions backend/app/crud/acmgseqvar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import Any, Sequence

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select

from app.crud.base import CrudBase
from app.models.acmgseqvar import AcmgSeqVar
from app.schemas.acmgseqvar import AcmgSeqVarCreate, AcmgSeqVarUpdate


class CrudAcmgSeqVar(CrudBase[AcmgSeqVar, AcmgSeqVarCreate, AcmgSeqVarUpdate]):
async def get_multi_by_user(
self, session: AsyncSession, *, skip: int = 0, limit: int = 100, user_id: Any
) -> Sequence[AcmgSeqVar]:
query = select(self.model).filter(self.model.user == user_id).offset(skip).limit(limit)
result = await session.execute(query)
all_scalars: Sequence[AcmgSeqVar] = result.scalars().all() # type: ignore[assignment]
return all_scalars

async def get_by_user(
self, session: AsyncSession, *, user_id: Any, seqvar_name: Any
) -> AcmgSeqVar | None:
query = select(self.model).filter(
self.model.user == user_id, self.model.seqvar_name == seqvar_name
)
result = await session.execute(query)
return result.scalars().first()
1 change: 1 addition & 0 deletions backend/app/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from app.models.acmgseqvar import AcmgSeqVar # noqa
from app.models.adminmsg import AdminMessage # noqa
from app.models.bookmark import Bookmark # noqa
from app.models.caseinfo import CaseInfo # noqa
Expand Down
38 changes: 38 additions & 0 deletions backend/app/models/acmgseqvar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Models for ACMG sequence variant."""

import uuid as uuid_module
from typing import TYPE_CHECKING

from fastapi_users_db_sqlalchemy.generics import GUID # noqa
from sqlalchemy import JSON, Column, ForeignKey, String, UniqueConstraint, Uuid
from sqlalchemy.orm import Mapped, mapped_column

from app.db.session import Base
from app.schemas.acmgseqvar import AcmgRank

UUID_ID = uuid_module.UUID


class AcmgSeqVar(Base):
"""ACMG sequence variant."""

__tablename__ = "acmgseqvar"

__table_args__ = (UniqueConstraint("user", "seqvar_name", name="uq_acmgseqvar"),)

if TYPE_CHECKING: # pragma: no cover
id: UUID_ID
user: UUID_ID
seqvar_name: str
acmg_rank: AcmgRank
else:
#: UUID of the ACMG sequence variant.
id: Mapped[UUID_ID] = mapped_column(
GUID, primary_key=True, index=True, default=uuid_module.uuid4
)
#: User who created the ACMG sequence variant.
user = Column(Uuid, ForeignKey("user.id", ondelete="CASCADE"), nullable=False)
#: Sequence variant ID.
seqvar_name = Column(String(255), nullable=False)
#: ACMG criteria.
acmg_rank = Column(JSON, nullable=True)
1 change: 1 addition & 0 deletions backend/app/schemas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from app.schemas.acmgseqvar import AcmgSeqVarCreate, AcmgSeqVarRead, AcmgSeqVarUpdate # noqa
from app.schemas.adminmsg import AdminMessageCreate, AdminMessageRead, AdminMessageUpdate # noqa
from app.schemas.auth import OAuth2ProviderConfig, OAuth2ProviderPublic # noqa
from app.schemas.bookmark import BookmarkCreate, BookmarkRead, BookmarkUpdate # noqa
Expand Down
Loading
Loading