post documentation and code clean-up.

This commit is contained in:
lwark
2024-07-03 15:13:55 -05:00
parent 12ca3157e5
commit c460e5eeca
21 changed files with 421 additions and 238 deletions

View File

@@ -1,6 +1,7 @@
'''
Contains pandas convenience functions for interacting with excel workbooks
Contains pandas and openpyxl convenience functions for interacting with excel workbooks
'''
from .reports import *
from .parser import *
from .writer import *

View File

@@ -1,5 +1,5 @@
'''
contains parser object for pulling values from client generated submission sheets.
contains parser objects for pulling values from client generated submission sheets.
'''
import sys
from copy import copy
@@ -78,7 +78,7 @@ class SheetParser(object):
def parse_reagents(self, extraction_kit: str | None = None):
"""
Pulls reagent info from the excel sheet
Calls reagent parser class to pull info from the excel sheet
Args:
extraction_kit (str | None, optional): Relevant extraction kit for reagent map. Defaults to None.
@@ -91,16 +91,22 @@ class SheetParser(object):
def parse_samples(self):
"""
Pulls sample info from the excel sheet
Calls sample parser to pull info from the excel sheet
"""
parser = SampleParser(xl=self.xl, submission_type=self.submission_type)
self.sub['samples'] = parser.reconcile_samples()
def parse_equipment(self):
"""
Calls equipment parser to pull info from the excel sheet
"""
parser = EquipmentParser(xl=self.xl, submission_type=self.submission_type)
self.sub['equipment'] = parser.parse_equipment()
def parse_tips(self):
"""
Calls tips parser to pull info from the excel sheet
"""
parser = TipParser(xl=self.xl, submission_type=self.submission_type)
self.sub['tips'] = parser.parse_tips()
@@ -160,8 +166,16 @@ class SheetParser(object):
class InfoParser(object):
"""
Object to parse generic info from excel sheet.
"""
def __init__(self, xl: Workbook, submission_type: str|SubmissionType, sub_object: BasicSubmission|None=None):
"""
Args:
xl (Workbook): Openpyxl workbook from submitted excel file.
submission_type (str | SubmissionType): Type of submission expected (Wastewater, Bacterial Culture, etc.)
sub_object (BasicSubmission | None, optional): Submission object holding methods. Defaults to None.
"""
logger.info(f"\n\nHello from InfoParser!\n\n")
if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type)
@@ -221,6 +235,7 @@ class InfoParser(object):
new['name'] = k
relevant.append(new)
# logger.debug(f"relevant map for {sheet}: {pformat(relevant)}")
# NOTE: make sure relevant is not an empty list.
if not relevant:
continue
for item in relevant:
@@ -231,6 +246,7 @@ class InfoParser(object):
case "submission_type":
value, missing = is_missing(value)
value = value.title()
# NOTE: is field a JSON?
case thing if thing in self.sub_object.jsons():
value, missing = is_missing(value)
if missing: continue
@@ -248,12 +264,23 @@ class InfoParser(object):
dicto[item['name']] = dict(value=value, missing=missing)
except (KeyError, IndexError):
continue
# Return after running the parser components held in submission object.
return self.sub_object.custom_info_parser(input_dict=dicto, xl=self.xl)
class ReagentParser(object):
"""
Object to pull reagents from excel sheet.
"""
def __init__(self, xl: Workbook, submission_type: str, extraction_kit: str, sub_object:BasicSubmission|None=None):
"""
Args:
xl (Workbook): Openpyxl workbook from submitted excel file.
submission_type (str): Type of submission expected (Wastewater, Bacterial Culture, etc.)
extraction_kit (str): Extraction kit used.
sub_object (BasicSubmission | None, optional): Submission object holding methods. Defaults to None.
"""
# logger.debug("\n\nHello from ReagentParser!\n\n")
self.submission_type_obj = submission_type
self.sub_object = sub_object
@@ -284,7 +311,7 @@ class ReagentParser(object):
pass
return reagent_map
def parse_reagents(self) -> List[PydReagent]:
def parse_reagents(self) -> List[dict]:
"""
Extracts reagent information from the excel form.
@@ -312,7 +339,7 @@ class ReagentParser(object):
comment = ""
except (KeyError, IndexError):
listo.append(
PydReagent(role=item.strip(), lot=None, expiry=None, name=None, comment="", missing=True))
dict(role=item.strip(), lot=None, expiry=None, name=None, comment="", missing=True))
continue
# NOTE: If the cell is blank tell the PydReagent
if check_not_nan(lot):
@@ -336,17 +363,17 @@ class ReagentParser(object):
class SampleParser(object):
"""
object to pull data for samples in excel sheet and construct individual sample objects
Object to pull data for samples in excel sheet and construct individual sample objects
"""
def __init__(self, xl: Workbook, submission_type: SubmissionType, sample_map: dict | None = None, sub_object:BasicSubmission|None=None) -> None:
"""
convert sample sub-dataframe to dictionary of records
Args:
df (pd.DataFrame): input sample dataframe
elution_map (pd.DataFrame | None, optional): optional map of elution plate. Defaults to None.
"""
xl (Workbook): Openpyxl workbook from submitted excel file.
submission_type (SubmissionType): Type of submission expected (Wastewater, Bacterial Culture, etc.)
sample_map (dict | None, optional): Locations in database where samples are found. Defaults to None.
sub_object (BasicSubmission | None, optional): Submission object holding methods. Defaults to None.
"""
# logger.debug("\n\nHello from SampleParser!\n\n")
self.samples = []
self.xl = xl
@@ -383,10 +410,13 @@ class SampleParser(object):
sample_info_map = sample_map
return sample_info_map
def parse_plate_map(self):
def parse_plate_map(self) -> List[dict]:
"""
Parse sample location/name from plate map
"""
Returns:
List[dict]: List of sample ids and locations.
"""
invalids = [0, "0", "EMPTY"]
smap = self.sample_info_map['plate_map']
ws = self.xl[smap['sheet']]
@@ -412,7 +442,11 @@ class SampleParser(object):
def parse_lookup_table(self) -> List[dict]:
"""
Parse misc info from lookup table.
"""
Returns:
List[dict]: List of basic sample info.
"""
lmap = self.sample_info_map['lookup_table']
ws = self.xl[lmap['sheet']]
lookup_samples = []
@@ -460,7 +494,13 @@ class SampleParser(object):
new_samples.append(PydSample(**translated_dict))
return result, new_samples
def reconcile_samples(self):
def reconcile_samples(self) -> List[dict]:
"""
Merges sample info from lookup table and plate map.
Returns:
List[dict]: Reconciled samples
"""
# TODO: Move to pydantic validator?
if self.plate_map_samples is None or self.lookup_samples is None:
self.samples = self.lookup_samples or self.plate_map_samples
@@ -504,8 +544,15 @@ class SampleParser(object):
class EquipmentParser(object):
"""
Object to pull data for equipment in excel sheet
"""
def __init__(self, xl: Workbook, submission_type: str|SubmissionType) -> None:
"""
Args:
xl (Workbook): Openpyxl workbook from submitted excel file.
submission_type (str | SubmissionType): Type of submission expected (Wastewater, Bacterial Culture, etc.)
"""
if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type)
self.submission_type = submission_type
@@ -582,8 +629,15 @@ class EquipmentParser(object):
class TipParser(object):
"""
Object to pull data for tips in excel sheet
"""
def __init__(self, xl: Workbook, submission_type: str|SubmissionType) -> None:
"""
Args:
xl (Workbook): Openpyxl workbook from submitted excel file.
submission_type (str | SubmissionType): Type of submission expected (Wastewater, Bacterial Culture, etc.)
"""
if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type)
self.submission_type = submission_type
@@ -644,8 +698,6 @@ class PCRParser(object):
def __init__(self, filepath: Path | None=None, submission: BasicSubmission | None=None) -> None:
"""
Initializes object.
Args:
filepath (Path | None, optional): file to parse. Defaults to None.
"""

View File

@@ -90,6 +90,13 @@ class ReportMaker(object):
return html
def write_report(self, filename: Path | str, obj: QWidget | None = None):
"""
Writes info to files.
Args:
filename (Path | str): Basename of output file
obj (QWidget | None, optional): Parent object. Defaults to None.
"""
if isinstance(filename, str):
filename = Path(filename)
filename = filename.absolute()
@@ -108,6 +115,9 @@ class ReportMaker(object):
self.writer.close()
def fix_up_xl(self):
"""
Handles formatting of xl file.
"""
# logger.debug(f"Updating worksheet")
worksheet: Worksheet = self.writer.sheets['Report']
for idx, col in enumerate(self.summary_df, start=1): # loop through all columns

View File

@@ -1,3 +1,6 @@
'''
contains writer objects for pushing values to submission sheet templates.
'''
import logging
from copy import copy
from operator import itemgetter
@@ -27,8 +30,9 @@ class SheetWriter(object):
def __init__(self, submission: PydSubmission, missing_only: bool = False):
"""
Args:
filepath (Path | None, optional): file path to excel sheet. Defaults to None.
"""
submission (PydSubmission): Object containing submission information.
missing_only (bool, optional): Whether to only fill in missing values. Defaults to False.
"""
self.sub = OrderedDict(submission.improved_dict())
for k, v in self.sub.items():
match k:
@@ -116,6 +120,13 @@ class InfoWriter(object):
def __init__(self, xl: Workbook, submission_type: SubmissionType | str, info_dict: dict,
sub_object: BasicSubmission | None = None):
"""
Args:
xl (Workbook): Openpyxl workbook from submitted excel file.
submission_type (SubmissionType | str): Type of submission expected (Wastewater, Bacterial Culture, etc.)
info_dict (dict): Dictionary of information to write.
sub_object (BasicSubmission | None, optional): Submission object containing methods. Defaults to None.
"""
logger.debug(f"Info_dict coming into InfoWriter: {pformat(info_dict)}")
if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type)
@@ -186,6 +197,13 @@ class ReagentWriter(object):
def __init__(self, xl: Workbook, submission_type: SubmissionType | str, extraction_kit: KitType | str,
reagent_list: list):
"""
Args:
xl (Workbook): Openpyxl workbook from submitted excel file.
submission_type (SubmissionType | str): Type of submission expected (Wastewater, Bacterial Culture, etc.)
extraction_kit (KitType | str): Extraction kit used.
reagent_list (list): List of reagent dicts to be written to excel.
"""
self.xl = xl
if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type)
@@ -245,8 +263,13 @@ class SampleWriter(object):
"""
object to write sample data into excel file
"""
def __init__(self, xl: Workbook, submission_type: SubmissionType | str, sample_list: list):
"""
Args:
xl (Workbook): Openpyxl workbook from submitted excel file.
submission_type (SubmissionType | str): Type of submission expected (Wastewater, Bacterial Culture, etc.)
sample_list (list): List of sample dictionaries to be written to excel file.
"""
if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type)
self.submission_type = submission_type
@@ -303,6 +326,12 @@ class EquipmentWriter(object):
"""
def __init__(self, xl: Workbook, submission_type: SubmissionType | str, equipment_list: list):
"""
Args:
xl (Workbook): Openpyxl workbook from submitted excel file.
submission_type (SubmissionType | str): Type of submission expected (Wastewater, Bacterial Culture, etc.)
equipment_list (list): List of equipment dictionaries to write to excel file.
"""
if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type)
self.submission_type = submission_type
@@ -385,6 +414,12 @@ class TipWriter(object):
"""
def __init__(self, xl: Workbook, submission_type: SubmissionType | str, tips_list: list):
"""
Args:
xl (Workbook): Openpyxl workbook from submitted excel file.
submission_type (SubmissionType | str): Type of submission expected (Wastewater, Bacterial Culture, etc.)
tips_list (list): List of tip dictionaries to write to the excel file.
"""
if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type)
self.submission_type = submission_type
@@ -460,8 +495,15 @@ class TipWriter(object):
class DocxWriter(object):
"""
Object to render
"""
def __init__(self, base_dict: dict):
"""
Args:
base_dict (dict): dictionary of info to be written to template.
"""
self.sub_obj = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=base_dict['submission_type'])
env = jinja_template_loading()
temp_name = f"{base_dict['submission_type'].replace(' ', '').lower()}_subdocument.docx"
@@ -506,7 +548,13 @@ class DocxWriter(object):
output.append(contents)
return output
def create_merged_template(self, *args):
def create_merged_template(self, *args) -> BytesIO:
"""
Appends submission specific information
Returns:
BytesIO: Merged docx template
"""
merged_document = Document()
output = BytesIO()
for index, file in enumerate(args):