Post code-cleanup, moments before disaster.

This commit is contained in:
lwark
2025-03-26 14:19:13 -05:00
parent 3ec79fdcfe
commit d844211e1b
21 changed files with 183 additions and 285 deletions

View File

@@ -1,6 +1,7 @@
- [ ] Can my "to_dict", "to_sub_dict", "to_pydantic" methods be rewritten as properties? - [ ] Change "Manage Organizations" to the Pydantic version.
- [x] Can my "to_dict", "to_sub_dict", "to_pydantic" methods be rewritten as properties?
- [ ] Stop displacing date on Irida controls and just do what Turnaround time does. - [ ] Stop displacing date on Irida controls and just do what Turnaround time does.
- [ ] Get Manager window working for KitType, maybe SubmissionType - [x] Get Manager window working for KitType, maybe SubmissionType
- [x] Find a way to merge AddEdit with ReagentAdder - [x] Find a way to merge AddEdit with ReagentAdder
- [x] Find a way to merge omni_search and sample_search - [x] Find a way to merge omni_search and sample_search
- [x] Allow parsing of custom fields to a json 'custom' field in _basicsubmissions - [x] Allow parsing of custom fields to a json 'custom' field in _basicsubmissions

View File

@@ -56,6 +56,12 @@ class BaseClass(Base):
omni_inheritable = [] omni_inheritable = []
searchables = [] searchables = []
def __repr__(self) -> str:
try:
return f"<{self.__class__.__name__}({self.name})>"
except AttributeError:
return f"<{self.__class__.__name__}({self.__name__})>"
# @classproperty # @classproperty
# def skip_on_edit(cls): # def skip_on_edit(cls):
# if "association" in cls.__name__.lower() or cls.__name__.lower() == "discount": # if "association" in cls.__name__.lower() or cls.__name__.lower() == "discount":
@@ -444,6 +450,9 @@ class BaseClass(Base):
else: else:
return super().__setattr__(key, value) return super().__setattr__(key, value)
def delete(self):
logger.error(f"Delete has not been implemented for {self.__class__.__name__}")
class ConfigItem(BaseClass): class ConfigItem(BaseClass):
""" """

View File

