diff --git a/genotype_api/api/endpoints/analyses.py b/genotype_api/api/endpoints/analyses.py index dce8583..dda9900 100644 --- a/genotype_api/api/endpoints/analyses.py +++ b/genotype_api/api/endpoints/analyses.py @@ -15,7 +15,8 @@ get_analyses_with_skip_and_limit, ) from genotype_api.database.crud.update import refresh_sample_status -from genotype_api.database.models import Analysis, AnalysisRead, AnalysisReadWithGenotype, User +from genotype_api.database.models import Analysis, User +from genotype_api.dto.dto import AnalysisRead, AnalysisReadWithGenotype from genotype_api.database.session_handler import get_session from genotype_api.file_parsing.files import check_file from genotype_api.file_parsing.vcf import SequenceAnalysis diff --git a/genotype_api/api/endpoints/plates.py b/genotype_api/api/endpoints/plates.py index f1d4adc..7da256b 100644 --- a/genotype_api/api/endpoints/plates.py +++ b/genotype_api/api/endpoints/plates.py @@ -30,11 +30,13 @@ from genotype_api.database.models import ( Analysis, Plate, + User, +) +from genotype_api.dto.dto import ( PlateCreate, PlateReadWithAnalyses, PlateReadWithAnalysisDetail, PlateReadWithAnalysisDetailSingle, - User, ) from genotype_api.database.session_handler import get_session from genotype_api.file_parsing.excel import GenotypeAnalysis diff --git a/genotype_api/api/endpoints/samples.py b/genotype_api/api/endpoints/samples.py index eaae350..00a4e70 100644 --- a/genotype_api/api/endpoints/samples.py +++ b/genotype_api/api/endpoints/samples.py @@ -25,10 +25,9 @@ from genotype_api.database.models import ( Analysis, Sample, - SampleRead, - SampleReadWithAnalysisDeep, User, ) +from genotype_api.dto.dto import SampleRead, SampleReadWithAnalysisDeep from genotype_api.database.session_handler import get_session from genotype_api.models import MatchResult, SampleDetail from genotype_api.security import get_active_user diff --git a/genotype_api/api/endpoints/users.py b/genotype_api/api/endpoints/users.py index 98fc292..52b1065 100644 --- a/genotype_api/api/endpoints/users.py +++ b/genotype_api/api/endpoints/users.py @@ -8,7 +8,8 @@ from starlette.responses import JSONResponse from genotype_api.database.crud.read import get_user -from genotype_api.database.models import User, UserCreate, UserRead, UserReadWithPlates +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 from genotype_api.security import get_active_user diff --git a/genotype_api/database/crud/create.py b/genotype_api/database/crud/create.py index a1da800..2910a29 100644 --- a/genotype_api/database/crud/create.py +++ b/genotype_api/database/crud/create.py @@ -4,7 +4,8 @@ from sqlmodel import Session from sqlmodel.sql.expression import Select, SelectOfScalar -from genotype_api.database.models import Analysis, Plate, PlateCreate, Sample, User, UserCreate +from genotype_api.database.models import Analysis, Plate, Sample, User +from genotype_api.dto.dto import UserCreate, PlateCreate SelectOfScalar.inherit_cache = True Select.inherit_cache = True diff --git a/genotype_api/database/crud/delete.py b/genotype_api/database/crud/delete.py index d7a0681..1138073 100644 --- a/genotype_api/database/crud/delete.py +++ b/genotype_api/database/crud/delete.py @@ -3,7 +3,7 @@ from sqlmodel import Session from sqlmodel.sql.expression import Select, SelectOfScalar -from genotype_api.database.models import Analysis, Plate +from genotype_api.database.models import Analysis, Plate, Sample SelectOfScalar.inherit_cache = True Select.inherit_cache = True diff --git a/genotype_api/database/crud/read.py b/genotype_api/database/crud/read.py index ddeb0eb..09588d0 100644 --- a/genotype_api/database/crud/read.py +++ b/genotype_api/database/crud/read.py @@ -15,8 +15,8 @@ Plate, Sample, User, - PlateReadWithAnalysisDetailSingle, ) +from genotype_api.dto.dto import PlateReadWithAnalysisDetailSingle SelectOfScalar.inherit_cache = True Select.inherit_cache = True diff --git a/genotype_api/database/crud/update.py b/genotype_api/database/crud/update.py index ba10161..b859e26 100644 --- a/genotype_api/database/crud/update.py +++ b/genotype_api/database/crud/update.py @@ -4,10 +4,11 @@ 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.match import check_sample from genotype_api.database.models import Sample, Plate from sqlmodel.sql.expression import Select, SelectOfScalar +from genotype_api.service.match_genotype_service.match_genotype import MatchGenotypeService + SelectOfScalar.inherit_cache = True Select.inherit_cache = True @@ -16,7 +17,7 @@ def refresh_sample_status(sample: Sample, session: Session) -> Sample: if len(sample.analyses) != 2: sample.status = None else: - results = check_sample(sample=sample) + results = MatchGenotypeService.check_sample(sample=sample) sample.status = "fail" if "fail" in results.dict().values() else "pass" session.add(sample) diff --git a/genotype_api/database/models.py b/genotype_api/database/models.py index cf5b0fd..db9da68 100644 --- a/genotype_api/database/models.py +++ b/genotype_api/database/models.py @@ -1,13 +1,11 @@ from collections import Counter from datetime import datetime -from pydantic import EmailStr, constr, validator +from pydantic import EmailStr, constr from sqlalchemy import Index from sqlmodel import Field, Relationship, SQLModel from genotype_api.constants import SEXES, STATUS, TYPES -from genotype_api.models import PlateStatusCounts, SampleDetail -from genotype_api.service.match_genotype_service.utils import check_snps, check_sex class GenotypeBase(SQLModel): @@ -36,14 +34,6 @@ def is_ok(self) -> bool: return "0" not in self.alleles -class GenotypeRead(GenotypeBase): - id: int - - -class GenotypeCreate(GenotypeBase): - pass - - class AnalysisBase(SQLModel): type: TYPES source: str | None @@ -68,14 +58,6 @@ def check_no_calls(self) -> dict[str, int]: return Counter(calls) -class AnalysisRead(AnalysisBase): - id: int - - -class AnalysisCreate(AnalysisBase): - pass - - class SampleSlim(SQLModel): status: STATUS | None comment: str | None @@ -113,14 +95,6 @@ def sequence_analysis(self) -> Analysis | None: return None -class SampleRead(SampleBase): - id: constr(max_length=32) - - -class SampleCreate(SampleBase): - pass - - class SNPBase(SQLModel): ref: constr(max_length=1) | None chrom: constr(max_length=5) | None @@ -134,10 +108,6 @@ class SNP(SNPBase, table=True): id: constr(max_length=32) | None = Field(default=None, primary_key=True) -class SNPRead(SNPBase): - id: constr(max_length=32) - - class UserBase(SQLModel): email: EmailStr = Field(index=True, unique=True) name: str | None = "" @@ -149,14 +119,6 @@ class User(UserBase, table=True): plates: list["Plate"] = Relationship(back_populates="user") -class UserRead(UserBase): - id: int - - -class UserCreate(UserBase): - pass - - class PlateBase(SQLModel): created_at: datetime | None = datetime.now() plate_id: constr(max_length=16) = Field(index=True, unique=True) @@ -171,94 +133,3 @@ class Plate(PlateBase, table=True): id: int | None = Field(default=None, primary_key=True) user: "User" = Relationship(back_populates="plates") analyses: list["Analysis"] = Relationship(back_populates="plate") - - -class PlateRead(PlateBase): - id: str - user: UserRead | None - - -class PlateCreate(PlateBase): - analyses: list[Analysis] | None = [] - - -class UserReadWithPlates(UserRead): - plates: list[Plate] | None = [] - - -class SampleReadWithAnalysis(SampleRead): - analyses: list[AnalysisRead] | None = [] - - -class AnalysisReadWithGenotype(AnalysisRead): - genotypes: list[Genotype] | None = [] - - -class SampleReadWithAnalysisDeep(SampleRead): - analyses: list[AnalysisReadWithGenotype] | None = [] - detail: SampleDetail | None - - @validator("detail") - def get_detail(cls, value, values) -> SampleDetail: - analyses = values.get("analyses") - if len(analyses) != 2: - return SampleDetail() - genotype_analysis = [analysis for analysis in analyses if analysis.type == "genotype"][0] - sequence_analysis = [analysis for analysis in analyses if analysis.type == "sequence"][0] - status = check_snps( - genotype_analysis=genotype_analysis, sequence_analysis=sequence_analysis - ) - sex = check_sex( - sample_sex=values.get("sex"), - genotype_analysis=genotype_analysis, - sequence_analysis=sequence_analysis, - ) - - return SampleDetail(**status, sex=sex) - - class Config: - validate_all = True - - -class AnalysisReadWithSample(AnalysisRead): - sample: SampleSlim | None - - -class AnalysisReadWithSampleDeep(AnalysisRead): - sample: SampleReadWithAnalysisDeep | None - - -class PlateReadWithAnalyses(PlateRead): - analyses: list[AnalysisReadWithSample] | None = [] - - -class PlateReadWithAnalysisDetail(PlateRead): - analyses: list[AnalysisReadWithSample] | None = [] - detail: PlateStatusCounts | None - - @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 - - -class PlateReadWithAnalysisDetailSingle(PlateRead): - analyses: list[AnalysisReadWithSample] | None = [] - detail: PlateStatusCounts | None - - @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/dto/dto.py b/genotype_api/dto/dto.py new file mode 100644 index 0000000..9cd43a6 --- /dev/null +++ b/genotype_api/dto/dto.py @@ -0,0 +1,135 @@ +from collections import Counter + +from pydantic import constr, validator + +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 + + +class GenotypeRead(models.GenotypeBase): + id: int + + +class GenotypeCreate(models.GenotypeBase): + pass + + +class AnalysisRead(models.AnalysisBase): + id: int + + +class AnalysisCreate(models.AnalysisBase): + pass + + +class SampleRead(genotype_api.database.models.SampleBase): + id: constr(max_length=32) + + +class SampleCreate(genotype_api.database.models.SampleBase): + pass + + +class SNPRead(models.SNPBase): + id: constr(max_length=32) + + +class UserRead(models.UserBase): + id: int + + +class UserCreate(models.UserBase): + pass + + +class PlateRead(models.PlateBase): + id: str + user: UserRead | None + + +class PlateCreate(models.PlateBase): + analyses: list[models.Analysis] | None = [] + + +class UserReadWithPlates(UserRead): + plates: list[models.Plate] | None = [] + + +class SampleReadWithAnalysis(SampleRead): + analyses: list[AnalysisRead] | None = [] + + +class AnalysisReadWithGenotype(AnalysisRead): + genotypes: list[models.Genotype] | None = [] + + +class SampleReadWithAnalysisDeep(SampleRead): + analyses: list[AnalysisReadWithGenotype] | None = [] + detail: SampleDetail | None + + @validator("detail") + def get_detail(cls, value, values) -> SampleDetail: + analyses = values.get("analyses") + if len(analyses) != 2: + return SampleDetail() + genotype_analysis = [analysis for analysis in analyses if analysis.type == "genotype"][0] + sequence_analysis = [analysis for analysis in analyses if analysis.type == "sequence"][0] + status = check_snps( + genotype_analysis=genotype_analysis, sequence_analysis=sequence_analysis + ) + sex = check_sex( + sample_sex=values.get("sex"), + genotype_analysis=genotype_analysis, + sequence_analysis=sequence_analysis, + ) + + return SampleDetail(**status, sex=sex) + + class Config: + validate_all = True + + +class AnalysisReadWithSample(AnalysisRead): + sample: models.SampleSlim | None + + +class AnalysisReadWithSampleDeep(AnalysisRead): + sample: SampleReadWithAnalysisDeep | None + + +class PlateReadWithAnalyses(PlateRead): + analyses: list[AnalysisReadWithSample] | None = [] + + +class PlateReadWithAnalysisDetail(PlateRead): + analyses: list[AnalysisReadWithSample] | None = [] + detail: PlateStatusCounts | None + + @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 + + +class PlateReadWithAnalysisDetailSingle(PlateRead): + analyses: list[AnalysisReadWithSample] | None = [] + detail: PlateStatusCounts | None + + @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/service/match_genotype_service/utils.py b/genotype_api/service/match_genotype_service/utils.py index e840f35..b2a4cd8 100644 --- a/genotype_api/service/match_genotype_service/utils.py +++ b/genotype_api/service/match_genotype_service/utils.py @@ -3,10 +3,10 @@ from collections import Counter from genotype_api.constants import CUTOFS, SEXES -from genotype_api.database.models import Genotype +from genotype_api.database import models -def compare_genotypes(genotype_1: Genotype, genotype_2: Genotype) -> tuple[str, str]: +def compare_genotypes(genotype_1: models.Genotype, genotype_2: models.Genotype) -> tuple[str, str]: """Compare two genotypes if they have the same alleles.""" if "0" in genotype_1.alleles or "0" in genotype_2.alleles: