Update to add SQL Server support.

This commit is contained in:
lwark
2024-11-01 13:16:16 -05:00
parent ba1b3e5cf3
commit 816a0a45f8
13 changed files with 182 additions and 67 deletions

View File

@@ -1,6 +1,7 @@
## 202410.05 ## 202411.01
- Code clean up. - Code clean up.
- Improved flexibility of Irida chart for subtyping.
## 202410.03 ## 202410.03

View File

@@ -18,8 +18,18 @@ def set_sqlite_pragma(dbapi_connection, connection_record):
connection_record (_type_): _description_ connection_record (_type_): _description_
""" """
cursor = dbapi_connection.cursor() cursor = dbapi_connection.cursor()
# print(ctx.database_schema)
if ctx.database_schema == "sqlite": if ctx.database_schema == "sqlite":
cursor.execute("PRAGMA foreign_keys=ON") execution_phrase = "PRAGMA foreign_keys=ON"
# cursor.execute(execution_phrase)
elif ctx.database_schema == "mssql+pyodbc":
execution_phrase = "SET IDENTITY_INSERT dbo._wastewater ON;"
else:
print("Nothing to execute, returning")
cursor.close()
return
print(f"Executing {execution_phrase} in sql.")
cursor.execute(execution_phrase)
cursor.close() cursor.close()

View File

@@ -94,20 +94,7 @@ class BaseClass(Base):
Returns: Returns:
dict | list | str: Output of key:value dict or single (list, str) desired variable dict | list | str: Output of key:value dict or single (list, str) desired variable
""" """
# if issubclass(cls, BaseClass) and cls.__name__ != "BaseClass":
singles = list(set(cls.singles + BaseClass.singles)) singles = list(set(cls.singles + BaseClass.singles))
# else:
# singles = cls.singles
# output = dict(singles=singles)
# output = {}
# for k, v in dicto.items():
# if len(args) > 0 and k not in args:
# # logger.debug(f"{k} not selected as being of interest.")
# continue
# else:
# output[k] = v
# if len(args) == 1:
# return output[args[0]]
return dict(singles=singles) return dict(singles=singles)
@classmethod @classmethod
@@ -197,10 +184,10 @@ class ConfigItem(BaseClass):
ConfigItem|List[ConfigItem]: Config item(s) ConfigItem|List[ConfigItem]: Config item(s)
""" """
query = cls.__database_session__.query(cls) query = cls.__database_session__.query(cls)
# config_items = [item for item in config_items if item.key in args]
match len(args): match len(args):
case 0: case 0:
config_items = query.all() config_items = query.all()
# NOTE: If only one item sought, don't use a list, just return it.
case 1: case 1:
config_items = query.filter(cls.key == args[0]).first() config_items = query.filter(cls.key == args[0]).first()
case _: case _:

View File