@@ -2,6 +2,8 @@
All control related models. All control related models.
""" """
from __future__ import annotations from __future__ import annotations
import itertools
from pprint import pformat from pprint import pformat
from PyQt6.QtWidgets import QWidget, QCheckBox, QLabel from PyQt6.QtWidgets import QWidget, QCheckBox, QLabel
from pandas import DataFrame from pandas import DataFrame
@@ -16,7 +18,6 @@ from typing import List, Literal, Tuple, Generator
from dateutil.parser import parse from dateutil.parser import parse
from re import Pattern from re import Pattern
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
@@ -29,8 +30,8 @@ class ControlType(BaseClass):
targets = Column(JSON) #: organisms checked for targets = Column(JSON) #: organisms checked for
instances = relationship("Control", back_populates="controltype") #: control samples created of this type. instances = relationship("Control", back_populates="controltype") #: control samples created of this type.
def __repr__(self) -> str: # def __repr__(self) -> str:
return f"<ControlType({self.name})>" # return f"<ControlType({self.name})>"
@classmethod @classmethod
@setup_lookup @setup_lookup
@@ -88,7 +89,7 @@ class ControlType(BaseClass):
Returns: Returns:
Control: Associated Control class Control: Associated Control class
""" """
return Control.find_polymorphic_subclass(polymorphic_identity=self.name) return Control.find_polymorphic_subclass(polymorphic_identity=self.name)
@classmethod @classmethod
@@ -103,7 +104,7 @@ class ControlType(BaseClass):
return (k for k, v in ct.items() if v) 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:
""" """
Creates a re.Pattern that will look for positive control types Creates a re.Pattern that will look for positive control types
@@ -305,10 +306,10 @@ class PCRControl(Control):
id = Column(INTEGER, ForeignKey('_control.id'), primary_key=True) id = Column(INTEGER, ForeignKey('_control.id'), primary_key=True)
subtype = Column(String(16)) #: PC or NC subtype = Column(String(16)) #: PC or NC
target = Column(String(16)) #: N1, N2, etc. target = Column(String(16)) #: N1, N2, etc.
ct = Column(FLOAT) #: PCR result ct = Column(FLOAT) #: PCR result
reagent_lot = Column(String(64), ForeignKey("_reagent.lot", ondelete="SET NULL", reagent_lot = Column(String(64), ForeignKey("_reagent.lot", ondelete="SET NULL",
name="fk_reagent_lot")) name="fk_reagent_lot"))
reagent = relationship("Reagent", foreign_keys=reagent_lot) #: reagent used for this control reagent = relationship("Reagent", foreign_keys=reagent_lot) #: reagent used for this control
__mapper_args__ = dict(polymorphic_identity="PCR Control", __mapper_args__ = dict(polymorphic_identity="PCR Control",
polymorphic_load="inline", polymorphic_load="inline",
@@ -320,7 +321,7 @@ class PCRControl(Control):
Returns: Returns:
dict: Output dict of name, ct, subtype, target, reagent_lot and submitted_date dict: Output dict of name, ct, subtype, target, reagent_lot and submitted_date
""" """
return dict( return dict(
name=self.name, name=self.name,
ct=self.ct, ct=self.ct,
@@ -343,7 +344,7 @@ class PCRControl(Control):
Returns: Returns:
Tuple[Report, "PCRFigure"]: Report of status and resulting figure. Tuple[Report, "PCRFigure"]: Report of status and resulting figure.
""" """
from frontend.visualizations.pcr_charts import PCRFigure from frontend.visualizations.pcr_charts import PCRFigure
parent.mode_typer.clear() parent.mode_typer.clear()
parent.mode_typer.setEnabled(False) parent.mode_typer.setEnabled(False)
@@ -352,6 +353,7 @@ class PCRControl(Control):
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)
# NOTE: Get all PCR controls with ct over 0
try: try:
df = df[df.ct > 0.0] df = df[df.ct > 0.0]
except AttributeError: except AttributeError:
@@ -361,11 +363,11 @@ class PCRControl(Control):
def to_pydantic(self): def to_pydantic(self):
from backend.validators import PydPCRControl from backend.validators import PydPCRControl
return PydPCRControl(**self.to_sub_dict(), controltype_name=self.controltype_name, submission_id=self.submission_id) return PydPCRControl(**self.to_sub_dict(), controltype_name=self.controltype_name,
submission_id=self.submission_id)
class IridaControl(Control): class IridaControl(Control):
subtyping_allowed = ['kraken'] subtyping_allowed = ['kraken']
id = Column(INTEGER, ForeignKey('_control.id'), primary_key=True) id = Column(INTEGER, ForeignKey('_control.id'), primary_key=True)
@@ -379,11 +381,19 @@ class IridaControl(Control):
sample = relationship("BacterialCultureSample", back_populates="control") #: This control's submission sample sample = relationship("BacterialCultureSample", back_populates="control") #: This control's submission sample
sample_id = Column(INTEGER, sample_id = Column(INTEGER,
ForeignKey("_basicsample.id", ondelete="SET NULL", name="cont_BCS_id")) #: sample id key ForeignKey("_basicsample.id", ondelete="SET NULL", name="cont_BCS_id")) #: sample id key
__mapper_args__ = dict(polymorphic_identity="Irida Control", __mapper_args__ = dict(polymorphic_identity="Irida Control",
polymorphic_load="inline", polymorphic_load="inline",
inherit_condition=(id == Control.id)) inherit_condition=(id == Control.id))
@property
def targets(self):
if self.controltype.targets:
return list(itertools.chain.from_iterable([value for key, value in self.controltype.targets.items()
if key == self.subtype]))
else:
return ["None"]
@validates("subtype") @validates("subtype")
def enforce_subtype_literals(self, key: str, value: str) -> str: def enforce_subtype_literals(self, key: str, value: str) -> str:
""" """
@@ -398,7 +408,7 @@ class IridaControl(Control):
Returns: Returns:
str: Validated string. str: Validated string.
""" """
acceptables = ['ATCC49226', 'ATCC49619', 'EN-NOS', "EN-SSTI", "MCS-NOS", "MCS-SSTI", "SN-NOS", "SN-SSTI"] acceptables = ['ATCC49226', 'ATCC49619', 'EN-NOS', "EN-SSTI", "MCS-NOS", "MCS-SSTI", "SN-NOS", "SN-SSTI"]
if value.upper() not in acceptables: if value.upper() not in acceptables:
raise KeyError(f"Sub-type must be in {acceptables}") raise KeyError(f"Sub-type must be in {acceptables}")
@@ -416,19 +426,15 @@ class IridaControl(Control):
except TypeError: except TypeError:
kraken = {} kraken = {}
kraken_cnt_total = sum([item['kraken_count'] for item in kraken.values()]) kraken_cnt_total = sum([item['kraken_count'] for item in kraken.values()])
new_kraken = [dict(name=item, kraken_count=kraken[item]['kraken_count'], new_kraken = [dict(name=key, kraken_count=value['kraken_count'],
kraken_percent=f"{kraken[item]['kraken_count'] / kraken_cnt_total:0.2%}", kraken_percent=f"{value['kraken_count'] / kraken_cnt_total:0.2%}",
target=item in self.controltype.targets) target=key in self.controltype.targets)
for item in kraken] for key, value in kraken.items()]
new_kraken = sorted(new_kraken, key=itemgetter('kraken_count'), reverse=True) new_kraken = sorted(new_kraken, key=itemgetter('kraken_count'), reverse=True)
if self.controltype.targets:
targets = self.controltype.targets
else:
targets = ["None"]
output = dict( output = dict(
name=self.name, name=self.name,
type=self.controltype.name, type=self.controltype.name,
targets=", ".join(targets), targets=", ".join(self.targets),
kraken=new_kraken[0:10] kraken=new_kraken[0:10]
) )
return output return output
@@ -521,7 +527,7 @@ class IridaControl(Control):
Returns: Returns:
Tuple[Report, "IridaFigure"]: Report of status and resulting figure. Tuple[Report, "IridaFigure"]: Report of status and resulting figure.
""" """
from frontend.visualizations import IridaFigure from frontend.visualizations import IridaFigure
try: try:
checker = parent.findChild(QCheckBox, name="irida_check") checker = parent.findChild(QCheckBox, name="irida_check")

View File

@@ -16,7 +16,6 @@ from pandas import ExcelFile
from pathlib import Path from pathlib import Path
from . import Base, BaseClass, Organization, LogMixin from . import Base, BaseClass, Organization, LogMixin
from io import BytesIO from io import BytesIO
from inspect import getouterframes, currentframe
logger = logging.getLogger(f'submissions.{__name__}') logger = logging.getLogger(f'submissions.{__name__}')
@@ -98,7 +97,6 @@ class KitType(BaseClass):
Base of kits used in submission processing Base of kits used in submission processing
""" """
# query_alias = "kit_type"
omni_sort = BaseClass.omni_sort + ["kit_submissiontype_associations", "kit_reagentrole_associations", "processes"] omni_sort = BaseClass.omni_sort + ["kit_submissiontype_associations", "kit_reagentrole_associations", "processes"]
id = Column(INTEGER, primary_key=True) #: primary key id = Column(INTEGER, primary_key=True) #: primary key
@@ -128,13 +126,6 @@ class KitType(BaseClass):
creator=lambda ST: SubmissionTypeKitTypeAssociation( creator=lambda ST: SubmissionTypeKitTypeAssociation(
submission_type=ST)) #: Association proxy to SubmissionTypeKitTypeAssociation submission_type=ST)) #: Association proxy to SubmissionTypeKitTypeAssociation
def __repr__(self) -> str:
"""
Returns:
str: A representation of the object.
"""
return f"<KitType({self.name})>"
@classproperty @classproperty
def aliases(cls): def aliases(cls):
return super().aliases + [cls.query_alias, "kit_types", "kit_type"] return super().aliases + [cls.query_alias, "kit_types", "kit_type"]
@@ -187,9 +178,7 @@ class KitType(BaseClass):
# NOTE: Account for submission_type variable type. # NOTE: Account for submission_type variable type.
match submission_type: match submission_type:
case str(): case str():
# assocs = [item for item in self.kit_reagentrole_associations if # logger.debug(f"Query for {submission_type}")
# item.submission_type.name == submission_type]
logger.debug(f"Query for {submission_type}")
submission_type = SubmissionType.query(name=submission_type) submission_type = SubmissionType.query(name=submission_type)
case SubmissionType(): case SubmissionType():
pass pass
@@ -213,18 +202,12 @@ class KitType(BaseClass):
) )
if dlg.exec(): if dlg.exec():
dlg_result = dlg.parse_form() dlg_result = dlg.parse_form()
logger.debug(f"Dialog result: {dlg_result}") # logger.debug(f"Dialog result: {dlg_result}")
new_kit = self.__class__.query(name=dlg_result) new_kit = self.__class__.query(name=dlg_result)
logger.debug(f"Query result: {new_kit}") # logger.debug(f"Query result: {new_kit}")
# return new_kit.construct_xl_map_for_use(submission_type=submission_type)
else: else:
return None, new_kit return None, new_kit
assocs = [item for item in new_kit.kit_reagentrole_associations if item.submission_type == submission_type] assocs = [item for item in new_kit.kit_reagentrole_associations if item.submission_type == submission_type]
# for assoc in assocs:
# try:
# yield assoc.reagent_role.name, assoc.uses
# except TypeError:
# continue
output = {assoc.reagent_role.name: assoc.uses for assoc in assocs} output = {assoc.reagent_role.name: assoc.uses for assoc in assocs}
# logger.debug(f"Output: {output}") # logger.debug(f"Output: {output}")
return output, new_kit return output, new_kit
@@ -232,7 +215,6 @@ class KitType(BaseClass):
@classmethod @classmethod
def query_or_create(cls, **kwargs) -> Tuple[KitType, bool]: def query_or_create(cls, **kwargs) -> Tuple[KitType, bool]:
from backend.validators.pydant import PydKitType from backend.validators.pydant import PydKitType
from backend.validators.omni_gui_objects import BaseOmni
new = False new = False
disallowed = ['expiry'] disallowed = ['expiry']
sanitized_kwargs = {k: v for k, v in kwargs.items() if k not in disallowed} sanitized_kwargs = {k: v for k, v in kwargs.items() if k not in disallowed}
@@ -248,7 +230,7 @@ class KitType(BaseClass):
@setup_lookup @setup_lookup
def query(cls, def query(cls,
name: str = None, name: str = None,
used_for: str | SubmissionType | None = None, submissiontype: str | SubmissionType | None = None,
id: int | None = None, id: int | None = None,
limit: int = 0, limit: int = 0,
**kwargs **kwargs
@@ -266,11 +248,11 @@ class KitType(BaseClass):
KitType|List[KitType]: KitType(s) of interest. KitType|List[KitType]: KitType(s) of interest.
""" """
query: Query = cls.__database_session__.query(cls) query: Query = cls.__database_session__.query(cls)
match used_for: match submissiontype:
case str(): case str():
query = query.filter(cls.used_for.any(name=used_for)) query = query.filter(cls.submissiontype.any(name=submissiontype))
case SubmissionType(): case SubmissionType():
query = query.filter(cls.used_for.contains(used_for)) query = query.filter(cls.submissiontype.contains(submissiontype))
case _: case _:
pass pass
match name: match name:
@@ -399,9 +381,6 @@ class KitType(BaseClass):
def to_omni(self, expand: bool = False) -> "OmniKitType": def to_omni(self, expand: bool = False) -> "OmniKitType":
from backend.validators.omni_gui_objects import OmniKitType from backend.validators.omni_gui_objects import OmniKitType
# logger.debug(f"self.name: {self.name}")
# level = len(getouterframes(currentframe()))
# logger.warning(f"Function level is {level}")
if expand: if expand:
processes = [item.to_omni() for item in self.processes] processes = [item.to_omni() for item in self.processes]
kit_reagentrole_associations = [item.to_omni() for item in self.kit_reagentrole_associations] kit_reagentrole_associations = [item.to_omni() for item in self.kit_reagentrole_associations]
@@ -425,6 +404,8 @@ class ReagentRole(BaseClass):
Base of reagent type abstract Base of reagent type abstract
""" """
skip_on_edit = False
id = Column(INTEGER, primary_key=True) #: primary key id = Column(INTEGER, primary_key=True) #: primary key
name = Column(String(64)) #: name of role reagent plays name = Column(String(64)) #: name of role reagent plays
instances = relationship("Reagent", back_populates="role", instances = relationship("Reagent", back_populates="role",
@@ -442,13 +423,6 @@ class ReagentRole(BaseClass):
creator=lambda kit: KitTypeReagentRoleAssociation( creator=lambda kit: KitTypeReagentRoleAssociation(
kit_type=kit)) #: Association proxy to KitTypeReagentRoleAssociation kit_type=kit)) #: Association proxy to KitTypeReagentRoleAssociation
def __repr__(self) -> str:
"""
Returns:
str: Representation of object
"""
return f"<ReagentRole({self.name})>"
@classmethod @classmethod
def query_or_create(cls, **kwargs) -> Tuple[ReagentRole, bool]: def query_or_create(cls, **kwargs) -> Tuple[ReagentRole, bool]:
new = False new = False
@@ -603,7 +577,6 @@ class Reagent(BaseClass, LogMixin):
Returns: Returns:
dict: representation of the reagent's attributes dict: representation of the reagent's attributes
""" """
if extraction_kit is not None: if extraction_kit is not None:
# NOTE: Get the intersection of this reagent's ReagentType and all ReagentTypes in KitType # NOTE: Get the intersection of this reagent's ReagentType and all ReagentTypes in KitType
reagent_role = next((item for item in set(self.role).intersection(extraction_kit.reagent_roles)), reagent_role = next((item for item in set(self.role).intersection(extraction_kit.reagent_roles)),
@@ -713,8 +686,6 @@ class Reagent(BaseClass, LogMixin):
limit = 1 limit = 1
case _: case _:
pass pass
# if not role and "reagentrole" in kwargs.keys():
# role = kwargs['reagentrole']
match role: match role:
case str(): case str():
query = query.join(cls.role).filter(ReagentRole.name == role) query = query.join(cls.role).filter(ReagentRole.name == role)
@@ -772,6 +743,7 @@ class Reagent(BaseClass, LogMixin):
@check_authorization @check_authorization
def edit_from_search(self, obj, **kwargs): def edit_from_search(self, obj, **kwargs):
from frontend.widgets.omni_add_edit import AddEdit from frontend.widgets.omni_add_edit import AddEdit
# logger.debug(f"Calling edit_from_search for {self.name}")
dlg = AddEdit(parent=None, instance=self) dlg = AddEdit(parent=None, instance=self)
if dlg.exec(): if dlg.exec():
pyd = dlg.parse_form() pyd = dlg.parse_form()
@@ -1377,20 +1349,6 @@ class SubmissionType(BaseClass):
def to_omni(self, expand: bool = False): def to_omni(self, expand: bool = False):
from backend.validators.omni_gui_objects import OmniSubmissionType from backend.validators.omni_gui_objects import OmniSubmissionType
# level = len(getouterframes(currentframe()))
# logger.warning(f"Function level is {level}")
# try:
# info_map = self.submission_type.info_map
# except AttributeError:
# info_map = {}
# try:
# defaults = self.submission_type.defaults
# except AttributeError:
# defaults = {}
# try:
# sample_map = self.submission_type.sample_map
# except AttributeError:
# sample_map = {}
try: try:
template_file = self.template_file template_file = self.template_file
except AttributeError: except AttributeError:
@@ -1555,8 +1513,6 @@ class SubmissionTypeKitTypeAssociation(BaseClass):
def to_omni(self, expand: bool = False): def to_omni(self, expand: bool = False):
from backend.validators.omni_gui_objects import OmniSubmissionTypeKitTypeAssociation from backend.validators.omni_gui_objects import OmniSubmissionTypeKitTypeAssociation
# level = len(getouterframes(currentframe()))
# logger.warning(f"Function level is {level}")
if expand: if expand:
try: try:
submissiontype = self.submission_type.to_omni() submissiontype = self.submission_type.to_omni()
@@ -1569,10 +1525,6 @@ class SubmissionTypeKitTypeAssociation(BaseClass):
else: else:
submissiontype = self.submission_type.name submissiontype = self.submission_type.name
kittype = self.kit_type.name kittype = self.kit_type.name
# try:
# processes = [item.to_omni() for item in self.submission_type.processes]
# except AttributeError:
# processes = []
return OmniSubmissionTypeKitTypeAssociation( return OmniSubmissionTypeKitTypeAssociation(
instance_object=self, instance_object=self,
submissiontype=submissiontype, submissiontype=submissiontype,
@@ -1580,7 +1532,6 @@ class SubmissionTypeKitTypeAssociation(BaseClass):
mutable_cost_column=self.mutable_cost_column, mutable_cost_column=self.mutable_cost_column,
mutable_cost_sample=self.mutable_cost_sample, mutable_cost_sample=self.mutable_cost_sample,
constant_cost=self.constant_cost constant_cost=self.constant_cost
# processes=processes,
) )
@@ -1719,7 +1670,6 @@ class KitTypeReagentRoleAssociation(BaseClass):
pass pass
setattr(instance, k, v) setattr(instance, k, v)
logger.info(f"Instance from query or create: {instance.__dict__}\nis new: {new}") logger.info(f"Instance from query or create: {instance.__dict__}\nis new: {new}")
# sys.exit()
return instance, new return instance, new
@classmethod @classmethod
@@ -1854,6 +1804,8 @@ class SubmissionReagentAssociation(BaseClass):
DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
""" """
skip_on_edit = True
reagent_id = Column(INTEGER, ForeignKey("_reagent.id"), primary_key=True) #: id of associated reagent reagent_id = Column(INTEGER, ForeignKey("_reagent.id"), primary_key=True) #: id of associated reagent
submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id"), primary_key=True) #: id of associated submission submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id"), primary_key=True) #: id of associated submission
comments = Column(String(1024)) #: Comments about reagents comments = Column(String(1024)) #: Comments about reagents
@@ -1869,10 +1821,10 @@ class SubmissionReagentAssociation(BaseClass):
str: Representation of this SubmissionReagentAssociation str: Representation of this SubmissionReagentAssociation
""" """
try: try:
return f"<{self.submission.rsl_plate_num} & {self.reagent.lot}>" return f"<SubmissionReagentAssociation({self.submission.rsl_plate_num} & {self.reagent.lot})>"
except AttributeError: except AttributeError:
logger.error(f"Reagent {self.reagent.lot} submission association {self.reagent_id} has no submissions!") logger.error(f"Reagent {self.reagent.lot} submission association {self.reagent_id} has no submissions!")
return f"<Unknown Submission & {self.reagent.lot}>" return f"<SubmissionReagentAssociation(Unknown Submission & {self.reagent.lot})>"
def __init__(self, reagent=None, submission=None): def __init__(self, reagent=None, submission=None):
if isinstance(reagent, list): if isinstance(reagent, list):
@@ -1963,12 +1915,12 @@ class Equipment(BaseClass, LogMixin):
submissions = association_proxy("equipment_submission_associations", submissions = association_proxy("equipment_submission_associations",
"submission") #: proxy to equipment_submission_associations.submission "submission") #: proxy to equipment_submission_associations.submission
def __repr__(self) -> str: # def __repr__(self) -> str:
""" # """
Returns: # Returns:
str: representation of this Equipment # str: representation of this Equipment
""" # """
return f"<Equipment({self.name})>" # return f"<Equipment({self.name})>"
def to_dict(self, processes: bool = False) -> dict: def to_dict(self, processes: bool = False) -> dict:
""" """
@@ -1987,7 +1939,7 @@ class Equipment(BaseClass, LogMixin):
def get_processes(self, submission_type: str | SubmissionType | None = None, def get_processes(self, submission_type: str | SubmissionType | None = None,
extraction_kit: str | KitType | None = None, extraction_kit: str | KitType | None = None,
equipment_role: str | EquipmentRole | None = None) -> List[str]: equipment_role: str | EquipmentRole | None = None) -> Generator[Process, None, None]:
""" """
Get all processes associated with this Equipment for a given SubmissionType Get all processes associated with this Equipment for a given SubmissionType
@@ -2133,12 +2085,12 @@ class EquipmentRole(BaseClass):
submission_types = association_proxy("equipmentrole_submissiontype_associations", submission_types = association_proxy("equipmentrole_submissiontype_associations",
"submission_type") #: proxy to equipmentrole_submissiontype_associations.submission_type "submission_type") #: proxy to equipmentrole_submissiontype_associations.submission_type
def __repr__(self) -> str: # def __repr__(self) -> str:
""" # """
Returns: # Returns:
str: Representation of this EquipmentRole # str: Representation of this EquipmentRole
""" # """
return f"<EquipmentRole({self.name})>" # return f"<EquipmentRole({self.name})>"
def to_dict(self) -> dict: def to_dict(self) -> dict:
""" """
@@ -2395,7 +2347,6 @@ class Process(BaseClass):
id = Column(INTEGER, primary_key=True) #: Process id, primary key id = Column(INTEGER, primary_key=True) #: Process id, primary key
name = Column(String(64), unique=True) #: Process name name = Column(String(64), unique=True) #: Process name
# version = Column(String(32))
submission_types = relationship("SubmissionType", back_populates='processes', submission_types = relationship("SubmissionType", back_populates='processes',
secondary=submissiontypes_processes) #: relation to SubmissionType secondary=submissiontypes_processes) #: relation to SubmissionType
equipment = relationship("Equipment", back_populates='processes', equipment = relationship("Equipment", back_populates='processes',
@@ -2410,12 +2361,12 @@ class Process(BaseClass):
secondary=process_tiprole) #: relation to KitType secondary=process_tiprole) #: relation to KitType
def __repr__(self) -> str: # def __repr__(self) -> str:
""" # """
Returns: # Returns:
str: Representation of this Process # str: Representation of this Process
""" # """
return f"<Process({self.name})>" # return f"<Process({self.name})>"
def set_attribute(self, key, value): def set_attribute(self, key, value):
match key: match key:
@@ -2506,13 +2457,20 @@ class Process(BaseClass):
def to_omni(self, expand: bool = False): def to_omni(self, expand: bool = False):
from backend.validators.omni_gui_objects import OmniProcess from backend.validators.omni_gui_objects import OmniProcess
if expand:
submission_types = [item.to_omni() for item in self.submission_types]
equipment_roles = [item.to_omni() for item in self.equipment_roles]
tip_roles = [item.to_omni() for item in self.tip_roles]
else:
submission_types = [item.name for item in self.submission_types]
equipment_roles = [item.name for item in self.equipment_roles]
tip_roles = [item.name for item in self.tip_roles]
return OmniProcess( return OmniProcess(
instance_object=self, instance_object=self,
name=self.name, name=self.name,
# version=self.version, submission_types=submission_types,
submission_types=[item.to_omni() for item in self.submission_types], equipment_roles=equipment_roles,
equipment_roles=[item.to_omni() for item in self.equipment_roles], tip_roles=tip_roles
tip_roles=[item.to_omni() for item in self.tip_roles]
) )
@@ -2538,8 +2496,8 @@ class TipRole(BaseClass):
def tips(self): def tips(self):
return self.instances return self.instances
def __repr__(self): # def __repr__(self):
return f"<TipRole({self.name})>" # return f"<TipRole({self.name})>"
@classmethod @classmethod
def query_or_create(cls, **kwargs) -> Tuple[TipRole, bool]: def query_or_create(cls, **kwargs) -> Tuple[TipRole, bool]:
@@ -2573,10 +2531,14 @@ class TipRole(BaseClass):
def to_omni(self, expand: bool = False): def to_omni(self, expand: bool = False):
from backend.validators.omni_gui_objects import OmniTipRole from backend.validators.omni_gui_objects import OmniTipRole
if expand:
tips = [item.to_omni() for item in self.tips]
else:
tips = [item.name for item in self.tips]
return OmniTipRole( return OmniTipRole(
instance_object=self, instance_object=self,
name=self.name, name=self.name,
tips=[item.to_omni() for item in self.tips] tips=tips
) )
@@ -2605,8 +2567,8 @@ class Tips(BaseClass, LogMixin):
def tiprole(self): def tiprole(self):
return self.role return self.role
def __repr__(self): # def __repr__(self):
return f"<Tips({self.name})>" # return f"<Tips({self.name})>"
@classmethod @classmethod
def query_or_create(cls, **kwargs) -> Tuple[Tips, bool]: def query_or_create(cls, **kwargs) -> Tuple[Tips, bool]:

View File

@@ -29,8 +29,6 @@ class Organization(BaseClass):
Base of organization Base of organization
""" """
id = Column(INTEGER, primary_key=True) #: primary key id = Column(INTEGER, primary_key=True) #: primary key
name = Column(String(64)) #: organization name name = Column(String(64)) #: organization name
submissions = relationship("BasicSubmission", submissions = relationship("BasicSubmission",
@@ -43,8 +41,8 @@ class Organization(BaseClass):
def contact(self): def contact(self):
return self.contacts return self.contacts
def __repr__(self) -> str: # def __repr__(self) -> str:
return f"<Organization({self.name})>" # return f"<Organization({self.name})>"
@classmethod @classmethod
@setup_lookup @setup_lookup
@@ -139,8 +137,8 @@ class Contact(BaseClass):
secondary=orgs_contacts) #: relationship to joined organization secondary=orgs_contacts) #: relationship to joined organization
submissions = relationship("BasicSubmission", back_populates="contact") #: submissions this contact has submitted submissions = relationship("BasicSubmission", back_populates="contact") #: submissions this contact has submitted
def __repr__(self) -> str: # def __repr__(self) -> str:
return f"<Contact({self.name})>" # return f"<Contact({self.name})>"
@classproperty @classproperty
def searchables(cls): def searchables(cls):

View File

@@ -402,7 +402,6 @@ class BasicSubmission(BaseClass, LogMixin):
columns = set([assoc.column for assoc in self.submission_sample_associations]) columns = set([assoc.column for assoc in self.submission_sample_associations])
return len(columns) return len(columns)
def calculate_base_cost(self) -> None: def calculate_base_cost(self) -> None:
""" """
Calculates cost of the plate Calculates cost of the plate
@@ -733,7 +732,7 @@ class BasicSubmission(BaseClass, LogMixin):
@classmethod @classmethod
def find_polymorphic_subclass(cls, polymorphic_identity: str | SubmissionType | None = None, def find_polymorphic_subclass(cls, polymorphic_identity: str | SubmissionType | None = None,
attrs: dict | None = None): attrs: dict | None = None) -> BasicSubmission:
""" """
Find subclass based on polymorphic identity or relevant attributes. Find subclass based on polymorphic identity or relevant attributes.

View File

@@ -59,7 +59,6 @@ class SheetParser(object):
Pulls basic information from the excel sheet Pulls basic information from the excel sheet
""" """
parser = InfoParser(xl=self.xl, submission_type=self.submission_type, sub_object=self.sub_object) parser = InfoParser(xl=self.xl, submission_type=self.submission_type, sub_object=self.sub_object)
# info = parser.parsed_info
self.info_map = parser.info_map self.info_map = parser.info_map
# NOTE: in order to accommodate generic submission types we have to check for the type in the excel sheet and rerun accordingly # NOTE: in order to accommodate generic submission types we have to check for the type in the excel sheet and rerun accordingly
try: try:
@@ -274,37 +273,12 @@ class ReagentParser(object):
Returns: Returns:
dict: locations of reagent info for the kit. dict: locations of reagent info for the kit.
""" """
# report = Report()
# if isinstance(submission_type, dict):
# submission_type = submission_type['value']
# if isinstance(submission_type, str):
# submission_type = SubmissionType.query(name=submission_type)
# logger.debug("Running kit map")
associations, self.kit_object = self.kit_object.construct_xl_map_for_use(submission_type=self.submission_type_obj) associations, self.kit_object = self.kit_object.construct_xl_map_for_use(submission_type=self.submission_type_obj)
reagent_map = {k: v for k, v in associations.items() if k != 'info'} reagent_map = {k: v for k, v in associations.items() if k != 'info'}
try: try:
del reagent_map['info'] del reagent_map['info']
except KeyError: except KeyError:
pass pass
# # NOTE: If reagent map is empty, maybe the wrong kit was given, check if there's only one kit for that submission type and use it if so.
# if not reagent_map:
# temp_kit_object = self.submission_type_obj.default_kit
# if temp_kit_object:
# self.kit_object = temp_kit_object
# logger.warning(f"Attempting to salvage with default kit {self.kit_object} and submission_type: {self.submission_type_obj}")
# return self.fetch_kit_map(submission_type=self.submission_type_obj)
# else:
# logger.error(f"Still no reagent map, displaying error.")
# 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 (IndexError, KeyError):
# location_string = ""
# 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 put the right kit in:\n\n{location_string}?",
# status="Critical"))
# logger.debug(f"Here is the map coming out: {reagent_map}")
return reagent_map return reagent_map
@property @property
@@ -375,9 +349,6 @@ class SampleParser(object):
self.sub_object = sub_object self.sub_object = sub_object
self.sample_type = self.sub_object.get_default_info("sample_type", submission_type=submission_type) self.sample_type = self.sub_object.get_default_info("sample_type", submission_type=submission_type)
self.samp_object = BasicSample.find_polymorphic_subclass(polymorphic_identity=self.sample_type) self.samp_object = BasicSample.find_polymorphic_subclass(polymorphic_identity=self.sample_type)
# self.sample_map = self.sample_map(submission_type=submission_type, sample_map=sample_map)
# self.plate_map_samples = self.parse_plate_map()
# self.lookup_samples = self.parse_lookup_table()
@property @property
def sample_map(self) -> dict: def sample_map(self) -> dict:
@@ -391,11 +362,6 @@ class SampleParser(object):
dict: Info locations. dict: Info locations.
""" """
# if sample_map is None:
# sample_info_map = self.sub_object.construct_sample_map(submission_type=self.submission_type_obj)
# else:
# sample_info_map = sample_map
# return sample_info_map
return self.sub_object.construct_sample_map(submission_type=self.submission_type_obj) return self.sub_object.construct_sample_map(submission_type=self.submission_type_obj)
@property @property
@@ -519,7 +485,6 @@ class EquipmentParser(object):
submission_type = SubmissionType.query(name=submission_type) submission_type = SubmissionType.query(name=submission_type)
self.submission_type = submission_type self.submission_type = submission_type
self.xl = xl self.xl = xl
# self.equipment_map = self.fetch_equipment_map()
@property @property
def equipment_map(self) -> dict: def equipment_map(self) -> dict:
@@ -597,7 +562,6 @@ class TipParser(object):
submission_type = SubmissionType.query(name=submission_type) submission_type = SubmissionType.query(name=submission_type)
self.submission_type = submission_type self.submission_type = submission_type
self.xl = xl self.xl = xl
# self.map = self.fetch_tip_map()
@property @property
def tip_map(self) -> dict: def tip_map(self) -> dict:
@@ -669,7 +633,6 @@ class PCRParser(object):
else: else:
self.submission_obj = submission self.submission_obj = submission
rsl_plate_num = self.submission_obj.rsl_plate_num rsl_plate_num = self.submission_obj.rsl_plate_num
# self.pcr = self.parse_general()
self.samples = self.submission_obj.parse_pcr(xl=self.xl, rsl_plate_num=rsl_plate_num) self.samples = self.submission_obj.parse_pcr(xl=self.xl, rsl_plate_num=rsl_plate_num)
self.controls = self.submission_obj.parse_pcr_controls(xl=self.xl, rsl_plate_num=rsl_plate_num) self.controls = self.submission_obj.parse_pcr_controls(xl=self.xl, rsl_plate_num=rsl_plate_num)

View File

@@ -28,7 +28,8 @@ class RSLNamer(object):
logger.info(f"got submission type: {self.submission_type}") logger.info(f"got submission type: {self.submission_type}")
if self.submission_type: if self.submission_type:
self.sub_object = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.submission_type) self.sub_object = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.submission_type)
self.parsed_name = self.retrieve_rsl_number(filename=filename, regex=self.sub_object.get_regex(submission_type=submission_type)) self.parsed_name = self.retrieve_rsl_number(filename=filename, regex=self.sub_object.get_regex(
submission_type=submission_type))
if not data: if not data:
data = dict(submission_type=self.submission_type) data = dict(submission_type=self.submission_type)
if "submission_type" not in data.keys(): if "submission_type" not in data.keys():
@@ -50,24 +51,36 @@ class RSLNamer(object):
Returns: Returns:
str: parsed submission type str: parsed submission type
""" """
def st_from_path(filename:Path) -> str:
if filename.exists(): def st_from_path(filepath: Path) -> str:
wb = load_workbook(filename) """
Sub def to get submissiontype from a file path
Args:
filepath ():
Returns:
"""
if filepath.exists():
wb = load_workbook(filepath)
try: try:
# NOTE: Gets first category in the metadata. # NOTE: Gets first category in the metadata.
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.template_file_sheets for item in SubmissionType.query() if item.template_file} sts = {item.name: item.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:
# NOTE: On failure recurse using filename as string for string method # NOTE: On failure recurse using filepath as string for string method
submission_type = cls.retrieve_submission_type(filename=filename.stem.__str__()) submission_type = cls.retrieve_submission_type(filename=filepath.stem.__str__())
else: else:
submission_type = cls.retrieve_submission_type(filename=filename.stem.__str__()) submission_type = cls.retrieve_submission_type(filename=filepath.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"): if filename.startswith("tmp"):
return "Bacterial Culture" return "Bacterial Culture"
regex = BasicSubmission.regex regex = BasicSubmission.regex
@@ -78,9 +91,10 @@ class RSLNamer(object):
submission_type = None submission_type = None
logger.critical(f"No submission type found or submission type found!: {e}") logger.critical(f"No submission type found or submission type found!: {e}")
return submission_type return submission_type
match filename: match filename:
case Path(): case Path():
submission_type = st_from_path(filename=filename) submission_type = st_from_path(filepath=filename)
case str(): case str():
submission_type = st_from_str(filename=filename) submission_type = st_from_str(filename=filename)
case _: case _:

View File

@@ -1,5 +1,5 @@
from __future__ import annotations from __future__ import annotations
import logging, sys import logging
from pydantic import BaseModel, field_validator, Field from pydantic import BaseModel, field_validator, Field
from typing import List, ClassVar from typing import List, ClassVar
from backend.db.models import * from backend.db.models import *
@@ -16,7 +16,7 @@ class BaseOmni(BaseModel):
try: try:
return f"<{self.__class__.__name__}({self.name})>" return f"<{self.__class__.__name__}({self.name})>"
except AttributeError: except AttributeError:
return f"<{self.__class__.__name__}(NO NAME)>" return f"<{self.__class__.__name__}({self.__repr_name__})>"
@classproperty @classproperty
def aliases(cls): def aliases(cls):
@@ -478,7 +478,6 @@ class OmniProcess(BaseOmni):
# NOTE: How am I going to figure out relatioinships without getting into recursion issues? # NOTE: How am I going to figure out relatioinships without getting into recursion issues?
name: str = Field(default="", description="property") #: Process name name: str = Field(default="", description="property") #: Process name
# version: str = Field(default="", description="property") #: Version (string to account for "in_use" or whatever)
submission_types: List[OmniSubmissionType] | List[str] = Field(default=[], description="relationship", submission_types: List[OmniSubmissionType] | List[str] = Field(default=[], description="relationship",
title="SubmissionType") title="SubmissionType")
equipment_roles: List[OmniEquipmentRole] | List[str] = Field(default=[], description="relationship", equipment_roles: List[OmniEquipmentRole] | List[str] = Field(default=[], description="relationship",
@@ -507,13 +506,6 @@ class OmniProcess(BaseOmni):
return "" return ""
return value return value
# @field_validator("version", mode="before")
# @classmethod
# def rescue_name_none(cls, value):
# if not value:
# return "1"
# return value
def to_sql(self): def to_sql(self):
instance, new = self.class_object.query_or_create(name=self.name) instance, new = self.class_object.query_or_create(name=self.name)
for st in self.submission_types: for st in self.submission_types:
@@ -539,12 +531,8 @@ class OmniKitType(BaseOmni):
class_object: ClassVar[Any] = KitType class_object: ClassVar[Any] = KitType
name: str = Field(default="", description="property") name: str = Field(default="", description="property")
kit_submissiontype_associations: List[OmniSubmissionTypeKitTypeAssociation] | List[str] = Field(default=[], kit_submissiontype_associations: List[OmniSubmissionTypeKitTypeAssociation] | List[str] = Field(default=[], description="relationship", title="SubmissionTypeKitTypeAssociation")
description="relationship", kit_reagentrole_associations: List[OmniKitTypeReagentRoleAssociation] | List[str] = Field(default=[], description="relationship", title="KitTypeReagentRoleAssociation")
title="SubmissionTypeKitTypeAssociation")
kit_reagentrole_associations: List[OmniKitTypeReagentRoleAssociation] | List[str] = Field(default=[],
description="relationship",
title="KitTypeReagentRoleAssociation")
processes: List[OmniProcess] | List[str] = Field(default=[], description="relationship", title="Process") processes: List[OmniProcess] | List[str] = Field(default=[], description="relationship", title="Process")
@field_validator("name", mode="before") @field_validator("name", mode="before")
@@ -577,7 +565,6 @@ class OmniKitType(BaseOmni):
new_rr.append(new_assoc) new_rr.append(new_assoc)
logger.debug(f"Setting kit_reagentrole_associations to {pformat([item.__dict__ for item in new_rr])}") logger.debug(f"Setting kit_reagentrole_associations to {pformat([item.__dict__ for item in new_rr])}")
kit.kit_reagentrole_associations = new_rr kit.kit_reagentrole_associations = new_rr
# sys.exit()
new_st = [] new_st = []
for st_assoc in self.kit_submissiontype_associations: for st_assoc in self.kit_submissiontype_associations:
new_assoc = st_assoc.to_sql() new_assoc = st_assoc.to_sql()

View File

@@ -288,8 +288,6 @@ class PydTips(BaseModel):
tips = Tips.query(name=self.name, limit=1) tips = Tips.query(name=self.name, limit=1)
# logger.debug(f"Tips query has yielded: {tips}") # logger.debug(f"Tips query has yielded: {tips}")
assoc = SubmissionTipsAssociation.query_or_create(tips=tips, submission=submission, role=self.role, limit=1) assoc = SubmissionTipsAssociation.query_or_create(tips=tips, submission=submission, role=self.role, limit=1)
# if assoc is None:
# assoc = SubmissionTipsAssociation(submission=submission, tips=tips, role_name=self.role)
return assoc, report return assoc, report
@@ -355,14 +353,13 @@ class PydEquipment(BaseModel, extra='ignore'):
# TODO: This seems precarious. What if there is more than one process? # TODO: This seems precarious. What if there is more than one process?
# NOTE: It looks like the way fetching the processes is done in the SQL model, this shouldn't be a problem, but I'll include a failsafe. # NOTE: It looks like the way fetching the processes is done in the SQL model, this shouldn't be a problem, but I'll include a failsafe.
# NOTE: I need to find a way to filter this by the kit involved. # NOTE: I need to find a way to filter this by the kit involved.
if len(self.processes) > 1: if len(self.processes) > 1:
process = Process.query(submissiontype=submission.get_submission_type(), kittype=extraction_kit, equipmentrole=self.role) process = Process.query(submissiontype=submission.get_submission_type(), kittype=extraction_kit, equipmentrole=self.role)
else: else:
process = Process.query(name=self.processes[0]) process = Process.query(name=self.processes[0])
if process is None: if process is None:
logger.error(f"Found unknown process: {process}.") logger.error(f"Found unknown process: {process}.")
logger.debug(f"Using process: {process}") # logger.debug(f"Using process: {process}")
assoc.process = process assoc.process = process
assoc.role = self.role assoc.role = self.role
else: else:
@@ -746,7 +743,16 @@ class PydSubmission(BaseModel, extra='allow'):
output = {k: self.filter_field(k) for k in fields} output = {k: self.filter_field(k) for k in fields}
return output return output
def filter_field(self, key: str): def filter_field(self, key: str) -> Any:
"""
Attempts to get value from field dictionary
Args:
key (str): name of the field of interest
Returns:
Any (): Value found.
"""
item = getattr(self, key) item = getattr(self, key)
match item: match item:
case dict(): case dict():
@@ -780,9 +786,8 @@ class PydSubmission(BaseModel, extra='allow'):
""" """
report = Report() report = Report()
dicto = self.improved_dict() dicto = self.improved_dict()
logger.debug(f"Pydantic submission type: {self.submission_type['value']}") # logger.debug(f"Pydantic submission type: {self.submission_type['value']}")
logger.debug(f"Pydantic improved_dict: {pformat(dicto)}") # logger.debug(f"Pydantic improved_dict: {pformat(dicto)}")
# At this point, pcr_info is not duplicated
instance, result = BasicSubmission.query_or_create(submission_type=self.submission_type['value'], instance, result = BasicSubmission.query_or_create(submission_type=self.submission_type['value'],
rsl_plate_num=self.rsl_plate_num['value']) rsl_plate_num=self.rsl_plate_num['value'])
# logger.debug(f"Created or queried instance: {instance}") # logger.debug(f"Created or queried instance: {instance}")
@@ -792,8 +797,7 @@ class PydSubmission(BaseModel, extra='allow'):
report.add_result(result) report.add_result(result)
self.handle_duplicate_samples() self.handle_duplicate_samples()
for key, value in dicto.items(): for key, value in dicto.items():
logger.debug(f"Checking key {key}, value {value}") # logger.debug(f"Checking key {key}, value {value}")
# At this point, pcr_info is not duplicated.
if isinstance(value, dict): if isinstance(value, dict):
try: try:
value = value['value'] value = value['value']
@@ -849,8 +853,7 @@ class PydSubmission(BaseModel, extra='allow'):
value = value value = value
instance.set_attribute(key=key, value=value) instance.set_attribute(key=key, value=value)
case item if item in instance.jsons: case item if item in instance.jsons:
# At this point pcr_info is not duplicated # logger.debug(f"Validating json value: {item} to value:{pformat(value)}")
logger.debug(f"Validating json value: {item} to value:{pformat(value)}")
try: try:
ii = value.items() ii = value.items()
except AttributeError: except AttributeError:
@@ -860,8 +863,7 @@ class PydSubmission(BaseModel, extra='allow'):
value[k] = v.strftime("%Y-%m-%d %H:%M:%S") value[k] = v.strftime("%Y-%m-%d %H:%M:%S")
else: else:
pass pass
logger.debug(f"Setting json value: {item} to value:{pformat(value)}") # logger.debug(f"Setting json value: {item} to value:{pformat(value)}")
# At this point, pcr_info is not duplicated.
instance.set_attribute(key=key, value=value) instance.set_attribute(key=key, value=value)
case _: case _:
try: try:
@@ -878,7 +880,6 @@ class PydSubmission(BaseModel, extra='allow'):
continue continue
else: else:
logger.warning(f"{key} already == {value} so no updating.") logger.warning(f"{key} already == {value} so no updating.")
logger.debug(f"Entering cost calculation for {instance}")
try: try:
instance.calculate_base_cost() instance.calculate_base_cost()
except (TypeError, AttributeError) as e: except (TypeError, AttributeError) as e:
@@ -937,7 +938,6 @@ class PydSubmission(BaseModel, extra='allow'):
"/", "") "/", "")
return render return render
# @report_result
def check_kit_integrity(self, extraction_kit: str | dict | None = None, exempt: List[PydReagent] = []) -> Tuple[ def check_kit_integrity(self, extraction_kit: str | dict | None = None, exempt: List[PydReagent] = []) -> Tuple[
List[PydReagent], Report, List[PydReagent]]: List[PydReagent], Report, List[PydReagent]]:
""" """
@@ -1212,7 +1212,6 @@ class PydIridaControl(BaseModel, extra='ignore'):
contains: list | dict #: unstructured hashes in contains.tsv for each organism contains: list | dict #: unstructured hashes in contains.tsv for each organism
matches: list | dict #: unstructured hashes in matches.tsv for each organism matches: list | dict #: unstructured hashes in matches.tsv for each organism
kraken: list | dict #: unstructured output from kraken_report kraken: list | dict #: unstructured output from kraken_report
# subtype: str #: EN-NOS, MCS-NOS, etc
subtype: Literal["ATCC49226", "ATCC49619", "EN-NOS", "EN-SSTI", "MCS-NOS", "MCS-SSTI", "SN-NOS", "SN-SSTI"] subtype: Literal["ATCC49226", "ATCC49619", "EN-NOS", "EN-SSTI", "MCS-NOS", "MCS-SSTI", "SN-NOS", "SN-SSTI"]
refseq_version: str #: version of refseq used in fastq parsing refseq_version: str #: version of refseq used in fastq parsing
kraken2_version: str kraken2_version: str
@@ -1264,7 +1263,6 @@ class PydProcess(BaseModel, extra="allow"):
instance = Process.query(name=self.name) instance = Process.query(name=self.name)
if not instance: if not instance:
instance = Process() instance = Process()
# dicto = instance.omnigui_instance_dict
fields = [item for item in self.model_fields] fields = [item for item in self.model_fields]
for field in fields: for field in fields:
logger.debug(f"Field: {field}") logger.debug(f"Field: {field}")
@@ -1315,5 +1313,3 @@ class PydElastic(BaseModel, extra="allow", arbitrary_types_allowed=True):
field_value = getattr(self, field) field_value = getattr(self, field)
self.instance.__setattr__(field, field_value) self.instance.__setattr__(field, field_value)
return self.instance return self.instance

View File

@@ -19,10 +19,6 @@ class IridaFigure(CustomFigure):
super().__init__(df=df, modes=modes, settings=settings) super().__init__(df=df, modes=modes, settings=settings)
self.df = df self.df = df
# try:
# months = int(settings['months'])
# except KeyError:
# months = 6
self.construct_chart(df=df, modes=modes, start_date=settings['start_date'], end_date=settings['end_date']) self.construct_chart(df=df, modes=modes, start_date=settings['start_date'], end_date=settings['end_date'])

View File

@@ -3,10 +3,8 @@ Functions for constructing irida controls graphs using plotly.
""" """
from pprint import pformat from pprint import pformat
from . import CustomFigure from . import CustomFigure
import plotly.express as px
import pandas as pd
from PyQt6.QtWidgets import QWidget from PyQt6.QtWidgets import QWidget
import logging import logging, plotly.express as px, pandas as pd
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
@@ -17,10 +15,6 @@ class PCRFigure(CustomFigure):
months: int = 6): months: int = 6):
super().__init__(df=df, modes=modes, settings=settings) super().__init__(df=df, modes=modes, settings=settings)
self.df = df self.df = df
# try:
# months = int(settings['months'])
# except KeyError:
# months = 6
self.construct_chart(df=df) self.construct_chart(df=df)
def construct_chart(self, df: pd.DataFrame): def construct_chart(self, df: pd.DataFrame):

View File

@@ -3,10 +3,8 @@ Construct turnaround time charts
""" """
from pprint import pformat from pprint import pformat
from . import CustomFigure from . import CustomFigure
import plotly.express as px
import pandas as pd
from PyQt6.QtWidgets import QWidget from PyQt6.QtWidgets import QWidget
import logging import logging, plotly.express as px, pandas as pd
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")

View File

@@ -230,7 +230,9 @@ class App(QMainWindow):
def update_data(self): def update_data(self):
self.table_widget.sub_wid.setData(page=self.table_widget.pager.page_anchor, page_size=page_size) self.table_widget.sub_wid.setData(page=self.table_widget.pager.page_anchor, page_size=page_size)
# TODO: Change this to the Pydantic version.
def manage_orgs(self): def manage_orgs(self):
from frontend.widgets.omni_manager_pydant import ManagerWindow as ManagerWindowPyd
dlg = ManagerWindow(parent=self, object_type=Organization, extras=[], add_edit='edit', managers=set()) dlg = ManagerWindow(parent=self, object_type=Organization, extras=[], add_edit='edit', managers=set())
if dlg.exec(): if dlg.exec():
new_org = dlg.parse_form() new_org = dlg.parse_form()
@@ -245,13 +247,9 @@ class App(QMainWindow):
logger.debug("\n\nBeginning parsing\n\n") logger.debug("\n\nBeginning parsing\n\n")
output = dlg.parse_form() output = dlg.parse_form()
logger.debug(f"Kit output: {pformat(output.__dict__)}") logger.debug(f"Kit output: {pformat(output.__dict__)}")
# with open(f"{output.name}.obj", "wb") as f:
# pickle.dump(output, f)
logger.debug("\n\nBeginning transformation\n\n") logger.debug("\n\nBeginning transformation\n\n")
sql = output.to_sql() sql = output.to_sql()
assert isinstance(sql, KitType) assert isinstance(sql, KitType)
# with open(f"{output.name}.sql", "wb") as f:
# pickle.dump(sql, f)
sql.save() sql.save()

View File

@@ -113,10 +113,6 @@ class ControlsViewer(InfoPane):
if issubclass(self.fig.__class__, CustomFigure): if issubclass(self.fig.__class__, CustomFigure):
self.save_button.setEnabled(True) self.save_button.setEnabled(True)
# NOTE: construct html for webview # NOTE: construct html for webview
# try:
# html = self.fig.html
# except AttributeError:
# html = ""
self.webview.setHtml(self.fig.html) self.webview.setHtml(self.fig.html)
self.webview.update() self.webview.update()
return report return report

View File

@@ -28,12 +28,12 @@ class AddEdit(QDialog):
self.object_type = instance.__class__ self.object_type = instance.__class__
# self.managers = deepcopy(managers) # self.managers = deepcopy(managers)
self.managers = managers self.managers = managers
if instance.level < 2: # if instance.level < 2:
try: # try:
logger.debug(f"Parent instance: {self.parent().instance}") # logger.debug(f"Parent instance: {self.parent().instance}")
self.managers.add(self.parent().instance) # self.managers.add(self.parent().instance)
except AttributeError: # except AttributeError:
pass # pass
logger.debug(f"Managers: {managers}") logger.debug(f"Managers: {managers}")
self.layout = QGridLayout(self) self.layout = QGridLayout(self)
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
@@ -111,14 +111,13 @@ class EditProperty(QWidget):
case _: case _:
logger.error(f"{column_type} not a supported type.") logger.error(f"{column_type} not a supported type.")
return return
# if not self.is_list:
self.layout.addWidget(self.label, 0, 0, 1, 1) self.layout.addWidget(self.label, 0, 0, 1, 1)
self.layout.addWidget(self.widget, 0, 1, 1, 3) self.layout.addWidget(self.widget, 0, 1, 1, 3)
self.setLayout(self.layout) self.setLayout(self.layout)
def relationship_property_set(self, relationship, value=None): def relationship_property_set(self, relationship, value=None):
self.widget = QComboBox() self.widget = QComboBox()
logger.debug(self.parent().managers) # logger.debug(self.parent().managers)
for manager in self.parent().managers: for manager in self.parent().managers:
if self.name in manager.aliases: if self.name in manager.aliases:
logger.debug(f"Name: {self.name} is in aliases: {manager.aliases}") logger.debug(f"Name: {self.name} is in aliases: {manager.aliases}")

View File

@@ -47,7 +47,6 @@ class ManagerWindow(QDialog):
self.manager = None self.manager = None
else: else:
self.manager = manager self.manager = manager
# logger.debug(f"Managers: {managers}") # logger.debug(f"Managers: {managers}")
self.extras = extras self.extras = extras
self.context = kwargs self.context = kwargs
@@ -104,7 +103,6 @@ class ManagerWindow(QDialog):
self.options.setEditable(False) self.options.setEditable(False)
self.options.setMinimumWidth(self.minimumWidth()) self.options.setMinimumWidth(self.minimumWidth())
self.layout.addWidget(self.options, 1, 0, 1, 1) self.layout.addWidget(self.options, 1, 0, 1, 1)
# if len(options) > 0:
self.add_button = QPushButton("Add New") self.add_button = QPushButton("Add New")
self.layout.addWidget(self.add_button, 1, 1, 1, 1) self.layout.addWidget(self.add_button, 1, 1, 1, 1)
self.add_button.clicked.connect(self.add_new) self.add_button.clicked.connect(self.add_new)
@@ -126,12 +124,6 @@ class ManagerWindow(QDialog):
for item in deletes: for item in deletes:
item.setParent(None) item.setParent(None)
# logger.debug(f"Current options text lower: {self.options.currentText().lower()}") # logger.debug(f"Current options text lower: {self.options.currentText().lower()}")
# NOTE: Find the instance this manager will update
# try:
# check = "blank" not in self.options.currentText().lower() and self.options.currentText() != ""
# except AttributeError:
# check = False
# if check:
if self.add_edit == "edit" and initial: if self.add_edit == "edit" and initial:
# logger.debug(f"Querying with {self.options.currentText()}") # logger.debug(f"Querying with {self.options.currentText()}")
self.instance = self.class_object.query(name=self.options.currentText(), limit=1) self.instance = self.class_object.query(name=self.options.currentText(), limit=1)
@@ -173,7 +165,6 @@ class ManagerWindow(QDialog):
Returns: Returns:
Any: The instance with updated fields. Any: The instance with updated fields.
""" """
# TODO: Need Relationship property here too?
results = [item.parse_form() for item in self.findChildren(EditProperty)] results = [item.parse_form() for item in self.findChildren(EditProperty)]
for result in results: for result in results:
# logger.debug(f"Incoming result: {result}") # logger.debug(f"Incoming result: {result}")
@@ -211,7 +202,6 @@ class ManagerWindow(QDialog):
else: else:
value = current_value + [data] value = current_value + [data]
setattr(self.instance, name, value) setattr(self.instance, name, value)
# self.instance.save()
def toggle_textedit(self, caller_child=None): def toggle_textedit(self, caller_child=None):
already_exists = self.findChildren(LargeTextEdit) already_exists = self.findChildren(LargeTextEdit)
@@ -305,7 +295,6 @@ class EditRelationship(QWidget):
# logger.debug(f"self.relationship: {self.relationship}") # logger.debug(f"self.relationship: {self.relationship}")
# logger.debug(f"Relationship uses list: {self.relationship.property.uselist}") # logger.debug(f"Relationship uses list: {self.relationship.property.uselist}")
# NOTE: value is a database object in this case. # NOTE: value is a database object in this case.
# logger.debug(f"Data for edit relationship: {self.data}") # logger.debug(f"Data for edit relationship: {self.data}")
self.widget = QTableView() self.widget = QTableView()
self.add_button = QPushButton("Add New") self.add_button = QPushButton("Add New")
@@ -319,7 +308,6 @@ class EditRelationship(QWidget):
else: else:
value = [] value = []
self.data = value self.data = value
# self.update_buttons()
checked_manager, is_primary = check_object_in_manager(self.parent().manager, self.objectName()) checked_manager, is_primary = check_object_in_manager(self.parent().manager, self.objectName())
if checked_manager: if checked_manager:
logger.debug(f"Checked manager for {self.objectName()}: {checked_manager}") logger.debug(f"Checked manager for {self.objectName()}: {checked_manager}")
@@ -369,7 +357,6 @@ class EditRelationship(QWidget):
new_instance = dlg.parse_form() new_instance = dlg.parse_form()
# NOTE: My custom __setattr__ should take care of any list problems. # NOTE: My custom __setattr__ should take care of any list problems.
self.parent().instance.__setattr__(self.objectName(), new_instance) self.parent().instance.__setattr__(self.objectName(), new_instance)
# self.parent().instance.save()
self.parent().update_data() self.parent().update_data()
def add_existing(self): def add_existing(self):
@@ -381,7 +368,6 @@ class EditRelationship(QWidget):
instance = self.class_object.query(**row) instance = self.class_object.query(**row)
# NOTE: My custom __setattr__ should take care of any list problems. # NOTE: My custom __setattr__ should take care of any list problems.
self.parent().instance.__setattr__(self.objectName(), instance) self.parent().instance.__setattr__(self.objectName(), instance)
# self.parent().instance.save()
self.parent().update_data() self.parent().update_data()
def set_data(self) -> None: def set_data(self) -> None:
@@ -420,7 +406,6 @@ class EditRelationship(QWidget):
Args: Args:
event (_type_): the item of interest event (_type_): the item of interest
""" """
# print(self.widget.isEnabled())
if not self.widget.isEnabled(): if not self.widget.isEnabled():
logger.warning(f"{self.objectName()} is disabled.") logger.warning(f"{self.objectName()} is disabled.")
return return
@@ -471,7 +456,6 @@ class EditRelationship(QWidget):
except ValueError as e: except ValueError as e:
logger.error(f"Remove failed for {self.objectName().lower()} due to {e}.") logger.error(f"Remove failed for {self.objectName().lower()} due to {e}.")
self.parent().instance.save() self.parent().instance.save()
# self.parent().update_data()
self.set_data() self.set_data()
def parse_form(self): def parse_form(self):
@@ -555,7 +539,6 @@ class JsonEditScreen(QDialog):
output.append(value) output.append(value)
else: else:
raise ValueError(f"Inappropriate data type: {type(self.json_field)}") raise ValueError(f"Inappropriate data type: {type(self.json_field)}")
# output[key] = value
return output return output

View File

@@ -120,7 +120,6 @@ class ManagerWindow(QDialog):
# logger.debug(f"Querying with {self.options.currentText()}") # logger.debug(f"Querying with {self.options.currentText()}")
self.instance = self.class_object.query(name=self.options.currentText(), limit=1) self.instance = self.class_object.query(name=self.options.currentText(), limit=1)
except AttributeError: except AttributeError:
# self.instance = None
pass pass
# logger.debug(f"Instance: {self.instance}") # logger.debug(f"Instance: {self.instance}")
if not self.instance: if not self.instance:
@@ -164,11 +163,6 @@ class ManagerWindow(QDialog):
# NOTE: RelationshipDeclareds will be given a list of existing related objects. # NOTE: RelationshipDeclareds will be given a list of existing related objects.
case "relationship": case "relationship":
# NOTE: field.comparator.class_object.class_ gives the relationship class # NOTE: field.comparator.class_object.class_ gives the relationship class
# try:
# logger.debug(
# f"Creating relationship widget with value: {[pformat(item.__dict__) for item in value]}")
# except AttributeError:
# logger.debug(f"Creating relationship widget with value: {value}")
widget = EditRelationship(self, key=key, class_object=info.title, value=value) widget = EditRelationship(self, key=key, class_object=info.title, value=value)
case _: case _:
continue continue
@@ -294,7 +288,6 @@ class EditRelationship(QWidget):
value = [] value = []
self.data = value self.data = value
# logger.debug(f"Set data: {self.data}") # logger.debug(f"Set data: {self.data}")
# self.update_buttons()
# logger.debug(f"Parent manager: {self.parent().manager}") # logger.debug(f"Parent manager: {self.parent().manager}")
checked_manager, is_primary = check_object_in_manager(self.parent().manager, self.objectName()) checked_manager, is_primary = check_object_in_manager(self.parent().manager, self.objectName())
if checked_manager: if checked_manager:
@@ -374,8 +367,6 @@ class EditRelationship(QWidget):
return return
logger.debug(f"Updating \n{pformat(obj)} with \n{pformat(new_instance.__dict__)}") logger.debug(f"Updating \n{pformat(obj)} with \n{pformat(new_instance.__dict__)}")
obj.__dict__.update(new_instance.__dict__) obj.__dict__.update(new_instance.__dict__)
# # self.parent().omni_object.__setattr__(self.objectName(), obj)
# # instance.__dict__.update(new_instance.__dict__)
logger.debug(f"Final instance: {pformat(self.parent().omni_object.__dict__)}") logger.debug(f"Final instance: {pformat(self.parent().omni_object.__dict__)}")
# NOTE: somewhere in the update_data I'm losing changes. # NOTE: somewhere in the update_data I'm losing changes.
self.parent().update_data() self.parent().update_data()

View File

@@ -45,7 +45,6 @@ class SearchBox(QDialog):
self.setLayout(self.layout) self.setLayout(self.layout)
self.setWindowTitle(f"Search {self.object_type.__name__}") self.setWindowTitle(f"Search {self.object_type.__name__}")
self.update_widgets() self.update_widgets()
# self.update_data()
if returnable: if returnable:
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn) self.buttonBox = QDialogButtonBox(QBtn)
@@ -60,7 +59,6 @@ class SearchBox(QDialog):
Changes form inputs based on sample type Changes form inputs based on sample type
""" """
search_fields = [] search_fields = []
# search_fields = self.object_type.searchables
logger.debug(f"Search fields: {search_fields}") logger.debug(f"Search fields: {search_fields}")
deletes = [item for item in self.findChildren(FieldSearch)] deletes = [item for item in self.findChildren(FieldSearch)]
for item in deletes: for item in deletes:
@@ -69,7 +67,6 @@ class SearchBox(QDialog):
if not self.sub_class: if not self.sub_class:
logger.warning(f"No subclass selected.") logger.warning(f"No subclass selected.")
self.update_data() self.update_data()
# return
else: else:
if self.sub_class.currentText() == "Any": if self.sub_class.currentText() == "Any":
self.object_type = self.original_type self.object_type = self.original_type

View File

@@ -315,8 +315,6 @@ class SubmissionFormWidget(QWidget):
query = self.findChildren(QWidget, name=object_name) query = self.findChildren(QWidget, name=object_name)
else: else:
query = self.findChildren(QWidget) query = self.findChildren(QWidget)
# if object_name is not None:
# query = [widget for widget in query if widget.objectName() == object_name]
return query return query
@report_result @report_result
@@ -338,7 +336,6 @@ class SubmissionFormWidget(QWidget):
if self.disabler.checkbox.isChecked(): if self.disabler.checkbox.isChecked():
_, result, _ = self.pyd.check_kit_integrity(exempt=exempt) _, result, _ = self.pyd.check_kit_integrity(exempt=exempt)
report.add_result(result) report.add_result(result)
# result = self.pyd.check_reagent_expiries(exempt=exempt)
if len(result.results) > 0: if len(result.results) > 0:
return report return report
base_submission = self.pyd.to_sql() base_submission = self.pyd.to_sql()
@@ -372,7 +369,6 @@ class SubmissionFormWidget(QWidget):
pass pass
# NOTE: add reagents to submission object # NOTE: add reagents to submission object
if base_submission is None: if base_submission is None:
# self.app.table_widget.sub_wid.setData()
return return
for reagent in base_submission.reagents: for reagent in base_submission.reagents:
reagent.update_last_used(kit=base_submission.extraction_kit) reagent.update_last_used(kit=base_submission.extraction_kit)
@@ -752,11 +748,9 @@ class SubmissionFormWidget(QWidget):
looked_up_reg = None looked_up_reg = None
if looked_up_reg: if looked_up_reg:
try: try:
# relevant_reagents.remove(str(looked_up_reg.lot))
relevant_reagents.insert(0, relevant_reagents.pop(relevant_reagents.index(looked_up_reg.lot))) relevant_reagents.insert(0, relevant_reagents.pop(relevant_reagents.index(looked_up_reg.lot)))
except ValueError as e: except ValueError as e:
logger.error(f"Error reordering relevant reagents: {e}") logger.error(f"Error reordering relevant reagents: {e}")
# relevant_reagents.insert(0, str(looked_up_reg.lot))
else: else:
if len(relevant_reagents) > 1: if len(relevant_reagents) > 1:
idx = relevant_reagents.index(str(reagent.lot)) idx = relevant_reagents.index(str(reagent.lot))

View File

@@ -2,16 +2,11 @@
Contains miscellaenous functions used by both frontend and backend. Contains miscellaenous functions used by both frontend and backend.
''' '''
from __future__ import annotations from __future__ import annotations
import builtins import builtins, importlib, time, logging, re, yaml, sys, os, stat, platform, getpass, json, numpy as np, pandas as pd
import importlib
import time
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from json import JSONDecodeError from json import JSONDecodeError
import logging, re, yaml, sys, os, stat, platform, getpass, json, numpy as np, pandas as pd
from threading import Thread from threading import Thread
from inspect import getmembers, isfunction, stack from inspect import getmembers, isfunction, stack
from types import GeneratorType
from dateutil.easter import easter from dateutil.easter import easter
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
from logging import handlers from logging import handlers
@@ -19,11 +14,9 @@ from pathlib import Path
from sqlalchemy.orm import Session, InstrumentedAttribute from sqlalchemy.orm import Session, InstrumentedAttribute
from sqlalchemy import create_engine, text, MetaData from sqlalchemy import create_engine, text, MetaData
from pydantic import field_validator, BaseModel, Field from pydantic import field_validator, BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict, PydanticBaseSettingsSource, YamlConfigSettingsSource
from typing import Any, Tuple, Literal, List, Generator from typing import Any, Tuple, Literal, List, Generator
from sqlalchemy.orm.relationships import _RelationshipDeclared from sqlalchemy.orm.relationships import _RelationshipDeclared
from __init__ import project_path from __init__ import project_path
from configparser import ConfigParser from configparser import ConfigParser
from tkinter import Tk # NOTE: This is for choosing database path before app is created. from tkinter import Tk # NOTE: This is for choosing database path before app is created.
@@ -148,7 +141,6 @@ def check_key_or_attr(key: str, interest: dict | object, check_none: bool = Fals
return False return False
def check_not_nan(cell_contents) -> bool: def check_not_nan(cell_contents) -> bool:
""" """
Check to ensure excel sheet cell contents are not blank. Check to ensure excel sheet cell contents are not blank.
@@ -432,7 +424,6 @@ class Settings(BaseSettings, extra="allow"):
return package return package
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.set_from_db() self.set_from_db()
self.set_scripts() self.set_scripts()
@@ -1231,3 +1222,29 @@ class classproperty(property):
builtins.classproperty = classproperty builtins.classproperty = classproperty
ctx = get_config(None) ctx = get_config(None)
class Settings2(BaseSettings, extra="allow"):
model_config = SettingsConfigDict(yaml_file="C:\\Users\lwark\AppData\Local\submissions\config\config.yml",
yaml_file_encoding='utf-8')
@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
return (
YamlConfigSettingsSource(settings_cls),
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
)