Bug fixes for omni gui objects.

This commit is contained in:
lwark
2025-04-16 13:47:07 -05:00
parent 58f5d361b3
commit 1a1f766890
6 changed files with 263 additions and 70 deletions

View File

@@ -457,12 +457,12 @@ class BaseClass(Base):
"""
match input_date:
case datetime() | date():
output_date = input_date#.strftime("%Y-%m-%d %H:%M:%S")
output_date = input_date
case int():
output_date = datetime.fromordinal(
datetime(1900, 1, 1).toordinal() + input_date - 2)#.date().strftime("%Y-%m-%d %H:%M:%S")
datetime(1900, 1, 1).toordinal() + input_date - 2)
case _:
output_date = parse(input_date)#.strftime("%Y-%m-%d %H:%M:%S")
output_date = parse(input_date)
if eod:
addition_time = datetime.max.time()
else:

View File

@@ -126,7 +126,13 @@ class KitType(BaseClass):
submission_type=ST)) #: Association proxy to SubmissionTypeKitTypeAssociation
@classproperty
def aliases(cls):
def aliases(cls) -> List[str]:
"""
Gets other names the sql object of this class might go by.
Returns:
List[str]: List of names
"""
return super().aliases + [cls.query_alias, "kit_types", "kit_type"]
@hybrid_property
@@ -1077,7 +1083,13 @@ class SubmissionType(BaseClass):
return self.processes
@classproperty
def aliases(cls):
def aliases(cls) -> List[str]:
"""
Gets other names the sql object of this class might go by.
Returns:
List[str]: List of names
"""
return super().aliases + ["submission_types", "submission_type"]
@classproperty

View File

@@ -1540,12 +1540,15 @@ class BacterialCulture(BasicSubmission):
return report
parser = ConcentrationParser(filepath=fname, submission=self)
conc_samples = [sample for sample in parser.samples]
# logger.debug(f"Concentration samples: {pformat(conc_samples)}")
for sample in self.samples:
logger.debug(f"Sample {sample.submitter_id}")
# logger.debug(f"Sample {sample.submitter_id}")
# logger.debug(f"Match {item['submitter_id']}")
try:
# NOTE: Fix for ENs which have no rsl_number...
sample_dict = next(item for item in conc_samples if item['submitter_id'] == sample.submitter_id)
sample_dict = next(item for item in conc_samples if str(item['submitter_id']).upper() == sample.submitter_id)
except StopIteration:
logger.error(f"Couldn't find sample dict for {sample.submitter_id}")
continue
logger.debug(f"Sample {sample.submitter_id} conc. = {sample_dict['concentration']}")
if sample_dict['concentration']:
@@ -1734,12 +1737,10 @@ class Wastewater(BasicSubmission):
sample[f"ct_{sample['target'].lower()}"] = sample['ct'] if isinstance(sample['ct'], float) else 0.0
# NOTE: Set assessment
# logger.debug(f"Sample assessemnt: {sample['assessment']}")
# sample[f"{sample['target'].lower()}_status"] = sample['assessment']
# NOTE: Get sample having other target
other_targets = [s for s in samples if re.sub('-N\\d*$', '', s['sample']) == sample['sample']]
for s in other_targets:
sample[f"ct_{s['target'].lower()}"] = s['ct'] if isinstance(s['ct'], float) else 0.0
# sample[f"{s['target'].lower()}_status"] = s['assessment']
try:
del sample['ct']
except KeyError:
@@ -2177,7 +2178,6 @@ class WastewaterArtic(BasicSubmission):
except AttributeError:
plate_num = "1"
plate_num = plate_num.strip("-")
# repeat_num = re.search(r"R(?P<repeat>\d)?$", "PBS20240426-2R").groups()[0]
try:
repeat_num = re.search(r"R(?P<repeat>\d)?$", processed).groups()[0]
except:
@@ -2663,16 +2663,6 @@ class BasicSample(BaseClass, LogMixin):
def delete(self):
raise AttributeError(f"Delete not implemented for {self.__class__}")
# @classmethod
# def get_searchables(cls) -> List[dict]:
# """
# Delivers a list of fields that can be used in fuzzy search.
#
# Returns:
# List[str]: List of fields.
# """
# return [dict(label="Submitter ID", field="submitter_id")]
@classmethod
def samples_to_df(cls, sample_list: List[BasicSample], **kwargs) -> pd.DataFrame:
"""
@@ -2729,8 +2719,6 @@ class WastewaterSample(BasicSample):
Derivative wastewater sample
"""
# searchables = BasicSample.searchables + ['ww_processing_num', 'ww_full_sample_id', 'rsl_number']
id = Column(INTEGER, ForeignKey('_basicsample.id'), primary_key=True)
ww_processing_num = Column(String(64)) #: wastewater processing number
ww_full_sample_id = Column(String(64)) #: full id given by entrics

