''' Contains all validators ''' import logging, re import sys from pathlib import Path from openpyxl import load_workbook from backend.db.models import BasicSubmission, SubmissionType from tools import jinja_template_loading from jinja2 import Template from dateutil.parser import parse from datetime import datetime logger = logging.getLogger(f"submissions.{__name__}") class RSLNamer(object): """ Object that will enforce proper formatting on RSL plate names. """ def __init__(self, filename: str, submission_type: str | None = None, data: dict | None = None): # NOTE: Preferred method is path retrieval, but might also need validation for just string. filename = Path(filename) if Path(filename).exists() else filename self.submission_type = submission_type if not self.submission_type: self.submission_type = self.retrieve_submission_type(filename=filename) logger.info(f"got submission type: {self.submission_type}") if 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)) if not data: data = dict(submission_type=self.submission_type) if "submission_type" not in data.keys(): data['submission_type'] = self.submission_type self.parsed_name = self.sub_object.enforce_name(instr=self.parsed_name, data=data) logger.info(f"Parsed name: {self.parsed_name}") @classmethod def retrieve_submission_type(cls, filename: str | Path) -> str: """ Gets submission type from excel file properties or sheet names or regex pattern match or user input Args: filename (str | Path): filename Raises: TypeError: Raised if unsupported variable type for filename given. Returns: str: parsed submission type """ def st_from_path(filename:Path) -> str: if filename.exists(): wb = load_workbook(filename) try: # NOTE: Gets first category in the metadata. categories = wb.properties.category.split(";") submission_type = next(item.strip().title() for item in categories) except (StopIteration, AttributeError): sts = {item.name: item.template_file_sheets for item in SubmissionType.query() if item.template_file} try: submission_type = next(k.title() for k,v in sts.items() if wb.sheetnames==v) except StopIteration: # NOTE: On failure recurse using filename as string for string method submission_type = cls.retrieve_submission_type(filename=filename.stem.__str__()) else: submission_type = cls.retrieve_submission_type(filename=filename.stem.__str__()) return submission_type def st_from_str(filename:str) -> str: if filename.startswith("tmp"): return "Bacterial Culture" regex = BasicSubmission.regex m = regex.search(filename) try: submission_type = m.lastgroup except AttributeError as e: submission_type = None logger.critical(f"No submission type found or submission type found!: {e}") return submission_type match filename: case Path(): submission_type = st_from_path(filename=filename) case str(): submission_type = st_from_str(filename=filename) case _: raise TypeError(f"Unsupported filename type: {type(filename)}.") submission_type = None try: check = submission_type is None except UnboundLocalError: check = True if check: if "pytest" in sys.modules: raise ValueError("Submission Type came back as None.") from frontend.widgets import ObjectSelector dlg = ObjectSelector(title="Couldn't parse submission type.", message="Please select submission type from list below.", obj_type=SubmissionType) if dlg.exec(): submission_type = dlg.parse_form() submission_type = submission_type.replace("_", " ") return submission_type @classmethod def retrieve_rsl_number(cls, filename: str | Path, regex: re.Pattern | None = None): """ Uses regex to retrieve the plate number and submission type from an input string Args: regex (str): string to construct pattern filename (str): string to be parsed """ if regex is None: regex = BasicSubmission.regex match filename: case Path(): m = regex.search(filename.stem) case str(): m = regex.search(filename) case _: m = None if m is not None: try: parsed_name = m.group().upper().strip(".") except: parsed_name = None else: parsed_name = None return parsed_name @classmethod def construct_new_plate_name(cls, data: dict) -> str: """ Make a brand-new plate name from submission data. Args: data (dict): incoming submission data Returns: str: Output filename """ if "submitted_date" in data.keys(): if isinstance(data['submitted_date'], dict): if data['submitted_date']['value'] is not None: today = data['submitted_date']['value'] else: today = datetime.now() else: today = data['submitted_date'] else: try: today = re.search(r"\d{4}(_|-)?\d{2}(_|-)?\d{2}", data['rsl_plate_num']) today = parse(today.group()) except (AttributeError, KeyError): today = datetime.now() if "rsl_plate_num" in data.keys(): plate_number = data['rsl_plate_num'].split("-")[-1][0] else: previous = BasicSubmission.query(start_date=today, end_date=today, submissiontype=data['submission_type']) plate_number = len(previous) + 1 return f"RSL-{data['abbreviation']}-{today.year}{str(today.month).zfill(2)}{str(today.day).zfill(2)}-{plate_number}" @classmethod def construct_export_name(cls, template: Template, **kwargs) -> str: """ Make export file name from jinja template. (currently unused) Args: template (jinja2.Template): Template stored in BasicSubmission Returns: str: output file name. """ environment = jinja_template_loading() template = environment.from_string(template) return template.render(**kwargs) def calculate_repeat(self) -> str: """ Determines what repeat number this plate is. Returns: str: Repeat number. """ regex = re.compile(r"-\d(?PR\d)") m = regex.search(self.parsed_name) if m is not None: return m.group("repeat") else: return "" from .pydant import PydSubmission, PydKitType, PydContact, PydOrganization, PydSample, PydReagent, PydReagentRole, \ PydEquipment, PydEquipmentRole, PydTips, PydPCRControl, PydIridaControl, PydProcess, PydElastic