Skip to content

Commit

Permalink
add(plate service) (#111) (patch)
Browse files Browse the repository at this point in the history
# Description

add plate service
  • Loading branch information
ChrOertlin authored Mar 25, 2024
1 parent 9aa3612 commit 4fbdea7
Show file tree
Hide file tree
Showing 16 changed files with 292 additions and 165 deletions.
4 changes: 2 additions & 2 deletions genotype_api/api/endpoints/analyses.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
get_analysis_by_id,
)
from genotype_api.database.models import Analysis, User
from genotype_api.dto.analysis import AnalysisWithGenotypeResponse, AnalysisResponse
from genotype_api.dto.analysis import AnalysisGenotypeResponse, AnalysisResponse
from genotype_api.database.session_handler import get_session
from genotype_api.security import get_active_user
from genotype_api.services.analysis_service.analysis_service import AnalysisService

router = APIRouter()


@router.get("/{analysis_id}", response_model=AnalysisWithGenotypeResponse)
@router.get("/{analysis_id}", response_model=AnalysisGenotypeResponse)
def read_analysis(
analysis_id: int,
session: Session = Depends(get_session),
Expand Down
139 changes: 40 additions & 99 deletions genotype_api/api/endpoints/plates.py
Original file line number Diff line number Diff line change
@@ -1,117 +1,65 @@
"""Routes for plates"""

from datetime import datetime
from io import BytesIO
from pathlib import Path
from typing import Literal, Sequence

from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile, status
from typing import Literal
from fastapi import APIRouter, Depends, File, Query, UploadFile, status
from fastapi.responses import JSONResponse
from sqlalchemy import asc, desc
from sqlmodel import Session
from sqlmodel.sql.expression import Select, SelectOfScalar

from genotype_api.database.crud.create import create_analyses_samples, create_plate
from genotype_api.database.crud.delete import delete_analysis
from genotype_api.database.crud.read import (
check_analyses_objects,
get_analyses_from_plate,
get_plate,
get_plate_read_analysis_single,
get_ordered_plates,
get_user_by_email,
)
from genotype_api.database.crud.update import (
refresh_sample_status,
refresh_plate,
update_plate_sign_off,
)
from genotype_api.database.filter_models.plate_models import PlateSignOff, PlateOrderParams
from genotype_api.database.models import (
Analysis,
Plate,
User,
)
from genotype_api.dto.dto import (
PlateCreate,
PlateReadWithAnalyses,
PlateReadWithAnalysisDetail,
PlateReadWithAnalysisDetailSingle,
)
from genotype_api.database.filter_models.plate_models import PlateOrderParams
from genotype_api.database.models import User
from genotype_api.database.session_handler import get_session
from genotype_api.file_parsing.excel import GenotypeAnalysis
from genotype_api.file_parsing.files import check_file
from genotype_api.dto.plate import PlateResponse
from genotype_api.security import get_active_user
from genotype_api.services.plate_service.plate_service import PlateService

SelectOfScalar.inherit_cache = True
Select.inherit_cache = True

router = APIRouter()


def get_plate_id_from_file(file_name: Path) -> str:
# Get the plate id from the standardized name of the plate
return file_name.name.split("_", 1)[0]
def get_plate_service(session: Session = Depends(get_session)) -> PlateService:
return PlateService(session)


@router.post("/plate", response_model=PlateReadWithAnalyses)
@router.post(
"/plate",
response_model=PlateResponse,
response_model_exclude={"detail"},
)
def upload_plate(
file: UploadFile = File(...),
session: Session = Depends(get_session),
plate_service: PlateService = Depends(get_plate_service),
current_user: User = Depends(get_active_user),
):
file_name: Path = check_file(file_path=file.filename, extension=".xlsx")
plate_id: str = get_plate_id_from_file(file_name)
db_plate = session.get(Plate, plate_id)
if db_plate:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Plate with id {db_plate.id} already exists",
)

excel_parser = GenotypeAnalysis(
excel_file=BytesIO(file.file.read()),
file_name=str(file_name),
include_key="-CG-",
)
analyses: list[Analysis] = list(excel_parser.generate_analyses())
check_analyses_objects(session=session, analyses=analyses, analysis_type="genotype")
create_analyses_samples(session=session, analyses=analyses)
plate_obj = PlateCreate(plate_id=plate_id)
plate_obj.analyses = analyses
plate: Plate = create_plate(session=session, plate=plate_obj)
for analysis in plate.analyses:
refresh_sample_status(sample=analysis.sample, session=session)
refresh_plate(session=session, plate=plate)
return plate


