From bbb65d3fe6459c0de4eee6d34de4d406fdf6cce1 Mon Sep 17 00:00:00 2001 From: Landon Wark Date: Thu, 26 Jan 2023 14:54:32 -0600 Subject: [PATCH] Added in ability to overwrite submissions. --- alembic.ini | 2 +- ...mmit.py => 8753ed70f148_initial_commit.py} | 36 ++++---- src/submissions/backend/db/__init__.py | 53 +++++++++--- src/submissions/backend/db/models/controls.py | 3 +- .../backend/db/models/submissions.py | 7 +- src/submissions/backend/excel/parser.py | 82 +++++++++++-------- src/submissions/configure/__init__.py | 28 ++----- src/submissions/frontend/__init__.py | 14 +++- .../frontend/custom_widgets/__init__.py | 19 +++++ 9 files changed, 151 insertions(+), 93 deletions(-) rename alembic/versions/{03da9270e51f_initial_commit.py => 8753ed70f148_initial_commit.py} (96%) diff --git a/alembic.ini b/alembic.ini index 9dfe5a6..c3d6540 100644 --- a/alembic.ini +++ b/alembic.ini @@ -55,7 +55,7 @@ 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:///submissions.db +sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db [post_write_hooks] diff --git a/alembic/versions/03da9270e51f_initial_commit.py b/alembic/versions/8753ed70f148_initial_commit.py similarity index 96% rename from alembic/versions/03da9270e51f_initial_commit.py rename to alembic/versions/8753ed70f148_initial_commit.py index 9d50b12..0f37ba1 100644 --- a/alembic/versions/03da9270e51f_initial_commit.py +++ b/alembic/versions/8753ed70f148_initial_commit.py @@ -1,8 +1,8 @@ """initial commit -Revision ID: 03da9270e51f +Revision ID: 8753ed70f148 Revises: -Create Date: 2023-01-19 09:01:03.022482 +Create Date: 2023-01-26 12:48:42.340619 """ from alembic import op @@ -10,7 +10,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '03da9270e51f' +revision = '8753ed70f148' down_revision = None branch_labels = None depends_on = None @@ -50,18 +50,6 @@ def upgrade() -> None: sa.ForeignKeyConstraint(['kit_id'], ['_kits.id'], name='fk_RT_kits_id', ondelete='SET NULL', use_alter=True), sa.PrimaryKeyConstraint('id') ) - op.create_table('_control_samples', - sa.Column('id', sa.INTEGER(), nullable=False), - sa.Column('parent_id', sa.String(), nullable=True), - sa.Column('name', sa.String(length=255), nullable=True), - sa.Column('submitted_date', sa.TIMESTAMP(), nullable=True), - sa.Column('contains', sa.JSON(), nullable=True), - sa.Column('matches', sa.JSON(), nullable=True), - sa.Column('kraken', sa.JSON(), nullable=True), - sa.ForeignKeyConstraint(['parent_id'], ['_control_types.id'], name='fk_control_parent_id'), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('name') - ) op.create_table('_organizations', sa.Column('id', sa.INTEGER(), nullable=False), sa.Column('name', sa.String(length=64), nullable=True), @@ -102,8 +90,6 @@ def upgrade() -> None: sa.Column('submission_type', sa.String(length=32), nullable=True), sa.Column('technician', sa.String(length=64), nullable=True), sa.Column('reagents_id', sa.String(), nullable=True), - sa.Column('control_id', sa.INTEGER(), nullable=True), - sa.ForeignKeyConstraint(['control_id'], ['_control_samples.id'], name='fk_BC_control_id', ondelete='SET NULL'), sa.ForeignKeyConstraint(['extraction_kit_id'], ['_kits.id'], name='fk_BS_extkit_id', ondelete='SET NULL'), sa.ForeignKeyConstraint(['reagents_id'], ['_reagents.id'], name='fk_BS_reagents_id', ondelete='SET NULL'), sa.ForeignKeyConstraint(['submitting_lab_id'], ['_organizations.id'], name='fk_BS_sublab_id', ondelete='SET NULL'), @@ -121,6 +107,20 @@ def upgrade() -> None: sa.ForeignKeyConstraint(['rsl_plate_id'], ['_submissions.id'], name='fk_BCS_sample_id', ondelete='SET NULL'), sa.PrimaryKeyConstraint('id') ) + op.create_table('_control_samples', + sa.Column('id', sa.INTEGER(), nullable=False), + sa.Column('parent_id', sa.String(), nullable=True), + sa.Column('name', sa.String(length=255), nullable=True), + sa.Column('submitted_date', sa.TIMESTAMP(), nullable=True), + sa.Column('contains', sa.JSON(), nullable=True), + sa.Column('matches', sa.JSON(), nullable=True), + sa.Column('kraken', sa.JSON(), nullable=True), + sa.Column('submission_id', sa.INTEGER(), nullable=True), + sa.ForeignKeyConstraint(['parent_id'], ['_control_types.id'], name='fk_control_parent_id'), + sa.ForeignKeyConstraint(['submission_id'], ['_submissions.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) op.create_table('_reagents_submissions', sa.Column('reagent_id', sa.INTEGER(), nullable=True), sa.Column('submission_id', sa.INTEGER(), nullable=True), @@ -153,13 +153,13 @@ def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.drop_table('_ww_samples') op.drop_table('_reagents_submissions') + op.drop_table('_control_samples') op.drop_table('_bc_samples') op.drop_table('_submissions') op.drop_table('_orgs_contacts') op.drop_table('_reagentstypes_kittypes') op.drop_table('_reagents') op.drop_table('_organizations') - op.drop_table('_control_samples') op.drop_table('_reagent_types') op.drop_table('_kits') op.drop_table('_control_types') diff --git a/src/submissions/backend/db/__init__.py b/src/submissions/backend/db/__init__.py index d77c909..5e10b91 100644 --- a/src/submissions/backend/db/__init__.py +++ b/src/submissions/backend/db/__init__.py @@ -1,7 +1,9 @@ from . import models import pandas as pd -# from sqlite3 import IntegrityError -from sqlalchemy.exc import IntegrityError +import sqlalchemy.exc +import sqlite3 +# from sqlalchemy.exc import IntegrityError, OperationalError +# from sqlite3 import IntegrityError, OperationalError import logging from datetime import date, datetime from sqlalchemy import and_ @@ -19,48 +21,69 @@ def get_kits_by_use( ctx:dict, kittype_str:str|None) -> list: def store_submission(ctx:dict, base_submission:models.BasicSubmission) -> None: + logger.debug(f"Hello from store_submission") for sample in base_submission.samples: sample.rsl_plate = base_submission + logger.debug(f"Attempting to add sample: {sample.to_string()}") try: ctx['database_session'].add(sample) - except IntegrityError: + except (sqlite3.IntegrityError, sqlalchemy.exc.IntegrityError) as e: + logger.debug(f"Hit an integrity error : {e}") continue ctx['database_session'].add(base_submission) + logger.debug(f"Attempting to add submission: {base_submission.rsl_plate_num}") try: ctx['database_session'].commit() - except IntegrityError: + except (sqlite3.IntegrityError, sqlalchemy.exc.IntegrityError) as e: + logger.debug(f"Hit an integrity error : {e}") ctx['database_session'].rollback() return {"message":"This plate number already exists, so we can't add it."} + except (sqlite3.OperationalError, sqlalchemy.exc.IntegrityError) as e: + logger.debug(f"Hit an operational error: {e}") + ctx['database_session'].rollback() + return {"message":"The database is locked for editing."} return None def store_reagent(ctx:dict, reagent:models.Reagent) -> None: logger.debug(reagent.__dict__) ctx['database_session'].add(reagent) - ctx['database_session'].commit() + try: + ctx['database_session'].commit() + except OperationalError: + return {"message":"The database is locked for editing."} def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmission: query = info_dict['submission_type'].replace(" ", "") + instance = ctx['database_session'].query(models.BasicSubmission).filter(models.BasicSubmission.rsl_plate_num==info_dict['rsl_plate_num']).first() + msg = "This submission already exists.\nWould you like to overwrite?" model = getattr(models, query) info_dict['submission_type'] = info_dict['submission_type'].replace(" ", "_").lower() - instance = model() + if instance == None: + instance = model() + msg = None for item in info_dict: logger.debug(f"Setting {item} to {info_dict[item]}") match item: case "extraction_kit": q_str = info_dict[item] logger.debug(f"Looking up kit {q_str}") - field_value = lookup_kittype_by_name(ctx=ctx, name=q_str) + try: + field_value = lookup_kittype_by_name(ctx=ctx, name=q_str) + except (sqlite3.IntegrityError, sqlalchemy.exc.IntegrityError) as e: + logger.error(f"Hit an integrity error: {e}") logger.debug(f"Got {field_value} for kit {q_str}") case "submitting_lab": q_str = info_dict[item].replace(" ", "_").lower() - logger.debug(f"looking up organization: {q_str}") + logger.debug(f"Looking up organization: {q_str}") field_value = lookup_org_by_name(ctx=ctx, name=q_str) logger.debug(f"Got {field_value} for organization {q_str}") case "submitter_plate_num": # Because of unique constraint, the submitter plate number cannot be None, so... - if info_dict[item] == None: + logger.debug(f"Submitter plate id: {info_dict[item]}") + if info_dict[item] == None or info_dict[item] == "None": + logger.debug(f"Got None as a submitter plate number, inserting random string to preserve database unique constraint.") info_dict[item] = uuid.uuid4().hex.upper() field_value = info_dict[item] # case "samples": @@ -75,7 +98,9 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio logger.debug(f"Could not set attribute: {item} to {info_dict[item]}") continue # logger.debug(instance.__dict__) - return instance + logger.debug(f"Constructed instance: {instance.to_string()}") + logger.debug(msg) + return instance, {'message':msg} # looked_up = [] # for reagent in reagents: # my_reagent = lookup_reagent(reagent) @@ -129,7 +154,7 @@ def lookup_kittype_by_use(ctx:dict, used_by:str) -> list[models.KitType]: def lookup_kittype_by_name(ctx:dict, name:str) -> models.KitType: logger.debug(f"Querying kittype: {name}") return ctx['database_session'].query(models.KitType).filter(models.KitType.name==name).first() - + def lookup_regent_by_type_name(ctx:dict, type_name:str) -> list[models.ReagentType]: # return [item for item in ctx['database_session'].query(models.Reagent).join(models.Reagent.type, aliased=True).filter(models.ReagentType.name==type_name).all()] @@ -235,7 +260,11 @@ def lookup_all_sample_types(ctx:dict) -> list[str]: def get_all_available_modes(ctx:dict) -> list[str]: rel = ctx['database_session'].query(models.Control).first() - cols = [item.name for item in list(rel.__table__.columns) if isinstance(item.type, JSON)] + try: + cols = [item.name for item in list(rel.__table__.columns) if isinstance(item.type, JSON)] + except AttributeError as e: + logger.debug(f"Failed to get available modes from db: {e}") + cols = [] return cols diff --git a/src/submissions/backend/db/models/controls.py b/src/submissions/backend/db/models/controls.py index f2b8569..b0522a9 100644 --- a/src/submissions/backend/db/models/controls.py +++ b/src/submissions/backend/db/models/controls.py @@ -32,5 +32,6 @@ class Control(Base): matches = Column(JSON) #: unstructured hashes in matches.tsv for each organism kraken = Column(JSON) #: unstructured output from kraken_report # UniqueConstraint('name', name='uq_control_name') - submissions = relationship("BacterialCulture", back_populates="control") + submission_id = Column(INTEGER, ForeignKey("_submissions.id")) + submission = relationship("BacterialCulture", back_populates="controls", foreign_keys=[submission_id]) diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index fb98298..fa16cb1 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -30,6 +30,9 @@ class BasicSubmission(Base): "with_polymorphic": "*", } + def to_string(self): + return f"{self.rsl_plate_num} - {self.submitter_plate_num}" + def to_dict(self): try: sub_lab = self.submitting_lab.name @@ -90,8 +93,8 @@ class BasicSubmission(Base): # Below are the custom submission class BacterialCulture(BasicSubmission): - control = relationship("Control", back_populates="submissions") #: A control sample added to submission - control_id = Column(INTEGER, ForeignKey("_control_samples.id", ondelete="SET NULL", name="fk_BC_control_id")) + # control_id = Column(INTEGER, ForeignKey("_control_samples.id", ondelete="SET NULL", name="fk_BC_control_id")) + controls = relationship("Control", back_populates="submission", uselist=True) #: A control sample added to submission samples = relationship("BCSample", back_populates="rsl_plate", uselist=True) # bc_sample_id = Column(INTEGER, ForeignKey("_bc_samples.id", ondelete="SET NULL", name="fk_BC_sample_id")) __mapper_args__ = {"polymorphic_identity": "bacterial_culture", "polymorphic_load": "inline"} diff --git a/src/submissions/backend/excel/parser.py b/src/submissions/backend/excel/parser.py index f7ebe39..cce49e2 100644 --- a/src/submissions/backend/excel/parser.py +++ b/src/submissions/backend/excel/parser.py @@ -45,13 +45,14 @@ class SheetParser(object): def _parse_generic(self, sheet_name:str): - submission_info = self.xl.parse(sheet_name=sheet_name) - self.sub['submitter_plate_num'] = submission_info.iloc[0][1] - self.sub['rsl_plate_num'] = str(submission_info.iloc[10][1]) - self.sub['submitted_date'] = submission_info.iloc[1][1].date()#.strftime("%Y-%m-%d") - self.sub['submitting_lab'] = submission_info.iloc[0][3] - self.sub['sample_count'] = str(submission_info.iloc[2][3]) - self.sub['extraction_kit'] = submission_info.iloc[3][3] + submission_info = self.xl.parse(sheet_name=sheet_name, dtype=object) + + self.sub['submitter_plate_num'] = submission_info.iloc[0][1] #if pd.isnull(submission_info.iloc[0][1]) else string_formatter(submission_info.iloc[0][1]) + self.sub['rsl_plate_num'] = submission_info.iloc[10][1] #if pd.isnull(submission_info.iloc[10][1]) else string_formatter(submission_info.iloc[10][1]) + self.sub['submitted_date'] = submission_info.iloc[1][1] #if pd.isnull(submission_info.iloc[1][1]) else submission_info.iloc[1][1].date()#.strftime("%Y-%m-%d") + self.sub['submitting_lab'] = submission_info.iloc[0][3] #if pd.isnull(submission_info.iloc[0][3]) else string_formatter(submission_info.iloc[0][3]) + self.sub['sample_count'] = submission_info.iloc[2][3] #if pd.isnull(submission_info.iloc[2][3]) else string_formatter(submission_info.iloc[2][3]) + self.sub['extraction_kit'] = submission_info.iloc[3][3] #if #pd.isnull(submission_info.iloc[3][3]) else string_formatter(submission_info.iloc[3][3]) return submission_info @@ -67,16 +68,17 @@ class SheetParser(object): tech = ", ".join(tech_reg.findall(tech)) self.sub['technician'] = tech # reagents - self.sub['lot_wash_1'] = submission_info.iloc[1][6] - self.sub['lot_wash_2'] = submission_info.iloc[2][6] - self.sub['lot_binding_buffer'] = submission_info.iloc[3][6] - self.sub['lot_magnetic_beads'] = submission_info.iloc[4][6] - self.sub['lot_lysis_buffer'] = submission_info.iloc[5][6] - self.sub['lot_elution_buffer'] = submission_info.iloc[6][6] - self.sub['lot_isopropanol'] = submission_info.iloc[9][6] - self.sub['lot_ethanol'] = submission_info.iloc[10][6] - self.sub['lot_positive_control'] = submission_info.iloc[103][1] - self.sub['lot_plate'] = submission_info.iloc[12][6] + + self.sub['lot_wash_1'] = submission_info.iloc[1][6] #if pd.isnull(submission_info.iloc[1][6]) else string_formatter(submission_info.iloc[1][6]) + self.sub['lot_wash_2'] = submission_info.iloc[2][6] #if pd.isnull(submission_info.iloc[2][6]) else string_formatter(submission_info.iloc[2][6]) + self.sub['lot_binding_buffer'] = submission_info.iloc[3][6] #if pd.isnull(submission_info.iloc[3][6]) else string_formatter(submission_info.iloc[3][6]) + self.sub['lot_magnetic_beads'] = submission_info.iloc[4][6] #if pd.isnull(submission_info.iloc[4][6]) else string_formatter(submission_info.iloc[4][6]) + self.sub['lot_lysis_buffer'] = submission_info.iloc[5][6] #if np.nan(submission_info.iloc[5][6]) else string_formatter(submission_info.iloc[5][6]) + self.sub['lot_elution_buffer'] = submission_info.iloc[6][6] #if pd.isnull(submission_info.iloc[6][6]) else string_formatter(submission_info.iloc[6][6]) + self.sub['lot_isopropanol'] = submission_info.iloc[9][6] #if pd.isnull(submission_info.iloc[9][6]) else string_formatter(submission_info.iloc[9][6]) + self.sub['lot_ethanol'] = submission_info.iloc[10][6] #if pd.isnull(submission_info.iloc[10][6]) else string_formatter(submission_info.iloc[10][6]) + self.sub['lot_positive_control'] = submission_info.iloc[103][1] #if pd.isnull(submission_info.iloc[103][1]) else string_formatter(submission_info.iloc[103][1]) + self.sub['lot_plate'] = submission_info.iloc[12][6] #if pd.isnull(submission_info.iloc[12][6]) else string_formatter(submission_info.iloc[12][6]) sample_parser = SampleParser(submission_info.iloc[15:111]) sample_parse = getattr(sample_parser, f"parse_{self.sub['submission_type'].lower()}_samples") logger.debug(f"Parser result: {self.sub}") @@ -86,25 +88,26 @@ class SheetParser(object): def _parse_wastewater(self): # submission_info = self.xl.parse("WW Submissions (ENTER HERE)") submission_info = self._parse_generic("WW Submissions (ENTER HERE)") - enrichment_info = self.xl.parse("Enrichment Worksheet") - extraction_info = self.xl.parse("Extraction Worksheet") - qprc_info = self.xl.parse("qPCR Worksheet") + enrichment_info = self.xl.parse("Enrichment Worksheet", dtype=object) + extraction_info = self.xl.parse("Extraction Worksheet", dtype=object) + qprc_info = self.xl.parse("qPCR Worksheet", dtype=object) self.sub['technician'] = f"Enr: {enrichment_info.columns[2]}, Ext: {extraction_info.columns[2]}, PCR: {qprc_info.columns[2]}" # reagents - self.sub['lot_lysis_buffer'] = enrichment_info.iloc[0][14] - self.sub['lot_proteinase_K'] = enrichment_info.iloc[1][14] - self.sub['lot_magnetic_virus_particles'] = enrichment_info.iloc[2][14] - self.sub['lot_enrichment_reagent_1'] = enrichment_info.iloc[3][14] - self.sub['lot_binding_buffer'] = extraction_info.iloc[0][14] - self.sub['lot_magnetic_beads'] = extraction_info.iloc[1][14] - self.sub['lot_wash'] = extraction_info.iloc[2][14] - self.sub['lot_ethanol'] = extraction_info.iloc[3][14] - self.sub['lot_elution_buffer'] = extraction_info.iloc[4][14] - self.sub['lot_master_mix'] = qprc_info.iloc[0][14] - self.sub['lot_pre_mix_1'] = qprc_info.iloc[1][14] - self.sub['lot_pre_mix_2'] = qprc_info.iloc[2][14] - self.sub['lot_positive_control'] = qprc_info.iloc[3][14] - self.sub['lot_ddh2o'] = qprc_info.iloc[4][14] + logger.debug(qprc_info) + self.sub['lot_lysis_buffer'] = enrichment_info.iloc[0][14] #if pd.isnull(enrichment_info.iloc[0][14]) else string_formatter(enrichment_info.iloc[0][14]) + self.sub['lot_proteinase_K'] = enrichment_info.iloc[1][14] #if pd.isnull(enrichment_info.iloc[1][14]) else string_formatter(enrichment_info.iloc[1][14]) + self.sub['lot_magnetic_virus_particles'] = enrichment_info.iloc[2][14] #if pd.isnull(enrichment_info.iloc[2][14]) else string_formatter(enrichment_info.iloc[2][14]) + self.sub['lot_enrichment_reagent_1'] = enrichment_info.iloc[3][14] #if pd.isnull(enrichment_info.iloc[3][14]) else string_formatter(enrichment_info.iloc[3][14]) + self.sub['lot_binding_buffer'] = extraction_info.iloc[0][14] #if pd.isnull(extraction_info.iloc[0][14]) else string_formatter(extraction_info.iloc[0][14]) + self.sub['lot_magnetic_beads'] = extraction_info.iloc[1][14] #if pd.isnull(extraction_info.iloc[1][14]) else string_formatter(extraction_info.iloc[1][14]) + self.sub['lot_wash'] = extraction_info.iloc[2][14] #if pd.isnull(extraction_info.iloc[2][14]) else string_formatter(extraction_info.iloc[2][14]) + self.sub['lot_ethanol'] = extraction_info.iloc[3][14] #if pd.isnull(extraction_info.iloc[3][14]) else string_formatter(extraction_info.iloc[3][14]) + self.sub['lot_elution_buffer'] = extraction_info.iloc[4][14] #if pd.isnull(extraction_info.iloc[4][14]) else string_formatter(extraction_info.iloc[4][14]) + self.sub['lot_master_mix'] = qprc_info.iloc[0][14] #if pd.isnull(qprc_info.iloc[0][14]) else string_formatter(qprc_info.iloc[0][14]) + self.sub['lot_pre_mix_1'] = qprc_info.iloc[1][14] #if pd.isnull(qprc_info.iloc[1][14]) else string_formatter(qprc_info.iloc[1][14]) + self.sub['lot_pre_mix_2'] = qprc_info.iloc[2][14] #if pd.isnull(qprc_info.iloc[2][14]) else string_formatter(qprc_info.iloc[2][14]) + self.sub['lot_positive_control'] = qprc_info.iloc[3][14] #if pd.isnull(qprc_info.iloc[3][14]) else string_formatter(qprc_info.iloc[3][14]) + self.sub['lot_ddh2o'] = qprc_info.iloc[4][14] #if pd.isnull(qprc_info.iloc[4][14]) else string_formatter(qprc_info.iloc[4][14]) sample_parser = SampleParser(submission_info.iloc[16:40]) sample_parse = getattr(sample_parser, f"parse_{self.sub['submission_type'].lower()}_samples") self.sub['samples'] = sample_parse() @@ -164,4 +167,13 @@ class SampleParser(object): new.well_number = sample['Unnamed: 1'] new_list.append(new) return new_list - \ No newline at end of file + + +def string_formatter(input): + logger.debug(f"{input} : {type(input)}") + match input: + case int() | float() | np.float64: + return "{:0.0f}".format(input) + case _: + return input + \ No newline at end of file diff --git a/src/submissions/configure/__init__.py b/src/submissions/configure/__init__.py index 4873d5b..0ecfe37 100644 --- a/src/submissions/configure/__init__.py +++ b/src/submissions/configure/__init__.py @@ -73,15 +73,14 @@ def get_config(settings_path: str|None) -> dict: Returns: setting: dictionary of settings. """ - # with open("C:\\Users\\lwark\\Desktop\\packagedir.txt", "w") as f: - # f.write(package_dir.__str__()) def join(loader, node): seq = loader.construct_sequence(node) return ''.join([str(i) for i in seq]) ## register the tag handler yaml.add_constructor('!join', join) - # if user hasn't defined config path in cli args + logger.debug(f"Making directory: {CONFIGDIR.__str__()}") + # make directories try: CONFIGDIR.mkdir(parents=True) except FileExistsError: @@ -91,10 +90,9 @@ def get_config(settings_path: str|None) -> dict: LOGDIR.mkdir(parents=True) except FileExistsError: pass + # if user hasn't defined config path in cli args if settings_path == None: - # Check user .config/ozma directory - # if Path.exists(Path.joinpath(CONFIGDIR, "config.yml")): - # settings_path = Path.joinpath(CONFIGDIR, "config.yml") + # Check user .config/submissions directory if CONFIGDIR.joinpath("config.yml").exists(): settings_path = CONFIGDIR.joinpath("config.yml") # Check user .ozma directory @@ -116,7 +114,6 @@ def get_config(settings_path: str|None) -> dict: logger.error("No config.yml file found. Using empty dictionary.") return {} logger.debug(f"Using {settings_path} for config file.") - with open(settings_path, "r") as stream: try: settings = yaml.load(stream, Loader=yaml.Loader) @@ -182,22 +179,17 @@ def setup_logger(verbosity:int=3): # ch = StreamToLogger(logger=logger, log_level=verbosity) match verbosity: case 3: - # verb = logging.DEBUG ch.setLevel(logging.DEBUG) case 2: - # verb = logging.INFO ch.setLevel(logging.INFO) case 1: - # verb = logging.WARNING ch.setLevel(logging.WARNING) ch.name = "Stream" # create formatter and add it to the handlers - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + formatter = logging.Formatter('%(asctime)s - %(levelname)s - {%(pathname)s:%(lineno)d} - %(message)s') fh.setFormatter(formatter) ch.setFormatter(formatter) - # ch.setLevel(logging.ERROR) # add the handlers to the logger - logger.addHandler(fh) logger.addHandler(ch) def handle_exception(exc_type, exc_value, exc_traceback): @@ -206,18 +198,10 @@ def setup_logger(verbosity:int=3): return logger.critical("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) - sys.exit(f"Uncaught error: {exc_type}, {exc_traceback}, check logs.") + # sys.exit(f"Uncaught error: {exc_type}, {exc_traceback}, check logs.") sys.excepthook = handle_exception - # stderr_logger = logging.getLogger('STDERR') - # sys.stderr = logger return logger - # sl = StreamToLogger(stderr_logger, logging.ERROR) - # sys.stderr = sl - - - - # def set_logger_verbosity(verbosity): # """Does what it says. diff --git a/src/submissions/frontend/__init__.py b/src/submissions/frontend/__init__.py index 81acbf0..b5d4262 100644 --- a/src/submissions/frontend/__init__.py +++ b/src/submissions/frontend/__init__.py @@ -27,7 +27,7 @@ from backend.db import (construct_submission_info, lookup_reagent, ) from backend.excel.reports import make_report_xlsx import numpy -from frontend.custom_widgets import AddReagentQuestion, AddReagentForm, SubmissionsSheet, ReportDatePicker, KitAdder, ControlsDatePicker +from frontend.custom_widgets import AddReagentQuestion, AddReagentForm, SubmissionsSheet, ReportDatePicker, KitAdder, ControlsDatePicker, OverwriteSubQuestion import logging import difflib @@ -240,9 +240,16 @@ class App(QMainWindow): parsed_reagents.append(wanted_reagent) logger.debug(info) info['samples'] = self.samples - base_submission = construct_submission_info(ctx=self.ctx, info_dict=info) + base_submission, output = construct_submission_info(ctx=self.ctx, info_dict=info) + if output['message'] != None: + dlg = OverwriteSubQuestion(output['message'], base_submission.rsl_plate_num) + if dlg.exec(): + base_submission.reagents = [] + else: + return for reagent in parsed_reagents: base_submission.reagents.append(reagent) + logger.debug(f"Sending submission: {base_submission.rsl_plate_num} to database.") result = store_submission(ctx=self.ctx, base_submission=base_submission) if result != None: msg = QMessageBox() @@ -250,8 +257,11 @@ class App(QMainWindow): msg.setText("Error") msg.setInformativeText(result['message']) msg.setWindowTitle("Error") + msg.show() msg.exec() self.table_widget.sub_wid.setData() + for item in self.table_widget.formlayout.parentWidget().findChildren(QWidget): + item.setParent(None) def add_reagent(self, reagent_lot:str|None=None, reagent_type:str|None=None): diff --git a/src/submissions/frontend/custom_widgets/__init__.py b/src/submissions/frontend/custom_widgets/__init__.py index 72cbe39..2b2be59 100644 --- a/src/submissions/frontend/custom_widgets/__init__.py +++ b/src/submissions/frontend/custom_widgets/__init__.py @@ -44,6 +44,25 @@ class AddReagentQuestion(QDialog): self.setLayout(self.layout) +class OverwriteSubQuestion(QDialog): + def __init__(self, message:str, rsl_plate_num:str): + super().__init__() + + self.setWindowTitle(f"Overwrite {rsl_plate_num}?") + + QBtn = QDialogButtonBox.StandardButton.Yes | QDialogButtonBox.StandardButton.No + + self.buttonBox = QDialogButtonBox(QBtn) + self.buttonBox.accepted.connect(self.accept) + self.buttonBox.rejected.connect(self.reject) + + self.layout = QVBoxLayout() + message = QLabel(message) + self.layout.addWidget(message) + self.layout.addWidget(self.buttonBox) + self.setLayout(self.layout) + + class AddReagentForm(QDialog): def __init__(self, ctx:dict, reagent_lot:str|None, reagent_type:str|None): super().__init__()