Skip to content

Commit

Permalink
Refactor (#7)(minor)
Browse files Browse the repository at this point in the history
### Changed
- lots of refactoring - still not in production, so merging.
  • Loading branch information
mayabrandi committed Dec 30, 2021
1 parent 7529a18 commit ee48585
Show file tree
Hide file tree
Showing 14 changed files with 214 additions and 208 deletions.
18 changes: 9 additions & 9 deletions genotype_api/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@
Main functions for the genotype api
"""
from fastapi import FastAPI, status
from fastapi import FastAPI, status, Request
from fastapi.responses import JSONResponse
from genotype_api.database import create_db_and_tables
from genotype_api.api.endpoints import samples, sequences, snps, users
from genotype_api.api.endpoints import samples, snps, users
from genotype_api.api.endpoints import plates, analyses
from sqlalchemy.exc import NoResultFound

app = FastAPI()


@app.exception_handler(NoResultFound)
async def not_found_exception_handler(request: Request, exc: NoResultFound):
return JSONResponse("Document not found", status_code=status.HTTP_404_NOT_FOUND)


@app.get("/")
def welcome():
return {"hello": "Welcome to the genotype api"}
Expand Down Expand Up @@ -50,13 +57,6 @@ def welcome():
responses={status.HTTP_404_NOT_FOUND: {"description": "Not found"}},
)

app.include_router(
sequences.router,
prefix="/sequences",
tags=["sequences"],
responses={status.HTTP_404_NOT_FOUND: {"description": "Not found"}},
)


@app.on_event("startup")
def on_startup():
Expand Down
39 changes: 28 additions & 11 deletions genotype_api/api/endpoints/analyses.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
"""Routes for analysis"""

from pathlib import Path
from typing import List

import genotype_api.crud.analyses
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status, Query
from fastapi import APIRouter, Depends, status, Query, UploadFile, File
from fastapi.responses import JSONResponse

from genotype_api.crud.analyses import get_analysis, check_analyses_objects, create_analyses
from genotype_api.crud.samples import create_analyses_sample_objects
from genotype_api.database import get_session
from genotype_api.files import check_file
from genotype_api.models import Analysis, AnalysisRead, AnalysisReadWithGenotype
from sqlmodel import Session, select, delete
from sqlmodel import Session, select

from genotype_api.vcf import SequenceAnalysis

router = APIRouter()


@router.get("/{analysis_id}", response_model=AnalysisReadWithGenotype)
def read_analysis(analysis_id: int, session: Session = Depends(get_session)):
"""Return analysis."""
analysis = session.get(Analysis, analysis_id)
if not analysis:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Analysis not found")

return analysis
return get_analysis(session=session, analysis_id=analysis_id)


@router.get("/", response_model=List[AnalysisRead])
Expand All @@ -33,11 +36,25 @@ def read_analyses(
return analyses


@router.delete("/{analysis_id}", response_model=AnalysisRead)
@router.delete("/{analysis_id}")
def delete_analysis(analysis_id: int, session: Session = Depends(get_session)):
"""Delete analysis based on analysis_id"""
analysis = session.get(Analysis, analysis_id)
analysis = get_analysis(session=session, analysis_id=analysis_id)
session.delete(analysis)
session.commit()

return analysis
return JSONResponse(f"Deleted analysis: {analysis_id}", status_code=status.HTTP_200_OK)


@router.post("/sequence", response_model=List[Analysis])
def upload_sequence_analysis(file: UploadFile = File(...), session: Session = Depends(get_session)):
"""Reading vcf file, creating and uploading sequence analyses and sample objects to db"""

file_name: Path = check_file(file_path=file.filename, extension=".vcf")
content = file.file.read().decode("utf-8")
sequence_analysis = SequenceAnalysis(vcf_file=content, source=str(file_name))
analyses: List[Analysis] = list(sequence_analysis.generate_analyses())
check_analyses_objects(session=session, analyses=analyses, analysis_type="sequence")
create_analyses_sample_objects(session=session, analyses=analyses)
create_analyses(session=session, analyses=analyses)
return analyses
76 changes: 37 additions & 39 deletions genotype_api/api/endpoints/plates.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,97 +4,89 @@
from pathlib import Path
from typing import List

from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status, Query
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, Query, status
from fastapi.responses import JSONResponse
from sqlmodel import Session, select

from genotype_api.crud.analyses import create_analysis, get_analysis_type_sample
from genotype_api.crud.samples import get_sample, create_sample
from genotype_api.crud.plates import create_plate
from genotype_api.crud.analyses import (
get_analyses_from_plate,
check_analyses_objects,
)
from genotype_api.crud.samples import create_analyses_sample_objects
from genotype_api.crud.plates import create_plate, get_plate
from genotype_api.database import get_session
from genotype_api.excel import GenotypeAnalysis
from genotype_api.files import check_file
from genotype_api.models import (
Plate,
PlateReadWithAnalyses,
PlateRead,
Sample,
Analysis,
PlateCreate,
)

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]


@router.post("/plate", response_model=Plate)
def upload_plate(file: UploadFile = File(...), session: Session = Depends(get_session)):
file_name: Path = Path(file.filename)
if not file_name.name.endswith(".xlsx"):
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Please select an excel book for upload",
)
# Get the plate id from the standardized name of the plate
plate_id = file_name.name.split("_", 1)[0]
# Check if plate exists
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=400, detail="Plate already uploaded")
plate_obj = PlateCreate(plate_id=plate_id)

content = BytesIO(file.file.read())
excel_parser = GenotypeAnalysis(
excel_file=content, file_name=str(file_name), include_key="-CG-"
excel_file=BytesIO(file.file.read()), file_name=str(file_name), include_key="-CG-"
)
for analysis_obj in excel_parser.generate_analyses():
db_analysis: Analysis = get_analysis_type_sample(
session=session, sample_id=analysis_obj.sample_id, analysis_type="genotype"
)
if db_analysis:
raise HTTPException(status_code=400, detail="Analysis already exists")
if not get_sample(session=session, sample_id=analysis_obj.sample_id):
create_sample(session=session, sample=Sample(id=analysis_obj.sample_id))
plate_obj.analyses.append(create_analysis(session=session, analysis=analysis_obj))

analyses: List[Analysis] = list(excel_parser.generate_analyses())
check_analyses_objects(session=session, analyses=analyses, analysis_type="genotype")
create_analyses_sample_objects(session=session, analyses=analyses)
plate_obj = PlateCreate(plate_id=plate_id)
plate_obj.analyses = analyses
return create_plate(session=session, plate=plate_obj)


@router.post("/{plate_id}/sign-off", response_model=Plate)
@router.patch("/{plate_id}/sign-off", response_model=Plate)
def sign_off_plate(
plate_id: str,
plate_id: int,
method_document: str = Query(...),
method_version: str = Query(...),
session: Session = Depends(get_session),
):
"""Sign off a plate.
This means that current User sign off that the plate is checked
Add Depends with curent user
Add Depends with current user
"""

plate = session.get(Plate, plate_id)
if not plate:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Plate not found")
plate: Plate = get_plate(session=session, plate_id=plate_id)

# plate.user = current_user
plate.signed_at = datetime.now()
plate.method_document = method_document
plate.method_version = method_version
session.commit()
session.refresh(plate)
return plate


@router.get("/{plate_id}", response_model=PlateReadWithAnalyses)
def read_plate(plate_id: int, session: Session = Depends(get_session)):
"""Display information about a plate."""
plate = session.get(Plate, plate_id)
if not plate:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Plate not found")
return plate

return get_plate(session=session, plate_id=plate_id)


@router.get("/", response_model=List[PlateRead])
def read_plates(
skip: int = 0, limit: int = Query(default=100, lte=100), session: Session = Depends(get_session)
):
"""Display information about a plate."""
"""Display all plates"""
plates: List[Plate] = session.exec(select(Plate).offset(skip).limit(limit)).all()

return plates
Expand All @@ -104,7 +96,13 @@ def read_plates(
def delete_plate(plate_id: int, session: Session = Depends(get_session)):
"""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:
session.delete(analysis)
session.delete(plate)
session.commit()

return plate
return JSONResponse(
f"Deleted plate: {plate_id} and analyses: {analyse_ids}", status_code=status.HTTP_200_OK
)
82 changes: 43 additions & 39 deletions genotype_api/api/endpoints/samples.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, status, Query
from fastapi import APIRouter, Depends, status, Query
from fastapi.responses import JSONResponse

from genotype_api.constants import STATUS, SEXES
from genotype_api.database import get_session
from genotype_api.models import Sample, SampleReadWithAnalysis, SampleRead
from genotype_api import crud
from genotype_api.crud.samples import (
get_incomplete_samples,
get_plate_samples,
get_commented_samples,
get_samples_like,
get_sample,
)
from sqlmodel import Session, select
from sqlmodel.sql.expression import SelectOfScalar
Expand All @@ -16,10 +20,7 @@

@router.get("/{sample_id}", response_model=SampleReadWithAnalysis)
def read_sample(sample_id: str, session: Session = Depends(get_session)):
sample = session.get(Sample, sample_id)
if not sample:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Sample not found")
return sample
return get_sample(session=session, sample_id=sample_id)


@router.get("/", response_model=List[SampleReadWithAnalysis])
Expand All @@ -29,7 +30,6 @@ def read_samples(
plate_id: Optional[str] = None,
incomplete: Optional[bool] = False,
commented: Optional[bool] = False,
query_string: Optional[str] = None,
session: Session = Depends(get_session),
):
statement: SelectOfScalar = select(Sample)
Expand All @@ -39,39 +39,27 @@ def read_samples(
statement: SelectOfScalar = get_incomplete_samples(statement=statement)
if commented:
statement: SelectOfScalar = get_commented_samples(statement=statement)
if query_string:
statement: SelectOfScalar = get_samples_like(statement=statement, query_string=query_string)
samples: List[Sample] = session.exec(statement.offset(skip).limit(limit)).all()
return samples


@router.post("/", response_model=SampleRead)
def create_sample(sample: Sample, session: Session = Depends(get_session)):
sample_in_db = session.get(Sample, sample.id)
if sample_in_db:
raise HTTPException(status_code=400, detail="Sample already registered")
db_sample = Sample.from_orm(sample)
session.add(db_sample)
session.commit()
session.refresh(db_sample)
return db_sample
return crud.samples.create_sample(session=session, sample=sample)


@router.put("/update-sex/{sample_id}", response_model=SampleRead)
@router.patch("/{sample_id}/sex", response_model=SampleRead)
def update_sex(
sample_id: str,
sex: str = Query(...),
genotype_sex: Optional[str] = None,
sequence_sex: Optional[str] = None,
comment: str = Query(...),
sex: SEXES = Query(...),
genotype_sex: Optional[SEXES] = None,
sequence_sex: Optional[SEXES] = None,
session: Session = Depends(get_session),
):
sample_in_db = session.get(Sample, sample_id)
if not sample_in_db:
raise HTTPException(status_code=404, detail="Sample not in db")
"""Updating sex field on sample and sample analyses"""

sample_in_db: Sample = get_sample(session=session, sample_id=sample_id)
sample_in_db.sex = sex
sample_in_db.comment = comment
for analysis in sample_in_db.analyses:
if analysis.type == "genotype":
analysis.sex = genotype_sex
Expand All @@ -84,32 +72,48 @@ def update_sex(
return sample_in_db


@router.put("/update-status/{sample_id}", response_model=SampleRead)
def update_status(
@router.patch("/{sample_id}/comment", response_model=SampleRead)
def update_comment(
sample_id: str,
status: str = Query(...),
comment: str = Query(...),
session: Session = Depends(get_session),
):
sample_in_db = session.get(Sample, sample_id)
if not sample_in_db:
raise HTTPException(status_code=404, detail="Sample not in db")
"""Updating comment field on sample"""

sample_in_db.status = status
if sample_in_db.comment:
comment = f"{sample_in_db.comment}\n\n{comment}"
sample_in_db: Sample = get_sample(session=session, sample_id=sample_id)
sample_in_db.comment = comment
session.add(sample_in_db)
session.commit()
session.refresh(sample_in_db)
return sample_in_db


@router.patch("/{sample_id}/status", response_model=SampleRead)
def update_status(
sample_id: str,
status: STATUS = Query(...),
session: Session = Depends(get_session),
):
"""Updating status field on sample"""

sample_in_db: Sample = get_sample(session=session, sample_id=sample_id)
sample_in_db.status = status
session.add(sample_in_db)
session.commit()
session.refresh(sample_in_db)
return sample_in_db


@router.delete("/{sample_id}", response_model=Sample)
def delete_sample(sample_id: str, session: Session = Depends(get_session)):
"""Delete sample."""
sample = session.get(Sample, sample_id)
"""Delete sample and its Analyses"""

sample: Sample = get_sample(session=session, sample_id=sample_id)
for analysis in sample.analyses:
session.delete(analysis)
session.delete(sample)
session.commit()

return sample
return JSONResponse(
f"Deleted sample: {sample_id} and analyses: {[analysis.id for analysis in sample.analyses]}",
status_code=status.HTTP_200_OK,
)
Loading

0 comments on commit ee48585

Please sign in to comment.