View File

@@ -24,10 +24,25 @@ class BaseOmni(BaseModel):
return f"<{self.__class__.__name__}({self.__repr_name__})>"
@classproperty
def aliases(cls):
def aliases(cls) -> List[str]:
"""
Gets other names the sql object of this class might go by.
Returns:
List[str]: List of names
"""
return cls.class_object.aliases
def check_all_attributes(self, attributes: dict) -> bool:
"""
Compares this pobject to dictionary of attributes to determine equality.
Args:
attributes (dict):
Returns:
bool: result
"""
# logger.debug(f"Incoming attributes: {attributes}")
attributes = {k : v for k, v in attributes.items() if k in self.list_searchables.keys()}
for key, value in attributes.items():
@@ -47,7 +62,14 @@ class BaseOmni(BaseModel):
# logger.debug("Everything checks out, these are the same object.")
return True
def __setattr__(self, key, value):
def __setattr__(self, key: str, value: Any):
"""
Overrides built in dunder method
Args:
key (str):
value (Any):
"""
try:
class_value = getattr(self.class_object, key)
except AttributeError:
@@ -151,12 +173,22 @@ class OmniSubmissionType(BaseOmni):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
@property
def dataframe_dict(self) -> dict:
"""
Dictionary of gui relevant values.
Returns:
dict: result
"""
return dict(
name=self.name
)
def to_sql(self):
"""
Convert this object to an instance of its class object.
"""
instance, new = self.class_object.query_or_create(name=self.name)
instance.info_map = self.info_map
instance.defaults = self.defaults
@@ -191,12 +223,23 @@ class OmniReagentRole(BaseOmni):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
@property
def dataframe_dict(self) -> dict:
"""
Dictionary of gui relevant values.
Returns:
dict: result
"""
return dict(
name=self.name
)
def to_sql(self):
"""
Convert this object to an instance of its class object.
"""
instance, new = self.class_object.query_or_create(name=self.name)
if new:
instance.eol_ext = self.eol_ext
@@ -252,7 +295,14 @@ class OmniSubmissionTypeKitTypeAssociation(BaseOmni):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
@property
def dataframe_dict(self) -> dict:
"""
Dictionary of gui relevant values.
Returns:
dict: result
"""
if isinstance(self.submissiontype, OmniSubmissionType):
submissiontype = self.submissiontype.name
else:
@@ -270,6 +320,10 @@ class OmniSubmissionTypeKitTypeAssociation(BaseOmni):
)
def to_sql(self):
"""
Convert this object to an instance of its class object.
"""
# logger.debug(f"Self kittype: {self.submissiontype}")
if issubclass(self.submissiontype.__class__, BaseOmni):
submissiontype = SubmissionType.query(name=self.submissiontype.name)
@@ -288,7 +342,13 @@ class OmniSubmissionTypeKitTypeAssociation(BaseOmni):
return instance
@property
def list_searchables(self):
def list_searchables(self) -> dict:
"""
Provides attributes for checking this object against a dictionary.
Returns:
dict: result
"""
if isinstance(self.kittype, OmniKitType):
kit = self.kittype.name
else:
@@ -334,7 +394,14 @@ class OmniKitTypeReagentRoleAssociation(BaseOmni):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
@property
def dataframe_dict(self) -> dict:
"""
Dictionary of gui relevant values.
Returns:
dict: result
"""
if isinstance(self.submission_type, OmniSubmissionType):
submission_type = self.submission_type.name
else:
@@ -349,12 +416,16 @@ class OmniKitTypeReagentRoleAssociation(BaseOmni):
else:
reagent_role = self.reagent_role
return dict(
reagent_role=reagent_role,
submission_type=submission_type,
kit_type=kit_type
reagentrole=reagent_role,
submissiontype=submission_type,
kittype=kit_type
)
def to_sql(self):
"""
Convert this object to an instance of its class object.
"""
if isinstance(self.reagent_role, OmniReagentRole):
reagent_role = self.reagent_role.name
else:
@@ -387,7 +458,13 @@ class OmniKitTypeReagentRoleAssociation(BaseOmni):
return instance
@property
def list_searchables(self):
def list_searchables(self) -> dict:
"""
Provides attributes for checking this object against a dictionary.
Returns:
dict: result
"""
if isinstance(self.kit_type, OmniKitType):
kit = self.kit_type.name
else:
@@ -420,12 +497,23 @@ class OmniEquipmentRole(BaseOmni):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
@property
def dataframe_dict(self) -> dict:
"""
Dictionary of gui relevant values.
Returns:
dict: result
"""
return dict(
name=self.name
)
def to_sql(self):
"""
Convert this object to an instance of its class object.
"""
instance, new = self.class_object.query_or_create(name=self.name)
return instance
@@ -447,12 +535,23 @@ class OmniTips(BaseOmni):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
@property
def dataframe_dict(self) -> dict:
"""
Dictionary of gui relevant values.
Returns:
dict: result
"""
return dict(
name=self.name
)
def to_sql(self):
"""
Convert this object to an instance of its class object.
"""
instance, new = self.class_object.query_or_create(name=self.name)
return instance
@@ -475,13 +574,24 @@ class OmniTipRole(BaseOmni):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
@property
def dataframe_dict(self) -> dict:
"""
Dictionary of gui relevant values.
Returns:
dict: result
"""
return dict(
name=self.name,
tips=[item.name for item in self.tips]
)
def to_sql(self):
"""
Convert this object to an instance of its class object.
"""
instance, new = self.class_object.query_or_create(name=self.name)
for tips in self.tips:
tips.to_sql()
@@ -504,10 +614,17 @@ class OmniProcess(BaseOmni):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
submissiontypes = [item.name for item in self.submission_types]
@property
def dataframe_dict(self) -> dict:
"""
Dictionary of gui relevant values.
Returns:
dict: result
"""
submissiontypes = [item if isinstance(item, str) else item.name for item in self.submission_types]
logger.debug(f"Submission Types: {submissiontypes}")
equipmentroles = [item.name for item in self.equipment_roles]
equipmentroles = [item if isinstance(item, str) else item.name for item in self.equipment_roles]
logger.debug(f"Equipment Roles: {equipmentroles}")
return dict(
name=self.name,
@@ -523,6 +640,10 @@ class OmniProcess(BaseOmni):
return value
def to_sql(self):
"""
Convert this object to an instance of its class object.
"""
instance, new = self.class_object.query_or_create(name=self.name)
for st in self.submission_types:
try:
@@ -548,7 +669,13 @@ class OmniProcess(BaseOmni):
return instance
@property
def list_searchables(self):
def list_searchables(self) -> dict:
"""
Provides attributes for checking this object against a dictionary.
Returns:
dict: result
"""
return dict(name=self.name)
@@ -572,17 +699,24 @@ class OmniKitType(BaseOmni):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
@property
def dataframe_dict(self) -> dict:
"""
Dictionary of gui relevant values.
Returns:
dict: result
"""
return dict(
name=self.name
)
def to_sql(self) -> KitType:
"""
Convert this object to an instance of its class object.
"""
kit, is_new = KitType.query_or_create(name=self.name)
# if is_new:
# logger.debug(f"New kit made: {kit}")
# else:
# logger.debug(f"Kit retrieved: {kit}")
new_rr = []
for rr_assoc in self.kit_reagentrole_associations:
new_assoc = rr_assoc.to_sql()
@@ -603,9 +737,6 @@ class OmniKitType(BaseOmni):
if new_process not in new_processes:
new_processes.append(new_process)
kit.processes = new_processes
# logger.debug(f"Kit: {pformat(kit.__dict__)}")
# for item in kit.kit_reagentrole_associations:
# logger.debug(f"KTRRassoc: {item.__dict__}")
return kit
@@ -622,7 +753,14 @@ class OmniOrganization(BaseOmni):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
@property
def dataframe_dict(self) -> dict:
"""
Dictionary of gui relevant values.
Returns:
dict: result
"""
return dict(
name=self.name,
cost_centre=self.cost_centre,
@@ -639,14 +777,27 @@ class OmniContact(BaseOmni):
phone: str = Field(default="", description="property")
@property
def list_searchables(self):
def list_searchables(self) -> dict:
"""
Provides attributes for checking this object against a dictionary.
Returns:
dict: result
"""
return dict(name=self.name, email=self.email)
def __init__(self, instance_object: Any, **data):
super().__init__(**data)
self.instance_object = instance_object
def to_dataframe_dict(self):
@property
def dataframe_dict(self) -> dict:
"""
Dictionary of gui relevant values.
Returns:
dict: result
"""
return dict(
name=self.name,
email=self.email,
@@ -654,9 +805,9 @@ class OmniContact(BaseOmni):
)
def to_sql(self):
"""
Convert this object to an instance of its class object.
"""
contact, is_new = Contact.query_or_create(name=self.name, email=self.email, phone=self.phone)
# if is_new:
# logger.debug(f"New contact made: {contact}")
# else:
# logger.debug(f"Contact retrieved: {contact}")
return contact

View File

@@ -4,6 +4,8 @@ Contains all operations for creating charts, graphs and visual effects.
from datetime import timedelta, date
from pathlib import Path
from typing import Generator
import plotly
from PyQt6.QtWidgets import QWidget
import pandas as pd, logging
from plotly.graph_objects import Figure
@@ -126,8 +128,8 @@ class CustomFigure(Figure):
html = f'<html><body>'
if self is not None:
# NOTE: Just cannot get this load from string to freaking work.
html += self.to_html(include_plotlyjs='cdn', full_html=False)
# html += plotly.offline.plot(self, output_type='div', include_plotlyjs=True)
# html += self.to_html(include_plotlyjs='cdn', full_html=False)
html += plotly.offline.plot(self, output_type='div', include_plotlyjs="cdn")
else:
html += "<h1>No data was retrieved for the given parameters.</h1>"
html += '</body></html>'

View File

@@ -6,7 +6,7 @@ from json.decoder import JSONDecodeError
from datetime import datetime, timedelta
from pprint import pformat
from typing import Any, List, Literal
from PyQt6.QtCore import QSortFilterProxyModel, Qt
from PyQt6.QtCore import QSortFilterProxyModel, Qt, QModelIndex
from PyQt6.QtGui import QAction, QCursor
from PyQt6.QtWidgets import (
QLabel, QDialog,
@@ -114,7 +114,16 @@ class ManagerWindow(QDialog):
# logger.debug(f"Instance: {self.instance}")
self.update_data()
def update_instance(self, initial: bool = False):
def update_instance(self, initial: bool = False) -> None:
"""
Gets the proper instance of this object's class object.
Args:
initial (bool): Whether this is the initial creation of this object.
Returns:
None
"""
if self.add_edit == "edit" or initial:
try:
# logger.debug(f"Querying with {self.options.currentText()}")
@@ -146,6 +155,7 @@ class ManagerWindow(QDialog):
[item for item in self.findChildren(QDialogButtonBox)]
for item in deletes:
item.setParent(None)
logger.debug(f"Self.omni_object: {self.omni_object}")
fields = self.omni_object.__class__.model_fields
for key, info in fields.items():
# logger.debug(f"Attempting to set {key}, {info} widget")
@@ -193,14 +203,22 @@ class ManagerWindow(QDialog):
# logger.debug(f"Instance coming from parsed form: {self.omni_object.__dict__}")
return self.omni_object
def add_new(self):
def add_new(self) -> None:
"""
Creates a new instance of this object's class object.
Returns:
None
"""
new_instance = self.class_object()
self.instance = new_instance
self.update_options()
class EditProperty(QWidget):
"""
Class to manage info items of SQL objects.
"""
def __init__(self, parent: ManagerWindow, key: str, column_type: Any, value):
super().__init__(parent)
self.label = QLabel(key.title().replace("_", " "))
@@ -245,7 +263,13 @@ class EditProperty(QWidget):
self.layout.addWidget(self.widget, 0, 1, 1, 3)
self.setLayout(self.layout)
def parse_form(self):
def parse_form(self) -> dict:
"""
Gets values from this EditProperty form.
Returns:
dict: Dictionary of values.
"""
# logger.debug(f"Parsing widget {self.objectName()}: {type(self.widget)}")
match self.widget:
case QLineEdit():
@@ -269,7 +293,7 @@ class EditRelationship(QWidget):
from backend.db import models
super().__init__(parent)
self.class_object = getattr(models, class_object)
logger.debug(f"Attempt value: {value}")
# logger.debug(f"Attempt value: {value}")
# logger.debug(f"Class object: {self.class_object}")
self.setParent(parent)
# logger.debug(f"Edit relationship class_object: {self.class_object}")
@@ -317,7 +341,13 @@ class EditRelationship(QWidget):
self.setLayout(self.layout)
self.set_data()
def update_buttons(self):
def update_buttons(self) -> None:
"""
Enables/disables buttons based on whether property is a list and has data.
Returns:
None
"""
if not self.relationship.property.uselist and len(self.data) >= 1:
# logger.debug(f"Property {self.relationship} doesn't use list and data is of length: {len(self.data)}")
self.add_button.setEnabled(False)
@@ -326,14 +356,23 @@ class EditRelationship(QWidget):
self.add_button.setEnabled(True)
self.existing_button.setEnabled(True)
def parse_row(self, x):
def parse_row(self, x: QModelIndex) -> None:
"""
Args:
x ():
Returns:
"""
context = {item: x.sibling(x.row(), self.df.columns.get_loc(item)).data() for item in self.df.columns}
# logger.debug(f"Context: {pformat(context)}")
try:
object = self.class_object.query(**context)
except KeyError:
object = None
self.widget.doubleClicked.disconnect()
self.add_edit(instance=object)
self.add_new(instance=object)
def add_new(self, instance: Any = None, add_edit: Literal["add", "edit"] = "add", index: int | None = None):
if add_edit == "edit":
@@ -388,12 +427,13 @@ class EditRelationship(QWidget):
"""
sets data in model
"""
# logger.debug(f"Self.data: {self.data}")
logger.debug(f"Self.data: {self.data}")
try:
records = [item.to_dataframe_dict() for item in self.data]
except AttributeError:
records = [item.dataframe_dict for item in self.data]
except AttributeError as e:
logger.error(e)
records = []
# logger.debug(f"Records: {records}")
logger.debug(f"Records: {records}")
self.df = DataFrame.from_records(records)
try:
self.columns_of_interest = [dict(name=item, column=self.df.columns.get_loc(item)) for item in self.extras]