@@ -66,18 +66,18 @@ class ControlType(BaseClass):
Returns: Returns:
List[str]: list of subtypes available List[str]: list of subtypes available
""" """
# NOTE: Get first instance since all should have same subtypes
# NOTE: Get mode of instance
if not self.instances: if not self.instances:
return return
# NOTE: Get first instance since all should have same subtypes
# NOTE: Get mode of instance
jsoner = getattr(self.instances[0], mode) jsoner = getattr(self.instances[0], mode)
try: try:
# NOTE: Pick genera (all should have same subtypes) # NOTE: Pick genera (all should have same subtypes)
genera = list(jsoner.keys())[0] genera = list(jsoner.keys())[0]
except IndexError: except IndexError:
return [] return []
# NOTE: remove items that don't have relevant data # NOTE subtypes now created for all modes, but ignored for all but allowed_for_subtyping later in the ControlsChart
subtypes = [item for item in jsoner[genera] if "_hashes" not in item and "_ratio" not in item] subtypes = sorted(list(jsoner[genera].keys()), reverse=True)
return subtypes return subtypes
def get_instance_class(self) -> Control: def get_instance_class(self) -> Control:
@@ -98,7 +98,7 @@ class ControlType(BaseClass):
Generator[str, None, None]: Control types that have targets Generator[str, None, None]: Control types that have targets
""" """
ct = cls.query(name=control_type).targets ct = cls.query(name=control_type).targets
return (item for item in ct.keys() if ct[item]) return (k for k, v in ct.items() if v)
@classmethod @classmethod
def build_positive_regex(cls, control_type:str) -> Pattern: def build_positive_regex(cls, control_type:str) -> Pattern:
@@ -141,6 +141,98 @@ class Control(BaseClass):
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<{self.controltype_name}({self.name})>" return f"<{self.controltype_name}({self.name})>"
# @classmethod
# @setup_lookup
# def query(cls,
# submission_type: str | None = None,
# subtype: str | None = None,
# start_date: date | str | int | None = None,
# end_date: date | str | int | None = None,
# control_name: str | None = None,
# limit: int = 0, **kwargs
# ) -> Control | List[Control]:
# """
# Lookup control objects in the database based on a number of parameters.
#
# Args:
# submission_type (str | None, optional): Control archetype. Defaults to None.
# start_date (date | str | int | None, optional): Beginning date to search by. Defaults to 2023-01-01 if end_date not None.
# end_date (date | str | int | None, optional): End date to search by. Defaults to today if start_date not None.
# control_name (str | None, optional): Name of control. Defaults to None.
# limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
#
# Returns:
# PCRControl|List[PCRControl]: Control object of interest.
# """
# from backend.db import SubmissionType
# query: Query = cls.__database_session__.query(cls)
# match submission_type:
# case str():
# from backend import BasicSubmission, SubmissionType
# # logger.debug(f"Lookup controls by SubmissionType str: {submission_type}")
# query = query.join(BasicSubmission).join(SubmissionType).filter(SubmissionType.name == submission_type)
# case SubmissionType():
# from backend import BasicSubmission
# # logger.debug(f"Lookup controls by SubmissionType: {submission_type}")
# query = query.join(BasicSubmission).filter(BasicSubmission.submission_type_name == submission_type.name)
# case _:
# pass
# # NOTE: by control type
# match subtype:
# case str():
# if cls.__name__ == "Control":
# raise ValueError(f"Cannot query base class Control with subtype.")
# elif cls.__name__ == "IridaControl":
# query = query.filter(cls.subtype == subtype)
# else:
# try:
# query = query.filter(cls.subtype == subtype)
# except AttributeError as e:
# logger.error(e)
# case _:
# pass
# # NOTE: by date range
# if start_date is not None and end_date is None:
# logger.warning(f"Start date with no end date, using today.")
# end_date = date.today()
# if end_date is not None and start_date is None:
# logger.warning(f"End date with no start date, using 90 days ago.")
# # start_date = date(2023, 1, 1)
# start_date = date.today() - timedelta(days=90)
# if start_date is not None:
# match start_date:
# case date():
# # logger.debug(f"Lookup control by start date({start_date})")
# start_date = start_date.strftime("%Y-%m-%d")
# case int():
# # logger.debug(f"Lookup control by ordinal start date {start_date}")
# start_date = datetime.fromordinal(
# datetime(1900, 1, 1).toordinal() + start_date - 2).date().strftime("%Y-%m-%d")
# case _:
# # logger.debug(f"Lookup control with parsed start date {start_date}")
# start_date = parse(start_date).strftime("%Y-%m-%d")
# match end_date:
# case date():
# # logger.debug(f"Lookup control by end date({end_date})")
# end_date = end_date.strftime("%Y-%m-%d")
# case int():
# # logger.debug(f"Lookup control by ordinal end date {end_date}")
# end_date = datetime.fromordinal(datetime(1900, 1, 1).toordinal() + end_date - 2).date().strftime(
# "%Y-%m-%d")
# case _:
# # logger.debug(f"Lookup control with parsed end date {end_date}")
# end_date = parse(end_date).strftime("%Y-%m-%d")
# # logger.debug(f"Looking up BasicSubmissions from start date: {start_date} and end date: {end_date}")
# query = query.filter(cls.submitted_date.between(start_date, end_date))
# match control_name:
# case str():
# # logger.debug(f"Lookup control by name {control_name}")
# query = query.filter(cls.name.startswith(control_name))
# limit = 1
# case _:
# pass
# return cls.execute_query(query=query, limit=limit)
@classmethod @classmethod
def find_polymorphic_subclass(cls, polymorphic_identity: str | ControlType | None = None, def find_polymorphic_subclass(cls, polymorphic_identity: str | ControlType | None = None,
attrs: dict | None = None) -> Control: attrs: dict | None = None) -> Control:
@@ -155,10 +247,9 @@ class Control(BaseClass):
Control: Subclass of interest. Control: Subclass of interest.
""" """
if isinstance(polymorphic_identity, dict): if isinstance(polymorphic_identity, dict):
# logger.debug(f"Controlling for dict value")
polymorphic_identity = polymorphic_identity['value'] polymorphic_identity = polymorphic_identity['value']
if isinstance(polymorphic_identity, ControlType): # if isinstance(polymorphic_identity, ControlType):
polymorphic_identity = polymorphic_identity.name # polymorphic_identity = polymorphic_identity.name
model = cls model = cls
match polymorphic_identity: match polymorphic_identity:
case str(): case str():
@@ -167,6 +258,12 @@ class Control(BaseClass):
except Exception as e: except Exception as e:
logger.error( logger.error(
f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, falling back to BasicSubmission") f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, falling back to BasicSubmission")
case ControlType():
try:
model = cls.__mapper__.polymorphic_map[polymorphic_identity.name].class_
except Exception as e:
logger.error(
f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, falling back to BasicSubmission")
case _: case _:
pass pass
if attrs and any([not hasattr(cls, attr) for attr in attrs.keys()]): if attrs and any([not hasattr(cls, attr) for attr in attrs.keys()]):
@@ -228,7 +325,7 @@ class PCRControl(Control):
@classmethod @classmethod
@setup_lookup @setup_lookup
def query(cls, def query(cls,
sub_type: str | None = None, submission_type: str | None = None,
start_date: date | str | int | None = None, start_date: date | str | int | None = None,
end_date: date | str | int | None = None, end_date: date | str | int | None = None,
control_name: str | None = None, control_name: str | None = None,
@@ -238,7 +335,7 @@ class PCRControl(Control):
Lookup control objects in the database based on a number of parameters. Lookup control objects in the database based on a number of parameters.
Args: Args:
sub_type (str | None, optional): Control archetype. Defaults to None. submission_type (str | None, optional): Control archetype. Defaults to None.
start_date (date | str | int | None, optional): Beginning date to search by. Defaults to 2023-01-01 if end_date not None. start_date (date | str | int | None, optional): Beginning date to search by. Defaults to 2023-01-01 if end_date not None.
end_date (date | str | int | None, optional): End date to search by. Defaults to today if start_date not None. end_date (date | str | int | None, optional): End date to search by. Defaults to today if start_date not None.
control_name (str | None, optional): Name of control. Defaults to None. control_name (str | None, optional): Name of control. Defaults to None.
@@ -254,8 +351,9 @@ class PCRControl(Control):
logger.warning(f"Start date with no end date, using today.") logger.warning(f"Start date with no end date, using today.")
end_date = date.today() end_date = date.today()
if end_date is not None and start_date is None: if end_date is not None and start_date is None:
logger.warning(f"End date with no start date, using Jan 1, 2023") logger.warning(f"End date with no start date, using 90 days ago.")
start_date = date(2023, 1, 1) # start_date = date(2023, 1, 1)
start_date = date.today() - timedelta(days=90)
if start_date is not None: if start_date is not None:
match start_date: match start_date:
case date(): case date():
@@ -281,15 +379,15 @@ class PCRControl(Control):
end_date = parse(end_date).strftime("%Y-%m-%d") end_date = parse(end_date).strftime("%Y-%m-%d")
# logger.debug(f"Looking up BasicSubmissions from start date: {start_date} and end date: {end_date}") # logger.debug(f"Looking up BasicSubmissions from start date: {start_date} and end date: {end_date}")
query = query.filter(cls.submitted_date.between(start_date, end_date)) query = query.filter(cls.submitted_date.between(start_date, end_date))
match sub_type: match submission_type:
case str(): case str():
from backend import BasicSubmission, SubmissionType from backend import BasicSubmission, SubmissionType
# logger.debug(f"Lookup controls by SubmissionType str: {sub_type}") # logger.debug(f"Lookup controls by SubmissionType str: {submission_type}")
query = query.join(BasicSubmission).join(SubmissionType).filter(SubmissionType.name == sub_type) query = query.join(BasicSubmission).join(SubmissionType).filter(SubmissionType.name == submission_type)
case SubmissionType(): case SubmissionType():
from backend import BasicSubmission from backend import BasicSubmission
# logger.debug(f"Lookup controls by SubmissionType: {sub_type}") # logger.debug(f"Lookup controls by SubmissionType: {submission_type}")
query = query.join(BasicSubmission).filter(BasicSubmission.submission_type_name==sub_type.name) query = query.join(BasicSubmission).filter(BasicSubmission.submission_type_name==submission_type.name)
case _: case _:
pass pass
match control_name: match control_name:
@@ -319,7 +417,8 @@ class PCRControl(Control):
parent.mode_typer.clear() parent.mode_typer.clear()
parent.mode_typer.setEnabled(False) parent.mode_typer.setEnabled(False)
report = Report() report = Report()
controls = cls.query(sub_type=chart_settings['sub_type'], start_date=chart_settings['start_date'], logger.debug(f"Chart settings: {pformat(chart_settings)}")
controls = cls.query(submission_type=chart_settings['sub_type'], start_date=chart_settings['start_date'],
end_date=chart_settings['end_date']) end_date=chart_settings['end_date'])
data = [control.to_sub_dict() for control in controls] data = [control.to_sub_dict() for control in controls]
df = DataFrame.from_records(data) df = DataFrame.from_records(data)
@@ -332,11 +431,14 @@ class PCRControl(Control):
class IridaControl(Control): class IridaControl(Control):
subtyping_allowed = ['kraken']
id = Column(INTEGER, ForeignKey('_control.id'), primary_key=True) id = Column(INTEGER, ForeignKey('_control.id'), primary_key=True)
contains = Column(JSON) #: unstructured hashes in contains.tsv for each organism contains = Column(JSON) #: unstructured hashes in contains.tsv for each organism
matches = Column(JSON) #: unstructured hashes in matches.tsv for each organism matches = Column(JSON) #: unstructured hashes in matches.tsv for each organism
kraken = Column(JSON) #: unstructured output from kraken_report kraken = Column(JSON) #: unstructured output from kraken_report
sub_type = Column(String(16), nullable=False) #: EN-NOS, MCS-NOS, etc subtype = Column(String(16), nullable=False) #: EN-NOS, MCS-NOS, etc
refseq_version = Column(String(16)) #: version of refseq used in fastq parsing refseq_version = Column(String(16)) #: version of refseq used in fastq parsing
kraken2_version = Column(String(16)) #: version of kraken2 used in fastq parsing kraken2_version = Column(String(16)) #: version of kraken2 used in fastq parsing
kraken2_db_version = Column(String(32)) #: folder name of kraken2 db kraken2_db_version = Column(String(32)) #: folder name of kraken2 db
@@ -488,16 +590,17 @@ class IridaControl(Control):
# NOTE: by control type # NOTE: by control type
match sub_type: match sub_type:
case str(): case str():
query = query.filter(cls.sub_type == sub_type) query = query.filter(cls.subtype == sub_type)
case _: case _:
pass pass
# NOTE: by date range # NOTE: If one date exists, we need the other one to exist as well.
if start_date is not None and end_date is None: if start_date is not None and end_date is None:
logger.warning(f"Start date with no end date, using today.") logger.warning(f"Start date with no end date, using today.")
end_date = date.today() end_date = date.today()
if end_date is not None and start_date is None: if end_date is not None and start_date is None:
logger.warning(f"End date with no start date, using Jan 1, 2023") logger.warning(f"End date with no start date, using 90 days ago.")
start_date = date(2023, 1, 1) # start_date = date(2023, 1, 1)
start_date = date.today() - timedelta(days=90)
if start_date is not None: if start_date is not None:
match start_date: match start_date:
case date(): case date():

View File

@@ -2,16 +2,12 @@
All kit and reagent related models All kit and reagent related models
""" """
from __future__ import annotations from __future__ import annotations
import datetime import datetime, json, zipfile, yaml, logging, re
import json
import sys
from pprint import pformat from pprint import pformat
import yaml
from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey, Interval, Table, FLOAT, BLOB from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey, Interval, Table, FLOAT, BLOB
from sqlalchemy.orm import relationship, validates, Query from sqlalchemy.orm import relationship, validates, Query
from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.associationproxy import association_proxy
from datetime import date from datetime import date
import logging, re
from tools import check_authorization, setup_lookup, Report, Result, check_regex_match, yaml_regex_creator from tools import check_authorization, setup_lookup, Report, Result, check_regex_match, yaml_regex_creator
from typing import List, Literal, Generator, Any from typing import List, Literal, Generator, Any
from pandas import ExcelFile from pandas import ExcelFile
@@ -688,16 +684,17 @@ class SubmissionType(BaseClass):
return f"<SubmissionType({self.name})>" return f"<SubmissionType({self.name})>"
def get_template_file_sheets(self) -> List[str]: def get_template_file_sheets(self) -> List[str]:
logger.debug(f"Submission type to get sheets for: {self.name}")
""" """
Gets names of sheet in the stored blank form. Gets names of sheet in the stored blank form.
Returns: Returns:
List[str]: List of sheet names List[str]: List of sheet names
""" """
# print(f"Getting template file from {self.__database_session__.get_bind()}") try:
if "pytest" in sys.modules:
return ExcelFile("C:\\Users\lwark\Documents\python\submissions\mytests\\test_assets\RSL-AR-20240513-1.xlsx").sheet_names
return ExcelFile(BytesIO(self.template_file), engine="openpyxl").sheet_names return ExcelFile(BytesIO(self.template_file), engine="openpyxl").sheet_names
except zipfile.BadZipfile:
return []
def set_template_file(self, filepath: Path | str): def set_template_file(self, filepath: Path | str):
""" """

View File

@@ -4,6 +4,7 @@ Models for the main submission and sample types.
from __future__ import annotations from __future__ import annotations
import sys import sys
import types import types
import zipfile
from copy import deepcopy from copy import deepcopy
from getpass import getuser from getpass import getuser
import logging, uuid, tempfile, re, base64 import logging, uuid, tempfile, re, base64
@@ -733,6 +734,7 @@ class BasicSubmission(BaseClass):
return model return model
# Child class custom functions # Child class custom functions
@classmethod @classmethod
def custom_info_parser(cls, input_dict: dict, xl: Workbook | None = None, custom_fields: dict = {}) -> dict: def custom_info_parser(cls, input_dict: dict, xl: Workbook | None = None, custom_fields: dict = {}) -> dict:
""" """
@@ -1170,7 +1172,8 @@ class BasicSubmission(BaseClass):
# logger.debug(f"Retrieved instance: {instance}") # logger.debug(f"Retrieved instance: {instance}")
if instance is None: if instance is None:
used_class = cls.find_polymorphic_subclass(attrs=kwargs, polymorphic_identity=submission_type) used_class = cls.find_polymorphic_subclass(attrs=kwargs, polymorphic_identity=submission_type)
instance = used_class(**kwargs) # instance = used_class(**kwargs)
instance = used_class(**sanitized_kwargs)
match submission_type: match submission_type:
case str(): case str():
submission_type = SubmissionType.query(name=submission_type) submission_type = SubmissionType.query(name=submission_type)
@@ -1216,7 +1219,10 @@ class BasicSubmission(BaseClass):
fname = self.__backup_path__.joinpath(f"{self.rsl_plate_num}-backup({date.today().strftime('%Y%m%d')})") fname = self.__backup_path__.joinpath(f"{self.rsl_plate_num}-backup({date.today().strftime('%Y%m%d')})")
msg = QuestionAsker(title="Delete?", message=f"Are you sure you want to delete {self.rsl_plate_num}?\n") msg = QuestionAsker(title="Delete?", message=f"Are you sure you want to delete {self.rsl_plate_num}?\n")
if msg.exec(): if msg.exec():
try:
self.backup(fname=fname, full_backup=True) self.backup(fname=fname, full_backup=True)
except zipfile.BadZipfile:
logger.error("Couldn't open zipfile for writing.")
self.__database_session__.delete(self) self.__database_session__.delete(self)
try: try:
self.__database_session__.commit() self.__database_session__.commit()
@@ -1419,7 +1425,7 @@ class Wastewater(BasicSubmission):
""" """
derivative submission type from BasicSubmission derivative submission type from BasicSubmission
""" """
id = Column(INTEGER, ForeignKey('_basicsubmission.id'), primary_key=True) id = Column(INTEGER, ForeignKey('_basicsubmission.id'), primary_key=True, autoincrement=False)
ext_technician = Column(String(64)) #: Name of technician doing extraction ext_technician = Column(String(64)) #: Name of technician doing extraction
pcr_technician = Column(String(64)) #: Name of technician doing pcr pcr_technician = Column(String(64)) #: Name of technician doing pcr
pcr_info = Column(JSON) #: unstructured output from pcr table logger or user(Artic) pcr_info = Column(JSON) #: unstructured output from pcr table logger or user(Artic)
@@ -2279,7 +2285,7 @@ class BasicSample(BaseClass):
try: try:
model = cls.__mapper__.polymorphic_map[polymorphic_identity].class_ model = cls.__mapper__.polymorphic_map[polymorphic_identity].class_
except Exception as e: except Exception as e:
logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}") logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, using {cls}")
model = cls model = cls
logger.info(f"Recruiting model: {model}") logger.info(f"Recruiting model: {model}")
return model return model
@@ -2856,6 +2862,7 @@ class SubmissionSampleAssociation(BaseClass):
SubmissionSampleAssociation: Queried or new association. SubmissionSampleAssociation: Queried or new association.
""" """
# logger.debug(f"Attempting create or query with {kwargs}") # logger.debug(f"Attempting create or query with {kwargs}")
disallowed = ['id']
match submission: match submission:
case BasicSubmission(): case BasicSubmission():
pass pass

View File

@@ -123,8 +123,8 @@ class SheetParser(object):
""" """
Enforce that the parser has an extraction kit Enforce that the parser has an extraction kit
""" """
from frontend.widgets.pop_ups import ObjectSelector
if 'extraction_kit' not in self.sub.keys() or not check_not_nan(self.sub['extraction_kit']['value']): if 'extraction_kit' not in self.sub.keys() or not check_not_nan(self.sub['extraction_kit']['value']):
from frontend.widgets.pop_ups import ObjectSelector
dlg = ObjectSelector(title="Kit Needed", message="At minimum a kit is needed. Please select one.", dlg = ObjectSelector(title="Kit Needed", message="At minimum a kit is needed. Please select one.",
obj_type=KitType) obj_type=KitType)
if dlg.exec(): if dlg.exec():
@@ -199,6 +199,7 @@ class InfoParser(object):
if k == "custom": if k == "custom":
continue continue
if isinstance(v, str): if isinstance(v, str):
logger.debug(f"Found string for {k}, setting value to {v}")
dicto[k] = dict(value=v, missing=False) dicto[k] = dict(value=v, missing=False)
continue continue
# logger.debug(f"Looking for {k} in self.map") # logger.debug(f"Looking for {k} in self.map")
@@ -270,13 +271,13 @@ class ReagentParser(object):
if isinstance(extraction_kit, dict): if isinstance(extraction_kit, dict):
extraction_kit = extraction_kit['value'] extraction_kit = extraction_kit['value']
self.kit_object = KitType.query(name=extraction_kit) self.kit_object = KitType.query(name=extraction_kit)
# logger.debug(f"Got extraction kit object: {self.kit_object}") logger.debug(f"Got extraction kit object: {self.kit_object}")
self.map = self.fetch_kit_info_map(submission_type=submission_type) self.map = self.fetch_kit_info_map(submission_type=submission_type)
# logger.debug(f"Reagent Parser map: {self.map}") # logger.debug(f"Reagent Parser map: {self.map}")
self.xl = xl self.xl = xl
@report_result @report_result
def fetch_kit_info_map(self, submission_type: str) -> Tuple[Report, dict]: def fetch_kit_info_map(self, submission_type: str|SubmissionType) -> Tuple[Report, dict]:
""" """
Gets location of kit reagents from database Gets location of kit reagents from database
@@ -296,8 +297,13 @@ class ReagentParser(object):
pass pass
# logger.debug(f"Reagent map: {pformat(reagent_map)}") # logger.debug(f"Reagent map: {pformat(reagent_map)}")
if not reagent_map.keys(): if not reagent_map.keys():
try:
ext_kit_loc = self.submission_type_obj.info_map['extraction_kit']['read'][0]
location_string = f"Sheet: {ext_kit_loc['sheet']}, Row: {ext_kit_loc['row']}, Column: {ext_kit_loc['column']}?"
except:
location_string = ""
report.add_result(Result(owner=__name__, code=0, msg=f"No kit map found for {self.kit_object.name}.\n\n" report.add_result(Result(owner=__name__, code=0, msg=f"No kit map found for {self.kit_object.name}.\n\n"
f"Are you sure you used the right kit?", f"Are you sure you put the right kit in:\n\n{location_string}?",
status="Critical")) status="Critical"))
return report, reagent_map return report, reagent_map
@@ -409,7 +415,6 @@ class SampleParser(object):
""" """
invalids = [0, "0", "EMPTY"] invalids = [0, "0", "EMPTY"]
smap = self.sample_info_map['plate_map'] smap = self.sample_info_map['plate_map']
print(smap)
ws = self.xl[smap['sheet']] ws = self.xl[smap['sheet']]
plate_map_samples = [] plate_map_samples = []
for ii, row in enumerate(range(smap['start_row'], smap['end_row'] + 1), start=1): for ii, row in enumerate(range(smap['start_row'], smap['end_row'] + 1), start=1):

View File

@@ -52,7 +52,7 @@ class RSLNamer(object):
str: parsed submission type str: parsed submission type
""" """
def st_from_path(filename:Path) -> str: def st_from_path(filename:Path) -> str:
logger.info(f"Using path method for {filename}.") # logger.info(f"Using path method for {filename}.")
if filename.exists(): if filename.exists():
wb = load_workbook(filename) wb = load_workbook(filename)
try: try:
@@ -60,7 +60,7 @@ class RSLNamer(object):
categories = wb.properties.category.split(";") categories = wb.properties.category.split(";")
submission_type = next(item.strip().title() for item in categories) submission_type = next(item.strip().title() for item in categories)
except (StopIteration, AttributeError): except (StopIteration, AttributeError):
sts = {item.name: item.get_template_file_sheets() for item in SubmissionType.query()} sts = {item.name: item.get_template_file_sheets() for item in SubmissionType.query() if item.template_file}
try: try:
submission_type = next(k.title() for k,v in sts.items() if wb.sheetnames==v) submission_type = next(k.title() for k,v in sts.items() if wb.sheetnames==v)
except StopIteration: except StopIteration:
@@ -70,8 +70,10 @@ class RSLNamer(object):
submission_type = cls.retrieve_submission_type(filename=filename.stem.__str__()) submission_type = cls.retrieve_submission_type(filename=filename.stem.__str__())
return submission_type return submission_type
def st_from_str(filename:str) -> str: def st_from_str(filename:str) -> str:
if filename.startswith("tmp"):
return "Bacterial Culture"
regex = BasicSubmission.construct_regex() regex = BasicSubmission.construct_regex()
logger.info(f"Using string method for {filename}.") # logger.info(f"Using string method for {filename}.")
# logger.debug(f"Using regex: {regex}") # logger.debug(f"Using regex: {regex}")
m = regex.search(filename) m = regex.search(filename)
try: try:
@@ -95,7 +97,7 @@ class RSLNamer(object):
check = True check = True
if check: if check:
if "pytest" in sys.modules: if "pytest" in sys.modules:
return "Bacterial Culture" raise ValueError("Submission Type came back as None.")
# logger.debug("Final option, ask the user for submission type") # logger.debug("Final option, ask the user for submission type")
from frontend.widgets import ObjectSelector from frontend.widgets import ObjectSelector
dlg = ObjectSelector(title="Couldn't parse submission type.", dlg = ObjectSelector(title="Couldn't parse submission type.",

View File

@@ -349,7 +349,7 @@ class PydEquipment(BaseModel, extra='ignore'):
""" """
if isinstance(submission, str): if isinstance(submission, str):
# logger.debug(f"Got string, querying {submission}") # logger.debug(f"Got string, querying {submission}")
submission = BasicSubmission.query(rsl_number=submission) submission = BasicSubmission.query(rsl_plate_num=submission)
equipment = Equipment.query(asset_number=self.asset_number) equipment = Equipment.query(asset_number=self.asset_number)
if equipment is None: if equipment is None:
logger.error("No equipment found. Returning None.") logger.error("No equipment found. Returning None.")
@@ -361,7 +361,8 @@ class PydEquipment(BaseModel, extra='ignore'):
role=self.role, limit=1) role=self.role, limit=1)
except TypeError as e: except TypeError as e:
logger.error(f"Couldn't get association due to {e}, returning...") logger.error(f"Couldn't get association due to {e}, returning...")
return equipment, None # return equipment, None
assoc = None
if assoc is None: if assoc is None:
assoc = SubmissionEquipmentAssociation(submission=submission, equipment=equipment) assoc = SubmissionEquipmentAssociation(submission=submission, equipment=equipment)
process = Process.query(name=self.processes[0]) process = Process.query(name=self.processes[0])
@@ -829,6 +830,7 @@ class PydSubmission(BaseModel, extra='allow'):
equip, association = equip.toSQL(submission=instance) equip, association = equip.toSQL(submission=instance)
if association is not None: if association is not None:
instance.submission_equipment_associations.append(association) instance.submission_equipment_associations.append(association)
logger.debug(f"Equipment associations:\n\n{pformat(instance.submission_equipment_associations)}")
case "tips": case "tips":
for tips in self.tips: for tips in self.tips:
if tips is None: if tips is None:

View File

@@ -230,7 +230,7 @@ class App(QMainWindow):
st = SubmissionType.import_from_json(filepath=fname) st = SubmissionType.import_from_json(filepath=fname)
if st: if st:
# NOTE: Do not delete the print statement below. # NOTE: Do not delete the print statement below.
print(pformat(st.to_export_dict())) # print(pformat(st.to_export_dict()))
choice = input("Save the above submission type? [y/N]: ") choice = input("Save the above submission type? [y/N]: ")
if choice.lower() == "y": if choice.lower() == "y":
pass pass

View File

@@ -103,7 +103,8 @@ class ControlsViewer(QWidget):
sub_types = self.archetype.get_modes(mode=self.mode) sub_types = self.archetype.get_modes(mode=self.mode)
except AttributeError: except AttributeError:
sub_types = [] sub_types = []
if sub_types: # NOTE: added in allowed to have subtypes in case additions made in future.
if sub_types and self.mode.lower() in self.archetype.get_instance_class().subtyping_allowed:
# NOTE: block signal that will rerun controls getter and update mode_sub_typer # NOTE: block signal that will rerun controls getter and update mode_sub_typer
with QSignalBlocker(self.mode_sub_typer) as blocker: with QSignalBlocker(self.mode_sub_typer) as blocker:
self.mode_sub_typer.addItems(sub_types) self.mode_sub_typer.addItems(sub_types)

View File

@@ -167,7 +167,7 @@ class SubmissionDetails(QDialog):
Renders submission to html, then creates and saves .pdf file to user selected file. Renders submission to html, then creates and saves .pdf file to user selected file.
""" """
fname = select_save_file(obj=self, default_name=self.export_plate, extension="pdf") fname = select_save_file(obj=self, default_name=self.export_plate, extension="pdf")
save_pdf(obj=self, filename=fname) save_pdf(obj=self.webview, filename=fname)
class SubmissionComment(QDialog): class SubmissionComment(QDialog):
""" """

View File

@@ -37,7 +37,7 @@ a = Analysis(
("src\\submissions\\resources\\*", "files\\resources"), ("src\\submissions\\resources\\*", "files\\resources"),
("alembic.ini", "files"), ("alembic.ini", "files"),
], ],
hiddenimports=[], hiddenimports=["pyodbc"],
hookspath=[], hookspath=[],
hooksconfig={}, hooksconfig={},
runtime_hooks=[], runtime_hooks=[],