Bug fixes.

This commit is contained in:
lwark
2024-12-18 13:35:04 -06:00
parent b1544da730
commit 5fd36308b2
10 changed files with 70 additions and 43 deletions

View File

@@ -1,6 +1,6 @@
import sys, os import sys, os
from tools import ctx, setup_logger, check_if_app from tools import ctx, setup_logger, check_if_app
from threading import Thread
# environment variable must be set to enable qtwebengine in network path # environment variable must be set to enable qtwebengine in network path
if check_if_app(): if check_if_app():
os.environ['QTWEBENGINE_DISABLE_SANDBOX'] = "1" os.environ['QTWEBENGINE_DISABLE_SANDBOX'] = "1"
@@ -23,12 +23,12 @@ def run_startup():
for script in startup_scripts: for script in startup_scripts:
try: try:
func = getattr(scripts, script) func = getattr(scripts, script)
# func = modules[script]
except AttributeError as e: except AttributeError as e:
logger.error(f"Couldn't run startup script {script} due to {e}") logger.error(f"Couldn't run startup script {script} due to {e}")
continue continue
logger.info(f"Running startup script: {func.__name__}") logger.info(f"Running startup script: {func.__name__}")
func.script(ctx) thread = Thread(target=func.script, args=(ctx, ))
thread.start()
def run_teardown(): def run_teardown():
@@ -45,7 +45,8 @@ def run_teardown():
logger.error(f"Couldn't run teardown script {script} due to {e}") logger.error(f"Couldn't run teardown script {script} due to {e}")
continue continue
logger.info(f"Running teardown script: {func.__name__}") logger.info(f"Running teardown script: {func.__name__}")
func.script(ctx) thread = Thread(target=func.script, args=(ctx,))
thread.start()
if __name__ == '__main__': if __name__ == '__main__':
run_startup() run_startup()

View File

@@ -269,7 +269,7 @@ from .controls import *
from .organizations import * from .organizations import *
from .kits import * from .kits import *
from .submissions import * from .submissions import *
from .audit import AuditLog from .audit import *
# NOTE: Add a creator to the submission for reagent association. Assigned here due to circular import constraints. # NOTE: Add a creator to the submission for reagent association. Assigned here due to circular import constraints.
# https://docs.sqlalchemy.org/en/20/orm/extensions/associationproxy.html#sqlalchemy.ext.associationproxy.association_proxy.params.creator # https://docs.sqlalchemy.org/en/20/orm/extensions/associationproxy.html#sqlalchemy.ext.associationproxy.association_proxy.params.creator

View File

@@ -332,7 +332,6 @@ class KitType(BaseClass):
return new_kit return new_kit
class ReagentRole(BaseClass): class ReagentRole(BaseClass):
""" """
Base of reagent type abstract Base of reagent type abstract

View File

@@ -1265,7 +1265,9 @@ class BasicSubmission(BaseClass, LogMixin):
logger.error(f"Couldn't save association with {equip} due to {e}") logger.error(f"Couldn't save association with {equip} due to {e}")
if equip.tips: if equip.tips:
for tips in equip.tips: for tips in equip.tips:
logger.debug(f"Attempting to add tips assoc: {tips} (pydantic)")
tassoc = tips.to_sql(submission=self) tassoc = tips.to_sql(submission=self)
logger.debug(f"Attempting to add tips assoc: {tips.__dict__} (sql)")
if tassoc not in self.submission_tips_associations: if tassoc not in self.submission_tips_associations:
tassoc.save() tassoc.save()
else: else:

View File

@@ -2,6 +2,6 @@
Contains pandas and openpyxl convenience functions for interacting with excel workbooks Contains pandas and openpyxl convenience functions for interacting with excel workbooks
''' '''
from .reports import *
from .parser import * from .parser import *
from .reports import *
from .writer import * from .writer import *

View File

