Attempting fix of failure to update reagent on edit.

This commit is contained in:
lwark
2024-12-02 14:52:39 -06:00
parent b45a125c51
commit 2c281166d4
10 changed files with 74 additions and 38 deletions

View File

@@ -70,5 +70,5 @@ def update_log(mapper, connection, target):
logger.info(f"No changes detected, not updating logs.") logger.info(f"No changes detected, not updating logs.")
event.listen(LogMixin, 'after_update', update_log, propagate=True) # event.listen(LogMixin, 'after_update', update_log, propagate=True)
event.listen(LogMixin, 'after_insert', update_log, propagate=True) # event.listen(LogMixin, 'after_insert', update_log, propagate=True)

View File

@@ -182,7 +182,7 @@ class BaseClass(Base):
query: Query = cls.__database_session__.query(model) query: Query = cls.__database_session__.query(model)
# logger.debug(f"Grabbing singles using {model.get_default_info}") # logger.debug(f"Grabbing singles using {model.get_default_info}")
singles = model.get_default_info('singles') singles = model.get_default_info('singles')
logger.info(f"Querying: {model}, with kwargs: {kwargs}") # logger.info(f"Querying: {model}, with kwargs: {kwargs}")
for k, v in kwargs.items(): for k, v in kwargs.items():
logger.info(f"Using key: {k} with value: {v}") logger.info(f"Using key: {k} with value: {v}")
try: try:

View File

@@ -273,7 +273,7 @@ class Control(BaseClass):
except StopIteration as e: except StopIteration as e:
raise AttributeError( raise AttributeError(
f"Couldn't find existing class/subclass of {cls} with all attributes:\n{pformat(attrs.keys())}") f"Couldn't find existing class/subclass of {cls} with all attributes:\n{pformat(attrs.keys())}")
logger.info(f"Recruiting model: {model}") # logger.info(f"Recruiting model: {model}")
return model return model
@classmethod @classmethod
@@ -535,8 +535,8 @@ class IridaControl(Control):
except AttributeError: except AttributeError:
consolidate = False consolidate = False
report = Report() report = Report()
# logger.debug(f"settings: {pformat(chart_settings)}") logger.debug(f"settings: {pformat(chart_settings)}")
controls = cls.query(sub_type=chart_settings['sub_type'], start_date=chart_settings['start_date'], controls = cls.query(subtype=chart_settings['sub_type'], start_date=chart_settings['start_date'],
end_date=chart_settings['end_date']) end_date=chart_settings['end_date'])
# logger.debug(f"Controls found: {controls}") # logger.debug(f"Controls found: {controls}")
if not controls: if not controls:

View File

@@ -12,7 +12,7 @@ from tools import check_authorization, setup_lookup, Report, Result, check_regex
from typing import List, Literal, Generator, Any from typing import List, Literal, Generator, Any
from pandas import ExcelFile from pandas import ExcelFile
from pathlib import Path from pathlib import Path
from . import Base, BaseClass, Organization from . import Base, BaseClass, Organization, LogMixin
from io import BytesIO from io import BytesIO
logger = logging.getLogger(f'submissions.{__name__}') logger = logging.getLogger(f'submissions.{__name__}')
@@ -797,7 +797,7 @@ class SubmissionType(BaseClass):
fmap = item.uses fmap = item.uses
if fmap is None: if fmap is None:
fmap = {} fmap = {}
yield getattr(item, f"{field}_role"), fmap yield getattr(item, f"{field}_role").name, fmap
def get_default_kit(self) -> KitType | None: def get_default_kit(self) -> KitType | None:
if len(self.kit_types) == 1: if len(self.kit_types) == 1:
@@ -1361,7 +1361,7 @@ class Equipment(BaseClass):
def __repr__(self) -> str: def __repr__(self) -> str:
""" """
Returns: Returns:
str: represenation of this Equipment str: representation of this Equipment
""" """
return f"<Equipment({self.name})>" return f"<Equipment({self.name})>"
@@ -1502,7 +1502,7 @@ class Equipment(BaseClass):
equipment_role = EquipmentRole.query(name=equipment_role) equipment_role = EquipmentRole.query(name=equipment_role)
equipment = cls.query() equipment = cls.query()
options = "\n".join([f"{ii}. {item.name}" for ii, item in enumerate(equipment)]) options = "\n".join([f"{ii}. {item.name}" for ii, item in enumerate(equipment)])
choices = input(f"Enter equipment numbers to add to {equipment_role.name} (space seperated):\n{options}\n\n") choices = input(f"Enter equipment numbers to add to {equipment_role.name} (space separated):\n{options}\n\n")
output = [] output = []
for choice in choices.split(" "): for choice in choices.split(" "):
try: try:

