Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

add(plate service) #111

Merged
merged 33 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3e41587
add models
ChrOertlin Mar 20, 2024
5416e03
Merge branch 'main' into add-plate-service
ChrOertlin Mar 21, 2024
4184990
update signoff service
ChrOertlin Mar 22, 2024
61bed0a
fix plate service
ChrOertlin Mar 22, 2024
97b4b0e
cleanup
ChrOertlin Mar 22, 2024
3e637d3
remove depr. crud function
ChrOertlin Mar 22, 2024
1d81f27
cleanup service
ChrOertlin Mar 22, 2024
1fde28b
remove non existing model hint
ChrOertlin Mar 22, 2024
f0e69e0
fix import issue
ChrOertlin Mar 22, 2024
f21e017
fix statement
ChrOertlin Mar 22, 2024
55c8928
fix session import
ChrOertlin Mar 22, 2024
6c9d83c
fix depend
ChrOertlin Mar 22, 2024
efd1158
fix ref issue
ChrOertlin Mar 22, 2024
0530894
initialise empty model
ChrOertlin Mar 22, 2024
8467b2d
datetime typehints
ChrOertlin Mar 22, 2024
2160f54
fixes
ChrOertlin Mar 25, 2024
4467a34
conflicts
ChrOertlin Mar 25, 2024
fc19cd6
fix call
ChrOertlin Mar 25, 2024
77c3e87
fix plate analyses
ChrOertlin Mar 25, 2024
644e8e4
fix validator
ChrOertlin Mar 25, 2024
bdfd1dc
test
ChrOertlin Mar 25, 2024
e20fb43
test dependency injection
ChrOertlin Mar 25, 2024
2ded10d
test dependency injection
ChrOertlin Mar 25, 2024
e7c0c3a
test model
ChrOertlin Mar 25, 2024
641788c
fix user fetch
ChrOertlin Mar 25, 2024
041c3e5
fix response
ChrOertlin Mar 25, 2024
ccce05d
fix response
ChrOertlin Mar 25, 2024
be712be
fix signoff
ChrOertlin Mar 25, 2024
f595dfe
Update genotype_api/services/plate_service/plate_service.py
ChrOertlin Mar 25, 2024
8be17b4
fix typehint
ChrOertlin Mar 25, 2024
d62895d
fix typehint
ChrOertlin Mar 25, 2024
e61e645
Update genotype_api/dto/plate.py
ChrOertlin Mar 25, 2024
b567b78
review
ChrOertlin Mar 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"},
ChrOertlin marked this conversation as resolved.
Show resolved Hide resolved
)
def upload_plate(
file: UploadFile = File(...),
session: Session = Depends(get_session),
plate_service: PlateService = Depends(get_plate_service),
ChrOertlin marked this conversation as resolved.
Show resolved Hide resolved
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={
ChrOertlin marked this conversation as resolved.
Show resolved Hide resolved
"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
15 changes: 3 additions & 12 deletions genotype_api/database/crud/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,33 +76,24 @@ 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"""
ChrOertlin marked this conversation as resolved.
Show resolved Hide resolved
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 +153,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
ChrOertlin marked this conversation as resolved.
Show resolved Hide resolved
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
Loading