@@ -4,20 +4,24 @@ from datetime import datetime
from tools import Settings from tools import Settings
from backend import BasicSample from backend import BasicSample
from backend.db import IridaControl, ControlType from backend.db import IridaControl, ControlType
from sqlalchemy.orm import Session
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
def script(ctx:Settings): def script(ctx: Settings):
""" """
Grabs Irida controls from secondary database. Grabs Irida controls from secondary database.
Args: Args:
ctx (Settings): Settings inherited from app. ctx (Settings): Settings inherited from app.
""" """
ct = ControlType.query(name="Irida Control") # NOTE: Because the main session will be busy in another thread, this requires a new session.
existing_controls = [item.name for item in IridaControl.query()] new_session = Session(ctx.database_session.get_bind())
# ct = ControlType.query(name="Irida Control")
ct = new_session.query(ControlType).filter(ControlType.name == "Irida Control").first()
# existing_controls = [item.name for item in IridaControl.query()]
existing_controls = [item.name for item in new_session.query(IridaControl)]
prm_list = ", ".join([f"'{thing}'" for thing in existing_controls]) prm_list = ", ".join([f"'{thing}'" for thing in existing_controls])
ctrl_db_path = ctx.directory_path.joinpath("submissions_parser_output", "submissions.db") ctrl_db_path = ctx.directory_path.joinpath("submissions_parser_output", "submissions.db")
try: try:
@@ -25,29 +29,39 @@ def script(ctx:Settings):
except AttributeError as e: except AttributeError as e:
logger.error(f"Error, could not import from irida due to {e}") logger.error(f"Error, could not import from irida due to {e}")
return return
sql = f"SELECT name, submitted_date, submission_id, contains, matches, kraken, subtype, refseq_version, " \ sql = "SELECT name, submitted_date, submission_id, contains, matches, kraken, subtype, refseq_version, " \
f"kraken2_version, kraken2_db_version, sample_id FROM _iridacontrol INNER JOIN _control on _control.id " \ "kraken2_version, kraken2_db_version, sample_id FROM _iridacontrol INNER JOIN _control on _control.id " \
f"= _iridacontrol.id WHERE _control.name NOT IN ({prm_list})" f"= _iridacontrol.id WHERE _control.name NOT IN ({prm_list})"
cursor = conn.execute(sql) cursor = conn.execute(sql)
records = [dict(name=row[0], submitted_date=row[1], submission_id=row[2], contains=row[3], matches=row[4], kraken=row[5], records = [
subtype=row[6], refseq_version=row[7], kraken2_version=row[8], kraken2_db_version=row[9], dict(name=row[0], submitted_date=row[1], submission_id=row[2], contains=row[3], matches=row[4], kraken=row[5],
sample_id=row[10]) for row in cursor] subtype=row[6], refseq_version=row[7], kraken2_version=row[8], kraken2_db_version=row[9],
sample_id=row[10]) for row in cursor]
for record in records: for record in records:
instance = IridaControl.query(name=record['name']) # instance = IridaControl.query(name=record['name'])
instance = new_session.query(IridaControl).filter(IridaControl.name == record['name']).first()
if instance: if instance:
logger.warning(f"Irida Control {instance.name} already exists, skipping.") logger.warning(f"Irida Control {instance.name} already exists, skipping.")
continue continue
record['contains'] = json.loads(record['contains']) for thing in ['contains', 'matches', 'kraken']:
assert isinstance(record['contains'], dict) if record[thing]:
record['matches'] = json.loads(record['matches']) record[thing] = json.loads(record[thing])
assert isinstance(record['matches'], dict) assert isinstance(record[thing], dict)
record['kraken'] = json.loads(record['kraken']) else:
assert isinstance(record['kraken'], dict) record[thing] = {}
# record['matches'] = json.loads(record['matches'])
# assert isinstance(record['matches'], dict)
# record['kraken'] = json.loads(record['kraken'])
# assert isinstance(record['kraken'], dict)
record['submitted_date'] = datetime.strptime(record['submitted_date'], "%Y-%m-%d %H:%M:%S.%f") record['submitted_date'] = datetime.strptime(record['submitted_date'], "%Y-%m-%d %H:%M:%S.%f")
assert isinstance(record['submitted_date'], datetime) assert isinstance(record['submitted_date'], datetime)
instance = IridaControl(controltype=ct, **record) instance = IridaControl(controltype=ct, **record)
sample = BasicSample.query(submitter_id=instance.name) # sample = BasicSample.query(submitter_id=instance.name)
sample = new_session.query(BasicSample).filter(BasicSample.submitter_id == instance.name).first()
if sample: if sample:
instance.sample = sample instance.sample = sample
instance.submission = sample.submissions[0] instance.submission = sample.submissions[0]
instance.save() # instance.save()
new_session.add(instance)
new_session.commit()
new_session.close()

