From 3e415875dc247a48cdbec64df24681e842c0742a Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Wed, 20 Mar 2024 14:48:25 +0100 Subject: [PATCH 01/31] add models --- genotype_api/dto/plate.py | 26 +++++++++++++++++++ .../services/plate_service/plate_service.py | 9 +++++++ 2 files changed, 35 insertions(+) create mode 100644 genotype_api/dto/plate.py create mode 100644 genotype_api/services/plate_service/plate_service.py diff --git a/genotype_api/dto/plate.py b/genotype_api/dto/plate.py new file mode 100644 index 0000000..2b2813b --- /dev/null +++ b/genotype_api/dto/plate.py @@ -0,0 +1,26 @@ +"""Module for the plate dtos.""" + +from collections import Counter + +from pydantic import BaseModel, validator + +from genotype_api.models import PlateStatusCounts + + +class PlateResponse(BaseModel): + pass + + +class PlateWithAnalysesResponse(BaseModel): + pass + + @validator("detail") + def check_detail(cls, value, values): + analyses = values.get("analyses") + statuses = [str(analysis.sample.status) for analysis in analyses] + commented = sum(1 for analysis in analyses if analysis.sample.comment) + status_counts = Counter(statuses) + return PlateStatusCounts(**status_counts, total=len(analyses), commented=commented) + + class Config: + validate_all = True diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py new file mode 100644 index 0000000..367cb8e --- /dev/null +++ b/genotype_api/services/plate_service/plate_service.py @@ -0,0 +1,9 @@ +"""Module to holds the plate service.""" + +from sqlmodel import Session + + +class PlateService: + + def __init__(self, session: Session): + self.session: Session = session From 41849905d50f65d8bd3132b4212d159f6a736e16 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Fri, 22 Mar 2024 09:21:41 +0100 Subject: [PATCH 02/31] update signoff service --- genotype_api/api/endpoints/analyses.py | 4 +- genotype_api/api/endpoints/plates.py | 66 +++------- genotype_api/constants.py | 2 +- genotype_api/database/models.py | 4 +- genotype_api/dto/analysis.py | 18 ++- genotype_api/dto/dto.py | 3 +- genotype_api/dto/genotype.py | 2 +- genotype_api/dto/plate.py | 55 ++++++-- genotype_api/dto/sample.py | 10 ++ genotype_api/dto/user.py | 9 ++ genotype_api/models.py | 14 +-- .../analysis_service/analysis_service.py | 6 +- .../services/plate_service/plate_service.py | 119 ++++++++++++++++++ 13 files changed, 225 insertions(+), 87 deletions(-) create mode 100644 genotype_api/dto/sample.py create mode 100644 genotype_api/dto/user.py diff --git a/genotype_api/api/endpoints/analyses.py b/genotype_api/api/endpoints/analyses.py index 8215456..7ca1d78 100644 --- a/genotype_api/api/endpoints/analyses.py +++ b/genotype_api/api/endpoints/analyses.py @@ -9,7 +9,7 @@ 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 @@ -17,7 +17,7 @@ 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), diff --git a/genotype_api/api/endpoints/plates.py b/genotype_api/api/endpoints/plates.py index d51d3a0..a5f2d90 100644 --- a/genotype_api/api/endpoints/plates.py +++ b/genotype_api/api/endpoints/plates.py @@ -1,46 +1,32 @@ """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 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, ) -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.filter_models.plate_models import PlateOrderParams from genotype_api.database.models import ( Analysis, Plate, User, ) from genotype_api.dto.dto import ( - PlateCreate, - PlateReadWithAnalyses, PlateReadWithAnalysisDetail, PlateReadWithAnalysisDetailSingle, ) 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 PlateAnalysesResponse, 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 @@ -53,39 +39,17 @@ def get_plate_id_from_file(file_name: Path) -> str: return file_name.name.split("_", 1)[0] -@router.post("/plate", response_model=PlateReadWithAnalyses) +@router.post("/plate", response_model=PlateAnalysesResponse) def upload_plate( file: UploadFile = File(...), session: Session = Depends(get_session), 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) + plate_service = PlateService(session) + return plate_service.upload_plate(file) + + +@router.patch("/{plate_id}/sign-off", response_model=PlateResponse) def sign_off_plate( plate_id: int, method_document: str = Query(...), @@ -97,15 +61,13 @@ def sign_off_plate( This means that current User sign off that the plate is checked Add Depends with current user """ - - plate: Plate = get_plate(session=session, plate_id=plate_id) - plate_sign_off = PlateSignOff( + plate_service = PlateService(session) + return plate_service.update_plate_sign_off( + plate_id=plate_id, user_id=current_user.id, - signed_at=datetime.now(), - method_document=method_document, 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( diff --git a/genotype_api/constants.py b/genotype_api/constants.py index 78d1325..4eb1578 100644 --- a/genotype_api/constants.py +++ b/genotype_api/constants.py @@ -19,7 +19,7 @@ class Sexes(str, Enum): UNKNOWN = "unknown" -class STATUS(str, Enum): +class Status(str, Enum): PASS = "pass" FAIL = "fail" CANCEL = "cancel" diff --git a/genotype_api/database/models.py b/genotype_api/database/models.py index 5af2280..426b7b2 100644 --- a/genotype_api/database/models.py +++ b/genotype_api/database/models.py @@ -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): @@ -59,7 +59,7 @@ def check_no_calls(self) -> dict[str, int]: class SampleSlim(SQLModel): - status: STATUS | None + status: Status | None comment: str | None diff --git a/genotype_api/dto/analysis.py b/genotype_api/dto/analysis.py index ce8e284..1a51715 100644 --- a/genotype_api/dto/analysis.py +++ b/genotype_api/dto/analysis.py @@ -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): @@ -18,7 +19,7 @@ class AnalysisResponse(BaseModel): id: int | None -class AnalysisWithGenotypeResponse(BaseModel): +class AnalysisGenotypeResponse(BaseModel): type: Types | None source: str | None sex: Sexes | None @@ -26,4 +27,15 @@ class AnalysisWithGenotypeResponse(BaseModel): 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 diff --git a/genotype_api/dto/dto.py b/genotype_api/dto/dto.py index 2ee7919..1a889b0 100644 --- a/genotype_api/dto/dto.py +++ b/genotype_api/dto/dto.py @@ -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 diff --git a/genotype_api/dto/genotype.py b/genotype_api/dto/genotype.py index 306ab30..ede217a 100644 --- a/genotype_api/dto/genotype.py +++ b/genotype_api/dto/genotype.py @@ -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) diff --git a/genotype_api/dto/plate.py b/genotype_api/dto/plate.py index 2b2813b..ebed94b 100644 --- a/genotype_api/dto/plate.py +++ b/genotype_api/dto/plate.py @@ -1,21 +1,58 @@ -"""Module for the plate dtos.""" +"""Module for the plate DTOs.""" from collections import Counter - -from pydantic import BaseModel, validator - -from genotype_api.models import PlateStatusCounts +from pydantic import BaseModel, validator, Field +from genotype_api.dto.analysis import AnalysisSampleResponse +from genotype_api.dto.user import UserInfoResponse class PlateResponse(BaseModel): - pass + created_at: str + plate_id: str + signed_by: int + signed_at: str + method_document: str + method_version: str + id: str + + +class PlateAnalysesResponse(BaseModel): + created_at: str + plate_id: str + signed_by: int + signed_at: str + method_document: str + method_version: str + id: str + user: UserInfoResponse + analyses: list[AnalysisSampleResponse] = [] + + +class PlateStatusCounts(BaseModel): + total: int = Field(0, nullable=True) + failed: int = Field(0, alias="STATUS.FAIL", nullable=True) + passed: int = Field(0, alias="STATUS.PASS", nullable=True) + cancelled: int = Field(0, alias="STATUS.CANCEL", nullable=True) + unknown: int = Field(0, alias="None", nullable=True) + commented: int = Field(0, nullable=True) + + class Config: + allow_population_by_field_name = True -class PlateWithAnalysesResponse(BaseModel): - pass +class PlateAnalysesDetailResponse(BaseModel): + created_at: str + plate_id: str + signed_by: int + signed_at: str + method_document: str + method_version: str + id: str + user: UserInfoResponse + analyses: list[AnalysisSampleResponse] = [] @validator("detail") - def check_detail(cls, value, values): + def check_detail(self, values): analyses = values.get("analyses") statuses = [str(analysis.sample.status) for analysis in analyses] commented = sum(1 for analysis in analyses if analysis.sample.comment) diff --git a/genotype_api/dto/sample.py b/genotype_api/dto/sample.py new file mode 100644 index 0000000..c05b8b1 --- /dev/null +++ b/genotype_api/dto/sample.py @@ -0,0 +1,10 @@ +"""Module for the sample DTOs.""" + +from pydantic import BaseModel + +from genotype_api.constants import Status + + +class SampleStatusResponse(BaseModel): + status: Status | None = None + comment: str | None = None diff --git a/genotype_api/dto/user.py b/genotype_api/dto/user.py new file mode 100644 index 0000000..d3a8aa2 --- /dev/null +++ b/genotype_api/dto/user.py @@ -0,0 +1,9 @@ +"""Module for the plate DTOs.""" + +from pydantic import BaseModel, EmailStr + + +class UserInfoResponse(BaseModel): + email: EmailStr + name: str | None = None + id: int diff --git a/genotype_api/models.py b/genotype_api/models.py index 93a9e4c..5d13d6b 100644 --- a/genotype_api/models.py +++ b/genotype_api/models.py @@ -1,16 +1,4 @@ -from pydantic import BaseModel, validator, Field - - -class PlateStatusCounts(BaseModel): - total: int = Field(0, nullable=True) - failed: int = Field(0, alias="STATUS.FAIL", nullable=True) - passed: int = Field(0, alias="STATUS.PASS", nullable=True) - cancelled: int = Field(0, alias="STATUS.CANCEL", nullable=True) - unknown: int = Field(0, alias="None", nullable=True) - commented: int = Field(0, nullable=True) - - class Config: - allow_population_by_field_name = True +from pydantic import BaseModel, validator class SampleDetailStats(BaseModel): diff --git a/genotype_api/services/analysis_service/analysis_service.py b/genotype_api/services/analysis_service/analysis_service.py index 41753ed..9b49f84 100644 --- a/genotype_api/services/analysis_service/analysis_service.py +++ b/genotype_api/services/analysis_service/analysis_service.py @@ -14,7 +14,7 @@ ) from genotype_api.database.crud.update import refresh_sample_status from genotype_api.database.models import Analysis -from genotype_api.dto.analysis import AnalysisWithGenotypeResponse, AnalysisResponse +from genotype_api.dto.analysis import AnalysisGenotypeResponse, AnalysisResponse from genotype_api.file_parsing.files import check_file from genotype_api.file_parsing.vcf import SequenceAnalysis @@ -25,9 +25,9 @@ class AnalysisService: def __init__(self, session: Session): self.session: Session = session - def get_analysis_with_genotype(self, analysis_id: int) -> AnalysisWithGenotypeResponse: + def get_analysis_with_genotype(self, analysis_id: int) -> AnalysisGenotypeResponse: analysis: Analysis = get_analysis_by_id(session=self.session, analysis_id=analysis_id) - return AnalysisWithGenotypeResponse( + return AnalysisGenotypeResponse( type=analysis.type, source=analysis.source, sex=analysis.sex, diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index 367cb8e..f77487d 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -1,9 +1,128 @@ """Module to holds the plate service.""" +from datetime import datetime +from http.client import HTTPException +from io import BytesIO +from pathlib import Path + +from fastapi import UploadFile from sqlmodel import Session +from starlette import status + +from genotype_api.api.endpoints.plates import get_plate_id_from_file +from genotype_api.constants import Types +from genotype_api.database.crud.create import create_analyses_samples, create_plate +from genotype_api.database.crud.read import check_analyses_objects, get_plate +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 +from genotype_api.database.models import Plate, Analysis +from genotype_api.dto.analysis import AnalysisSampleResponse +from genotype_api.dto.dto import PlateCreate +from genotype_api.dto.plate import PlateAnalysesResponse, PlateResponse +from genotype_api.dto.sample import SampleStatusResponse +from genotype_api.dto.user import UserInfoResponse +from genotype_api.file_parsing.excel import GenotypeAnalysis +from genotype_api.file_parsing.files import check_file class PlateService: def __init__(self, session: Session): self.session: Session = session + + @staticmethod + def _get_analyses_on_plate(plate: Plate) -> list[AnalysisSampleResponse]: + analyses_response: list[AnalysisSampleResponse] = [] + for analysis in plate.analyses: + sample_status = SampleStatusResponse( + status=analysis.sample.status, comment=analysis.sample.comment + ) + analysis_response = AnalysisSampleResponse( + type=analysis.type, + source=analysis.source, + sex=analysis.sex, + created_at=analysis.created_at, + sample_id=analysis.sample_id, + plate_id=analysis.plate_id, + id=analysis.id, + sample=sample_status, + ) + analyses_response.append(analysis_response) + return analyses_response + + @staticmethod + def _get_plate_user(plate: Plate) -> UserInfoResponse: + return UserInfoResponse(email=plate.user.email, name=plate.user.name, id=plate.user.id) + + def get_plate_analyses_response(self, plate: Plate) -> PlateAnalysesResponse: + analyses_response: list[AnalysisSampleResponse] = self._get_analyses_on_plate(plate) + user: UserInfoResponse = self._get_plate_user(plate) + return PlateAnalysesResponse( + created_at=plate.created_at, + plate_id=plate.plate_id, + signed_by=plate.signed_by, + signed_at=plate.signed_at, + method_document=plate.method_document, + method_version=plate.method_version, + id=plate.id, + user=user, + analyses=analyses_response, + ) + + def upload_plate(self, file: UploadFile) -> PlateAnalysesResponse: + file_name: Path = check_file(file_path=file.filename, extension=".xlsx") + plate_id: str = get_plate_id_from_file(file_name) + db_plate = self.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=self.session, analyses=analyses, analysis_type=Types.GENOTYPE + ) + create_analyses_samples(session=self.session, analyses=analyses) + plate_obj = PlateCreate(plate_id=plate_id) + plate_obj.analyses = analyses + plate: Plate = create_plate(session=self.session, plate=plate_obj) + for analysis in plate.analyses: + refresh_sample_status(sample=analysis.sample, session=self.session) + refresh_plate(session=self.session, plate=plate) + + return self.get_plate_analyses_response(plate=plate) + + @staticmethod + def _get_plate_response(plate: Plate) -> PlateResponse: + return PlateResponse( + created_at=plate.created_at, + plate_id=plate.plate_id, + signed_by=plate.signed_by, + signed_at=plate.signed_at, + method_document=plate.method_document, + method_version=plate.method_version, + id=plate.id, + ) + + def update_plate_sign_off( + self, plate_id: int, user_id: int, method_document: str, method_version: str + ) -> PlateResponse: + plate: Plate = get_plate(session=self.session, plate_id=plate_id) + plate_sign_off = PlateSignOff( + user_id=user_id, + signed_at=datetime.now(), + method_document=method_document, + method_version=method_version, + ) + update_plate_sign_off(session=self.session, plate=plate, plate_sign_off=plate_sign_off) + return self._get_plate_response(plate) From 61bed0aa914968b9aa3f203cbb207f79d332aa8c Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Fri, 22 Mar 2024 13:45:43 +0100 Subject: [PATCH 03/31] fix plate service --- genotype_api/api/endpoints/plates.py | 74 +++++++++---------- genotype_api/database/crud/read.py | 4 +- genotype_api/dto/plate.py | 25 +------ .../services/plate_service/plate_service.py | 44 +++++++++-- 4 files changed, 74 insertions(+), 73 deletions(-) diff --git a/genotype_api/api/endpoints/plates.py b/genotype_api/api/endpoints/plates.py index a5f2d90..da27692 100644 --- a/genotype_api/api/endpoints/plates.py +++ b/genotype_api/api/endpoints/plates.py @@ -1,30 +1,20 @@ """Routes for plates""" from pathlib import Path -from typing import Literal, Sequence +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.delete import delete_analysis -from genotype_api.database.crud.read import ( - get_analyses_from_plate, - get_plate_read_analysis_single, - get_ordered_plates, -) + from genotype_api.database.filter_models.plate_models import PlateOrderParams from genotype_api.database.models import ( - Analysis, - Plate, User, ) -from genotype_api.dto.dto import ( - PlateReadWithAnalysisDetail, - PlateReadWithAnalysisDetailSingle, -) + from genotype_api.database.session_handler import get_session -from genotype_api.dto.plate import PlateAnalysesResponse, PlateResponse +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 @@ -39,29 +29,40 @@ def get_plate_id_from_file(file_name: Path) -> str: return file_name.name.split("_", 1)[0] -@router.post("/plate", response_model=PlateAnalysesResponse) +def get_plate_service() -> PlateService: + session: Session = get_session() + return PlateService(session) + + +@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), ): - plate_service = PlateService(session) return plate_service.upload_plate(file) -@router.patch("/{plate_id}/sign-off", response_model=PlateResponse) +@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 """ - plate_service = PlateService(session) return plate_service.update_plate_sign_off( plate_id=plate_id, user_id=current_user.id, @@ -72,7 +73,7 @@ def sign_off_plate( @router.get( "/{plate_id}", - response_model=PlateReadWithAnalysisDetailSingle, + response_model=PlateResponse, response_model_by_alias=False, response_model_exclude={ "analyses": { @@ -94,16 +95,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, ) @@ -112,33 +114,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""" sort_func = desc if sort_order == "descend" else asc order_params = PlateOrderParams(order_by=order_by, skip=skip, limit=limit) - plates: Sequence[Plate] = get_ordered_plates( - session=session, order_params=order_params, sort_func=sort_func - ) - return plates + + return plate_service.read_plates(order_params=order_params, sort_func=sort_func) -@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, ) diff --git a/genotype_api/database/crud/read.py b/genotype_api/database/crud/read.py index 70e56fa..2ee7c8e 100644 --- a/genotype_api/database/crud/read.py +++ b/genotype_api/database/crud/read.py @@ -76,7 +76,7 @@ def get_analysis_by_type_and_sample_id( ).one() -def get_plate(session: Session, plate_id: int) -> Plate: +def get_plate_by_id(session: Session, plate_id: int) -> Plate: """Get plate""" statement = select(Plate).where(Plate.id == plate_id) @@ -86,7 +86,7 @@ def get_plate(session: Session, plate_id: int) -> Plate: def get_plate_read_analysis_single( session: Session, plate_id: int ) -> PlateReadWithAnalysisDetailSingle: - plate: Plate = get_plate(session=session, plate_id=plate_id) + plate: Plate = get_plate_by_id(session=session, plate_id=plate_id) return PlateReadWithAnalysisDetailSingle.from_orm(plate) diff --git a/genotype_api/dto/plate.py b/genotype_api/dto/plate.py index ebed94b..b798f0e 100644 --- a/genotype_api/dto/plate.py +++ b/genotype_api/dto/plate.py @@ -6,28 +6,6 @@ from genotype_api.dto.user import UserInfoResponse -class PlateResponse(BaseModel): - created_at: str - plate_id: str - signed_by: int - signed_at: str - method_document: str - method_version: str - id: str - - -class PlateAnalysesResponse(BaseModel): - created_at: str - plate_id: str - signed_by: int - signed_at: str - method_document: str - method_version: str - id: str - user: UserInfoResponse - analyses: list[AnalysisSampleResponse] = [] - - class PlateStatusCounts(BaseModel): total: int = Field(0, nullable=True) failed: int = Field(0, alias="STATUS.FAIL", nullable=True) @@ -40,7 +18,7 @@ class Config: allow_population_by_field_name = True -class PlateAnalysesDetailResponse(BaseModel): +class PlateResponse(BaseModel): created_at: str plate_id: str signed_by: int @@ -50,6 +28,7 @@ class PlateAnalysesDetailResponse(BaseModel): id: str user: UserInfoResponse analyses: list[AnalysisSampleResponse] = [] + detail: PlateStatusCounts @validator("detail") def check_detail(self, values): diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index f77487d..87421e7 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -4,6 +4,7 @@ from http.client import HTTPException from io import BytesIO from pathlib import Path +from typing import Sequence from fastapi import UploadFile from sqlmodel import Session @@ -12,17 +13,23 @@ from genotype_api.api.endpoints.plates import get_plate_id_from_file from genotype_api.constants import Types from genotype_api.database.crud.create import create_analyses_samples, create_plate -from genotype_api.database.crud.read import check_analyses_objects, get_plate +from genotype_api.database.crud.delete import delete_analysis, delete_plate +from genotype_api.database.crud.read import ( + check_analyses_objects, + get_plate_by_id, + get_ordered_plates, + get_analyses_from_plate, +) 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 +from genotype_api.database.filter_models.plate_models import PlateSignOff, PlateOrderParams from genotype_api.database.models import Plate, Analysis from genotype_api.dto.analysis import AnalysisSampleResponse from genotype_api.dto.dto import PlateCreate -from genotype_api.dto.plate import PlateAnalysesResponse, PlateResponse +from genotype_api.dto.plate import PlateResponse from genotype_api.dto.sample import SampleStatusResponse from genotype_api.dto.user import UserInfoResponse from genotype_api.file_parsing.excel import GenotypeAnalysis @@ -58,10 +65,10 @@ def _get_analyses_on_plate(plate: Plate) -> list[AnalysisSampleResponse]: def _get_plate_user(plate: Plate) -> UserInfoResponse: return UserInfoResponse(email=plate.user.email, name=plate.user.name, id=plate.user.id) - def get_plate_analyses_response(self, plate: Plate) -> PlateAnalysesResponse: + def get_plate_analyses_response(self, plate: Plate) -> PlateResponse: analyses_response: list[AnalysisSampleResponse] = self._get_analyses_on_plate(plate) user: UserInfoResponse = self._get_plate_user(plate) - return PlateAnalysesResponse( + return PlateResponse( created_at=plate.created_at, plate_id=plate.plate_id, signed_by=plate.signed_by, @@ -73,7 +80,7 @@ def get_plate_analyses_response(self, plate: Plate) -> PlateAnalysesResponse: analyses=analyses_response, ) - def upload_plate(self, file: UploadFile) -> PlateAnalysesResponse: + def upload_plate(self, file: UploadFile) -> PlateResponse: file_name: Path = check_file(file_path=file.filename, extension=".xlsx") plate_id: str = get_plate_id_from_file(file_name) db_plate = self.session.get(Plate, plate_id) @@ -100,7 +107,7 @@ def upload_plate(self, file: UploadFile) -> PlateAnalysesResponse: refresh_sample_status(sample=analysis.sample, session=self.session) refresh_plate(session=self.session, plate=plate) - return self.get_plate_analyses_response(plate=plate) + return self.get_plate_analyses_response(plate) @staticmethod def _get_plate_response(plate: Plate) -> PlateResponse: @@ -117,7 +124,7 @@ def _get_plate_response(plate: Plate) -> PlateResponse: def update_plate_sign_off( self, plate_id: int, user_id: int, method_document: str, method_version: str ) -> PlateResponse: - plate: Plate = get_plate(session=self.session, plate_id=plate_id) + plate: Plate = get_plate_by_id(session=self.session, plate_id=plate_id) plate_sign_off = PlateSignOff( user_id=user_id, signed_at=datetime.now(), @@ -126,3 +133,24 @@ def update_plate_sign_off( ) update_plate_sign_off(session=self.session, plate=plate, plate_sign_off=plate_sign_off) return self._get_plate_response(plate) + + def read_plate(self, plate_id: int) -> PlateAnalysesDetailResponse: + plate: Plate = get_plate_by_id(session=self.session, plate_id=plate_id) + return self.get_plate_analyses_response(plate) + + def read_plates( + self, order_params: PlateOrderParams, sort_func: callable + ) -> list[PlateResponse]: + plates: Sequence[Plate] = get_ordered_plates( + session=self.session, order_params=order_params, sort_func=sort_func + ) + return [self.get_plate_analyses_response(plate) for plate in plates] + + def delete_plate(self, plate_id) -> list[int]: + plate = get_plate_by_id(session=self.session, plate_id=plate_id) + analyses: list[Analysis] = get_analyses_from_plate(session=self.session, plate_id=plate_id) + analysis_ids = [analyse.id for analyse in analyses] + for analysis in analyses: + delete_analysis(session=self.session, analysis=analysis) + delete_plate(session=self.session, plate=plate) + return analysis_ids From 97b4b0e6f7fab73fbddbdcdf94a3611fb6e7dc81 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Fri, 22 Mar 2024 13:46:40 +0100 Subject: [PATCH 04/31] cleanup --- genotype_api/api/endpoints/plates.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/genotype_api/api/endpoints/plates.py b/genotype_api/api/endpoints/plates.py index da27692..f15d260 100644 --- a/genotype_api/api/endpoints/plates.py +++ b/genotype_api/api/endpoints/plates.py @@ -6,20 +6,15 @@ 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.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.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() From 3e637d372aa84563dbe4df18d3f57ce5be99ca72 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Fri, 22 Mar 2024 13:50:54 +0100 Subject: [PATCH 05/31] remove depr. crud function --- genotype_api/database/crud/read.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/genotype_api/database/crud/read.py b/genotype_api/database/crud/read.py index 2ee7c8e..88994f3 100644 --- a/genotype_api/database/crud/read.py +++ b/genotype_api/database/crud/read.py @@ -83,13 +83,6 @@ def get_plate_by_id(session: Session, plate_id: int) -> Plate: return session.exec(statement).one() -def get_plate_read_analysis_single( - session: Session, plate_id: int -) -> PlateReadWithAnalysisDetailSingle: - plate: Plate = get_plate_by_id(session=session, plate_id=plate_id) - return PlateReadWithAnalysisDetailSingle.from_orm(plate) - - def get_ordered_plates( session: Session, order_params: PlateOrderParams, sort_func: Callable ) -> Sequence[Plate]: From 1d81f272182c5d010c82d132c6a5cc40e4717e1e Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Fri, 22 Mar 2024 13:59:10 +0100 Subject: [PATCH 06/31] cleanup service --- .../services/plate_service/plate_service.py | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index 87421e7..ff99505 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -65,7 +65,7 @@ def _get_analyses_on_plate(plate: Plate) -> list[AnalysisSampleResponse]: def _get_plate_user(plate: Plate) -> UserInfoResponse: return UserInfoResponse(email=plate.user.email, name=plate.user.name, id=plate.user.id) - def get_plate_analyses_response(self, plate: Plate) -> PlateResponse: + def _get_plate_response(self, plate: Plate) -> PlateResponse: analyses_response: list[AnalysisSampleResponse] = self._get_analyses_on_plate(plate) user: UserInfoResponse = self._get_plate_user(plate) return PlateResponse( @@ -107,19 +107,7 @@ def upload_plate(self, file: UploadFile) -> PlateResponse: refresh_sample_status(sample=analysis.sample, session=self.session) refresh_plate(session=self.session, plate=plate) - return self.get_plate_analyses_response(plate) - - @staticmethod - def _get_plate_response(plate: Plate) -> PlateResponse: - return PlateResponse( - created_at=plate.created_at, - plate_id=plate.plate_id, - signed_by=plate.signed_by, - signed_at=plate.signed_at, - method_document=plate.method_document, - method_version=plate.method_version, - id=plate.id, - ) + return self._get_plate_response(plate) def update_plate_sign_off( self, plate_id: int, user_id: int, method_document: str, method_version: str @@ -136,7 +124,7 @@ def update_plate_sign_off( def read_plate(self, plate_id: int) -> PlateAnalysesDetailResponse: plate: Plate = get_plate_by_id(session=self.session, plate_id=plate_id) - return self.get_plate_analyses_response(plate) + return self._get_plate_response(plate) def read_plates( self, order_params: PlateOrderParams, sort_func: callable @@ -144,7 +132,7 @@ def read_plates( plates: Sequence[Plate] = get_ordered_plates( session=self.session, order_params=order_params, sort_func=sort_func ) - return [self.get_plate_analyses_response(plate) for plate in plates] + return [self._get_plate_response(plate) for plate in plates] def delete_plate(self, plate_id) -> list[int]: plate = get_plate_by_id(session=self.session, plate_id=plate_id) From 1fde28b4941a08608e744629bbc950e5718200cb Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Fri, 22 Mar 2024 13:59:56 +0100 Subject: [PATCH 07/31] remove non existing model hint --- genotype_api/services/plate_service/plate_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index ff99505..e55045f 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -122,7 +122,7 @@ def update_plate_sign_off( update_plate_sign_off(session=self.session, plate=plate, plate_sign_off=plate_sign_off) return self._get_plate_response(plate) - def read_plate(self, plate_id: int) -> PlateAnalysesDetailResponse: + def read_plate(self, plate_id: int) -> PlateResponse: plate: Plate = get_plate_by_id(session=self.session, plate_id=plate_id) return self._get_plate_response(plate) From f0e69e04f17636cef09123aa18b17bf269cae2cb Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Fri, 22 Mar 2024 14:23:15 +0100 Subject: [PATCH 08/31] fix import issue --- genotype_api/api/endpoints/plates.py | 5 ----- genotype_api/dto/plate.py | 2 +- genotype_api/services/plate_service/plate_service.py | 8 ++++++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/genotype_api/api/endpoints/plates.py b/genotype_api/api/endpoints/plates.py index f15d260..7fb5637 100644 --- a/genotype_api/api/endpoints/plates.py +++ b/genotype_api/api/endpoints/plates.py @@ -19,11 +19,6 @@ 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() -> PlateService: session: Session = get_session() return PlateService(session) diff --git a/genotype_api/dto/plate.py b/genotype_api/dto/plate.py index b798f0e..9adf242 100644 --- a/genotype_api/dto/plate.py +++ b/genotype_api/dto/plate.py @@ -31,7 +31,7 @@ class PlateResponse(BaseModel): detail: PlateStatusCounts @validator("detail") - def check_detail(self, values): + def check_detail(cls, value, values): analyses = values.get("analyses") statuses = [str(analysis.sample.status) for analysis in analyses] commented = sum(1 for analysis in analyses if analysis.sample.comment) diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index e55045f..a8cd8cb 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -10,7 +10,7 @@ from sqlmodel import Session from starlette import status -from genotype_api.api.endpoints.plates import get_plate_id_from_file + from genotype_api.constants import Types from genotype_api.database.crud.create import create_analyses_samples, create_plate from genotype_api.database.crud.delete import delete_analysis, delete_plate @@ -80,9 +80,13 @@ def _get_plate_response(self, plate: Plate) -> PlateResponse: analyses=analyses_response, ) + def _get_plate_id_from_file(self, file_name: Path) -> str: + # Get the plate id from the standardized name of the plate + return file_name.name.split("_", 1)[0] + def upload_plate(self, file: UploadFile) -> PlateResponse: file_name: Path = check_file(file_path=file.filename, extension=".xlsx") - plate_id: str = get_plate_id_from_file(file_name) + plate_id: str = self._get_plate_id_from_file(file_name) db_plate = self.session.get(Plate, plate_id) if db_plate: raise HTTPException( From f21e017fee3665dd2543d9638242e144c473cbc8 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Fri, 22 Mar 2024 14:32:10 +0100 Subject: [PATCH 09/31] fix statement --- genotype_api/database/crud/read.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/genotype_api/database/crud/read.py b/genotype_api/database/crud/read.py index 88994f3..74a5974 100644 --- a/genotype_api/database/crud/read.py +++ b/genotype_api/database/crud/read.py @@ -86,12 +86,13 @@ def get_plate_by_id(session: Session, plate_id: int) -> Plate: def get_ordered_plates( session: Session, order_params: PlateOrderParams, sort_func: Callable ) -> Sequence[Plate]: - plates: Sequence[Plate] = session.exec( + statement = ( select(Plate) .order_by(sort_func(order_params.order_by)) .offset(order_params.skip) .limit(order_params.limit) - ).all() + ) + plates: Sequence[Plate] = session.exec(statement).all() return plates From 55c892866b7ccbee5caf62c0f900e03d74348b52 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Fri, 22 Mar 2024 14:36:59 +0100 Subject: [PATCH 10/31] fix session import --- genotype_api/api/endpoints/plates.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/genotype_api/api/endpoints/plates.py b/genotype_api/api/endpoints/plates.py index 7fb5637..13541cf 100644 --- a/genotype_api/api/endpoints/plates.py +++ b/genotype_api/api/endpoints/plates.py @@ -1,11 +1,11 @@ """Routes for plates""" -from pathlib import Path 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 sqlalchemy.orm import Session + from genotype_api.database.filter_models.plate_models import PlateOrderParams from genotype_api.database.models import ( User, From 6c9d83c76fa8bea29ac7af414ccab06706ffca51 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Fri, 22 Mar 2024 14:44:03 +0100 Subject: [PATCH 11/31] fix depend --- genotype_api/api/endpoints/plates.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/genotype_api/api/endpoints/plates.py b/genotype_api/api/endpoints/plates.py index 13541cf..f0d0a4c 100644 --- a/genotype_api/api/endpoints/plates.py +++ b/genotype_api/api/endpoints/plates.py @@ -4,8 +4,7 @@ from fastapi import APIRouter, Depends, File, Query, UploadFile, status from fastapi.responses import JSONResponse from sqlalchemy import asc, desc -from sqlalchemy.orm import Session - +from sqlmodel import Session from genotype_api.database.filter_models.plate_models import PlateOrderParams from genotype_api.database.models import ( User, @@ -19,8 +18,7 @@ router = APIRouter() -def get_plate_service() -> PlateService: - session: Session = get_session() +def get_plate_service(session: Session = Depends(get_session)) -> PlateService: return PlateService(session) From efd11583c35b5aa3c32f38a06b18e0c0a35b5b83 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Fri, 22 Mar 2024 15:02:42 +0100 Subject: [PATCH 12/31] fix ref issue --- genotype_api/api/endpoints/users.py | 8 ++++---- genotype_api/database/crud/read.py | 2 +- genotype_api/dto/plate.py | 2 +- genotype_api/dto/user.py | 4 ++-- genotype_api/services/plate_service/plate_service.py | 11 +++++++---- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/genotype_api/api/endpoints/users.py b/genotype_api/api/endpoints/users.py index 081c06f..48db70d 100644 --- a/genotype_api/api/endpoints/users.py +++ b/genotype_api/api/endpoints/users.py @@ -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, ) @@ -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}") @@ -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: @@ -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) diff --git a/genotype_api/database/crud/read.py b/genotype_api/database/crud/read.py index 74a5974..f6f411c 100644 --- a/genotype_api/database/crud/read.py +++ b/genotype_api/database/crud/read.py @@ -158,7 +158,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() diff --git a/genotype_api/dto/plate.py b/genotype_api/dto/plate.py index 9adf242..6e6da8a 100644 --- a/genotype_api/dto/plate.py +++ b/genotype_api/dto/plate.py @@ -26,7 +26,7 @@ class PlateResponse(BaseModel): method_document: str method_version: str id: str - user: UserInfoResponse + user: UserInfoResponse | None = None analyses: list[AnalysisSampleResponse] = [] detail: PlateStatusCounts diff --git a/genotype_api/dto/user.py b/genotype_api/dto/user.py index d3a8aa2..6b619b2 100644 --- a/genotype_api/dto/user.py +++ b/genotype_api/dto/user.py @@ -4,6 +4,6 @@ class UserInfoResponse(BaseModel): - email: EmailStr + email: EmailStr | None = None name: str | None = None - id: int + id: int | None = None diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index a8cd8cb..d85fdc4 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -19,6 +19,7 @@ get_plate_by_id, get_ordered_plates, get_analyses_from_plate, + get_user_by_id, ) from genotype_api.database.crud.update import ( refresh_sample_status, @@ -26,7 +27,7 @@ update_plate_sign_off, ) from genotype_api.database.filter_models.plate_models import PlateSignOff, PlateOrderParams -from genotype_api.database.models import Plate, Analysis +from genotype_api.database.models import Plate, Analysis, User from genotype_api.dto.analysis import AnalysisSampleResponse from genotype_api.dto.dto import PlateCreate from genotype_api.dto.plate import PlateResponse @@ -61,9 +62,11 @@ def _get_analyses_on_plate(plate: Plate) -> list[AnalysisSampleResponse]: analyses_response.append(analysis_response) return analyses_response - @staticmethod - def _get_plate_user(plate: Plate) -> UserInfoResponse: - return UserInfoResponse(email=plate.user.email, name=plate.user.name, id=plate.user.id) + def _get_plate_user(self, plate: Plate) -> UserInfoResponse | None: + user: User = get_user_by_id(session=self.session, user_id=plate.signed_by) + if user: + return UserInfoResponse(email=user.email, name=user.name, id=user.id) + return None def _get_plate_response(self, plate: Plate) -> PlateResponse: analyses_response: list[AnalysisSampleResponse] = self._get_analyses_on_plate(plate) From 0530894ea14f60961bc5ec61a3c8abcab085551c Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Fri, 22 Mar 2024 15:10:07 +0100 Subject: [PATCH 13/31] initialise empty model --- genotype_api/dto/plate.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/genotype_api/dto/plate.py b/genotype_api/dto/plate.py index 6e6da8a..72fb917 100644 --- a/genotype_api/dto/plate.py +++ b/genotype_api/dto/plate.py @@ -19,16 +19,16 @@ class Config: class PlateResponse(BaseModel): - created_at: str - plate_id: str - signed_by: int - signed_at: str - method_document: str - method_version: str - id: str + created_at: str | None = None + plate_id: str | None = None + signed_by: int | None = None + signed_at: str | None = None + method_document: str | None = None + method_version: str | None = None + id: str | None = None user: UserInfoResponse | None = None - analyses: list[AnalysisSampleResponse] = [] - detail: PlateStatusCounts + analyses: list[AnalysisSampleResponse] | None = None + detail: PlateStatusCounts | None = None @validator("detail") def check_detail(cls, value, values): From 8467b2df650fa710fa57a1f0daf26ae0caa6b579 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Fri, 22 Mar 2024 15:16:07 +0100 Subject: [PATCH 14/31] datetime typehints --- genotype_api/dto/plate.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/genotype_api/dto/plate.py b/genotype_api/dto/plate.py index 72fb917..4aa0afe 100644 --- a/genotype_api/dto/plate.py +++ b/genotype_api/dto/plate.py @@ -1,6 +1,8 @@ """Module for the plate DTOs.""" from collections import Counter +from datetime import datetime + from pydantic import BaseModel, validator, Field from genotype_api.dto.analysis import AnalysisSampleResponse from genotype_api.dto.user import UserInfoResponse @@ -19,10 +21,10 @@ class Config: class PlateResponse(BaseModel): - created_at: str | None = None + created_at: datetime | None = None plate_id: str | None = None signed_by: int | None = None - signed_at: str | None = None + signed_at: datetime | None = None method_document: str | None = None method_version: str | None = None id: str | None = None From 2160f540fe70ef1bdfbb3c5411a8d787d418f4c8 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 09:21:01 +0100 Subject: [PATCH 15/31] fixes --- genotype_api/database/crud/read.py | 5 ++-- .../services/plate_service/plate_service.py | 5 ++-- tests/crud/read/test_read_plate.py | 0 tests/test_excel.py | 23 ------------------- 4 files changed, 5 insertions(+), 28 deletions(-) create mode 100644 tests/crud/read/test_read_plate.py delete mode 100644 tests/test_excel.py diff --git a/genotype_api/database/crud/read.py b/genotype_api/database/crud/read.py index f6f411c..a6861da 100644 --- a/genotype_api/database/crud/read.py +++ b/genotype_api/database/crud/read.py @@ -85,15 +85,14 @@ def get_plate_by_id(session: Session, plate_id: int) -> Plate: def get_ordered_plates( session: Session, order_params: PlateOrderParams, sort_func: Callable -) -> Sequence[Plate]: +) -> list[Plate]: statement = ( select(Plate) .order_by(sort_func(order_params.order_by)) .offset(order_params.skip) .limit(order_params.limit) ) - plates: Sequence[Plate] = session.exec(statement).all() - return plates + return session.exec(statement).all() def get_incomplete_samples(statement: SelectOfScalar) -> SelectOfScalar: diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index d85fdc4..68ce772 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -83,7 +83,8 @@ def _get_plate_response(self, plate: Plate) -> PlateResponse: analyses=analyses_response, ) - def _get_plate_id_from_file(self, file_name: Path) -> str: + @staticmethod + 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] @@ -136,7 +137,7 @@ def read_plate(self, plate_id: int) -> PlateResponse: def read_plates( self, order_params: PlateOrderParams, sort_func: callable ) -> list[PlateResponse]: - plates: Sequence[Plate] = get_ordered_plates( + plates: list[Plate] = get_ordered_plates( session=self.session, order_params=order_params, sort_func=sort_func ) return [self._get_plate_response(plate) for plate in plates] diff --git a/tests/crud/read/test_read_plate.py b/tests/crud/read/test_read_plate.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_excel.py b/tests/test_excel.py deleted file mode 100644 index def5b02..0000000 --- a/tests/test_excel.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Tests fpr the excel module""" - -""" -def test_read_excel(excel_file: Path, include_key: str): - # GIVEN an excel file and a include key - - # WHEN parsing an excel file - parser = GenotypeAnalysis(excel_file=excel_file, include_key=include_key) - - # THEN assert that the sample is in the header - assert "SAMPLE" in parser.header_row - - -def test_parse_analyses(excel_file: Path, include_key: str, sample_name: str): - # GIVEN a parsed excel file - parser = GenotypeAnalysis(excel_file=excel_file, include_key=include_key) - - # WHEN parsing the analyses - analyses = [analysis for analysis in parser.generate_analyses()] - - # THEN assert the number of analyses is 3 - assert len(analyses) == 3 -""" From fc19cd624ed366e9c0bbd08eef9e785407d3c3d3 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 10:36:10 +0100 Subject: [PATCH 16/31] fix call --- genotype_api/services/plate_service/plate_service.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index 68ce772..df81b9d 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -134,12 +134,8 @@ def read_plate(self, plate_id: int) -> PlateResponse: plate: Plate = get_plate_by_id(session=self.session, plate_id=plate_id) return self._get_plate_response(plate) - def read_plates( - self, order_params: PlateOrderParams, sort_func: callable - ) -> list[PlateResponse]: - plates: list[Plate] = get_ordered_plates( - session=self.session, order_params=order_params, sort_func=sort_func - ) + def read_plates(self, order_params: PlateOrderParams) -> list[PlateResponse]: + plates: list[Plate] = get_ordered_plates(session=self.session, order_params=order_params) return [self._get_plate_response(plate) for plate in plates] def delete_plate(self, plate_id) -> list[int]: From 77c3e87088ee9d649287f4c6f0fccc01e1ef489c Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 10:44:48 +0100 Subject: [PATCH 17/31] fix plate analyses --- .../services/plate_service/plate_service.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index df81b9d..897aec0 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -4,7 +4,7 @@ from http.client import HTTPException from io import BytesIO from pathlib import Path -from typing import Sequence + from fastapi import UploadFile from sqlmodel import Session @@ -43,24 +43,25 @@ def __init__(self, session: Session): self.session: Session = session @staticmethod - def _get_analyses_on_plate(plate: Plate) -> list[AnalysisSampleResponse]: + def _get_analyses_on_plate(plate: Plate) -> list[AnalysisSampleResponse] | None: analyses_response: list[AnalysisSampleResponse] = [] for analysis in plate.analyses: - sample_status = SampleStatusResponse( - status=analysis.sample.status, comment=analysis.sample.comment - ) - analysis_response = AnalysisSampleResponse( - type=analysis.type, - source=analysis.source, - sex=analysis.sex, - created_at=analysis.created_at, - sample_id=analysis.sample_id, - plate_id=analysis.plate_id, - id=analysis.id, - sample=sample_status, - ) - analyses_response.append(analysis_response) - return analyses_response + if analysis: + sample_status = SampleStatusResponse( + status=analysis.sample.status, comment=analysis.sample.comment + ) + analysis_response = AnalysisSampleResponse( + type=analysis.type, + source=analysis.source, + sex=analysis.sex, + created_at=analysis.created_at, + sample_id=analysis.sample_id, + plate_id=analysis.plate_id, + id=analysis.id, + sample=sample_status, + ) + analyses_response.append(analysis_response) + return analyses_response if analyses_response else None def _get_plate_user(self, plate: Plate) -> UserInfoResponse | None: user: User = get_user_by_id(session=self.session, user_id=plate.signed_by) From 644e8e44f269abeefb1999c834f5f3fc4a7ce8fe Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 10:55:48 +0100 Subject: [PATCH 18/31] fix validator --- genotype_api/dto/plate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/genotype_api/dto/plate.py b/genotype_api/dto/plate.py index 4aa0afe..e4a1994 100644 --- a/genotype_api/dto/plate.py +++ b/genotype_api/dto/plate.py @@ -35,6 +35,8 @@ class PlateResponse(BaseModel): @validator("detail") def check_detail(cls, value, values): analyses = values.get("analyses") + if not analyses: + return None statuses = [str(analysis.sample.status) for analysis in analyses] commented = sum(1 for analysis in analyses if analysis.sample.comment) status_counts = Counter(statuses) From bdfd1dc4ea4ace9439b6f8e49400a1987cc5de98 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 11:14:23 +0100 Subject: [PATCH 19/31] test --- genotype_api/api/endpoints/plates.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/genotype_api/api/endpoints/plates.py b/genotype_api/api/endpoints/plates.py index e6764cb..967cb70 100644 --- a/genotype_api/api/endpoints/plates.py +++ b/genotype_api/api/endpoints/plates.py @@ -6,10 +6,12 @@ from sqlmodel import Session +from genotype_api.database.crud.read import get_ordered_plates from genotype_api.database.filter_models.plate_models import PlateOrderParams from genotype_api.database.models import ( User, + Plate, ) from genotype_api.database.session_handler import get_session from genotype_api.dto.plate import PlateResponse @@ -96,7 +98,7 @@ def read_plate( @router.get( "/", - response_model=list[PlateResponse], + response_model=list[Plate], response_model_exclude={"analyses"}, response_model_by_alias=False, ) @@ -112,7 +114,8 @@ async def read_plates( order_params = PlateOrderParams( order_by=order_by, skip=skip, limit=limit, sort_order=sort_order ) - return plate_service.read_plates(order_params=order_params) + + return get_ordered_plates(session=get_session(), order_params=order_params) @router.delete("/{plate_id}") From e20fb437292154fa90e93aa730194e3bb3fe1373 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 11:31:12 +0100 Subject: [PATCH 20/31] test dependency injection --- genotype_api/api/endpoints/plates.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/genotype_api/api/endpoints/plates.py b/genotype_api/api/endpoints/plates.py index 967cb70..6affe43 100644 --- a/genotype_api/api/endpoints/plates.py +++ b/genotype_api/api/endpoints/plates.py @@ -1,18 +1,11 @@ """Routes for plates""" -from typing import Literal +from typing import Literal, Annotated from fastapi import APIRouter, Depends, File, Query, UploadFile, status from fastapi.responses import JSONResponse - from sqlmodel import Session - -from genotype_api.database.crud.read import get_ordered_plates from genotype_api.database.filter_models.plate_models import PlateOrderParams - -from genotype_api.database.models import ( - User, - Plate, -) +from genotype_api.database.models import User from genotype_api.database.session_handler import get_session from genotype_api.dto.plate import PlateResponse from genotype_api.security import get_active_user @@ -22,7 +15,9 @@ router = APIRouter() -def get_plate_service(session: Session = Depends(get_session)) -> PlateService: +async def get_plate_service( + session: Session = Annotated[Session, Depends(get_session)] +) -> PlateService: return PlateService(session) @@ -98,7 +93,7 @@ def read_plate( @router.get( "/", - response_model=list[Plate], + response_model=list[PlateResponse], response_model_exclude={"analyses"}, response_model_by_alias=False, ) @@ -107,15 +102,15 @@ async def read_plates( sort_order: Literal["ascend", "descend"] | None = "descend", skip: int | None = 0, limit: int | None = 10, - plate_service: PlateService = Depends(get_plate_service), + session: Session = Depends(get_session), current_user: User = Depends(get_active_user), ): """Display all plates""" order_params = PlateOrderParams( order_by=order_by, skip=skip, limit=limit, sort_order=sort_order ) - - return get_ordered_plates(session=get_session(), order_params=order_params) + plate_service = PlateService(session) + return plate_service.read_plates(order_params=order_params) @router.delete("/{plate_id}") From 2ded10de30ab02d1509a7fd21586753bd1888da3 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 12:20:16 +0100 Subject: [PATCH 21/31] test dependency injection --- genotype_api/api/endpoints/plates.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/genotype_api/api/endpoints/plates.py b/genotype_api/api/endpoints/plates.py index 6affe43..31d1357 100644 --- a/genotype_api/api/endpoints/plates.py +++ b/genotype_api/api/endpoints/plates.py @@ -15,9 +15,7 @@ router = APIRouter() -async def get_plate_service( - session: Session = Annotated[Session, Depends(get_session)] -) -> PlateService: +def get_plate_service(session: Session = Depends(get_session)) -> PlateService: return PlateService(session) From e7c0c3ac64d924efa310d4e2e1d83a564a8074c3 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 12:56:24 +0100 Subject: [PATCH 22/31] test model --- genotype_api/api/endpoints/plates.py | 7 +++---- genotype_api/dto/plate.py | 12 ++++++++++++ .../services/plate_service/plate_service.py | 19 ++++++++++++++++--- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/genotype_api/api/endpoints/plates.py b/genotype_api/api/endpoints/plates.py index 31d1357..5eee66f 100644 --- a/genotype_api/api/endpoints/plates.py +++ b/genotype_api/api/endpoints/plates.py @@ -7,7 +7,7 @@ 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.dto.plate import PlateResponse +from genotype_api.dto.plate import PlateResponse, PlateSimple from genotype_api.security import get_active_user from genotype_api.services.plate_service.plate_service import PlateService @@ -91,7 +91,7 @@ def read_plate( @router.get( "/", - response_model=list[PlateResponse], + response_model=list[PlateSimple], response_model_exclude={"analyses"}, response_model_by_alias=False, ) @@ -100,14 +100,13 @@ 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), ): """Display all plates""" order_params = PlateOrderParams( order_by=order_by, skip=skip, limit=limit, sort_order=sort_order ) - plate_service = PlateService(session) return plate_service.read_plates(order_params=order_params) diff --git a/genotype_api/dto/plate.py b/genotype_api/dto/plate.py index e4a1994..49e118d 100644 --- a/genotype_api/dto/plate.py +++ b/genotype_api/dto/plate.py @@ -20,6 +20,18 @@ class Config: allow_population_by_field_name = True +class PlateSimple(BaseModel): + + created_at: datetime | None = None + plate_id: str | None = None + signed_by: int | None = None + signed_at: datetime | None = None + method_document: str | None = None + method_version: str | None = None + id: str | None = None + user: UserInfoResponse | None = None + + class PlateResponse(BaseModel): created_at: datetime | None = None plate_id: str | None = None diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index 897aec0..3365efc 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -30,7 +30,7 @@ from genotype_api.database.models import Plate, Analysis, User from genotype_api.dto.analysis import AnalysisSampleResponse from genotype_api.dto.dto import PlateCreate -from genotype_api.dto.plate import PlateResponse +from genotype_api.dto.plate import PlateResponse, PlateSimple from genotype_api.dto.sample import SampleStatusResponse from genotype_api.dto.user import UserInfoResponse from genotype_api.file_parsing.excel import GenotypeAnalysis @@ -84,6 +84,19 @@ def _get_plate_response(self, plate: Plate) -> PlateResponse: analyses=analyses_response, ) + def _get_simple_plate(self, plate: Plate) -> PlateSimple: + user: UserInfoResponse = self._get_plate_user(plate) + return PlateSimple( + created_at=plate.created_at, + plate_id=plate.plate_id, + signed_by=plate.signed_by, + signed_at=plate.signed_at, + method_document=plate.method_document, + method_version=plate.method_version, + id=plate.id, + user=user, + ) + @staticmethod def _get_plate_id_from_file(file_name: Path) -> str: # Get the plate id from the standardized name of the plate @@ -135,9 +148,9 @@ def read_plate(self, plate_id: int) -> PlateResponse: plate: Plate = get_plate_by_id(session=self.session, plate_id=plate_id) return self._get_plate_response(plate) - def read_plates(self, order_params: PlateOrderParams) -> list[PlateResponse]: + def read_plates(self, order_params: PlateOrderParams) -> list[PlateSimple]: plates: list[Plate] = get_ordered_plates(session=self.session, order_params=order_params) - return [self._get_plate_response(plate) for plate in plates] + return [self._get_simple_plate(plate) for plate in plates] def delete_plate(self, plate_id) -> list[int]: plate = get_plate_by_id(session=self.session, plate_id=plate_id) From 641788c5c325179224a34d7ab8921f2bbee9f291 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 13:05:03 +0100 Subject: [PATCH 23/31] fix user fetch --- genotype_api/database/crud/read.py | 1 - genotype_api/services/plate_service/plate_service.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/genotype_api/database/crud/read.py b/genotype_api/database/crud/read.py index a5f0946..5a2221e 100644 --- a/genotype_api/database/crud/read.py +++ b/genotype_api/database/crud/read.py @@ -94,7 +94,6 @@ def get_ordered_plates(session: Session, order_params: PlateOrderParams) -> list 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) diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index 3365efc..89b0734 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -64,8 +64,8 @@ def _get_analyses_on_plate(plate: Plate) -> list[AnalysisSampleResponse] | None: return analyses_response if analyses_response else None def _get_plate_user(self, plate: Plate) -> UserInfoResponse | None: - user: User = get_user_by_id(session=self.session, user_id=plate.signed_by) - if user: + if plate.signed_by: + user: User = get_user_by_id(session=self.session, user_id=plate.signed_by) return UserInfoResponse(email=user.email, name=user.name, id=user.id) return None From 041c3e5068f8d109f664454c7b45875c7ae5bc24 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 13:08:15 +0100 Subject: [PATCH 24/31] fix response --- genotype_api/dto/plate.py | 12 ------------ .../services/plate_service/plate_service.py | 17 ++--------------- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/genotype_api/dto/plate.py b/genotype_api/dto/plate.py index 49e118d..e4a1994 100644 --- a/genotype_api/dto/plate.py +++ b/genotype_api/dto/plate.py @@ -20,18 +20,6 @@ class Config: allow_population_by_field_name = True -class PlateSimple(BaseModel): - - created_at: datetime | None = None - plate_id: str | None = None - signed_by: int | None = None - signed_at: datetime | None = None - method_document: str | None = None - method_version: str | None = None - id: str | None = None - user: UserInfoResponse | None = None - - class PlateResponse(BaseModel): created_at: datetime | None = None plate_id: str | None = None diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index 89b0734..8263876 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -30,7 +30,7 @@ from genotype_api.database.models import Plate, Analysis, User from genotype_api.dto.analysis import AnalysisSampleResponse from genotype_api.dto.dto import PlateCreate -from genotype_api.dto.plate import PlateResponse, PlateSimple +from genotype_api.dto.plate import PlateResponse from genotype_api.dto.sample import SampleStatusResponse from genotype_api.dto.user import UserInfoResponse from genotype_api.file_parsing.excel import GenotypeAnalysis @@ -84,19 +84,6 @@ def _get_plate_response(self, plate: Plate) -> PlateResponse: analyses=analyses_response, ) - def _get_simple_plate(self, plate: Plate) -> PlateSimple: - user: UserInfoResponse = self._get_plate_user(plate) - return PlateSimple( - created_at=plate.created_at, - plate_id=plate.plate_id, - signed_by=plate.signed_by, - signed_at=plate.signed_at, - method_document=plate.method_document, - method_version=plate.method_version, - id=plate.id, - user=user, - ) - @staticmethod def _get_plate_id_from_file(file_name: Path) -> str: # Get the plate id from the standardized name of the plate @@ -150,7 +137,7 @@ def read_plate(self, plate_id: int) -> PlateResponse: def read_plates(self, order_params: PlateOrderParams) -> list[PlateSimple]: plates: list[Plate] = get_ordered_plates(session=self.session, order_params=order_params) - return [self._get_simple_plate(plate) for plate in plates] + return [self._get_plate_response(plate) for plate in plates] def delete_plate(self, plate_id) -> list[int]: plate = get_plate_by_id(session=self.session, plate_id=plate_id) From ccce05d97802fe03c5db118c95262e4d02c479d4 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 13:10:25 +0100 Subject: [PATCH 25/31] fix response --- genotype_api/api/endpoints/plates.py | 6 +++--- genotype_api/services/plate_service/plate_service.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/genotype_api/api/endpoints/plates.py b/genotype_api/api/endpoints/plates.py index 5eee66f..ec2943a 100644 --- a/genotype_api/api/endpoints/plates.py +++ b/genotype_api/api/endpoints/plates.py @@ -1,13 +1,13 @@ """Routes for plates""" -from typing import Literal, Annotated +from typing import Literal from fastapi import APIRouter, Depends, File, Query, UploadFile, status from fastapi.responses import JSONResponse from sqlmodel import Session 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.dto.plate import PlateResponse, PlateSimple +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 @@ -91,7 +91,7 @@ def read_plate( @router.get( "/", - response_model=list[PlateSimple], + response_model=list[PlateResponse], response_model_exclude={"analyses"}, response_model_by_alias=False, ) diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index 8263876..9564f7e 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -135,7 +135,7 @@ def read_plate(self, plate_id: int) -> PlateResponse: plate: Plate = get_plate_by_id(session=self.session, plate_id=plate_id) return self._get_plate_response(plate) - def read_plates(self, order_params: PlateOrderParams) -> list[PlateSimple]: + def read_plates(self, order_params: PlateOrderParams) -> list[PlateResponse]: plates: list[Plate] = get_ordered_plates(session=self.session, order_params=order_params) return [self._get_plate_response(plate) for plate in plates] From be712be5013b713524be4c9457d659d36465b7dc Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 13:23:57 +0100 Subject: [PATCH 26/31] fix signoff --- genotype_api/api/endpoints/plates.py | 2 +- genotype_api/services/plate_service/plate_service.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/genotype_api/api/endpoints/plates.py b/genotype_api/api/endpoints/plates.py index ec2943a..e417435 100644 --- a/genotype_api/api/endpoints/plates.py +++ b/genotype_api/api/endpoints/plates.py @@ -51,7 +51,7 @@ def sign_off_plate( return plate_service.update_plate_sign_off( plate_id=plate_id, - user_id=current_user.id, + user_email=current_user.email, method_version=method_version, method_document=method_document, ) diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index 9564f7e..50ff3cf 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -7,6 +7,7 @@ from fastapi import UploadFile +from pydantic import EmailStr from sqlmodel import Session from starlette import status @@ -20,6 +21,7 @@ get_ordered_plates, get_analyses_from_plate, get_user_by_id, + get_user_by_email, ) from genotype_api.database.crud.update import ( refresh_sample_status, @@ -119,11 +121,12 @@ def upload_plate(self, file: UploadFile) -> PlateResponse: return self._get_plate_response(plate) def update_plate_sign_off( - self, plate_id: int, user_id: int, method_document: str, method_version: str + self, plate_id: int, user_email: EmailStr, method_document: str, method_version: str ) -> PlateResponse: plate: Plate = get_plate_by_id(session=self.session, plate_id=plate_id) + user: User = get_user_by_email(session=self.session, email=user_email) plate_sign_off = PlateSignOff( - user_id=user_id, + user_id=user.id, signed_at=datetime.now(), method_document=method_document, method_version=method_version, From f595dfe5d2de9ff66634905ee8a223258d536b09 Mon Sep 17 00:00:00 2001 From: ChristianOertlin Date: Mon, 25 Mar 2024 14:14:50 +0100 Subject: [PATCH 27/31] Update genotype_api/services/plate_service/plate_service.py --- genotype_api/services/plate_service/plate_service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index 50ff3cf..5cc3d2c 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -143,6 +143,7 @@ def read_plates(self, order_params: PlateOrderParams) -> list[PlateResponse]: return [self._get_plate_response(plate) for plate in plates] def delete_plate(self, plate_id) -> list[int]: + """Delete a plate with the given plate id and return associated analysis ids.""" plate = get_plate_by_id(session=self.session, plate_id=plate_id) analyses: list[Analysis] = get_analyses_from_plate(session=self.session, plate_id=plate_id) analysis_ids = [analyse.id for analyse in analyses] From 8be17b435cd111560886eacfcfbe4d2ea9a156ec Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 14:17:24 +0100 Subject: [PATCH 28/31] fix typehint --- genotype_api/services/plate_service/plate_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index 5cc3d2c..2691748 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -143,7 +143,7 @@ def read_plates(self, order_params: PlateOrderParams) -> list[PlateResponse]: return [self._get_plate_response(plate) for plate in plates] def delete_plate(self, plate_id) -> list[int]: - """Delete a plate with the given plate id and return associated analysis ids.""" + """Delete a plate with the given plate id and return associated analysis ids.""" plate = get_plate_by_id(session=self.session, plate_id=plate_id) analyses: list[Analysis] = get_analyses_from_plate(session=self.session, plate_id=plate_id) analysis_ids = [analyse.id for analyse in analyses] From d62895d215d03b4db8ed217bc9a13fa2c5c07403 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 14:17:26 +0100 Subject: [PATCH 29/31] fix typehint --- genotype_api/services/plate_service/plate_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index 2691748..7a4d238 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -146,7 +146,7 @@ def delete_plate(self, plate_id) -> list[int]: """Delete a plate with the given plate id and return associated analysis ids.""" plate = get_plate_by_id(session=self.session, plate_id=plate_id) analyses: list[Analysis] = get_analyses_from_plate(session=self.session, plate_id=plate_id) - analysis_ids = [analyse.id for analyse in analyses] + analysis_ids: list[int] = [analyse.id for analyse in analyses] for analysis in analyses: delete_analysis(session=self.session, analysis=analysis) delete_plate(session=self.session, plate=plate) From e61e645a93bb0d25e1a97405f8524b12a2a77b70 Mon Sep 17 00:00:00 2001 From: ChristianOertlin Date: Mon, 25 Mar 2024 16:08:54 +0100 Subject: [PATCH 30/31] Update genotype_api/dto/plate.py Co-authored-by: Henrik Stranneheim --- genotype_api/dto/plate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genotype_api/dto/plate.py b/genotype_api/dto/plate.py index e4a1994..8eb8ba3 100644 --- a/genotype_api/dto/plate.py +++ b/genotype_api/dto/plate.py @@ -30,7 +30,7 @@ class PlateResponse(BaseModel): id: str | None = None user: UserInfoResponse | None = None analyses: list[AnalysisSampleResponse] | None = None - detail: PlateStatusCounts | None = None + plate_status_counts: PlateStatusCounts | None = None @validator("detail") def check_detail(cls, value, values): From b567b78c3e03b3ae579e6cb0b36c508b828e6827 Mon Sep 17 00:00:00 2001 From: Christian Oertlin Date: Mon, 25 Mar 2024 16:29:26 +0100 Subject: [PATCH 31/31] review --- genotype_api/database/crud/read.py | 1 - genotype_api/dto/plate.py | 4 ++-- genotype_api/dto/user.py | 2 +- genotype_api/services/plate_service/plate_service.py | 8 ++++---- tests/crud/read/test_read_plate.py | 0 5 files changed, 7 insertions(+), 8 deletions(-) delete mode 100644 tests/crud/read/test_read_plate.py diff --git a/genotype_api/database/crud/read.py b/genotype_api/database/crud/read.py index 5a2221e..fa6c913 100644 --- a/genotype_api/database/crud/read.py +++ b/genotype_api/database/crud/read.py @@ -77,7 +77,6 @@ def get_analysis_by_type_and_sample_id( def get_plate_by_id(session: Session, plate_id: int) -> Plate: - """Get plate""" statement = select(Plate).where(Plate.id == plate_id) return session.exec(statement).one() diff --git a/genotype_api/dto/plate.py b/genotype_api/dto/plate.py index 8eb8ba3..c3b309b 100644 --- a/genotype_api/dto/plate.py +++ b/genotype_api/dto/plate.py @@ -5,7 +5,7 @@ from pydantic import BaseModel, validator, Field from genotype_api.dto.analysis import AnalysisSampleResponse -from genotype_api.dto.user import UserInfoResponse +from genotype_api.dto.user import UserResponse class PlateStatusCounts(BaseModel): @@ -28,7 +28,7 @@ class PlateResponse(BaseModel): method_document: str | None = None method_version: str | None = None id: str | None = None - user: UserInfoResponse | None = None + user: UserResponse | None = None analyses: list[AnalysisSampleResponse] | None = None plate_status_counts: PlateStatusCounts | None = None diff --git a/genotype_api/dto/user.py b/genotype_api/dto/user.py index 6b619b2..1556680 100644 --- a/genotype_api/dto/user.py +++ b/genotype_api/dto/user.py @@ -3,7 +3,7 @@ from pydantic import BaseModel, EmailStr -class UserInfoResponse(BaseModel): +class UserResponse(BaseModel): email: EmailStr | None = None name: str | None = None id: int | None = None diff --git a/genotype_api/services/plate_service/plate_service.py b/genotype_api/services/plate_service/plate_service.py index 7a4d238..b88cef0 100644 --- a/genotype_api/services/plate_service/plate_service.py +++ b/genotype_api/services/plate_service/plate_service.py @@ -34,7 +34,7 @@ from genotype_api.dto.dto import PlateCreate from genotype_api.dto.plate import PlateResponse from genotype_api.dto.sample import SampleStatusResponse -from genotype_api.dto.user import UserInfoResponse +from genotype_api.dto.user import UserResponse from genotype_api.file_parsing.excel import GenotypeAnalysis from genotype_api.file_parsing.files import check_file @@ -65,15 +65,15 @@ def _get_analyses_on_plate(plate: Plate) -> list[AnalysisSampleResponse] | None: analyses_response.append(analysis_response) return analyses_response if analyses_response else None - def _get_plate_user(self, plate: Plate) -> UserInfoResponse | None: + def _get_plate_user(self, plate: Plate) -> UserResponse | None: if plate.signed_by: user: User = get_user_by_id(session=self.session, user_id=plate.signed_by) - return UserInfoResponse(email=user.email, name=user.name, id=user.id) + return UserResponse(email=user.email, name=user.name, id=user.id) return None def _get_plate_response(self, plate: Plate) -> PlateResponse: analyses_response: list[AnalysisSampleResponse] = self._get_analyses_on_plate(plate) - user: UserInfoResponse = self._get_plate_user(plate) + user: UserResponse = self._get_plate_user(plate) return PlateResponse( created_at=plate.created_at, plate_id=plate.plate_id, diff --git a/tests/crud/read/test_read_plate.py b/tests/crud/read/test_read_plate.py deleted file mode 100644 index e69de29..0000000