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

update(pydantic) #104

Closed
wants to merge 14 commits into from
8 changes: 4 additions & 4 deletions genotype_api/api/endpoints/samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from starlette import status

import genotype_api.database.crud.create
from genotype_api.constants import SEXES
from genotype_api.database.crud.read import (
get_commented_samples,
get_incomplete_samples,
Expand All @@ -26,6 +25,7 @@
SampleReadWithAnalysisDeep,
User,
compare_genotypes,
Sexes,
)
from genotype_api.database.session_handler import get_session
from genotype_api.match import check_sample
Expand Down Expand Up @@ -122,9 +122,9 @@ def create_sample(
@router.put("/{sample_id}/sex", response_model=SampleRead)
def update_sex(
sample_id: str,
sex: SEXES = Query(...),
genotype_sex: SEXES | None = None,
sequence_sex: SEXES | None = None,
sex: Sexes = Query(...),
genotype_sex: Sexes | None = None,
sequence_sex: Sexes | None = None,
session: Session = Depends(get_session),
current_user: User = Depends(get_active_user),
):
Expand Down
10 changes: 5 additions & 5 deletions genotype_api/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pathlib import Path

from pydantic import BaseSettings
from pydantic_settings import BaseSettings

GENOTYPE_PACKAGE = Path(__file__).parent
PACKAGE_ROOT: Path = GENOTYPE_PACKAGE.parent
Expand All @@ -22,10 +22,10 @@ class Config:
class SecuritySettings(BaseSettings):
"""Settings for serving the genotype-api app"""

client_id = ""
algorithm = ""
jwks_uri = "https://www.googleapis.com/oauth2/v3/certs"
api_root_path = "/"
client_id: str = ""
algorithm: str = ""
jwks_uri: str = "https://www.googleapis.com/oauth2/v3/certs"
api_root_path: str = "/"

class Config:
env_file = str(ENV_FILE)
Expand Down
23 changes: 0 additions & 23 deletions genotype_api/constants.py
Original file line number Diff line number Diff line change
@@ -1,24 +1 @@
"""Constants used over the package"""

from enum import Enum
from pydantic import BaseModel


class TYPES(str, Enum):
GENOTYPE = "genotype"
SEQUENCE = "sequence"


class SEXES(str, Enum):
MALE = "male"
FEMALE = "female"
UNKNOWN = "unknown"


class STATUS(str, Enum):
PASS = "pass"
FAIL = "fail"
CANCEL = "cancel"


CUTOFS = dict(max_nocalls=15, max_mismatch=3, min_matches=35)
5 changes: 2 additions & 3 deletions genotype_api/database/crud/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
from sqlmodel import Session, select
from sqlmodel.sql.expression import Select, SelectOfScalar

from genotype_api.constants import TYPES
from genotype_api.database.models import Analysis, Plate, Sample, User
from genotype_api.database.models import Analysis, Plate, Sample, User, AnalysisTypes

SelectOfScalar.inherit_cache = True
Select.inherit_cache = True
Expand Down Expand Up @@ -96,7 +95,7 @@ def get_users(session: Session, skip: int = 0, limit: int = 100) -> list[User]:


def check_analyses_objects(
session: Session, analyses: list[Analysis], analysis_type: TYPES
session: Session, analyses: list[Analysis], analysis_type: AnalysisTypes
) -> None:
"""Raising 400 if any analysis in the list already exist in the database"""
for analysis_obj in analyses:
Expand Down
68 changes: 44 additions & 24 deletions genotype_api/database/models.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,39 @@
import enum
from collections import Counter
from datetime import datetime

from pydantic import EmailStr, constr, validator

from pydantic import validator
from sqlalchemy import Index
from sqlmodel import Field, Relationship, SQLModel

from genotype_api.constants import CUTOFS, SEXES, STATUS, TYPES
from genotype_api.models import PlateStatusCounts, SampleDetail


class AnalysisTypes(enum.Enum):
GENOTYPE: str = "genotype"
SEQUENCE: str = "sequence"


class Sexes(enum.Enum):
MALE: str = "male"
FEMALE: str = "female"
UNKNOWN: str = "unknown"


class Status(enum.Enum):
PASS: str = "pass"
FAIL: str = "fail"
CANCEL: str = "cancel"


CUTOFS = dict(max_nocalls=15, max_mismatch=3, min_matches=35)


class GenotypeBase(SQLModel):
rsnumber: constr(max_length=10) | None
rsnumber: str | None = Field(max_length=10)
analysis_id: int | None = Field(default=None, foreign_key="analysis.id")
allele_1: constr(max_length=1) | None
allele_2: constr(max_length=1) | None
allele_1: str | None = Field(max_length=1)
allele_2: str | None = Field(max_length=1)


class Genotype(GenotypeBase, table=True):
Expand Down Expand Up @@ -44,11 +64,11 @@ class GenotypeCreate(GenotypeBase):


class AnalysisBase(SQLModel):
type: TYPES
type: AnalysisTypes
source: str | None
sex: SEXES | None
sex: Sexes | None
created_at: datetime | None = datetime.now()
sample_id: constr(max_length=32) | None = Field(default=None, foreign_key="sample.id")
sample_id: str | None = Field(default=None, foreign_key="sample.id", max_length=32)
plate_id: str | None = Field(default=None, foreign_key="plate.id")


Expand Down Expand Up @@ -76,18 +96,18 @@ class AnalysisCreate(AnalysisBase):


class SampleSlim(SQLModel):
status: STATUS | None
status: Status | None
comment: str | None


class SampleBase(SampleSlim):
sex: SEXES | None
sex: Sexes | None
created_at: datetime | None = datetime.now()


class Sample(SampleBase, table=True):
__tablename__ = "sample"
id: constr(max_length=32) | None = Field(default=None, primary_key=True)
id: str | None = Field(default=None, primary_key=True, max_length=32)

analyses: list["Analysis"] = Relationship(back_populates="sample")

Expand All @@ -113,32 +133,32 @@ def sequence_analysis(self) -> Analysis | None:


class SampleRead(SampleBase):
id: constr(max_length=32)
id: str = Field(max_length=32)


class SampleCreate(SampleBase):
pass


class SNPBase(SQLModel):
ref: constr(max_length=1) | None
chrom: constr(max_length=5) | None
ref: str | None = Field(max_length=1)
chrom: str | None = Field(max_length=5)
islean marked this conversation as resolved.
Show resolved Hide resolved
pos: int | None


class SNP(SNPBase, table=True):
__tablename__ = "snp"
"""Represent a SNP position under investigation."""

id: constr(max_length=32) | None = Field(default=None, primary_key=True)
id: str | None = Field(default=None, primary_key=True, max_length=32)


class SNPRead(SNPBase):
id: constr(max_length=32)
id: str = Field(max_length=32)


class UserBase(SQLModel):
email: EmailStr = Field(index=True, unique=True)
ChrOertlin marked this conversation as resolved.
Show resolved Hide resolved
email: str = Field(index=True, unique=True)
name: str | None = ""


Expand All @@ -158,7 +178,7 @@ class UserCreate(UserBase):

class PlateBase(SQLModel):
created_at: datetime | None = datetime.now()
plate_id: constr(max_length=16) = Field(index=True, unique=True)
plate_id: str = Field(index=True, unique=True, max_length=16)
signed_by: int | None = Field(default=None, foreign_key="user.id")
signed_at: datetime | None
method_document: str | None
Expand Down Expand Up @@ -216,7 +236,7 @@ def get_detail(cls, value, values) -> SampleDetail:
return SampleDetail(**status, sex=sex)

class Config:
validate_all = True
validate_default = True


class AnalysisReadWithSample(AnalysisRead):
Expand Down Expand Up @@ -255,7 +275,7 @@ def check_detail(cls, value, values):
return PlateStatusCounts(**status_counts, total=len(analyses), commented=commented)

class Config:
validate_all = True
validate_default = True


class PlateReadWithAnalysisDetailSingle(PlateRead):
Expand All @@ -271,7 +291,7 @@ def check_detail(cls, value, values):
return PlateStatusCounts(**status_counts, total=len(analyses), commented=commented)

class Config:
validate_all = True
validate_default = True


def check_snps(genotype_analysis, sequence_analysis):
Expand Down Expand Up @@ -303,9 +323,9 @@ def check_snps(genotype_analysis, sequence_analysis):

def check_sex(sample_sex, genotype_analysis, sequence_analysis):
"""Check if any source disagrees on the sex"""
if not sample_sex or genotype_analysis.sex == SEXES.UNKNOWN:
if not sample_sex or genotype_analysis.sex == Sexes.UNKNOWN:
return "fail"
sexes = {genotype_analysis.sex, sequence_analysis.sex, sample_sex}
if {SEXES.MALE, SEXES.FEMALE}.issubset(sexes):
if {Sexes.MALE, Sexes.FEMALE}.issubset(sexes):
return "fail"
return "pass"
4 changes: 2 additions & 2 deletions genotype_api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class PlateStatusCounts(BaseModel):
commented: int = Field(0, nullable=True)

class Config:
allow_population_by_field_name = True
populate_by_name = True


class SampleDetailStats(BaseModel):
Expand Down Expand Up @@ -53,7 +53,7 @@ def validate_status(cls, value, values) -> SampleDetailStatus:
return SampleDetailStatus(sex=sex, snps=snps, nocalls=nocalls)

class Config:
validate_all = True
validate_default = True


class MatchCounts(BaseModel):
Expand Down
7 changes: 4 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
SQLAlchemy==1.4.30
SQLAlchemy
aiofiles
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⭐ Nice

bcrypt
bump2version
click
coloredlogs
email-validator
fastapi==0.75.0
fastapi
google-auth
gunicorn
httptools
numpy
openpyxl
passlib
pydantic==1.10.14
pydantic
pydantic-settings
pymysql
python-dotenv
python-jose[cryptography]
Expand Down
Loading