prior to db rebuild
This commit is contained in:
@@ -55,8 +55,8 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
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:///L:\Robotics Laboratory Support\Submissions\submissions.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
|
||||
|
||||
|
||||
|
||||
38
alembic/versions/78178df0286a_making_sample_ids_unique.py
Normal file
38
alembic/versions/78178df0286a_making_sample_ids_unique.py
Normal 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 ###
|
||||
@@ -18,7 +18,7 @@ from getpass import getuser
|
||||
import numpy as np
|
||||
import yaml
|
||||
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:
|
||||
None|dict : object that indicates issue raised for reporting in gui
|
||||
"""
|
||||
from tools import RSLNamer
|
||||
logger.debug(f"Hello from store_submission")
|
||||
# Add all samples to sample table
|
||||
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}")
|
||||
# Suuuuuper hacky way to be sure that the artic doesn't overwrite the ww plate in a ww sample
|
||||
# need something more elegant
|
||||
if "_artic" not in typer.submission_type:
|
||||
if "_artic" not in typer.submission_type.lower():
|
||||
sample.rsl_plate = base_submission
|
||||
else:
|
||||
sample.artic_rsl_plate = base_submission
|
||||
@@ -114,7 +113,7 @@ def construct_submission_info(ctx:Settings, info_dict:dict) -> models.BasicSubmi
|
||||
Returns:
|
||||
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
|
||||
query = info_dict['submission_type'].replace(" ", "")
|
||||
# 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
|
||||
# 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 = lookup_submission_by_rsl_num(ctx=ctx, rsl_num=info_dict['rsl_plate_num'])
|
||||
# get model based on submission type converted above
|
||||
logger.debug(f"Looking at models for submission type: {query}")
|
||||
model = getattr(models, query)
|
||||
@@ -866,8 +866,6 @@ def hitpick_plate(submission:models.BasicSubmission, plate_number:int=0) -> list
|
||||
plate_dicto = []
|
||||
for sample in submission.samples:
|
||||
# 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()
|
||||
if samp == None:
|
||||
continue
|
||||
@@ -963,7 +961,6 @@ def lookup_last_used_reagenttype_lot(ctx:Settings, type_name:str) -> models.Reag
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
def check_kit_integrity(sub:BasicSubmission|KitType, reagenttypes:list|None=None) -> dict|None:
|
||||
"""
|
||||
Ensures all reagents expected in kit are listed in Submission
|
||||
|
||||
@@ -18,7 +18,7 @@ class WWSample(Base):
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
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_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"))
|
||||
@@ -111,7 +111,7 @@ class BCSample(Base):
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
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
|
||||
concentration = Column(String(16)) #:
|
||||
rsl_plate_id = Column(INTEGER, ForeignKey("_submissions.id", ondelete="SET NULL", name="fk_BCS_sample_id")) #: id of parent plate
|
||||
|
||||
@@ -314,23 +314,29 @@ class SheetParser(object):
|
||||
continue
|
||||
logger.debug(f"massaged sample list for {self.sub['rsl_plate_num']}: {pprint.pprint(return_list)}")
|
||||
return return_list
|
||||
submission_info = self.xl.parse("cDNA", dtype=object)
|
||||
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')
|
||||
submission_info = self.xl.parse("First Strand", dtype=object)
|
||||
biomek_info = self.xl.parse("ArticV4 Biomek", dtype=object)
|
||||
sub_reagent_range = submission_info.iloc[56:, 1:4].dropna(how='all')
|
||||
biomek_reagent_range = biomek_info.iloc[60:, 0:3].dropna(how='all')
|
||||
# submission_info = self.xl.parse("cDNA", dtype=object)
|
||||
# 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['rsl_plate_num'] = RSLNamer(ctx=self.ctx, instr=self.filepath.__str__()).parsed_name
|
||||
self.sub['submitted_date'] = biomek_info.iloc[1][1]
|
||||
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['technician'] = f"MM: {biomek_info.iloc[2][1]}, Bio: {biomek_info.iloc[3][1]}"
|
||||
self.sub['reagents'] = []
|
||||
parse_reagents(sub_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_parse = getattr(sample_parser, f"parse_{self.sub['submission_type']['value'].lower()}_samples")
|
||||
self.sample_result, self.sub['samples'] = sample_parse()
|
||||
|
||||
@@ -93,7 +93,6 @@ class PydSubmission(BaseModel, extra=Extra.allow):
|
||||
else:
|
||||
return value
|
||||
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)
|
||||
|
||||
@field_validator("technician", mode="before")
|
||||
@@ -115,11 +114,6 @@ class PydSubmission(BaseModel, extra=Extra.allow):
|
||||
return_val = []
|
||||
for reagent in value:
|
||||
logger.debug(f"Pydantic reagent: {reagent}")
|
||||
# match reagent.type.lower():
|
||||
# case 'atcc':
|
||||
# continue
|
||||
# case _:
|
||||
# return_val.append(reagent)
|
||||
if reagent.type == None:
|
||||
continue
|
||||
else:
|
||||
@@ -132,7 +126,6 @@ class PydSubmission(BaseModel, extra=Extra.allow):
|
||||
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')
|
||||
@@ -142,13 +135,11 @@ class PydSubmission(BaseModel, extra=Extra.allow):
|
||||
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.")
|
||||
|
||||
|
||||
@field_validator("submission_type", mode='before')
|
||||
@classmethod
|
||||
@@ -161,14 +152,3 @@ class PydSubmission(BaseModel, extra=Extra.allow):
|
||||
return dict(value=value.title(), parsed=False)
|
||||
else:
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'''
|
||||
Operations for all user interactions.
|
||||
Constructs main application.
|
||||
'''
|
||||
import sys
|
||||
from PyQt6.QtWidgets import (
|
||||
@@ -31,7 +31,6 @@ class App(QMainWindow):
|
||||
self.ctx = ctx
|
||||
# indicate version and connected database in title bar
|
||||
try:
|
||||
# self.title = f"Submissions App (v{ctx['package'].__version__}) - {ctx['database']}"
|
||||
self.title = f"Submissions App (v{ctx.package.__version__}) - {ctx.database_path}"
|
||||
except (AttributeError, KeyError):
|
||||
self.title = f"Submissions App"
|
||||
|
||||
@@ -23,7 +23,10 @@ def select_open_file(obj:QMainWindow, file_extension:str) -> Path:
|
||||
Path: Path of file to be opened
|
||||
"""
|
||||
# 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])
|
||||
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__()
|
||||
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])
|
||||
return fname
|
||||
|
||||
|
||||
@@ -230,10 +230,9 @@ class ControlsDatePicker(QWidget):
|
||||
super().__init__()
|
||||
|
||||
self.start_date = QDateEdit(calendarPopup=True)
|
||||
# start date is three month prior to end date by default
|
||||
# NOTE: 2 month, but the variable name is the same cause I'm lazy
|
||||
threemonthsago = QDate.currentDate().addDays(-60)
|
||||
self.start_date.setDate(threemonthsago)
|
||||
# start date is two months prior to end date by default
|
||||
twomonthsago = QDate.currentDate().addDays(-60)
|
||||
self.start_date.setDate(twomonthsago)
|
||||
self.end_date = QDateEdit(calendarPopup=True)
|
||||
self.end_date.setDate(QDate.currentDate())
|
||||
self.layout = QHBoxLayout()
|
||||
@@ -299,4 +298,3 @@ class ImportReagent(QComboBox):
|
||||
logger.debug(f"New relevant reagents: {relevant_reagents}")
|
||||
self.setObjectName(f"lot_{reagent.type}")
|
||||
self.addItems(relevant_reagents)
|
||||
|
||||
|
||||
@@ -338,7 +338,8 @@ class SubmissionDetails(QDialog):
|
||||
# with open("test.html", "w") as f:
|
||||
# f.write(html)
|
||||
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:
|
||||
home_dir = Path.home().resolve().__str__()
|
||||
fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".pdf")[0])
|
||||
|
||||
@@ -136,7 +136,8 @@ def check_if_app(ctx:dict=None) -> bool:
|
||||
def retrieve_rsl_number(in_str:str) -> Tuple[str, str]:
|
||||
"""
|
||||
Uses regex to retrieve the plate number and submission type from an input string
|
||||
|
||||
DEPRECIATED. REPLACED BY RSLNamer.parsed_name
|
||||
|
||||
Args:
|
||||
in_str (str): string to be parsed
|
||||
|
||||
@@ -354,7 +355,7 @@ class Settings(BaseSettings):
|
||||
super_users: list
|
||||
power_users: list
|
||||
rerun_regex: str
|
||||
submission_types: dict
|
||||
submission_types: dict|None = None
|
||||
database_session: Session|None = None
|
||||
package: Any|None = None
|
||||
|
||||
@@ -609,4 +610,4 @@ def jinja_template_loading():
|
||||
# jinja template loading
|
||||
loader = FileSystemLoader(loader_path)
|
||||
env = Environment(loader=loader)
|
||||
return env
|
||||
return env
|
||||
|
||||
Reference in New Issue
Block a user