View File

@@ -618,6 +618,17 @@ class BasicSubmission(BaseClass, LogMixin):
result = assoc.save() result = assoc.save()
return result return result
def update_reagentassoc(self, reagent: Reagent, role: str):
from backend.db import SubmissionReagentAssociation
# NOTE: get the first reagent assoc that fills the given role.
try:
assoc = next(item for item in self.submission_reagent_associations if item.reagent and role in [role.name for role in item.reagent.role])
assoc.reagent = reagent
except StopIteration as e:
logger.error(f"Association for {role} not found, creating new association.")
assoc = SubmissionReagentAssociation(submission=self, reagent=reagent)
self.submission_reagent_associations.append(assoc)
def to_pydantic(self, backup: bool = False) -> "PydSubmission": def to_pydantic(self, backup: bool = False) -> "PydSubmission":
""" """
Converts this instance into a PydSubmission Converts this instance into a PydSubmission
@@ -758,7 +769,7 @@ class BasicSubmission(BaseClass, LogMixin):
except StopIteration as e: except StopIteration as e:
raise AttributeError( raise AttributeError(
f"Couldn't find existing class/subclass of {cls} with all attributes:\n{pformat(attrs.keys())}") f"Couldn't find existing class/subclass of {cls} with all attributes:\n{pformat(attrs.keys())}")
logger.info(f"Recruiting model: {model}") # logger.info(f"Recruiting model: {model}")
return model return model
# Child class custom functions # Child class custom functions
@@ -1414,7 +1425,7 @@ class BacterialCulture(BasicSubmission):
extends parent extends parent
""" """
template = super().filename_template() template = super().filename_template()
template += "_{{ submitting_lab }}_{{ submitter_plate_num }}" template += "_{{ submitting_lab.name }}_{{ submitter_plate_num }}"
return template return template
@classmethod @classmethod
@@ -2356,7 +2367,7 @@ class BasicSample(BaseClass):
except Exception as e: except Exception as e:
logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, using {cls}") 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
else: else:
model = cls model = cls
@@ -2370,7 +2381,7 @@ class BasicSample(BaseClass):
except StopIteration as e: except StopIteration as e:
raise AttributeError( raise AttributeError(
f"Couldn't find existing class/subclass of {cls} with all attributes:\n{pformat(attrs.keys())}") f"Couldn't find existing class/subclass of {cls} with all attributes:\n{pformat(attrs.keys())}")
logger.info(f"Recruiting model: {model}") # logger.info(f"Recruiting model: {model}")
return model return model
@classmethod @classmethod

View File

@@ -648,7 +648,8 @@ class TipParser(object):
Returns: Returns:
List[dict]: List of locations List[dict]: List of locations
""" """
return {k: v for k, v in self.submission_type.construct_tips_map()} # return {k: v for k, v in self.submission_type.construct_tips_map()}
return {k: v for k, v in self.submission_type.construct_field_map("tip")}
def parse_tips(self) -> List[dict]: def parse_tips(self) -> List[dict]:
""" """

View File

