Skip to content

Commit

Permalink
cigrid-ui-412 new column types (#74) (major)
Browse files Browse the repository at this point in the history
### CHANGED

- Types for method document and method version in the plate table were changed to string. 
- Added indexes to python models to match database schema.

---------

Co-authored-by: isakohlsson <isak.ohlsson@scilifelab.se>
  • Loading branch information
islean and islean authored May 16, 2023
1 parent 4b3279e commit 5e8a9fc
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 27 deletions.
6 changes: 4 additions & 2 deletions alembic/env.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from __future__ import with_statement

from sqlmodel import SQLModel

from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
from genotype_api.models import SQLModel

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
Expand Down Expand Up @@ -52,7 +52,9 @@ def run_migrations_online():
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section), prefix="sqlalchemy.", poolclass=pool.NullPool
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)

with connectable.connect() as connection:
Expand Down
67 changes: 67 additions & 0 deletions alembic/versions/7f77cbc00305_change_version_field_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Change version field type
Revision ID: 7f77cbc00305
Revises: 21ef5ce15822
Create Date: 2023-05-15 16:46:03.718794
"""

# revision identifiers, used by Alembic.
revision = "7f77cbc00305"
down_revision = "21ef5ce15822"
branch_labels = None
depends_on = None

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index("plate_id", table_name="plate")
op.create_index(op.f("ix_plate_plate_id"), "plate", ["plate_id"], unique=True)
op.alter_column("user", "email", existing_type=mysql.VARCHAR(length=128), nullable=False)
op.drop_index("email", table_name="user")
op.drop_index("google_id", table_name="user")
op.create_index(op.f("ix_user_email"), "user", ["email"], unique=True)
op.drop_column("user", "avatar")
op.drop_column("user", "google_id")
op.alter_column(
"plate",
"method_document",
existing_type=mysql.INTEGER(),
type_=mysql.VARCHAR(length=128),
)
op.alter_column(
"plate",
"method_version",
existing_type=mysql.INTEGER(),
type_=mysql.VARCHAR(length=16),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column(
"plate",
"method_version",
existing_type=mysql.VARCHAR(length=16),
type_=mysql.INTEGER(),
)
op.alter_column(
"plate",
"method_document",
existing_type=mysql.VARCHAR(length=128),
type_=mysql.INTEGER(),
)

op.add_column("user", sa.Column("google_id", mysql.VARCHAR(length=128), nullable=True))
op.add_column("user", sa.Column("avatar", mysql.TEXT(), nullable=True))
op.drop_index(op.f("ix_user_email"), table_name="user")
op.create_index("google_id", "user", ["google_id"], unique=False)
op.create_index("email", "user", ["email"], unique=False)
op.alter_column("user", "email", existing_type=mysql.VARCHAR(length=128), nullable=True)
op.drop_index(op.f("ix_plate_plate_id"), table_name="plate")
op.create_index("plate_id", "plate", ["plate_id"], unique=False)
11 changes: 9 additions & 2 deletions genotype_api/api/endpoints/analyses.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@
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_analysis
from genotype_api.crud.samples import create_analyses_sample_objects, refresh_sample_status
from genotype_api.crud.analyses import (
get_analysis,
check_analyses_objects,
create_analysis,
)
from genotype_api.crud.samples import (
create_analyses_sample_objects,
refresh_sample_status,
)
from genotype_api.database import get_session
from genotype_api.file_parsing.files import check_file
from genotype_api.models import Analysis, AnalysisRead, AnalysisReadWithGenotype, User
Expand Down
23 changes: 17 additions & 6 deletions genotype_api/api/endpoints/plates.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
get_analyses_from_plate,
check_analyses_objects,
)
from genotype_api.crud.samples import create_analyses_sample_objects, refresh_sample_status
from genotype_api.crud.samples import (
create_analyses_sample_objects,
refresh_sample_status,
)
from genotype_api.crud.plates import create_plate, get_plate
from genotype_api.crud.users import get_user_by_email
from genotype_api.database import get_session
Expand Down Expand Up @@ -61,7 +64,9 @@ def upload_plate(
)

excel_parser = GenotypeAnalysis(
excel_file=BytesIO(file.file.read()), file_name=str(file_name), include_key="-CG-"
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")
Expand All @@ -78,8 +83,8 @@ def upload_plate(
@router.patch("/{plate_id}/sign-off", response_model=Plate)
def sign_off_plate(
plate_id: int,
method_document: int = Query(...),
method_version: int = Query(...),
method_document: str = Query(...),
method_version: str = Query(...),
session: Session = Depends(get_session),
current_user: User = Depends(get_active_user),
):
Expand All @@ -106,7 +111,12 @@ def sign_off_plate(
response_model_exclude={
"analyses": {
"__all__": {
"sample": {"analyses": True, "created_at": True, "sex": True, "id": True},
"sample": {
"analyses": True,
"created_at": True,
"sex": True,
"id": True,
},
"source": True,
"created_at": True,
"type": True,
Expand Down Expand Up @@ -165,5 +175,6 @@ def delete_plate(
session.commit()

return JSONResponse(
f"Deleted plate: {plate_id} and analyses: {analyse_ids}", status_code=status.HTTP_200_OK
f"Deleted plate: {plate_id} and analyses: {analyse_ids}",
status_code=status.HTTP_200_OK,
)
3 changes: 2 additions & 1 deletion genotype_api/api/endpoints/samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ def match(
if count.get("match", 0) + count.get("unknown", 0) > 40:
match_results.append(
MatchResult(
sample_id=genotype.sample_id, match_results=MatchCounts.parse_obj(count)
sample_id=genotype.sample_id,
match_results=MatchCounts.parse_obj(count),
),
)
return match_results
Expand Down
3 changes: 2 additions & 1 deletion genotype_api/api/endpoints/snps.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ async def upload_snps(

@router.delete("/")
def delete_snps(
session: Session = Depends(get_session), current_user: User = Depends(get_active_user)
session: Session = Depends(get_session),
current_user: User = Depends(get_active_user),
):
"""Delete all SNPs"""

Expand Down
4 changes: 3 additions & 1 deletion genotype_api/crud/analyses.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ def check_analyses_objects(
"""Raising 400 if any analysis in the list already exist in the database"""
for analysis_obj in analyses:
db_analysis: Analysis = get_analysis_type_sample(
session=session, sample_id=analysis_obj.sample_id, analysis_type=analysis_type
session=session,
sample_id=analysis_obj.sample_id,
analysis_type=analysis_type,
)
if db_analysis:
session.delete(db_analysis)
4 changes: 3 additions & 1 deletion genotype_api/file_parsing/vcf.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ def generate_analyses(self) -> Iterable[Analysis]:
gt_info: Genotype
for gt_info in variant_object.genotypes:
db_genotype = DBGenotype(
rsnumber=variant_object.id, allele_1=gt_info.allele_1, allele_2=gt_info.allele_2
rsnumber=variant_object.id,
allele_1=gt_info.allele_1,
allele_2=gt_info.allele_2,
)
analyses[gt_info.sample_id].genotypes.append(db_genotype)
return [analysis for analysis in analyses.values()]
Expand Down
28 changes: 18 additions & 10 deletions genotype_api/models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from datetime import datetime
from typing import Optional, List, Dict, Tuple, Type, Any
from collections import Counter
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple, Type

from pydantic import BaseModel, EmailStr, constr, validator
from sqlalchemy import Index
from sqlmodel import Field, Relationship, SQLModel

from pydantic import constr, EmailStr, BaseModel, validator
from sqlmodel import SQLModel, Field, Relationship

from genotype_api.constants import TYPES, SEXES, STATUS, CUTOFS
from genotype_api.constants import CUTOFS, SEXES, STATUS, TYPES


class PlateStatusCounts(BaseModel):
Expand Down Expand Up @@ -71,6 +71,8 @@ class GenotypeBase(SQLModel):


class Genotype(GenotypeBase, table=True):
__tablename__ = "genotype"
__table_args__ = (Index("_analysis_rsnumber", "analysis_id", "rsnumber", unique=True),)
id: Optional[int] = Field(default=None, primary_key=True)

analysis: Optional["Analysis"] = Relationship(back_populates="genotypes")
Expand Down Expand Up @@ -105,6 +107,8 @@ class AnalysisBase(SQLModel):


class Analysis(AnalysisBase, table=True):
__tablename__ = "analysis"
__table_args__ = (Index("_sample_type", "sample_id", "type", unique=True),)
id: Optional[int] = Field(default=None, primary_key=True)

sample: Optional["Sample"] = Relationship(back_populates="analyses")
Expand Down Expand Up @@ -136,6 +140,7 @@ class SampleBase(SampleSlim):


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

analyses: Optional[List["Analysis"]] = Relationship(back_populates="sample")
Expand Down Expand Up @@ -176,6 +181,7 @@ class SNPBase(SQLModel):


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

id: Optional[constr(max_length=32)] = Field(default=None, primary_key=True)
Expand All @@ -186,11 +192,12 @@ class SNPRead(SNPBase):


class UserBase(SQLModel):
email: EmailStr = Field(index=True)
email: EmailStr = Field(index=True, unique=True)
name: Optional[str] = ""


class User(UserBase, table=True):
__tablename__ = "user"
id: Optional[int] = Field(default=None, primary_key=True)
plates: Optional[List["Plate"]] = Relationship(back_populates="user")

Expand All @@ -205,14 +212,15 @@ class UserCreate(UserBase):

class PlateBase(SQLModel):
created_at: Optional[datetime] = datetime.now()
plate_id: constr(max_length=16)
plate_id: constr(max_length=16) = Field(index=True, unique=True)
signed_by: Optional[int] = Field(default=None, foreign_key="user.id")
signed_at: Optional[datetime]
method_document: Optional[int] = 1477
method_version: Optional[int]
method_document: Optional[str]
method_version: Optional[str]


class Plate(PlateBase, table=True):
__tablename__ = "plate"
id: Optional[int] = Field(default=None, primary_key=True)
user: Optional["User"] = Relationship(back_populates="plates")
analyses: Optional[List["Analysis"]] = Relationship(back_populates="plate")
Expand Down
9 changes: 6 additions & 3 deletions genotype_api/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ async def __call__(self, request: Request):
credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request)
if not credentials:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Invalid authorization code."
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid authorization code.",
)
if credentials.scheme != "Bearer":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Invalid authentication scheme."
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid authentication scheme.",
)
self.verify_jwt(credentials.credentials)

Expand All @@ -53,7 +55,8 @@ def verify_jwt(self, jwtoken: str) -> Optional[dict]:
return decode_id_token(jwtoken)
except Exception:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token or expired token."
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid token or expired token.",
)


Expand Down

0 comments on commit 5e8a9fc

Please sign in to comment.