diff --git a/src/submissions/backend/db/__init__.py b/src/submissions/backend/db/__init__.py index 214143b..f4ba6c6 100644 --- a/src/submissions/backend/db/__init__.py +++ b/src/submissions/backend/db/__init__.py @@ -70,5 +70,5 @@ def update_log(mapper, connection, target): logger.info(f"No changes detected, not updating logs.") -event.listen(LogMixin, 'after_update', update_log, propagate=True) -event.listen(LogMixin, 'after_insert', update_log, propagate=True) +# event.listen(LogMixin, 'after_update', update_log, propagate=True) +# event.listen(LogMixin, 'after_insert', update_log, propagate=True) diff --git a/src/submissions/backend/db/models/__init__.py b/src/submissions/backend/db/models/__init__.py index 2967fe0..7931bfc 100644 --- a/src/submissions/backend/db/models/__init__.py +++ b/src/submissions/backend/db/models/__init__.py @@ -182,7 +182,7 @@ class BaseClass(Base): query: Query = cls.__database_session__.query(model) # logger.debug(f"Grabbing singles using {model.get_default_info}") 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(): logger.info(f"Using key: {k} with value: {v}") try: diff --git a/src/submissions/backend/db/models/controls.py b/src/submissions/backend/db/models/controls.py index ce9c104..547269f 100644 --- a/src/submissions/backend/db/models/controls.py +++ b/src/submissions/backend/db/models/controls.py @@ -273,7 +273,7 @@ class Control(BaseClass): except StopIteration as e: raise AttributeError( 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 @classmethod @@ -535,8 +535,8 @@ class IridaControl(Control): except AttributeError: consolidate = False report = Report() - # logger.debug(f"settings: {pformat(chart_settings)}") - controls = cls.query(sub_type=chart_settings['sub_type'], start_date=chart_settings['start_date'], + logger.debug(f"settings: {pformat(chart_settings)}") + controls = cls.query(subtype=chart_settings['sub_type'], start_date=chart_settings['start_date'], end_date=chart_settings['end_date']) # logger.debug(f"Controls found: {controls}") if not controls: diff --git a/src/submissions/backend/db/models/kits.py b/src/submissions/backend/db/models/kits.py index 90a0cd9..0a2d8b9 100644 --- a/src/submissions/backend/db/models/kits.py +++ b/src/submissions/backend/db/models/kits.py @@ -12,7 +12,7 @@ from tools import check_authorization, setup_lookup, Report, Result, check_regex from typing import List, Literal, Generator, Any from pandas import ExcelFile from pathlib import Path -from . import Base, BaseClass, Organization +from . import Base, BaseClass, Organization, LogMixin from io import BytesIO logger = logging.getLogger(f'submissions.{__name__}') @@ -797,7 +797,7 @@ class SubmissionType(BaseClass): fmap = item.uses if fmap is None: fmap = {} - yield getattr(item, f"{field}_role"), fmap + yield getattr(item, f"{field}_role").name, fmap def get_default_kit(self) -> KitType | None: if len(self.kit_types) == 1: @@ -1361,7 +1361,7 @@ class Equipment(BaseClass): def __repr__(self) -> str: """ Returns: - str: represenation of this Equipment + str: representation of this Equipment """ return f"" @@ -1502,7 +1502,7 @@ class Equipment(BaseClass): equipment_role = EquipmentRole.query(name=equipment_role) equipment = cls.query() 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 = [] for choice in choices.split(" "): try: diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index da0fd8d..c676cb8 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -618,6 +618,17 @@ class BasicSubmission(BaseClass, LogMixin): result = assoc.save() 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": """ Converts this instance into a PydSubmission @@ -758,7 +769,7 @@ class BasicSubmission(BaseClass, LogMixin): except StopIteration as e: raise AttributeError( 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 # Child class custom functions @@ -1414,7 +1425,7 @@ class BacterialCulture(BasicSubmission): extends parent """ template = super().filename_template() - template += "_{{ submitting_lab }}_{{ submitter_plate_num }}" + template += "_{{ submitting_lab.name }}_{{ submitter_plate_num }}" return template @classmethod @@ -2356,7 +2367,7 @@ class BasicSample(BaseClass): except Exception as e: logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, using {cls}") model = cls - logger.info(f"Recruiting model: {model}") + # logger.info(f"Recruiting model: {model}") return model else: model = cls @@ -2370,7 +2381,7 @@ class BasicSample(BaseClass): except StopIteration as e: raise AttributeError( 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 @classmethod diff --git a/src/submissions/backend/excel/parser.py b/src/submissions/backend/excel/parser.py index 2568fdc..338de12 100644 --- a/src/submissions/backend/excel/parser.py +++ b/src/submissions/backend/excel/parser.py @@ -648,7 +648,8 @@ class TipParser(object): Returns: 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]: """ diff --git a/src/submissions/backend/excel/writer.py b/src/submissions/backend/excel/writer.py index 3d744f2..a49eb0a 100644 --- a/src/submissions/backend/excel/writer.py +++ b/src/submissions/backend/excel/writer.py @@ -187,6 +187,9 @@ class InfoWriter(object): sheet.cell(row=loc['row'], column=loc['column'], value=v['value']) except AttributeError as 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']) @@ -208,8 +211,8 @@ class ReagentWriter(object): if isinstance(submission_type, str): submission_type = SubmissionType.query(name=submission_type) if isinstance(extraction_kit, str): - kit_type = KitType.query(name=extraction_kit) - reagent_map = {k: v for k, v in kit_type.construct_xl_map_for_use(submission_type)} + extraction_kit = KitType.query(name=extraction_kit) + 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) 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: return 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}") placeholder = copy(equipment) if mp_info == {}: @@ -427,7 +433,7 @@ class TipWriter(object): submission_type = SubmissionType.query(name=submission_type) self.submission_type = submission_type 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) def reconcile_map(self, tips_list: List[dict], tips_map: dict) -> Generator[dict, None, None]: diff --git a/src/submissions/backend/validators/pydant.py b/src/submissions/backend/validators/pydant.py index 1a87c24..daf51a9 100644 --- a/src/submissions/backend/validators/pydant.py +++ b/src/submissions/backend/validators/pydant.py @@ -115,7 +115,7 @@ class PydReagent(BaseModel): fields = list(self.model_fields.keys()) + extras 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 @@ -164,13 +164,14 @@ class PydReagent(BaseModel): report.add_result(Result(owner=__name__, code=0, msg="New reagent created.", status="Information")) else: if submission is not None and reagent not in submission.reagents: - assoc = SubmissionReagentAssociation(reagent=reagent, submission=submission) - assoc.comments = self.comment - else: - assoc = None + # assoc = SubmissionReagentAssociation(reagent=reagent, submission=submission) + # assoc.comments = self.comment + submission.update_reagentassoc(reagent=reagent, role=self.role) + # else: + # assoc = None # 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 - return reagent, assoc, report + return reagent, report class PydSample(BaseModel, extra='allow'): @@ -299,6 +300,13 @@ class PydTips(BaseModel): lot: str | None = Field(default=None) 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: """ Con @@ -324,6 +332,13 @@ class PydEquipment(BaseModel, extra='ignore'): role: str | 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') @classmethod def make_empty_list(cls, value): @@ -786,7 +801,7 @@ class PydSubmission(BaseModel, extra='allow'): """ report = Report() 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'], rsl_plate_num=self.rsl_plate_num['value']) 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}") match key: case "reagents": - # if report.results[0].code == 1: - # instance.submission_reagent_associations = [] - # logger.debug(f"Looking through {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}") - if assoc is not None: # and assoc not in instance.submission_reagent_associations: - if assoc not in instance.submission_reagent_associations: - instance.submission_reagent_associations.append(assoc) - else: - logger.warning(f"Reagent association {assoc} is already present in {instance}") + # if assoc is not None: # and assoc not in instance.submission_reagent_associations: + # if assoc not in instance.submission_reagent_associations: + # instance.submission_reagent_associations.append(assoc) + # else: + # logger.warning(f"Reagent association {assoc} is already present in {instance.submission_reagent_associations}") case "samples": for sample in self.samples: sample, associations, _ = sample.toSQL(submission=instance) diff --git a/src/submissions/frontend/widgets/submission_widget.py b/src/submissions/frontend/widgets/submission_widget.py index a074669..7d2561c 100644 --- a/src/submissions/frontend/widgets/submission_widget.py +++ b/src/submissions/frontend/widgets/submission_widget.py @@ -178,7 +178,7 @@ class SubmissionFormContainer(QWidget): # NOTE: create reagent object reagent = PydReagent(ctx=self.app.ctx, **info, missing=False) # NOTE: send reagent to db - sqlobj, assoc, result = reagent.toSQL() + sqlobj, result = reagent.toSQL() sqlobj.save() report.add_result(result) # 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] return query + # def update_pyd(self): + # results = self.parse_form() + # logger.debug(pformat(results)) + @report_result def submit_new_sample_function(self, *args) -> Report: """ @@ -448,8 +452,9 @@ class SubmissionFormWidget(QWidget): if field is not None: info[field] = value # 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 + 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()]}") for item in self.recover: # logger.debug(f"Attempting to recover: {item}") diff --git a/src/submissions/tools/__init__.py b/src/submissions/tools/__init__.py index 8abba64..12974b0 100644 --- a/src/submissions/tools/__init__.py +++ b/src/submissions/tools/__init__.py @@ -758,7 +758,7 @@ def setup_lookup(func): raise ValueError("Could not sanitize dictionary in query. Make sure you parse it first.") elif v is not None: sanitized_kwargs[k] = v - logger.debug(f"sanitized kwargs: {sanitized_kwargs}") + # logger.debug(f"sanitized kwargs: {sanitized_kwargs}") return func(*args, **sanitized_kwargs) return wrapper