From a45dabe3df0d515bc760bc697f50530fc0a7995b Mon Sep 17 00:00:00 2001 From: ChristianOertlin Date: Tue, 19 Mar 2024 12:28:01 +0100 Subject: [PATCH] refactor user and snp endpoints (#108) (patch) Refactor user and SNP endpoints --- genotype_api/api/endpoints/samples.py | 2 +- genotype_api/api/endpoints/snps.py | 30 ++++++++----------- genotype_api/api/endpoints/users.py | 30 +++++++++---------- genotype_api/database/crud/create.py | 19 +++++++----- genotype_api/database/crud/delete.py | 14 ++++++++- genotype_api/database/crud/read.py | 13 ++++++++ genotype_api/database/crud/update.py | 13 ++++++-- genotype_api/dto/dto.py | 2 +- .../match_genotype_service/match_genotype.py | 4 +-- .../match_genotype_service/utils.py | 2 +- .../services/snp_reader/snp_reader.py | 21 +++++++++++++ 11 files changed, 101 insertions(+), 49 deletions(-) rename genotype_api/{service => services}/match_genotype_service/match_genotype.py (93%) rename genotype_api/{service => services}/match_genotype_service/utils.py (97%) create mode 100644 genotype_api/services/snp_reader/snp_reader.py diff --git a/genotype_api/api/endpoints/samples.py b/genotype_api/api/endpoints/samples.py index 00a4e70..0906e8a 100644 --- a/genotype_api/api/endpoints/samples.py +++ b/genotype_api/api/endpoints/samples.py @@ -31,7 +31,7 @@ from genotype_api.database.session_handler import get_session from genotype_api.models import MatchResult, SampleDetail from genotype_api.security import get_active_user -from genotype_api.service.match_genotype_service.match_genotype import MatchGenotypeService +from genotype_api.services.match_genotype_service.match_genotype import MatchGenotypeService SelectOfScalar.inherit_cache = True Select.inherit_cache = True diff --git a/genotype_api/api/endpoints/snps.py b/genotype_api/api/endpoints/snps.py index 2a61804..61816e9 100644 --- a/genotype_api/api/endpoints/snps.py +++ b/genotype_api/api/endpoints/snps.py @@ -1,12 +1,16 @@ """Routes for the snps""" from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile -from sqlmodel import Session, delete, select +from sqlmodel import Session from sqlmodel.sql.expression import Select, SelectOfScalar +from genotype_api.database.crud.create import create_snps +from genotype_api.database.crud.read import get_snps, get_snps_by_limit_and_skip from genotype_api.database.models import SNP, User +from genotype_api.database.crud import delete from genotype_api.database.session_handler import get_session from genotype_api.security import get_active_user +from genotype_api.services.snp_reader.snp_reader import SNPReaderService SelectOfScalar.inherit_cache = True Select.inherit_cache = True @@ -21,7 +25,7 @@ def read_snps( session: Session = Depends(get_session), current_user: User = Depends(get_active_user), ) -> list[SNP]: - return session.exec(select(SNP).offset(skip).limit(limit)).all() + return get_snps_by_limit_and_skip(session=session, skip=skip, limit=limit) @router.post("/", response_model=list[SNP]) @@ -30,21 +34,12 @@ async def upload_snps( session: Session = Depends(get_session), current_user: User = Depends(get_active_user), ): - db_snps: list[SNP] = session.exec(select(SNP)).all() - if db_snps: + existing_snps: list[SNP] = get_snps(session) + if existing_snps: raise HTTPException(status_code=400, detail="SNPs already uploaded") - snps = [] - content = await snps_file.read() - - header = ["id", "ref", "chrom", "pos"] - for line in content.decode().split("\n"): - if len(line) <= 10: - continue - snp = SNP(**dict(zip(header, line.split()))) - session.add(snp) - snps.append(snp) - session.commit() - return snps + snps: list[SNP] = SNPReaderService.read_snps_from_file(snps_file) + new_snps: list[snps] = create_snps(session=session, snps=snps) + return new_snps @router.delete("/") @@ -54,6 +49,5 @@ def delete_snps( ): """Delete all SNPs""" - result = session.exec(delete(SNP)) - session.commit() + result = delete.delete_snps(session) return {"message": f"all snps deleted ({result.rowcount} snps)"} diff --git a/genotype_api/api/endpoints/users.py b/genotype_api/api/endpoints/users.py index 52b1065..bac0762 100644 --- a/genotype_api/api/endpoints/users.py +++ b/genotype_api/api/endpoints/users.py @@ -7,7 +7,13 @@ from starlette import status from starlette.responses import JSONResponse -from genotype_api.database.crud.read import get_user +from genotype_api.database.crud.read import ( + get_user, + get_user_by_email, + get_users_with_skip_and_limit, +) +from genotype_api.database.crud.update import update_user_email +from genotype_api.database.crud import delete, create from genotype_api.database.models import User from genotype_api.dto.dto import UserRead, UserCreate, UserReadWithPlates from genotype_api.database.session_handler import get_session @@ -43,8 +49,7 @@ def delete_user( status_code=status.HTTP_406_NOT_ACCEPTABLE, detail="User previously signed plates, please archive instead", ) - session.delete(user) - session.commit() + delete.delete_user(session=session, user=user) return JSONResponse(content="User deleted successfully", status_code=status.HTTP_200_OK) @@ -58,10 +63,7 @@ def change_user_email( user: User = get_user(session=session, user_id=user_id) if not user: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") - user.email = email - session.add(user) - session.commit() - session.refresh(user) + user: User = update_user_email(session=session, user=user, email=email) return user @@ -72,8 +74,7 @@ def read_users( session: Session = Depends(get_session), current_user: User = Depends(get_active_user), ) -> list[User]: - users: list[User] = session.exec(select(User).offset(skip).limit(limit)).all() - return users + return get_users_with_skip_and_limit(session=session, skip=skip, limit=limit) @router.post("/", response_model=User) @@ -82,11 +83,8 @@ def create_user( session: Session = Depends(get_session), current_user: User = Depends(get_active_user), ): - user_in_db: list[User] = session.exec(select(User).where(User.email == user.email)).all() - if user_in_db: + existing_user: User = get_user_by_email(session=session, email=user.email) + if existing_user: raise HTTPException(status_code=409, detail="Email already registered") - db_user = User.from_orm(user) - session.add(db_user) - session.commit() - session.refresh(db_user) - return db_user + new_user: User = create.create_user(session=session, user=user) + return new_user diff --git a/genotype_api/database/crud/create.py b/genotype_api/database/crud/create.py index 2910a29..5daa833 100644 --- a/genotype_api/database/crud/create.py +++ b/genotype_api/database/crud/create.py @@ -4,7 +4,7 @@ from sqlmodel import Session from sqlmodel.sql.expression import Select, SelectOfScalar -from genotype_api.database.models import Analysis, Plate, Sample, User +from genotype_api.database.models import Analysis, Plate, Sample, User, SNP from genotype_api.dto.dto import UserCreate, PlateCreate SelectOfScalar.inherit_cache = True @@ -51,10 +51,15 @@ def create_analyses_sample_objects(session: Session, analyses: list[Analysis]) - ] -def create_user(db: Session, user: UserCreate): - fake_hashed_password = user.password + "notreallyhashed" - db_user = User(email=user.email, hashed_password=fake_hashed_password) - db.add(db_user) - db.commit() - db.refresh(db_user) +def create_user(session: Session, user: UserCreate): + db_user = User.from_orm(user) + session.add(db_user) + session.commit() + session.refresh(db_user) return db_user + + +def create_snps(session: Session, snps: list[SNP]) -> list[SNP]: + session.add_all(snps) + session.commit() + return snps diff --git a/genotype_api/database/crud/delete.py b/genotype_api/database/crud/delete.py index 1138073..45c0aec 100644 --- a/genotype_api/database/crud/delete.py +++ b/genotype_api/database/crud/delete.py @@ -1,9 +1,10 @@ import logging +from sqlalchemy import delete from sqlmodel import Session from sqlmodel.sql.expression import Select, SelectOfScalar -from genotype_api.database.models import Analysis, Plate, Sample +from genotype_api.database.models import Analysis, Plate, Sample, User, SNP SelectOfScalar.inherit_cache = True Select.inherit_cache = True @@ -24,3 +25,14 @@ def delete_plate(session: Session, plate: Plate) -> None: def delete_sample(session: Session, sample: Sample) -> None: session.delete(sample) session.commit() + + +def delete_user(session: Session, user: User) -> None: + session.delete(user) + session.commit() + + +def delete_snps(session) -> any: + result = session.exec(delete(SNP)) + session.commit() + return result diff --git a/genotype_api/database/crud/read.py b/genotype_api/database/crud/read.py index 09588d0..e95a875 100644 --- a/genotype_api/database/crud/read.py +++ b/genotype_api/database/crud/read.py @@ -15,6 +15,7 @@ Plate, Sample, User, + SNP, ) from genotype_api.dto.dto import PlateReadWithAnalysisDetailSingle @@ -173,6 +174,10 @@ def get_users(session: Session, skip: int = 0, limit: int = 100) -> list[User]: return session.exec(statement).all() +def get_users_with_skip_and_limit(session: Session, skip: int, limit: int) -> list[User]: + return session.exec(select(User).offset(skip).limit(limit)).all() + + def check_analyses_objects( session: Session, analyses: list[Analysis], analysis_type: TYPES ) -> None: @@ -185,3 +190,11 @@ def check_analyses_objects( ) if db_analysis: session.delete(db_analysis) + + +def get_snps(session) -> list[SNP]: + return session.exec(select(SNP)).all() + + +def get_snps_by_limit_and_skip(session: Session, skip: int, limit: int) -> list[SNP]: + return session.exec(select(SNP).offset(skip).limit(limit)).all() diff --git a/genotype_api/database/crud/update.py b/genotype_api/database/crud/update.py index df2b618..6beebd5 100644 --- a/genotype_api/database/crud/update.py +++ b/genotype_api/database/crud/update.py @@ -1,15 +1,16 @@ import types +from pydantic import EmailStr from sqlmodel import Session from genotype_api.constants import TYPES from genotype_api.database.crud.read import get_sample from genotype_api.database.filter_models.plate_models import PlateSignOff from genotype_api.database.filter_models.sample_models import SampleSexesUpdate -from genotype_api.database.models import Sample, Plate +from genotype_api.database.models import Sample, Plate, User from sqlmodel.sql.expression import Select, SelectOfScalar -from genotype_api.service.match_genotype_service.match_genotype import MatchGenotypeService +from genotype_api.services.match_genotype_service.match_genotype import MatchGenotypeService SelectOfScalar.inherit_cache = True Select.inherit_cache = True @@ -74,3 +75,11 @@ def update_sample_sex(session: Session, sexes_update: SampleSexesUpdate) -> Samp session.refresh(sample) sample: Sample = refresh_sample_status(session=session, sample=sample) return sample + + +def update_user_email(session: Session, user: User, email: EmailStr) -> User: + user.email = email + session.add(user) + session.commit() + session.refresh(user) + return user diff --git a/genotype_api/dto/dto.py b/genotype_api/dto/dto.py index 9cd43a6..2ee7919 100644 --- a/genotype_api/dto/dto.py +++ b/genotype_api/dto/dto.py @@ -5,7 +5,7 @@ import genotype_api.database.models from genotype_api.database import models from genotype_api.models import SampleDetail, PlateStatusCounts -from genotype_api.service.match_genotype_service.utils import check_snps, check_sex +from genotype_api.services.match_genotype_service.utils import check_snps, check_sex class GenotypeRead(models.GenotypeBase): diff --git a/genotype_api/service/match_genotype_service/match_genotype.py b/genotype_api/services/match_genotype_service/match_genotype.py similarity index 93% rename from genotype_api/service/match_genotype_service/match_genotype.py rename to genotype_api/services/match_genotype_service/match_genotype.py index f4b8504..a8ce46f 100644 --- a/genotype_api/service/match_genotype_service/match_genotype.py +++ b/genotype_api/services/match_genotype_service/match_genotype.py @@ -1,10 +1,10 @@ -"""Module for the match genotype service.""" +"""Module for the match genotype services.""" from collections import Counter from genotype_api.database.models import Analysis, Sample from genotype_api.models import MatchResult, MatchCounts, SampleDetail -from genotype_api.service.match_genotype_service.utils import ( +from genotype_api.services.match_genotype_service.utils import ( compare_genotypes, check_snps, check_sex, diff --git a/genotype_api/service/match_genotype_service/utils.py b/genotype_api/services/match_genotype_service/utils.py similarity index 97% rename from genotype_api/service/match_genotype_service/utils.py rename to genotype_api/services/match_genotype_service/utils.py index b2a4cd8..e903b0c 100644 --- a/genotype_api/service/match_genotype_service/utils.py +++ b/genotype_api/services/match_genotype_service/utils.py @@ -1,4 +1,4 @@ -"""Utils functions for the match genotype service.""" +"""Utils functions for the match genotype services.""" from collections import Counter diff --git a/genotype_api/services/snp_reader/snp_reader.py b/genotype_api/services/snp_reader/snp_reader.py new file mode 100644 index 0000000..8e7b4e5 --- /dev/null +++ b/genotype_api/services/snp_reader/snp_reader.py @@ -0,0 +1,21 @@ +"""This module holds the SNP reader server.""" + +from fastapi.datastructures import UploadFile + +from genotype_api.database.models import SNP + +SNP_HEADER = ["id", "ref", "chrom", "pos"] + + +class SNPReaderService: + @staticmethod + def read_snps_from_file(snps_file: UploadFile) -> list[SNP]: + snps: list[SNP] = [] + content = snps_file.read() + header = SNP_HEADER + for line in content.decode().split("\n"): + if len(line) <= 10: + continue + snp = SNP(**dict(zip(header, line.split()))) + snps.append(snp) + return snps