@@ -187,6 +187,9 @@ class InfoWriter(object):
sheet.cell(row=loc['row'], column=loc['column'], value=v['value']) sheet.cell(row=loc['row'], column=loc['column'], value=v['value'])
except AttributeError as e: except AttributeError as e:
logger.error(f"Can't write {k} to that cell due to {e}") logger.error(f"Can't write {k} to that cell due to {e}")
except ValueError as e:
logger.error(f"Can't write {v} to that cell due to {e}")
sheet.cell(row=loc['row'], column=loc['column'], value=v['value'].name)
return self.sub_object.custom_info_writer(self.xl, info=final_info, custom_fields=self.info_map['custom']) return self.sub_object.custom_info_writer(self.xl, info=final_info, custom_fields=self.info_map['custom'])
@@ -208,8 +211,8 @@ class ReagentWriter(object):
if isinstance(submission_type, str): if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type) submission_type = SubmissionType.query(name=submission_type)
if isinstance(extraction_kit, str): if isinstance(extraction_kit, str):
kit_type = KitType.query(name=extraction_kit) extraction_kit = KitType.query(name=extraction_kit)
reagent_map = {k: v for k, v in kit_type.construct_xl_map_for_use(submission_type)} reagent_map = {k: v for k, v in extraction_kit.construct_xl_map_for_use(submission_type)}
self.reagents = self.reconcile_map(reagent_list=reagent_list, reagent_map=reagent_map) self.reagents = self.reconcile_map(reagent_list=reagent_list, reagent_map=reagent_map)
def reconcile_map(self, reagent_list: List[dict], reagent_map: dict) -> Generator[dict, None, None]: def reconcile_map(self, reagent_list: List[dict], reagent_map: dict) -> Generator[dict, None, None]:
@@ -359,7 +362,10 @@ class EquipmentWriter(object):
if equipment_list is None: if equipment_list is None:
return return
for ii, equipment in enumerate(equipment_list, start=1): for ii, equipment in enumerate(equipment_list, start=1):
mp_info = equipment_map[equipment['role']] try:
mp_info = equipment_map[equipment['role']]
except KeyError:
logger.error(f"No {equipment['role']} in {pformat(equipment_map)}")
# logger.debug(f"{equipment['role']} map: {mp_info}") # logger.debug(f"{equipment['role']} map: {mp_info}")
placeholder = copy(equipment) placeholder = copy(equipment)
if mp_info == {}: if mp_info == {}:
@@ -427,7 +433,7 @@ class TipWriter(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
tips_map = {k: v for k, v in self.submission_type.construct_tips_map()} tips_map = {k: v for k, v in self.submission_type.construct_field_map("tip")}
self.tips = self.reconcile_map(tips_list=tips_list, tips_map=tips_map) self.tips = self.reconcile_map(tips_list=tips_list, tips_map=tips_map)
def reconcile_map(self, tips_list: List[dict], tips_map: dict) -> Generator[dict, None, None]: def reconcile_map(self, tips_list: List[dict], tips_map: dict) -> Generator[dict, None, None]:

View File

@@ -115,7 +115,7 @@ class PydReagent(BaseModel):
fields = list(self.model_fields.keys()) + extras fields = list(self.model_fields.keys()) + extras
return {k: getattr(self, k) for k in fields} return {k: getattr(self, k) for k in fields}
def toSQL(self, submission: BasicSubmission | str = None) -> Tuple[Reagent, SubmissionReagentAssociation, Report]: def toSQL(self, submission: BasicSubmission | str = None) -> Tuple[Reagent, Report]:
""" """
Converts this instance into a backend.db.models.kit.Reagent instance Converts this instance into a backend.db.models.kit.Reagent instance
@@ -164,13 +164,14 @@ class PydReagent(BaseModel):
report.add_result(Result(owner=__name__, code=0, msg="New reagent created.", status="Information")) report.add_result(Result(owner=__name__, code=0, msg="New reagent created.", status="Information"))
else: else:
if submission is not None and reagent not in submission.reagents: if submission is not None and reagent not in submission.reagents:
assoc = SubmissionReagentAssociation(reagent=reagent, submission=submission) # assoc = SubmissionReagentAssociation(reagent=reagent, submission=submission)
assoc.comments = self.comment # assoc.comments = self.comment
else: submission.update_reagentassoc(reagent=reagent, role=self.role)
assoc = None # else:
# assoc = None
# add end-of-life extension from reagent type to expiry date # add end-of-life extension from reagent type to expiry date
# NOTE: this will now be done only in the reporting phase to account for potential changes in end-of-life extensions # NOTE: this will now be done only in the reporting phase to account for potential changes in end-of-life extensions
return reagent, assoc, report return reagent, report
class PydSample(BaseModel, extra='allow'): class PydSample(BaseModel, extra='allow'):
@@ -299,6 +300,13 @@ class PydTips(BaseModel):
lot: str | None = Field(default=None) lot: str | None = Field(default=None)
role: str role: str
@field_validator('role', mode='before')
@classmethod
def get_role_name(cls, value):
if isinstance(value, TipRole):
value = value.name
return value
def to_sql(self, submission: BasicSubmission) -> SubmissionTipsAssociation: def to_sql(self, submission: BasicSubmission) -> SubmissionTipsAssociation:
""" """
Con Con
@@ -324,6 +332,13 @@ class PydEquipment(BaseModel, extra='ignore'):
role: str | None role: str | None
tips: List[PydTips] | None = Field(default=None) tips: List[PydTips] | None = Field(default=None)
@field_validator('role', mode='before')
@classmethod
def get_role_name(cls, value):
if isinstance(value, EquipmentRole):
value = value.name
return value
@field_validator('processes', mode='before') @field_validator('processes', mode='before')
@classmethod @classmethod
def make_empty_list(cls, value): def make_empty_list(cls, value):
@@ -786,7 +801,7 @@ class PydSubmission(BaseModel, extra='allow'):
""" """
report = Report() report = Report()
dicto = self.improved_dict() dicto = self.improved_dict()
logger.warning(f"\n\nQuery or create: {self.submission_type['value']}, {self.rsl_plate_num['value']}") # logger.warning(f"\n\nQuery or create: {self.submission_type['value']}, {self.rsl_plate_num['value']}")
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"Result of query or create: {instance}") logger.debug(f"Result of query or create: {instance}")
@@ -807,17 +822,15 @@ class PydSubmission(BaseModel, extra='allow'):
# logger.debug(f"Setting {key} to {value}") # logger.debug(f"Setting {key} to {value}")
match key: match key:
case "reagents": case "reagents":
# if report.results[0].code == 1:
# instance.submission_reagent_associations = []
# logger.debug(f"Looking through {self.reagents}")
for reagent in self.reagents: for reagent in self.reagents:
reagent, assoc, _ = reagent.toSQL(submission=instance) logger.debug(f"Checking reagent {reagent.lot}")
reagent, _ = reagent.toSQL(submission=instance)
# logger.debug(f"Association: {assoc}") # logger.debug(f"Association: {assoc}")
if assoc is not None: # and assoc not in instance.submission_reagent_associations: # if assoc is not None: # and assoc not in instance.submission_reagent_associations:
if assoc not in instance.submission_reagent_associations: # if assoc not in instance.submission_reagent_associations:
instance.submission_reagent_associations.append(assoc) # instance.submission_reagent_associations.append(assoc)
else: # else:
logger.warning(f"Reagent association {assoc} is already present in {instance}") # logger.warning(f"Reagent association {assoc} is already present in {instance.submission_reagent_associations}")
case "samples": case "samples":
for sample in self.samples: for sample in self.samples:
sample, associations, _ = sample.toSQL(submission=instance) sample, associations, _ = sample.toSQL(submission=instance)