@router.patch("/{plate_id}/sign-off", response_model=Plate)
return plate_service.upload_plate(file)


@router.patch(
"/{plate_id}/sign-off",
response_model=PlateResponse,
response_model_exclude={"analyses", "user", "detail"},
)
def sign_off_plate(
plate_id: int,
method_document: str = Query(...),
method_version: str = Query(...),
session: Session = Depends(get_session),
plate_service: PlateService = Depends(get_plate_service),
current_user: User = Depends(get_active_user),
):
"""Sign off a plate.
This means that current User sign off that the plate is checked
Add Depends with current user
"""
user: User = get_user_by_email(session=session, email=current_user.email)
plate: Plate = get_plate(session=session, plate_id=plate_id)
plate_sign_off = PlateSignOff(
user_id=user.id,
signed_at=datetime.now(),
method_document=method_document,

return plate_service.update_plate_sign_off(
plate_id=plate_id,
user_email=current_user.email,
method_version=method_version,
method_document=method_document,
)
return update_plate_sign_off(session=session, plate=plate, plate_sign_off=plate_sign_off)


@router.get(
"/{plate_id}",
response_model=PlateReadWithAnalysisDetailSingle,
response_model=PlateResponse,
response_model_by_alias=False,
response_model_exclude={
"analyses": {
Expand All @@ -133,16 +81,17 @@ def sign_off_plate(
)
def read_plate(
plate_id: int,
session: Session = Depends(get_session),
plate_service: PlateService = Depends(get_plate_service),
current_user: User = Depends(get_active_user),
):
"""Display information about a plate."""
return get_plate_read_analysis_single(session=session, plate_id=plate_id)

return plate_service.read_plate(plate_id=plate_id)


@router.get(
"/",
response_model=list[PlateReadWithAnalysisDetail],
response_model=list[PlateResponse],
response_model_exclude={"analyses"},
response_model_by_alias=False,
)
Expand All @@ -151,33 +100,25 @@ async def read_plates(
sort_order: Literal["ascend", "descend"] | None = "descend",
skip: int | None = 0,
limit: int | None = 10,
session: Session = Depends(get_session),
plate_service: PlateService = Depends(get_plate_service),
current_user: User = Depends(get_active_user),
) -> Sequence[Plate]:
):
"""Display all plates"""

order_params = PlateOrderParams(
order_by=order_by, skip=skip, limit=limit, sort_order=sort_order
)
plates: Sequence[Plate] = get_ordered_plates(session=session, order_params=order_params)
return plates
return plate_service.read_plates(order_params=order_params)


@router.delete("/{plate_id}", response_model=Plate)
@router.delete("/{plate_id}")
def delete_plate(
plate_id: int,
session: Session = Depends(get_session),
plate_service: PlateService = Depends(get_plate_service),
current_user: User = Depends(get_active_user),
):
"""Delete plate."""
plate = session.get(Plate, plate_id)
analyses: list[Analysis] = get_analyses_from_plate(session=session, plate_id=plate_id)
analyse_ids = [analyse.id for analyse in analyses]
for analysis in analyses:
delete_analysis(session=session, analysis=analysis)
delete_plate(session=session, plate=plate)

analysis_ids = plate_service.delete_plate(plate_id)
return JSONResponse(
f"Deleted plate: {plate_id} and analyses: {analyse_ids}",
f"Deleted plate: {plate_id} and analyses: {analysis_ids}",
status_code=status.HTTP_200_OK,
)
8 changes: 4 additions & 4 deletions genotype_api/api/endpoints/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from starlette.responses import JSONResponse

from genotype_api.database.crud.read import (
get_user,
get_user_by_id,
get_user_by_email,
get_users_with_skip_and_limit,
)
Expand All @@ -32,7 +32,7 @@ def read_user(
session: Session = Depends(get_session),
current_user: User = Depends(get_active_user),
) -> User:
return get_user(session=session, user_id=user_id)
return get_user_by_id(session=session, user_id=user_id)


@router.delete("/{user_id}")
Expand All @@ -41,7 +41,7 @@ def delete_user(
session: Session = Depends(get_session),
current_user: User = Depends(get_active_user),
) -> JSONResponse:
user: User = get_user(session=session, user_id=user_id)
user: User = get_user_by_id(session=session, user_id=user_id)
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
if user.plates:
Expand All @@ -60,7 +60,7 @@ def change_user_email(
session: Session = Depends(get_session),
current_user: User = Depends(get_active_user),
) -> User:
user: User = get_user(session=session, user_id=user_id)
user: User = get_user_by_id(session=session, user_id=user_id)
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
user: User = update_user_email(session=session, user=user, email=email)
Expand Down
2 changes: 1 addition & 1 deletion genotype_api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Sexes(str, Enum):
UNKNOWN = "unknown"


class STATUS(str, Enum):
class Status(str, Enum):
PASS = "pass"
FAIL = "fail"
CANCEL = "cancel"
Expand Down
16 changes: 3 additions & 13 deletions genotype_api/database/crud/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,33 +76,23 @@ def get_analysis_by_type_and_sample_id(
).one()


def get_plate(session: Session, plate_id: int) -> Plate:
"""Get plate"""
def get_plate_by_id(session: Session, plate_id: int) -> Plate:
statement = select(Plate).where(Plate.id == plate_id)
return session.exec(statement).one()


def get_plate_read_analysis_single(
session: Session, plate_id: int
) -> PlateReadWithAnalysisDetailSingle:
plate: Plate = get_plate(session=session, plate_id=plate_id)
return PlateReadWithAnalysisDetailSingle.from_orm(plate)


def get_ordered_plates(session: Session, order_params: PlateOrderParams) -> list[Plate]:
sort_func = desc if order_params.sort_order == "descend" else asc
plates: list[Plate] = session.exec(
return session.exec(
select(Plate)
.order_by(sort_func(order_params.order_by))
.offset(order_params.skip)
.limit(order_params.limit)
).all()
return plates


def get_incomplete_samples(statement: SelectOfScalar) -> SelectOfScalar:
"""Returning sample query statement for samples with less than two analyses."""

return (
statement.group_by(Analysis.sample_id)
.order_by(Analysis.created_at)
Expand Down Expand Up @@ -162,7 +152,7 @@ def get_samples(statement: SelectOfScalar, sample_id: str) -> SelectOfScalar:
return statement.where(Sample.id.contains(sample_id))


def get_user(session: Session, user_id: int):
def get_user_by_id(session: Session, user_id: int):
statement = select(User).where(User.id == user_id)
return session.exec(statement).one()

Expand Down
4 changes: 2 additions & 2 deletions genotype_api/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from sqlalchemy import Index
from sqlmodel import Field, Relationship, SQLModel

from genotype_api.constants import Sexes, STATUS, Types
from genotype_api.constants import Sexes, Status, Types


class GenotypeBase(SQLModel):
Expand Down Expand Up @@ -59,7 +59,7 @@ def check_no_calls(self) -> dict[str, int]:


class SampleSlim(SQLModel):
status: STATUS | None
status: Status | None
comment: str | None


Expand Down
18 changes: 15 additions & 3 deletions genotype_api/dto/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from pydantic import BaseModel

from genotype_api.constants import Sexes, Types
from genotype_api.dto.genotype import GenotypeBase
from genotype_api.dto.genotype import GenotypeResponse
from genotype_api.dto.sample import SampleStatusResponse


class AnalysisResponse(BaseModel):
Expand All @@ -18,12 +19,23 @@ class AnalysisResponse(BaseModel):
id: int | None


class AnalysisWithGenotypeResponse(BaseModel):
class AnalysisGenotypeResponse(BaseModel):
type: Types | None
source: str | None
sex: Sexes | None
created_at: datetime | None
sample_id: str | None
plate_id: str | None
id: int | None
genotypes: list[GenotypeBase] | None
genotypes: list[GenotypeResponse] | None = None


class AnalysisSampleResponse(BaseModel):
type: Types | None
source: str | None
sex: Sexes | None
created_at: datetime | None
sample_id: str | None
plate_id: str | None
id: int | None
sample: SampleStatusResponse | None = None
3 changes: 2 additions & 1 deletion genotype_api/dto/dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

import genotype_api.database.models
from genotype_api.database import models
from genotype_api.models import SampleDetail, PlateStatusCounts
from genotype_api.models import SampleDetail
from genotype_api.dto.plate import PlateStatusCounts
from genotype_api.services.match_genotype_service.utils import check_snps, check_sex


Expand Down
2 changes: 1 addition & 1 deletion genotype_api/dto/genotype.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pydantic import BaseModel, Field


class GenotypeBase(BaseModel):
class GenotypeResponse(BaseModel):
rsnumber: str = Field(max_length=10)
analysis_id: int
allele_1: str = Field(max_length=1)
Expand Down
Loading

0 comments on commit 4fbdea7

Please sign in to comment.