View File

@@ -295,7 +295,8 @@ class PydTips(BaseModel):
Returns: Returns:
SubmissionTipsAssociation: Association between queried tips and submission SubmissionTipsAssociation: Association between queried tips and submission
""" """
tips = Tips.query(name=self.name, lot=self.lot, limit=1) tips = Tips.query(name=self.name, limit=1)
logger.debug(f"Tips query has yielded: {tips}")
assoc = SubmissionTipsAssociation.query(tip_id=tips.id, submission_id=submission.id, role=self.role, limit=1) assoc = SubmissionTipsAssociation.query(tip_id=tips.id, submission_id=submission.id, role=self.role, limit=1)
if assoc is None: if assoc is None:
assoc = SubmissionTipsAssociation(submission=submission, tips=tips, role_name=self.role) assoc = SubmissionTipsAssociation(submission=submission, tips=tips, role_name=self.role)
@@ -900,17 +901,20 @@ class PydSubmission(BaseModel, extra='allow'):
return render return render
# @report_result # @report_result
def check_kit_integrity(self, extraction_kit: str | dict | None = None) -> Tuple[List[PydReagent], Report]: def check_kit_integrity(self, extraction_kit: str | dict | None = None, exempt:List[PydReagent]=[]) -> Tuple[
List[PydReagent], Report]:
""" """
Ensures all reagents expected in kit are listed in Submission Ensures all reagents expected in kit are listed in Submission
Args: Args:
reagenttypes (list | None, optional): List to check against complete list. Defaults to None. extraction_kit (str | dict | None, optional): kit to be checked. Defaults to None.
exempt (List[PydReagent], optional): List of reagents that don't need to be checked. Defaults to []
Returns: Returns:
Report: Result object containing a message and any missing components. Tuple[List[PydReagent], Report]: List of reagents and Result object containing a message and any missing components.
""" """
report = Report() report = Report()
# logger.debug(f"The following reagents are exempt from the kit integrity check:\n{exempt}")
if isinstance(extraction_kit, str): if isinstance(extraction_kit, str):
extraction_kit = dict(value=extraction_kit) extraction_kit = dict(value=extraction_kit)
if extraction_kit is not None and extraction_kit != self.extraction_kit['value']: if extraction_kit is not None and extraction_kit != self.extraction_kit['value']:
@@ -922,7 +926,8 @@ class PydSubmission(BaseModel, extra='allow'):
expected_check = [item.role for item in ext_kit_rtypes] expected_check = [item.role for item in ext_kit_rtypes]
output_reagents = [rt for rt in self.reagents if rt.role in expected_check] output_reagents = [rt for rt in self.reagents if rt.role in expected_check]
missing_check = [item.role for item in output_reagents] missing_check = [item.role for item in output_reagents]
missing_reagents = [rt for rt in ext_kit_rtypes if rt.role not in missing_check] missing_reagents = [rt for rt in ext_kit_rtypes if rt.role not in missing_check and rt.role not in exempt]
# logger.debug(f"Missing reagents: {missing_reagents}")
missing_reagents += [rt for rt in output_reagents if rt.missing] missing_reagents += [rt for rt in output_reagents if rt.missing]
output_reagents += [rt for rt in missing_reagents if rt not in output_reagents] output_reagents += [rt for rt in missing_reagents if rt not in output_reagents]
# NOTE: if lists are equal return no problem # NOTE: if lists are equal return no problem
@@ -930,8 +935,8 @@ class PydSubmission(BaseModel, extra='allow'):
result = None result = None
else: else:
result = Result( result = Result(
msg=f"The excel sheet you are importing is missing some reagents expected by the kit.\n\nIt looks like you are missing: {[item.role.upper() for item in missing_reagents]}\n\nAlternatively, you may have set the wrong extraction kit.\n\nThe program will populate lists using existing reagents.\n\nPlease make sure you check the lots carefully!", msg=f"The excel sheet you are importing is missing some reagents expected by the kit.\n\nIt looks like you are missing: {[item.role.upper() for item in missing_reagents]}\n\nAlternatively, you may have set the wrong extraction kit.\n\nThe program will populate lists using existing reagents.\n\nPlease make sure you check the lots carefully!",
status="Warning") status="Warning")
report.add_result(result) report.add_result(result)
return output_reagents, report return output_reagents, report

