prior to db rebuild

This commit is contained in:
Landon Wark
2023-07-26 14:05:52 -05:00
parent 82dffe3af2
commit f22e697815
11 changed files with 76 additions and 53 deletions

View File

@@ -55,8 +55,8 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
# are written from script.py.mako # are written from script.py.mako
# output_encoding = utf-8 # output_encoding = utf-8
sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db ; sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db
; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\DB_backups\submissions-20230705.db sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\DB_backups\submissions-20230712.db
; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\python\submissions\tests\test_assets\submissions_test.db ; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\python\submissions\tests\test_assets\submissions_test.db

View File

@@ -0,0 +1,38 @@
"""making sample ids unique
Revision ID: 78178df0286a
Revises: 4c6221f01324
Create Date: 2023-07-26 13:55:41.864399
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '78178df0286a'
down_revision = '4c6221f01324'
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('_bc_samples', schema=None) as batch_op:
batch_op.create_unique_constraint("unique_bc_sample", ['sample_id'])
with op.batch_alter_table('_ww_samples', schema=None) as batch_op:
batch_op.create_unique_constraint("unique_ww_sample", ['ww_sample_full_id'])
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('_ww_samples', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='unique')
with op.batch_alter_table('_bc_samples', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='unique')
# ### end Alembic commands ###

View File

@@ -18,7 +18,7 @@ from getpass import getuser
import numpy as np import numpy as np
import yaml import yaml
from pathlib import Path from pathlib import Path
from tools import Settings from tools import Settings, check_regex_match, RSLNamer
@@ -43,7 +43,6 @@ def store_submission(ctx:Settings, base_submission:models.BasicSubmission) -> No
Returns: Returns:
None|dict : object that indicates issue raised for reporting in gui None|dict : object that indicates issue raised for reporting in gui
""" """
from tools import RSLNamer
logger.debug(f"Hello from store_submission") logger.debug(f"Hello from store_submission")
# Add all samples to sample table # Add all samples to sample table
typer = RSLNamer(ctx=ctx, instr=base_submission.rsl_plate_num) typer = RSLNamer(ctx=ctx, instr=base_submission.rsl_plate_num)
@@ -52,7 +51,7 @@ def store_submission(ctx:Settings, base_submission:models.BasicSubmission) -> No
logger.debug(f"Typer: {typer.submission_type}") logger.debug(f"Typer: {typer.submission_type}")
# Suuuuuper hacky way to be sure that the artic doesn't overwrite the ww plate in a ww sample # Suuuuuper hacky way to be sure that the artic doesn't overwrite the ww plate in a ww sample
# need something more elegant # need something more elegant
if "_artic" not in typer.submission_type: if "_artic" not in typer.submission_type.lower():
sample.rsl_plate = base_submission sample.rsl_plate = base_submission
else: else:
sample.artic_rsl_plate = base_submission sample.artic_rsl_plate = base_submission
@@ -114,7 +113,7 @@ def construct_submission_info(ctx:Settings, info_dict:dict) -> models.BasicSubmi
Returns: Returns:
models.BasicSubmission: Constructed submission object models.BasicSubmission: Constructed submission object
""" """
from tools import check_regex_match, RSLNamer # from tools import check_regex_match, RSLNamer
# convert submission type into model name # convert submission type into model name
query = info_dict['submission_type'].replace(" ", "") query = info_dict['submission_type'].replace(" ", "")
# Ensure an rsl plate number exists for the plate # Ensure an rsl plate number exists for the plate
@@ -127,7 +126,8 @@ def construct_submission_info(ctx:Settings, info_dict:dict) -> models.BasicSubmi
info_dict['rsl_plate_num'] = RSLNamer(ctx=ctx, instr=info_dict["rsl_plate_num"]).parsed_name info_dict['rsl_plate_num'] = RSLNamer(ctx=ctx, instr=info_dict["rsl_plate_num"]).parsed_name
# check database for existing object # check database for existing object
# instance = ctx['database_session'].query(models.BasicSubmission).filter(models.BasicSubmission.rsl_plate_num==info_dict['rsl_plate_num']).first() # instance = ctx['database_session'].query(models.BasicSubmission).filter(models.BasicSubmission.rsl_plate_num==info_dict['rsl_plate_num']).first()
instance = ctx.database_session.query(models.BasicSubmission).filter(models.BasicSubmission.rsl_plate_num==info_dict['rsl_plate_num']).first() # instance = ctx.database_session.query(models.BasicSubmission).filter(models.BasicSubmission.rsl_plate_num==info_dict['rsl_plate_num']).first()
instance = lookup_submission_by_rsl_num(ctx=ctx, rsl_num=info_dict['rsl_plate_num'])
# get model based on submission type converted above # get model based on submission type converted above
logger.debug(f"Looking at models for submission type: {query}") logger.debug(f"Looking at models for submission type: {query}")
model = getattr(models, query) model = getattr(models, query)
@@ -866,8 +866,6 @@ def hitpick_plate(submission:models.BasicSubmission, plate_number:int=0) -> list
plate_dicto = [] plate_dicto = []
for sample in submission.samples: for sample in submission.samples:
# have sample report back its info if it's positive, otherwise, None # have sample report back its info if it's positive, otherwise, None
method_list = [func for func in dir(sample) if callable(getattr(sample, func))]
logger.debug(f"Method list of sample: {method_list}")
samp = sample.to_hitpick() samp = sample.to_hitpick()
if samp == None: if samp == None:
continue continue
@@ -963,7 +961,6 @@ def lookup_last_used_reagenttype_lot(ctx:Settings, type_name:str) -> models.Reag
except AttributeError: except AttributeError:
return None return None
def check_kit_integrity(sub:BasicSubmission|KitType, reagenttypes:list|None=None) -> dict|None: def check_kit_integrity(sub:BasicSubmission|KitType, reagenttypes:list|None=None) -> dict|None:
""" """
Ensures all reagents expected in kit are listed in Submission Ensures all reagents expected in kit are listed in Submission

View File

@@ -18,7 +18,7 @@ class WWSample(Base):
id = Column(INTEGER, primary_key=True) #: primary key id = Column(INTEGER, primary_key=True) #: primary key
ww_processing_num = Column(String(64)) #: wastewater processing number ww_processing_num = Column(String(64)) #: wastewater processing number
ww_sample_full_id = Column(String(64), nullable=False) ww_sample_full_id = Column(String(64), nullable=False, unique=True)
rsl_number = Column(String(64)) #: rsl plate identification number rsl_number = Column(String(64)) #: rsl plate identification number
rsl_plate = relationship("Wastewater", back_populates="samples") #: relationship to parent plate rsl_plate = relationship("Wastewater", back_populates="samples") #: relationship to parent plate
rsl_plate_id = Column(INTEGER, ForeignKey("_submissions.id", ondelete="SET NULL", name="fk_WWS_submission_id")) rsl_plate_id = Column(INTEGER, ForeignKey("_submissions.id", ondelete="SET NULL", name="fk_WWS_submission_id"))
@@ -111,7 +111,7 @@ class BCSample(Base):
id = Column(INTEGER, primary_key=True) #: primary key id = Column(INTEGER, primary_key=True) #: primary key
well_number = Column(String(8)) #: location on parent plate well_number = Column(String(8)) #: location on parent plate
sample_id = Column(String(64), nullable=False) #: identification from submitter sample_id = Column(String(64), nullable=False, unique=True) #: identification from submitter
organism = Column(String(64)) #: bacterial specimen organism = Column(String(64)) #: bacterial specimen
concentration = Column(String(16)) #: concentration = Column(String(16)) #:
rsl_plate_id = Column(INTEGER, ForeignKey("_submissions.id", ondelete="SET NULL", name="fk_BCS_sample_id")) #: id of parent plate rsl_plate_id = Column(INTEGER, ForeignKey("_submissions.id", ondelete="SET NULL", name="fk_BCS_sample_id")) #: id of parent plate

View File

@@ -314,23 +314,29 @@ class SheetParser(object):
continue continue
logger.debug(f"massaged sample list for {self.sub['rsl_plate_num']}: {pprint.pprint(return_list)}") logger.debug(f"massaged sample list for {self.sub['rsl_plate_num']}: {pprint.pprint(return_list)}")
return return_list return return_list
submission_info = self.xl.parse("cDNA", dtype=object) submission_info = self.xl.parse("First Strand", dtype=object)
biomek_info = self.xl.parse("ArticV4_1 Biomek", dtype=object) biomek_info = self.xl.parse("ArticV4 Biomek", dtype=object)
# Reminder that the iloc uses row, column ordering sub_reagent_range = submission_info.iloc[56:, 1:4].dropna(how='all')
# sub_reagent_range = submission_info.iloc[56:, 1:4].dropna(how='all') biomek_reagent_range = biomek_info.iloc[60:, 0:3].dropna(how='all')
sub_reagent_range = submission_info.iloc[7:15, 5:9].dropna(how='all') # submission_info = self.xl.parse("cDNA", dtype=object)
biomek_reagent_range = biomek_info.iloc[62:, 0:3].dropna(how='all') # biomek_info = self.xl.parse("ArticV4_1 Biomek", dtype=object)
# # Reminder that the iloc uses row, column ordering
# # sub_reagent_range = submission_info.iloc[56:, 1:4].dropna(how='all')
# sub_reagent_range = submission_info.iloc[7:15, 5:9].dropna(how='all')
# biomek_reagent_range = biomek_info.iloc[62:, 0:3].dropna(how='all')
self.sub['submitter_plate_num'] = "" self.sub['submitter_plate_num'] = ""
self.sub['rsl_plate_num'] = RSLNamer(ctx=self.ctx, instr=self.filepath.__str__()).parsed_name self.sub['rsl_plate_num'] = RSLNamer(ctx=self.ctx, instr=self.filepath.__str__()).parsed_name
self.sub['submitted_date'] = biomek_info.iloc[1][1] self.sub['submitted_date'] = biomek_info.iloc[1][1]
self.sub['submitting_lab'] = "Enterics Wastewater Genomics" self.sub['submitting_lab'] = "Enterics Wastewater Genomics"
self.sub['sample_count'] = submission_info.iloc[34][6] self.sub['sample_count'] = submission_info.iloc[4][6]
# self.sub['sample_count'] = submission_info.iloc[34][6]
self.sub['extraction_kit'] = "ArticV4.1" self.sub['extraction_kit'] = "ArticV4.1"
self.sub['technician'] = f"MM: {biomek_info.iloc[2][1]}, Bio: {biomek_info.iloc[3][1]}" self.sub['technician'] = f"MM: {biomek_info.iloc[2][1]}, Bio: {biomek_info.iloc[3][1]}"
self.sub['reagents'] = [] self.sub['reagents'] = []
parse_reagents(sub_reagent_range) parse_reagents(sub_reagent_range)
parse_reagents(biomek_reagent_range) parse_reagents(biomek_reagent_range)
samples = massage_samples(biomek_info.iloc[25:33, 0:]) samples = massage_samples(biomek_info.iloc[22:31, 0:])
# samples = massage_samples(biomek_info.iloc[25:33, 0:])
sample_parser = SampleParser(self.ctx, pd.DataFrame.from_records(samples)) sample_parser = SampleParser(self.ctx, pd.DataFrame.from_records(samples))
sample_parse = getattr(sample_parser, f"parse_{self.sub['submission_type']['value'].lower()}_samples") sample_parse = getattr(sample_parser, f"parse_{self.sub['submission_type']['value'].lower()}_samples")
self.sample_result, self.sub['samples'] = sample_parse() self.sample_result, self.sub['samples'] = sample_parse()

View File

@@ -93,7 +93,6 @@ class PydSubmission(BaseModel, extra=Extra.allow):
else: else:
return value return value
else: else:
# logger.debug(f"Pydant values:{type(values)}\n{values}")
return dict(value=RSLNamer(ctx=values.data['ctx'], instr=values.data['filepath'].__str__()).parsed_name, parsed=False) return dict(value=RSLNamer(ctx=values.data['ctx'], instr=values.data['filepath'].__str__()).parsed_name, parsed=False)
@field_validator("technician", mode="before") @field_validator("technician", mode="before")
@@ -115,11 +114,6 @@ class PydSubmission(BaseModel, extra=Extra.allow):
return_val = [] return_val = []
for reagent in value: for reagent in value:
logger.debug(f"Pydantic reagent: {reagent}") logger.debug(f"Pydantic reagent: {reagent}")
# match reagent.type.lower():
# case 'atcc':
# continue
# case _:
# return_val.append(reagent)
if reagent.type == None: if reagent.type == None:
continue continue
else: else:
@@ -132,7 +126,6 @@ class PydSubmission(BaseModel, extra=Extra.allow):
if check_not_nan(value): if check_not_nan(value):
return int(value) return int(value)
else: else:
# raise ValueError(f"{value} could not be used to create an integer.")
return convert_nans_to_nones(value) return convert_nans_to_nones(value)
@field_validator("extraction_kit", mode='before') @field_validator("extraction_kit", mode='before')
@@ -142,14 +135,12 @@ class PydSubmission(BaseModel, extra=Extra.allow):
if check_not_nan(value): if check_not_nan(value):
return dict(value=value, parsed=True) return dict(value=value, parsed=True)
else: else:
# logger.debug(values.data)
dlg = KitSelector(ctx=values.data['ctx'], title="Kit Needed", message="At minimum a kit is needed. Please select one.") dlg = KitSelector(ctx=values.data['ctx'], title="Kit Needed", message="At minimum a kit is needed. Please select one.")
if dlg.exec(): if dlg.exec():
return dict(value=dlg.getValues(), parsed=False) return dict(value=dlg.getValues(), parsed=False)
else: else:
raise ValueError("Extraction kit needed.") raise ValueError("Extraction kit needed.")
@field_validator("submission_type", mode='before') @field_validator("submission_type", mode='before')
@classmethod @classmethod
def make_submission_type(cls, value, values): def make_submission_type(cls, value, values):
@@ -161,14 +152,3 @@ class PydSubmission(BaseModel, extra=Extra.allow):
return dict(value=value.title(), parsed=False) return dict(value=value.title(), parsed=False)
else: else:
return dict(value=RSLNamer(ctx=values.data['ctx'], instr=values.data['filepath'].__str__()).submission_type.title(), parsed=False) return dict(value=RSLNamer(ctx=values.data['ctx'], instr=values.data['filepath'].__str__()).submission_type.title(), parsed=False)
# @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

View File

@@ -1,5 +1,5 @@
''' '''
Operations for all user interactions. Constructs main application.
''' '''
import sys import sys
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
@@ -31,7 +31,6 @@ class App(QMainWindow):
self.ctx = ctx self.ctx = ctx
# indicate version and connected database in title bar # indicate version and connected database in title bar
try: try:
# self.title = f"Submissions App (v{ctx['package'].__version__}) - {ctx['database']}"
self.title = f"Submissions App (v{ctx.package.__version__}) - {ctx.database_path}" self.title = f"Submissions App (v{ctx.package.__version__}) - {ctx.database_path}"
except (AttributeError, KeyError): except (AttributeError, KeyError):
self.title = f"Submissions App" self.title = f"Submissions App"

View File

@@ -23,7 +23,10 @@ def select_open_file(obj:QMainWindow, file_extension:str) -> Path:
Path: Path of file to be opened Path: Path of file to be opened
""" """
# home_dir = str(Path(obj.ctx["directory_path"])) # home_dir = str(Path(obj.ctx["directory_path"]))
home_dir = str(Path(obj.ctx.directory_path)) try:
home_dir = Path(obj.ctx.directory_path).resolve().__str__()
except FileNotFoundError:
home_dir = Path.home().resolve().__str__()
fname = Path(QFileDialog.getOpenFileName(obj, 'Open file', home_dir, filter = f"{file_extension}(*.{file_extension})")[0]) fname = Path(QFileDialog.getOpenFileName(obj, 'Open file', home_dir, filter = f"{file_extension}(*.{file_extension})")[0])
return fname return fname
@@ -43,7 +46,7 @@ def select_save_file(obj:QMainWindow, default_name:str, extension:str) -> Path:
# home_dir = Path(obj.ctx["directory_path"]).joinpath(default_name).resolve().__str__() # home_dir = Path(obj.ctx["directory_path"]).joinpath(default_name).resolve().__str__()
home_dir = Path(obj.ctx.directory_path).joinpath(default_name).resolve().__str__() home_dir = Path(obj.ctx.directory_path).joinpath(default_name).resolve().__str__()
except FileNotFoundError: except FileNotFoundError:
home_dir = Path.home().resolve().__str__() home_dir = Path.home().joinpath(default_name).resolve().__str__()
fname = Path(QFileDialog.getSaveFileName(obj, "Save File", home_dir, filter = f"{extension}(*.{extension})")[0]) fname = Path(QFileDialog.getSaveFileName(obj, "Save File", home_dir, filter = f"{extension}(*.{extension})")[0])
return fname return fname

View File

@@ -230,10 +230,9 @@ class ControlsDatePicker(QWidget):
super().__init__() super().__init__()
self.start_date = QDateEdit(calendarPopup=True) self.start_date = QDateEdit(calendarPopup=True)
# start date is three month prior to end date by default # start date is two months prior to end date by default
# NOTE: 2 month, but the variable name is the same cause I'm lazy twomonthsago = QDate.currentDate().addDays(-60)
threemonthsago = QDate.currentDate().addDays(-60) self.start_date.setDate(twomonthsago)
self.start_date.setDate(threemonthsago)
self.end_date = QDateEdit(calendarPopup=True) self.end_date = QDateEdit(calendarPopup=True)
self.end_date.setDate(QDate.currentDate()) self.end_date.setDate(QDate.currentDate())
self.layout = QHBoxLayout() self.layout = QHBoxLayout()
@@ -299,4 +298,3 @@ class ImportReagent(QComboBox):
logger.debug(f"New relevant reagents: {relevant_reagents}") logger.debug(f"New relevant reagents: {relevant_reagents}")
self.setObjectName(f"lot_{reagent.type}") self.setObjectName(f"lot_{reagent.type}")
self.addItems(relevant_reagents) self.addItems(relevant_reagents)

View File

@@ -338,7 +338,8 @@ class SubmissionDetails(QDialog):
# with open("test.html", "w") as f: # with open("test.html", "w") as f:
# f.write(html) # f.write(html)
try: try:
home_dir = Path(self.ctx["directory_path"]).joinpath(f"Submission_Details_{self.base_dict['Plate Number']}.pdf").resolve().__str__() # home_dir = Path(self.ctx["directory_path"]).joinpath(f"Submission_Details_{self.base_dict['Plate Number']}.pdf").resolve().__str__()
home_dir = Path(self.ctx.directory_path).joinpath(f"Submission_Details_{self.base_dict['Plate Number']}.pdf").resolve().__str__()
except FileNotFoundError: except FileNotFoundError:
home_dir = Path.home().resolve().__str__() home_dir = Path.home().resolve().__str__()
fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".pdf")[0]) fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".pdf")[0])

View File

@@ -136,6 +136,7 @@ def check_if_app(ctx:dict=None) -> bool:
def retrieve_rsl_number(in_str:str) -> Tuple[str, str]: def retrieve_rsl_number(in_str:str) -> Tuple[str, str]:
""" """
Uses regex to retrieve the plate number and submission type from an input string Uses regex to retrieve the plate number and submission type from an input string
DEPRECIATED. REPLACED BY RSLNamer.parsed_name
Args: Args:
in_str (str): string to be parsed in_str (str): string to be parsed
@@ -354,7 +355,7 @@ class Settings(BaseSettings):
super_users: list super_users: list
power_users: list power_users: list
rerun_regex: str rerun_regex: str
submission_types: dict submission_types: dict|None = None
database_session: Session|None = None database_session: Session|None = None
package: Any|None = None package: Any|None = None