View File

@@ -178,7 +178,7 @@ class SubmissionFormContainer(QWidget):
# NOTE: create reagent object # NOTE: create reagent object
reagent = PydReagent(ctx=self.app.ctx, **info, missing=False) reagent = PydReagent(ctx=self.app.ctx, **info, missing=False)
# NOTE: send reagent to db # NOTE: send reagent to db
sqlobj, assoc, result = reagent.toSQL() sqlobj, result = reagent.toSQL()
sqlobj.save() sqlobj.save()
report.add_result(result) report.add_result(result)
# logger.debug(f"Reagent: {reagent}, Report: {report}") # logger.debug(f"Reagent: {reagent}, Report: {report}")
@@ -334,6 +334,10 @@ class SubmissionFormWidget(QWidget):
query = [widget for widget in query if widget.objectName() == object_name] query = [widget for widget in query if widget.objectName() == object_name]
return query return query
# def update_pyd(self):
# results = self.parse_form()
# logger.debug(pformat(results))
@report_result @report_result
def submit_new_sample_function(self, *args) -> Report: def submit_new_sample_function(self, *args) -> Report:
""" """
@@ -448,8 +452,9 @@ class SubmissionFormWidget(QWidget):
if field is not None: if field is not None:
info[field] = value info[field] = value
# logger.debug(f"Info: {pformat(info)}") # logger.debug(f"Info: {pformat(info)}")
# logger.debug(f"Reagents going into pyd: {pformat(reagents)}") logger.debug(f"Reagents going into pyd: {pformat(reagents)}")
self.pyd.reagents = reagents self.pyd.reagents = reagents
logger.debug(f"Reagents after insertion in pyd: {pformat(self.pyd.reagents)}")
# logger.debug(f"Attrs not in info: {[k for k, v in self.__dict__.items() if k not in info.keys()]}") # logger.debug(f"Attrs not in info: {[k for k, v in self.__dict__.items() if k not in info.keys()]}")
for item in self.recover: for item in self.recover:
# logger.debug(f"Attempting to recover: {item}") # logger.debug(f"Attempting to recover: {item}")

View File

@@ -758,7 +758,7 @@ def setup_lookup(func):
raise ValueError("Could not sanitize dictionary in query. Make sure you parse it first.") raise ValueError("Could not sanitize dictionary in query. Make sure you parse it first.")
elif v is not None: elif v is not None:
sanitized_kwargs[k] = v sanitized_kwargs[k] = v
logger.debug(f"sanitized kwargs: {sanitized_kwargs}") # logger.debug(f"sanitized kwargs: {sanitized_kwargs}")
return func(*args, **sanitized_kwargs) return func(*args, **sanitized_kwargs)
return wrapper return wrapper