Addition of autofilling excel forms. Improved pydantic validation.
This commit is contained in:
@@ -1,65 +1,153 @@
|
||||
import uuid
|
||||
from pydantic import BaseModel, validator
|
||||
from datetime import date
|
||||
from pydantic import BaseModel, field_validator, model_validator, Extra
|
||||
from datetime import date, datetime
|
||||
from typing import List, Any
|
||||
from tools import RSLNamer
|
||||
from pathlib import Path
|
||||
import re
|
||||
import logging
|
||||
from tools import check_not_nan, convert_nans_to_nones
|
||||
import numpy as np
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
class PydSubmission(BaseModel):
|
||||
class PydReagent(BaseModel):
|
||||
type: str|None
|
||||
lot: str|None
|
||||
exp: date|None
|
||||
|
||||
@field_validator("type", mode='before')
|
||||
@classmethod
|
||||
def remove_undesired_types(cls, value):
|
||||
match value:
|
||||
case "atcc":
|
||||
return None
|
||||
case _:
|
||||
return value
|
||||
|
||||
@field_validator("lot", mode='before')
|
||||
@classmethod
|
||||
def enforce_lot_string(cls, value):
|
||||
if value != None:
|
||||
return convert_nans_to_nones(str(value))
|
||||
return value
|
||||
|
||||
@field_validator("exp", mode="before")
|
||||
@classmethod
|
||||
def enforce_date(cls, value):
|
||||
if isinstance(value, float) or value == np.nan:
|
||||
raise ValueError(f"Date cannot be a float: {value}")
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
|
||||
class PydSubmission(BaseModel, extra=Extra.allow):
|
||||
ctx: dict
|
||||
filepath: Path
|
||||
submission_type: str
|
||||
submitter_plate_num: str|None
|
||||
rsl_plate_num: str
|
||||
rsl_plate_num: str|dict|None
|
||||
submitted_date: date
|
||||
submitting_lab: str
|
||||
submitting_lab: str|None
|
||||
sample_count: int
|
||||
extraction_kit: str
|
||||
technician: str
|
||||
reagents: List[dict]
|
||||
extraction_kit: str|dict|None
|
||||
technician: str|None
|
||||
reagents: List[PydReagent] = []
|
||||
samples: List[Any]
|
||||
|
||||
@validator("submitted_date", pre=True)
|
||||
# missing_fields: List[str] = []
|
||||
|
||||
@field_validator("submitted_date", mode="before")
|
||||
@classmethod
|
||||
def strip_datetime_string(cls, value):
|
||||
if isinstance(value, datetime):
|
||||
return value
|
||||
if isinstance(value, date):
|
||||
return value
|
||||
return re.sub(r"_\d$", "", value)
|
||||
|
||||
@validator("submitter_plate_num")
|
||||
@field_validator("submitter_plate_num")
|
||||
@classmethod
|
||||
def enforce_with_uuid(cls, value):
|
||||
if value == None or value == "" or value == "None":
|
||||
return uuid.uuid4().hex.upper()
|
||||
|
||||
@validator("rsl_plate_num", pre=True)
|
||||
@classmethod
|
||||
def rsl_from_file(cls, value, values):
|
||||
if value == None:
|
||||
logger.debug(f"Pydant values:\n{values}")
|
||||
return RSLNamer(values['filepath'].__str__()).parsed_name
|
||||
else:
|
||||
return value
|
||||
|
||||
@validator("technician")
|
||||
@field_validator("submitting_lab", mode="before")
|
||||
@classmethod
|
||||
def transform_nan(cls, value):
|
||||
return convert_nans_to_nones(value)
|
||||
|
||||
@field_validator("rsl_plate_num", mode='before')
|
||||
@classmethod
|
||||
def rsl_from_file(cls, value, values):
|
||||
logger.debug(f"RSL-plate initial value: {value}")
|
||||
if check_not_nan(value):
|
||||
if isinstance(value, str):
|
||||
return dict(value=value, parsed=True)
|
||||
else:
|
||||
return value
|
||||
else:
|
||||
logger.debug(f"Pydant values:{type(values)}\n{values}")
|
||||
return dict(value=RSLNamer(values.data['filepath'].__str__()).parsed_name, parsed=False)
|
||||
|
||||
@field_validator("technician")
|
||||
@classmethod
|
||||
def enforce_tech(cls, value):
|
||||
if value == "nan" or value == "None":
|
||||
value = "Unknown"
|
||||
# elif len(value.split(",")) > 1:
|
||||
# tech_reg = re.compile(r"\b[A-Z]{2}\b")
|
||||
# value = ", ".join(tech_reg.findall(value))
|
||||
return value
|
||||
|
||||
@validator("reagents")
|
||||
@field_validator("reagents")
|
||||
@classmethod
|
||||
def remove_atcc(cls, value):
|
||||
return_val = []
|
||||
for reagent in value:
|
||||
match reagent['type']:
|
||||
case 'atcc':
|
||||
continue
|
||||
case _:
|
||||
return_val.append(reagent)
|
||||
logger.debug(f"Pydantic reagent: {reagent}")
|
||||
# match reagent.type.lower():
|
||||
# case 'atcc':
|
||||
# continue
|
||||
# case _:
|
||||
# return_val.append(reagent)
|
||||
if reagent.type == None:
|
||||
continue
|
||||
else:
|
||||
return_val.append(reagent)
|
||||
return return_val
|
||||
|
||||
@field_validator("sample_count", mode='before')
|
||||
@classmethod
|
||||
def enforce_sample_count(cls, value):
|
||||
if check_not_nan(value):
|
||||
return int(value)
|
||||
else:
|
||||
# raise ValueError(f"{value} could not be used to create an integer.")
|
||||
return convert_nans_to_nones(value)
|
||||
|
||||
@field_validator("extraction_kit", mode='before')
|
||||
@classmethod
|
||||
def get_kit_if_none(cls, value, values):
|
||||
from frontend.custom_widgets.pop_ups import KitSelector
|
||||
if check_not_nan(value):
|
||||
return dict(value=value, parsed=True)
|
||||
else:
|
||||
# logger.debug(values.data)
|
||||
dlg = KitSelector(ctx=values.data['ctx'], title="Kit Needed", message="At minimum a kit is needed. Please select one.")
|
||||
if dlg.exec():
|
||||
return dict(value=dlg.getValues(), parsed=False)
|
||||
else:
|
||||
raise ValueError("Extraction kit needed.")
|
||||
|
||||
# @model_validator(mode="after")
|
||||
# def ensure_kit(cls, values):
|
||||
# logger.debug(f"Model values: {values}")
|
||||
# missing_fields = [k for k,v in values if v == None]
|
||||
# if len(missing_fields) > 0:
|
||||
# logger.debug(f"Missing fields: {missing_fields}")
|
||||
# values['missing_fields'] = missing_fields
|
||||
# return values
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user