View File

@@ -2,15 +2,17 @@
Contains all custom generated PyQT6 derivative widgets. Contains all custom generated PyQT6 derivative widgets.
""" """
from .app import App
from .controls_chart import *
from .equipment_usage import *
from .functions import * from .functions import *
from .gel_checker import *
from .info_tab import *
from .misc import * from .misc import *
from .omni_search import *
from .pop_ups import * from .pop_ups import *
from .submission_details import *
from .submission_table import * from .submission_table import *
from .submission_widget import * from .submission_widget import *
from .controls_chart import * from .summary import *
from .submission_details import * from .turnaround import *
from .equipment_usage import *
from .gel_checker import *
from .summary import Summary
from .turnaround import TurnaroundTime
from .app import App

View File

@@ -160,7 +160,7 @@ class RoleComboBox(QWidget):
PydEquipment|None: PydEquipment matching form PydEquipment|None: PydEquipment matching form
""" """
eq = Equipment.query(name=self.box.currentText()) eq = Equipment.query(name=self.box.currentText())
tips = [PydTips(name=item.currentText(), role=item.objectName().lstrip("tips").lstrip("_")) for item in tips = [PydTips(name=item.currentText(), role=item.objectName().lstrip("tips").lstrip("_"), lot="") for item in
self.findChildren(QComboBox) if item.objectName().startswith("tips")] self.findChildren(QComboBox) if item.objectName().startswith("tips")]
try: try:
return PydEquipment( return PydEquipment(

View File

@@ -6,7 +6,7 @@ from PyQt6.QtWidgets import (
QComboBox, QDateEdit, QLineEdit, QLabel, QCheckBox, QHBoxLayout, QGridLayout QComboBox, QDateEdit, QLineEdit, QLabel, QCheckBox, QHBoxLayout, QGridLayout
) )
from PyQt6.QtCore import pyqtSignal, Qt, QSignalBlocker from PyQt6.QtCore import pyqtSignal, Qt, QSignalBlocker
from . import select_open_file, select_save_file from .functions import select_open_file, select_save_file
import logging import logging
from pathlib import Path from pathlib import Path
from tools import Report, Result, check_not_nan, main_form_style, report_result from tools import Report, Result, check_not_nan, main_form_style, report_result
@@ -338,11 +338,15 @@ class SubmissionFormWidget(QWidget):
report = Report() report = Report()
result = self.parse_form() result = self.parse_form()
report.add_result(result) report.add_result(result)
# allow = not all([item.lot.isEnabled() for item in self.findChildren(self.ReagentFormWidget)])
exempt = [item.reagent.role for item in self.findChildren(self.ReagentFormWidget) if not item.lot.isEnabled()]
# if allow:
# logger.warning(f"Some reagents are disabled, allowing incomplete kit.")
if self.disabler.checkbox.isChecked(): if self.disabler.checkbox.isChecked():
_, result = self.pyd.check_kit_integrity() _, result = self.pyd.check_kit_integrity(exempt=exempt)
report.add_result(result) report.add_result(result)
if len(result.results) > 0: if len(result.results) > 0:
return return report
base_submission, result = self.pyd.to_sql() base_submission, result = self.pyd.to_sql()
# NOTE: check output message for issues # NOTE: check output message for issues
try: try: