logging and scrollable form

This commit is contained in:
Landon Wark
2023-01-24 15:22:15 -06:00
parent 7a53cfd9a1
commit f53d420d56
9 changed files with 129 additions and 121 deletions

View File

@@ -6,12 +6,11 @@ if getattr(sys, 'frozen', False):
else : else :
pass pass
from configure import get_config, create_database_session, setup_logger from configure import get_config, create_database_session, setup_logger
logger = setup_logger(verbosity=3)
ctx = get_config(None) ctx = get_config(None)
from PyQt6.QtWidgets import QApplication from PyQt6.QtWidgets import QApplication
from frontend import App from frontend import App
logger = setup_logger(verbose=True)
ctx["database_session"] = create_database_session(Path(ctx['database'])) ctx["database_session"] = create_database_session(Path(ctx['database']))
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -11,7 +11,7 @@ from sqlalchemy import JSON
import json import json
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
logger = logging.getLogger(__name__) logger = logging.getLogger(f"submissions.{__name__}")
def get_kits_by_use( ctx:dict, kittype_str:str|None) -> list: def get_kits_by_use( ctx:dict, kittype_str:str|None) -> list:
pass pass
@@ -35,7 +35,7 @@ def store_submission(ctx:dict, base_submission:models.BasicSubmission) -> None:
def store_reagent(ctx:dict, reagent:models.Reagent) -> None: def store_reagent(ctx:dict, reagent:models.Reagent) -> None:
print(reagent.__dict__) logger.debug(reagent.__dict__)
ctx['database_session'].add(reagent) ctx['database_session'].add(reagent)
ctx['database_session'].commit() ctx['database_session'].commit()
@@ -46,18 +46,18 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio
info_dict['submission_type'] = info_dict['submission_type'].replace(" ", "_").lower() info_dict['submission_type'] = info_dict['submission_type'].replace(" ", "_").lower()
instance = model() instance = model()
for item in info_dict: for item in info_dict:
print(f"Setting {item} to {info_dict[item]}") logger.debug(f"Setting {item} to {info_dict[item]}")
match item: match item:
case "extraction_kit": case "extraction_kit":
q_str = info_dict[item] q_str = info_dict[item]
print(f"Looking up kit {q_str}") logger.debug(f"Looking up kit {q_str}")
field_value = lookup_kittype_by_name(ctx=ctx, name=q_str) field_value = lookup_kittype_by_name(ctx=ctx, name=q_str)
print(f"Got {field_value} for kit {q_str}") logger.debug(f"Got {field_value} for kit {q_str}")
case "submitting_lab": case "submitting_lab":
q_str = info_dict[item].replace(" ", "_").lower() q_str = info_dict[item].replace(" ", "_").lower()
print(f"looking up organization: {q_str}") logger.debug(f"looking up organization: {q_str}")
field_value = lookup_org_by_name(ctx=ctx, name=q_str) field_value = lookup_org_by_name(ctx=ctx, name=q_str)
print(f"Got {field_value} for organization {q_str}") logger.debug(f"Got {field_value} for organization {q_str}")
case "submitter_plate_num": case "submitter_plate_num":
# Because of unique constraint, the submitter plate number cannot be None, so... # Because of unique constraint, the submitter plate number cannot be None, so...
if info_dict[item] == None: if info_dict[item] == None:
@@ -72,16 +72,16 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio
try: try:
setattr(instance, item, field_value) setattr(instance, item, field_value)
except AttributeError: except AttributeError:
print(f"Could not set attribute: {item} to {info_dict[item]}") logger.debug(f"Could not set attribute: {item} to {info_dict[item]}")
continue continue
# print(instance.__dict__) # logger.debug(instance.__dict__)
return instance return instance
# looked_up = [] # looked_up = []
# for reagent in reagents: # for reagent in reagents:
# my_reagent = lookup_reagent(reagent) # my_reagent = lookup_reagent(reagent)
# print(my_reagent) # logger.debug(my_reagent)
# looked_up.append(my_reagent) # looked_up.append(my_reagent)
# print(looked_up) # logger.debug(looked_up)
# instance.reagents = looked_up # instance.reagents = looked_up
# ctx['database_session'].add(instance) # ctx['database_session'].add(instance)
# ctx['database_session'].commit() # ctx['database_session'].commit()
@@ -89,7 +89,7 @@ def construct_submission_info(ctx:dict, info_dict:dict) -> models.BasicSubmissio
def construct_reagent(ctx:dict, info_dict:dict) -> models.Reagent: def construct_reagent(ctx:dict, info_dict:dict) -> models.Reagent:
reagent = models.Reagent() reagent = models.Reagent()
for item in info_dict: for item in info_dict:
print(f"Reagent info item: {item}") logger.debug(f"Reagent info item: {item}")
match item: match item:
case "lot": case "lot":
reagent.lot = info_dict[item].upper() reagent.lot = info_dict[item].upper()
@@ -100,7 +100,7 @@ def construct_reagent(ctx:dict, info_dict:dict) -> models.Reagent:
try: try:
reagent.expiry = reagent.expiry + reagent.type.eol_ext reagent.expiry = reagent.expiry + reagent.type.eol_ext
except TypeError as e: except TypeError as e:
print(f"WE got a type error: {e}.") logger.debug(f"WE got a type error: {e}.")
except AttributeError: except AttributeError:
pass pass
return reagent return reagent
@@ -116,9 +116,9 @@ def get_all_reagenttype_names(ctx:dict) -> list[str]:
return lookedup return lookedup
def lookup_reagenttype_by_name(ctx:dict, rt_name:str) -> models.ReagentType: def lookup_reagenttype_by_name(ctx:dict, rt_name:str) -> models.ReagentType:
print(f"Looking up ReagentType by name: {rt_name}") logger.debug(f"Looking up ReagentType by name: {rt_name}")
lookedup = ctx['database_session'].query(models.ReagentType).filter(models.ReagentType.name==rt_name).first() lookedup = ctx['database_session'].query(models.ReagentType).filter(models.ReagentType.name==rt_name).first()
print(f"Found ReagentType: {lookedup}") logger.debug(f"Found ReagentType: {lookedup}")
return lookedup return lookedup
@@ -127,7 +127,7 @@ def lookup_kittype_by_use(ctx:dict, used_by:str) -> list[models.KitType]:
return ctx['database_session'].query(models.KitType).filter(models.KitType.used_for.contains(used_by)) return ctx['database_session'].query(models.KitType).filter(models.KitType.used_for.contains(used_by))
def lookup_kittype_by_name(ctx:dict, name:str) -> models.KitType: def lookup_kittype_by_name(ctx:dict, name:str) -> models.KitType:
print(f"Querying kittype: {name}") logger.debug(f"Querying kittype: {name}")
return ctx['database_session'].query(models.KitType).filter(models.KitType.name==name).first() return ctx['database_session'].query(models.KitType).filter(models.KitType.name==name).first()
@@ -154,11 +154,11 @@ def lookup_all_orgs(ctx:dict) -> list[models.Organization]:
return ctx['database_session'].query(models.Organization).all() return ctx['database_session'].query(models.Organization).all()
def lookup_org_by_name(ctx:dict, name:str|None) -> models.Organization: def lookup_org_by_name(ctx:dict, name:str|None) -> models.Organization:
print(f"Querying organization: {name}") logger.debug(f"Querying organization: {name}")
return ctx['database_session'].query(models.Organization).filter(models.Organization.name==name).first() return ctx['database_session'].query(models.Organization).filter(models.Organization.name==name).first()
def submissions_to_df(ctx:dict, type:str|None=None): def submissions_to_df(ctx:dict, type:str|None=None):
print(f"Type: {type}") logger.debug(f"Type: {type}")
subs = [item.to_dict() for item in lookup_all_submissions_by_type(ctx=ctx, type=type)] subs = [item.to_dict() for item in lookup_all_submissions_by_type(ctx=ctx, type=type)]
df = pd.DataFrame.from_records(subs) df = pd.DataFrame.from_records(subs)
return df return df
@@ -205,7 +205,7 @@ def create_kit_from_yaml(ctx:dict, exp:dict) -> None:
except (UnicodeDecodeError, AttributeError): except (UnicodeDecodeError, AttributeError):
exp['password'] = exp['password'].encode() exp['password'] = exp['password'].encode()
if base64.b64encode(exp['password']) != b'cnNsX3N1Ym1pNTVpb25z': if base64.b64encode(exp['password']) != b'cnNsX3N1Ym1pNTVpb25z':
print(f"Not the correct password.") logger.debug(f"Not the correct password.")
return return
for type in exp: for type in exp:
if type == "password": if type == "password":
@@ -220,8 +220,8 @@ def create_kit_from_yaml(ctx:dict, exp:dict) -> None:
rt = look_up rt = look_up
rt.kits.append(kit) rt.kits.append(kit)
ctx['database_session'].add(rt) ctx['database_session'].add(rt)
print(rt.__dict__) logger.debug(rt.__dict__)
print(kit.__dict__) logger.debug(kit.__dict__)
ctx['database_session'].add(kit) ctx['database_session'].add(kit)
ctx['database_session'].commit() ctx['database_session'].commit()
@@ -252,7 +252,7 @@ def get_all_controls_by_type(ctx:dict, con_type:str, start_date:date|None=None,
list: Control instances. list: Control instances.
""" """
# print(f"Using dates: {start_date} to {end_date}") # logger.debug(f"Using dates: {start_date} to {end_date}")
query = ctx['database_session'].query(models.ControlType).filter_by(name=con_type) query = ctx['database_session'].query(models.ControlType).filter_by(name=con_type)
try: try:
output = query.first().instances output = query.first().instances
@@ -261,7 +261,7 @@ def get_all_controls_by_type(ctx:dict, con_type:str, start_date:date|None=None,
# Hacky solution to my not being able to get the sql query to work. # Hacky solution to my not being able to get the sql query to work.
if start_date != None and end_date != None: if start_date != None and end_date != None:
output = [item for item in output if item.submitted_date.date() > start_date and item.submitted_date.date() < end_date] output = [item for item in output if item.submitted_date.date() > start_date and item.submitted_date.date() < end_date]
# print(f"Type {con_type}: {query.first()}") # logger.debug(f"Type {con_type}: {query.first()}")
return output return output
@@ -271,7 +271,7 @@ def get_control_subtypes(ctx:dict, type:str, mode:str):
except TypeError: except TypeError:
return [] return []
jsoner = json.loads(getattr(outs, mode)) jsoner = json.loads(getattr(outs, mode))
print(f"JSON out: {jsoner}") logger.debug(f"JSON out: {jsoner}")
try: try:
genera = list(jsoner.keys())[0] genera = list(jsoner.keys())[0]
except IndexError: except IndexError:

View File

@@ -1,13 +1,12 @@
from . import Base from . import Base
from sqlalchemy import Column, String, TIMESTAMP, text, JSON, INTEGER, ForeignKey, UniqueConstraint, Table from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, Table
from sqlalchemy.orm import relationship, relationships from sqlalchemy.orm import relationship
from datetime import datetime as dt from datetime import datetime as dt
reagents_submissions = Table("_reagents_submissions", Base.metadata, Column("reagent_id", INTEGER, ForeignKey("_reagents.id")), Column("submission_id", INTEGER, ForeignKey("_submissions.id"))) reagents_submissions = Table("_reagents_submissions", Base.metadata, Column("reagent_id", INTEGER, ForeignKey("_reagents.id")), Column("submission_id", INTEGER, ForeignKey("_submissions.id")))
class BasicSubmission(Base): class BasicSubmission(Base):
# TODO: Figure out if I want seperate tables for different sample types.
__tablename__ = "_submissions" __tablename__ = "_submissions"
id = Column(INTEGER, primary_key=True) #: primary key id = Column(INTEGER, primary_key=True) #: primary key
@@ -32,7 +31,6 @@ class BasicSubmission(Base):
} }
def to_dict(self): def to_dict(self):
print(self.submitting_lab)
try: try:
sub_lab = self.submitting_lab.name sub_lab = self.submitting_lab.name
except AttributeError: except AttributeError:

View File

@@ -13,11 +13,14 @@ logger = logging.getLogger(f"submissions.{__name__}")
class SheetParser(object): class SheetParser(object):
def __init__(self, filepath:Path|None = None, **kwargs): def __init__(self, filepath:Path|None = None, **kwargs):
logger.debug(f"Parsing {filepath.__str__()}")
for kwarg in kwargs: for kwarg in kwargs:
setattr(self, f"_{kwarg}", kwargs[kwarg]) setattr(self, f"_{kwarg}", kwargs[kwarg])
if filepath == None: if filepath == None:
logger.debug(f"No filepath.")
self.xl = None self.xl = None
else: else:
try: try:
self.xl = pd.ExcelFile(filepath.__str__()) self.xl = pd.ExcelFile(filepath.__str__())
except ValueError: except ValueError:
@@ -76,6 +79,7 @@ class SheetParser(object):
self.sub['lot_plate'] = submission_info.iloc[12][6] self.sub['lot_plate'] = submission_info.iloc[12][6]
sample_parser = SampleParser(submission_info.iloc[15:111]) sample_parser = SampleParser(submission_info.iloc[15:111])
sample_parse = getattr(sample_parser, f"parse_{self.sub['submission_type'].lower()}_samples") sample_parse = getattr(sample_parser, f"parse_{self.sub['submission_type'].lower()}_samples")
logger.debug(f"Parser result: {self.sub}")
self.sub['samples'] = sample_parse() self.sub['samples'] = sample_parse()
@@ -121,9 +125,10 @@ class SampleParser(object):
new.sample_id = sample['Unnamed: 1'] new.sample_id = sample['Unnamed: 1']
new.organism = sample['Unnamed: 2'] new.organism = sample['Unnamed: 2']
new.concentration = sample['Unnamed: 3'] new.concentration = sample['Unnamed: 3']
# print(f"Sample object: {new.sample_id} = {type(new.sample_id)}") # logger.debug(f"Sample object: {new.sample_id} = {type(new.sample_id)}")
logger.debug(f"Got sample_id: {new.sample_id}")
try: try:
not_a_nan = not np.isnan(new.sample_id) and new.sample_id.lower() != 'blank' not_a_nan = not np.isnan(new.sample_id) and str(new.sample_id).lower() != 'blank'
except TypeError: except TypeError:
not_a_nan = True not_a_nan = True
if not_a_nan: if not_a_nan:

View File

@@ -3,6 +3,9 @@ from pandas import DataFrame
import numpy as np import numpy as np
from backend.db import models from backend.db import models
import json import json
import logging
logger = logging.getLogger(f"submissions.{__name__}")
def make_report_xlsx(records:list[dict]) -> DataFrame: def make_report_xlsx(records:list[dict]) -> DataFrame:
df = DataFrame.from_records(records) df = DataFrame.from_records(records)
@@ -10,7 +13,7 @@ def make_report_xlsx(records:list[dict]) -> DataFrame:
# table = df.pivot_table(values="Cost", index=["Submitting Lab", "Extraction Kit"], columns=["Cost", "Sample Count"], aggfunc={'Cost':np.sum,'Sample Count':np.sum}) # table = df.pivot_table(values="Cost", index=["Submitting Lab", "Extraction Kit"], columns=["Cost", "Sample Count"], aggfunc={'Cost':np.sum,'Sample Count':np.sum})
df2 = df.groupby(["Submitting Lab", "Extraction Kit"]).agg({'Cost': ['sum', 'count'], 'Sample Count':['sum']}) df2 = df.groupby(["Submitting Lab", "Extraction Kit"]).agg({'Cost': ['sum', 'count'], 'Sample Count':['sum']})
# df2['Cost'] = df2['Cost'].map('${:,.2f}'.format) # df2['Cost'] = df2['Cost'].map('${:,.2f}'.format)
print(df2.columns) logger.debug(df2.columns)
# df2['Cost']['sum'] = df2['Cost']['sum'].apply('${:,.2f}'.format) # df2['Cost']['sum'] = df2['Cost']['sum'].apply('${:,.2f}'.format)
df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')] = df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')].applymap('${:,.2f}'.format) df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')] = df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')].applymap('${:,.2f}'.format)
return df2 return df2
@@ -27,7 +30,7 @@ def make_report_xlsx(records:list[dict]) -> DataFrame:
# for ii in range(data_size): # for ii in range(data_size):
# new_dict = {} # new_dict = {}
# for genus in sub_dict: # for genus in sub_dict:
# print(genus) # logger.debug(genus)
# sub_name = list(sub_dict[genus].keys())[ii] # sub_name = list(sub_dict[genus].keys())[ii]
# new_dict[genus] = sub_dict[genus][sub_name] # new_dict[genus] = sub_dict[genus][sub_name]
# output.append({"date":dict_name, "name": sub_name, "data": new_dict}) # output.append({"date":dict_name, "name": sub_name, "data": new_dict})
@@ -55,10 +58,10 @@ def make_report_xlsx(records:list[dict]) -> DataFrame:
# # col_dict = entry[col_name] # # col_dict = entry[col_name]
# # series = pd.Series(data=col_dict.values(), index=col_dict.keys(), name=col_name) # # series = pd.Series(data=col_dict.values(), index=col_dict.keys(), name=col_name)
# # # df[col_name] = series.values # # # df[col_name] = series.values
# # # print(df.index) # # # logger.debug(df.index)
# # series_list.append(series) # # series_list.append(series)
# # df = DataFrame(series_list).T.fillna(0) # # df = DataFrame(series_list).T.fillna(0)
# # print(df) # # logger.debug(df)
# dfs['name'] = df # dfs['name'] = df
# return dfs # return dfs
@@ -74,14 +77,14 @@ def convert_control_by_mode(ctx:dict, control:models.Control, mode:str):
for key in data[genus]: for key in data[genus]:
_dict[key] = data[genus][key] _dict[key] = data[genus][key]
output.append(_dict) output.append(_dict)
# print(output) # logger.debug(output)
return output return output
def convert_data_list_to_df(ctx:dict, input:list[dict], subtype:str|None=None) -> DataFrame: def convert_data_list_to_df(ctx:dict, input:list[dict], subtype:str|None=None) -> DataFrame:
df = DataFrame.from_records(input) df = DataFrame.from_records(input)
safe = ['name', 'submitted_date', 'genus', 'target'] safe = ['name', 'submitted_date', 'genus', 'target']
print(df) logger.debug(df)
for column in df.columns: for column in df.columns:
if "percent" in column: if "percent" in column:
count_col = [item for item in df.columns if "count" in item][0] count_col = [item for item in df.columns if "count" in item][0]
@@ -90,5 +93,5 @@ def convert_data_list_to_df(ctx:dict, input:list[dict], subtype:str|None=None) -
if column not in safe: if column not in safe:
if subtype != None and column != subtype: if subtype != None and column != subtype:
del df[column] del df[column]
# print(df) # logger.debug(df)
return df return df

View File

@@ -1,5 +1,5 @@
import yaml import yaml
import sys, os, stat, platform import sys, os, stat, platform, shutil
import logging import logging
from logging import handlers from logging import handlers
from pathlib import Path from pathlib import Path
@@ -8,17 +8,17 @@ from sqlalchemy.orm import Session
from sqlalchemy import create_engine from sqlalchemy import create_engine
logger = logging.getLogger(__name__) logger = logging.getLogger(f"submissions.{__name__}")
package_dir = Path(__file__).parents[2].resolve() package_dir = Path(__file__).parents[2].resolve()
logger.debug(f"Package dir: {package_dir}") logger.debug(f"Package dir: {package_dir}")
if platform.system == "Windows": if platform.system() == "Windows":
os_config_dir = "AppData" os_config_dir = "AppData/local"
logger.debug(f"Got platform Windows, config_dir: {os_config_dir}") print(f"Got platform Windows, config_dir: {os_config_dir}")
else: else:
os_config_dir = ".config" os_config_dir = ".config"
logger.debug(f"Got platform other, config_dir: {os_config_dir}") print(f"Got platform other, config_dir: {os_config_dir}")
main_aux_dir = Path.home().joinpath(f"{os_config_dir}/submissions") main_aux_dir = Path.home().joinpath(f"{os_config_dir}/submissions")
@@ -27,6 +27,8 @@ CONFIGDIR = main_aux_dir.joinpath("config")
LOGDIR = main_aux_dir.joinpath("logs") LOGDIR = main_aux_dir.joinpath("logs")
class GroupWriteRotatingFileHandler(handlers.RotatingFileHandler): class GroupWriteRotatingFileHandler(handlers.RotatingFileHandler):
def doRollover(self): def doRollover(self):
@@ -79,6 +81,16 @@ def get_config(settings_path: str|None) -> dict:
## register the tag handler ## register the tag handler
yaml.add_constructor('!join', join) yaml.add_constructor('!join', join)
# if user hasn't defined config path in cli args # if user hasn't defined config path in cli args
logger.debug(f"Making directory: {CONFIGDIR.__str__()}")
try:
CONFIGDIR.mkdir(parents=True)
except FileExistsError:
pass
logger.debug(f"Making directory: {LOGDIR.__str__()}")
try:
LOGDIR.mkdir(parents=True)
except FileExistsError:
pass
if settings_path == None: if settings_path == None:
# Check user .config/ozma directory # Check user .config/ozma directory
# if Path.exists(Path.joinpath(CONFIGDIR, "config.yml")): # if Path.exists(Path.joinpath(CONFIGDIR, "config.yml")):
@@ -94,6 +106,7 @@ def get_config(settings_path: str|None) -> dict:
settings_path = Path(sys._MEIPASS).joinpath("files", "config.yml") settings_path = Path(sys._MEIPASS).joinpath("files", "config.yml")
else: else:
settings_path = package_dir.joinpath('config.yml') settings_path = package_dir.joinpath('config.yml')
shutil.copyfile(settings_path.__str__(), CONFIGDIR.joinpath("config.yml").__str__())
else: else:
if Path(settings_path).is_dir(): if Path(settings_path).is_dir():
settings_path = settings_path.joinpath("config.yml") settings_path = settings_path.joinpath("config.yml")
@@ -103,6 +116,7 @@ def get_config(settings_path: str|None) -> dict:
logger.error("No config.yml file found. Using empty dictionary.") logger.error("No config.yml file found. Using empty dictionary.")
return {} return {}
logger.debug(f"Using {settings_path} for config file.") logger.debug(f"Using {settings_path} for config file.")
with open(settings_path, "r") as stream: with open(settings_path, "r") as stream:
try: try:
settings = yaml.load(stream, Loader=yaml.Loader) settings = yaml.load(stream, Loader=yaml.Loader)
@@ -141,7 +155,7 @@ def create_database_session(database_path: Path|None) -> Session:
return session return session
def setup_logger(verbose:bool=False): def setup_logger(verbosity:int=3):
"""Set logger levels using settings. """Set logger levels using settings.
Args: Args:
@@ -161,34 +175,38 @@ def setup_logger(verbose:bool=False):
fh.setLevel(logging.DEBUG) fh.setLevel(logging.DEBUG)
fh.name = "File" fh.name = "File"
# create console handler with a higher log level # create console handler with a higher log level
ch = logging.StreamHandler() ch = logging.StreamHandler(stream=sys.stdout)
if verbose: match verbosity:
ch.setLevel(logging.DEBUG) case 3:
else: ch.setLevel(logging.DEBUG)
ch.setLevel(logging.WARNING) case 2:
ch.setLevel(logging.INFO)
case 1:
ch.setLevel(logging.WARNING)
ch.name = "Stream" ch.name = "Stream"
# create formatter and add it to the handlers # create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter) fh.setFormatter(formatter)
ch.setFormatter(formatter) ch.setFormatter(formatter)
ch.setLevel(logging.ERROR) # ch.setLevel(logging.ERROR)
# add the handlers to the logger # add the handlers to the logger
logger.addHandler(fh) logger.addHandler(fh)
logger.addHandler(ch) logger.addHandler(ch)
stderr_logger = logging.getLogger('STDERR') # stderr_logger = logging.getLogger('STDERR')
return logger return logger
# sl = StreamToLogger(stderr_logger, logging.ERROR) # sl = StreamToLogger(stderr_logger, logging.ERROR)
# sys.stderr = sl # sys.stderr = sl
def set_logger_verbosity(verbosity): # def set_logger_verbosity(verbosity):
"""Does what it says. # """Does what it says.
""" # """
handler = [item for item in logger.parent.handlers if item.name == "Stream"][0] # handler = [item for item in logger.parent.handlers if item.name == "Stream"][0]
match verbosity: # match verbosity:
case 3: # case 3:
handler.setLevel(logging.DEBUG) # handler.setLevel(logging.DEBUG)
case 2: # case 2:
handler.setLevel(logging.INFO) # handler.setLevel(logging.INFO)
case 1: # case 1:
handler.setLevel(logging.WARNING) # handler.setLevel(logging.WARNING)

View File

@@ -4,7 +4,7 @@ from PyQt6.QtWidgets import (
QTabWidget, QWidget, QVBoxLayout, QTabWidget, QWidget, QVBoxLayout,
QPushButton, QMenuBar, QFileDialog, QPushButton, QMenuBar, QFileDialog,
QLineEdit, QMessageBox, QComboBox, QDateEdit, QHBoxLayout, QLineEdit, QMessageBox, QComboBox, QDateEdit, QHBoxLayout,
QSpinBox QSpinBox, QScrollArea
) )
from PyQt6.QtGui import QAction, QIcon from PyQt6.QtGui import QAction, QIcon
from PyQt6.QtCore import QDateTime, QDate, QSignalBlocker from PyQt6.QtCore import QDateTime, QDate, QSignalBlocker
@@ -108,8 +108,9 @@ class App(QMainWindow):
prsr = SheetParser(fname, **self.ctx) prsr = SheetParser(fname, **self.ctx)
except PermissionError: except PermissionError:
return return
print(f"prsr.sub = {prsr.sub}") logger.debug(f"prsr.sub = {prsr.sub}")
# replace formlayout with tab1.layout # replace formlayout with tab1.layout
# self.form = self.table_widget.formlayout
for item in self.table_widget.formlayout.parentWidget().findChildren(QWidget): for item in self.table_widget.formlayout.parentWidget().findChildren(QWidget):
item.setParent(None) item.setParent(None)
variable_parser = re.compile(r""" variable_parser = re.compile(r"""
@@ -128,11 +129,11 @@ class App(QMainWindow):
mo = variable_parser.fullmatch(item).lastgroup mo = variable_parser.fullmatch(item).lastgroup
except AttributeError: except AttributeError:
mo = "other" mo = "other"
print(f"Mo: {mo}") logger.debug(f"Mo: {mo}")
match mo: match mo:
case 'submitting_lab': case 'submitting_lab':
self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title())) self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
print(f"{item}: {prsr.sub[item]}") logger.debug(f"{item}: {prsr.sub[item]}")
add_widget = QComboBox() add_widget = QComboBox()
labs = [item.__str__() for item in lookup_all_orgs(ctx=self.ctx)] labs = [item.__str__() for item in lookup_all_orgs(ctx=self.ctx)]
try: try:
@@ -167,15 +168,15 @@ class App(QMainWindow):
add_widget.setEditable(True) add_widget.setEditable(True)
# Ensure that all reagenttypes have a name that matches the items in the excel parser # Ensure that all reagenttypes have a name that matches the items in the excel parser
query_var = item.replace("lot_", "") query_var = item.replace("lot_", "")
print(f"Query for: {query_var}") logger.debug(f"Query for: {query_var}")
if isinstance(prsr.sub[item], numpy.float64): if isinstance(prsr.sub[item], numpy.float64):
print(f"{prsr.sub[item]} is a numpy float!") logger.debug(f"{prsr.sub[item]} is a numpy float!")
try: try:
prsr.sub[item] = int(prsr.sub[item]) prsr.sub[item] = int(prsr.sub[item])
except ValueError: except ValueError:
pass pass
relevant_reagents = [item.__str__() for item in lookup_regent_by_type_name_and_kit_name(ctx=self.ctx, type_name=query_var, kit_name=prsr.sub['extraction_kit'])] relevant_reagents = [item.__str__() for item in lookup_regent_by_type_name_and_kit_name(ctx=self.ctx, type_name=query_var, kit_name=prsr.sub['extraction_kit'])]
print(f"Relevant reagents: {relevant_reagents}") logger.debug(f"Relevant reagents: {relevant_reagents}")
if prsr.sub[item] not in relevant_reagents and prsr.sub[item] != 'nan': if prsr.sub[item] not in relevant_reagents and prsr.sub[item] != 'nan':
try: try:
check = not numpy.isnan(prsr.sub[item]) check = not numpy.isnan(prsr.sub[item])
@@ -187,7 +188,7 @@ class App(QMainWindow):
add_widget.addItems(relevant_reagents) add_widget.addItems(relevant_reagents)
# TODO: make samples not appear in frame. # TODO: make samples not appear in frame.
case 'samples': case 'samples':
print(f"{item}: {prsr.sub[item]}") logger.debug(f"{item}: {prsr.sub[item]}")
self.samples = prsr.sub[item] self.samples = prsr.sub[item]
case _: case _:
self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title())) self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
@@ -216,28 +217,7 @@ class App(QMainWindow):
html += '</body></html>' html += '</body></html>'
self.table_widget.webengineview.setHtml(html) self.table_widget.webengineview.setHtml(html)
self.table_widget.webengineview.update() self.table_widget.webengineview.update()
# type = self.table_widget.control_typer.currentText()
# mode = self.table_widget.mode_typer.currentText()
# controls = get_all_controls_by_type(ctx=self.ctx, type=type)
# data = []
# for control in controls:
# dicts = convert_control_by_mode(ctx=self.ctx, control=control, mode=mode)
# data.append(dicts)
# data = [item for sublist in data for item in sublist]
# # print(data)
# df = convert_data_list_to_df(ctx=self.ctx, input=data)
# fig = create_charts(ctx=self.ctx, df=df)
# print(fig)
# html = '<html><body>'
# html += plotly.offline.plot(fig, output_type='div', auto_open=True, image = 'png', image_filename='plot_image')
# html += '</body></html>'
# html = plotly.io.to_html(fig)
# # print(html)
# # with open("C:\\Users\\lwark\\Desktop\\test.html", "w") as f:
# # f.write(html)
# self.table_widget.webengineview.setHtml(html)
# self.table_widget.webengineview.update()
def submit_new_sample(self): def submit_new_sample(self):
@@ -297,8 +277,8 @@ class App(QMainWindow):
case QLineEdit(): case QLineEdit():
# ad hoc check to prevent double reporting of qdatedit under lineedit for some reason # ad hoc check to prevent double reporting of qdatedit under lineedit for some reason
if not isinstance(prev_item, QDateEdit) and not isinstance(prev_item, QComboBox) and not isinstance(prev_item, QSpinBox): if not isinstance(prev_item, QDateEdit) and not isinstance(prev_item, QComboBox) and not isinstance(prev_item, QSpinBox):
print(f"Previous: {prev_item}") logger.debug(f"Previous: {prev_item}")
print(f"Item: {item}") logger.debug(f"Item: {item}")
values.append(item.text()) values.append(item.text())
case QComboBox(): case QComboBox():
values.append(item.currentText()) values.append(item.currentText())
@@ -346,7 +326,7 @@ class App(QMainWindow):
except TypeError: except TypeError:
pass pass
if self.table_widget.datepicker.start_date.date() > self.table_widget.datepicker.end_date.date(): if self.table_widget.datepicker.start_date.date() > self.table_widget.datepicker.end_date.date():
print("that is not allowed!") logger.warning("Start date after end date is not allowed!")
# self.table_widget.datepicker.start_date.setDate(e_date) # self.table_widget.datepicker.start_date.setDate(e_date)
threemonthsago = self.table_widget.datepicker.end_date.date().addDays(-90) threemonthsago = self.table_widget.datepicker.end_date.date().addDays(-90)
with QSignalBlocker(self.table_widget.datepicker.start_date) as blocker: with QSignalBlocker(self.table_widget.datepicker.start_date) as blocker:
@@ -372,12 +352,12 @@ class App(QMainWindow):
def chart_maker(self): def chart_maker(self):
print(f"Control getter context: \n\tControl type: {self.con_type}\n\tMode: {self.mode}\n\tStart Date: {self.start_date}\n\tEnd Date: {self.end_date}") logger.debug(f"Control getter context: \n\tControl type: {self.con_type}\n\tMode: {self.mode}\n\tStart Date: {self.start_date}\n\tEnd Date: {self.end_date}")
if self.table_widget.sub_typer.currentText() == "": if self.table_widget.sub_typer.currentText() == "":
self.subtype = None self.subtype = None
else: else:
self.subtype = self.table_widget.sub_typer.currentText() self.subtype = self.table_widget.sub_typer.currentText()
print(f"Subtype: {self.subtype}") logger.debug(f"Subtype: {self.subtype}")
controls = get_all_controls_by_type(ctx=self.ctx, con_type=self.con_type, start_date=self.start_date, end_date=self.end_date) controls = get_all_controls_by_type(ctx=self.ctx, con_type=self.con_type, start_date=self.start_date, end_date=self.end_date)
if controls == None: if controls == None:
return return
@@ -386,14 +366,14 @@ class App(QMainWindow):
dicts = convert_control_by_mode(ctx=self.ctx, control=control, mode=self.mode) dicts = convert_control_by_mode(ctx=self.ctx, control=control, mode=self.mode)
data.append(dicts) data.append(dicts)
data = [item for sublist in data for item in sublist] data = [item for sublist in data for item in sublist]
# print(data) # logger.debug(data)
df = convert_data_list_to_df(ctx=self.ctx, input=data, subtype=self.subtype) df = convert_data_list_to_df(ctx=self.ctx, input=data, subtype=self.subtype)
if self.subtype == None: if self.subtype == None:
title = self.mode title = self.mode
else: else:
title = f"{self.mode} - {self.subtype}" title = f"{self.mode} - {self.subtype}"
fig = create_charts(ctx=self.ctx, df=df, ytitle=title) fig = create_charts(ctx=self.ctx, df=df, ytitle=title)
print(f"Updating figure...") logger.debug(f"Updating figure...")
html = '<html><body>' html = '<html><body>'
if fig != None: if fig != None:
html += plotly.offline.plot(fig, output_type='div', include_plotlyjs='cdn')#, image = 'png', auto_open=True, image_filename='plot_image') html += plotly.offline.plot(fig, output_type='div', include_plotlyjs='cdn')#, image = 'png', auto_open=True, image_filename='plot_image')
@@ -404,7 +384,7 @@ class App(QMainWindow):
# f.write(html) # f.write(html)
self.table_widget.webengineview.setHtml(html) self.table_widget.webengineview.setHtml(html)
self.table_widget.webengineview.update() self.table_widget.webengineview.update()
print("Figure updated... I hope.") logger.debug("Figure updated... I hope.")
# def datechange(self): # def datechange(self):
@@ -412,7 +392,7 @@ class App(QMainWindow):
# s_date = self.table_widget.datepicker.start_date.date() # s_date = self.table_widget.datepicker.start_date.date()
# e_date = self.table_widget.datepicker.end_date.date() # e_date = self.table_widget.datepicker.end_date.date()
# if s_date > e_date: # if s_date > e_date:
# print("that is not allowed!") # logger.debug("that is not allowed!")
# # self.table_widget.datepicker.start_date.setDate(e_date) # # self.table_widget.datepicker.start_date.setDate(e_date)
# threemonthsago = e_date.addDays(-90) # threemonthsago = e_date.addDays(-90)
# self.table_widget.datepicker.start_date.setDate(threemonthsago) # self.table_widget.datepicker.start_date.setDate(threemonthsago)
@@ -448,6 +428,12 @@ class AddSubForm(QWidget):
self.formlayout = QVBoxLayout(self) self.formlayout = QVBoxLayout(self)
self.formwidget.setLayout(self.formlayout) self.formwidget.setLayout(self.formlayout)
self.formwidget.setFixedWidth(300) self.formwidget.setFixedWidth(300)
self.interior = QScrollArea()
self.interior.setWidgetResizable(True)
self.interior.setFixedWidth(325)
self.interior.setParent(self.tab1)
self.interior.setWidget(self.formwidget)
self.sheetwidget = QWidget(self) self.sheetwidget = QWidget(self)
self.sheetlayout = QVBoxLayout(self) self.sheetlayout = QVBoxLayout(self)
@@ -455,22 +441,16 @@ class AddSubForm(QWidget):
self.sub_wid = SubmissionsSheet(parent.ctx) self.sub_wid = SubmissionsSheet(parent.ctx)
self.sheetlayout.addWidget(self.sub_wid) self.sheetlayout.addWidget(self.sub_wid)
self.tab1.layout = QHBoxLayout(self) self.tab1.layout = QHBoxLayout(self)
self.tab1.setLayout(self.tab1.layout) self.tab1.setLayout(self.tab1.layout)
# self.tab1.layout.addLayout(self.formlayout) # self.tab1.layout.addLayout(self.formlayout)
# self.tab1.layout.addWidget(self.formwidget)
self.tab1.layout.addWidget(self.formwidget) self.tab1.layout.addWidget(self.formwidget)
self.tab1.layout.addWidget(self.sheetwidget) self.tab1.layout.addWidget(self.sheetwidget)
# self.tab1.layout.addLayout(self.sheetlayout) # self.tab1.layout.addLayout(self.sheetlayout)
# self.tab1.setWidgetResizable(True)
# self.tab1.setVerticalScrollBar(QScrollBar())
# self.tab1.layout.addWidget(self.scroller)
# self.tab1.setWidget(self.scroller)
# self.tab1.setMinimumHeight(300)
self.datepicker = ControlsDatePicker() self.datepicker = ControlsDatePicker()
self.webengineview = QWebEngineView() self.webengineview = QWebEngineView()
# data = '''<html>Hello World</html>'''
# self.webengineview.setHtml(data)
self.tab2.layout = QVBoxLayout(self) self.tab2.layout = QVBoxLayout(self)
self.control_typer = QComboBox() self.control_typer = QComboBox()
con_types = get_all_Control_Types_names(ctx=parent.ctx) con_types = get_all_Control_Types_names(ctx=parent.ctx)
@@ -493,3 +473,5 @@ class AddSubForm(QWidget):
self.tab3.setLayout(self.tab3.layout) self.tab3.setLayout(self.tab3.layout)
self.layout.addWidget(self.tabs) self.layout.addWidget(self.tabs)
self.setLayout(self.layout) self.setLayout(self.layout)
print(self.tab1.layout.parentWidget().findChildren(QScrollArea))

View File

@@ -14,6 +14,9 @@ from jinja2 import Environment, FileSystemLoader
import sys import sys
from pathlib import Path from pathlib import Path
import logging
logger = logging.getLogger(f"submissions.{__name__}")
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
loader_path = Path(sys._MEIPASS).joinpath("files", "templates") loader_path = Path(sys._MEIPASS).joinpath("files", "templates")
@@ -62,7 +65,7 @@ class AddReagentForm(QDialog):
exp_input.setDate(QDate.currentDate()) exp_input.setDate(QDate.currentDate())
type_input = QComboBox() type_input = QComboBox()
type_input.addItems([item.replace("_", " ").title() for item in get_all_reagenttype_names(ctx=ctx)]) type_input.addItems([item.replace("_", " ").title() for item in get_all_reagenttype_names(ctx=ctx)])
print(f"Trying to find index of {reagent_type}") logger.debug(f"Trying to find index of {reagent_type}")
try: try:
reagent_type = reagent_type.replace("_", " ").title() reagent_type = reagent_type.replace("_", " ").title()
except AttributeError: except AttributeError:
@@ -132,7 +135,7 @@ class SubmissionsSheet(QTableView):
def show_details(self, item): def show_details(self, item):
index=(self.selectionModel().currentIndex()) index=(self.selectionModel().currentIndex())
# print(index) # logger.debug(index)
value=index.sibling(index.row(),0).data() value=index.sibling(index.row(),0).data()
dlg = SubmissionDetails(ctx=self.ctx, id=value) dlg = SubmissionDetails(ctx=self.ctx, id=value)
# dlg.show() # dlg.show()
@@ -245,7 +248,7 @@ class KitAdder(QWidget):
def submit(self): def submit(self):
labels, values, reagents = self.extract_form_info(self) labels, values, reagents = self.extract_form_info(self)
info = {item[0]:item[1] for item in zip(labels, values)} info = {item[0]:item[1] for item in zip(labels, values)}
print(info) logger.debug(info)
# info['reagenttypes'] = reagents # info['reagenttypes'] = reagents
# del info['name'] # del info['name']
# del info['extension_of_life_(months)'] # del info['extension_of_life_(months)']
@@ -257,7 +260,7 @@ class KitAdder(QWidget):
yml_type[used]['kits'][info['kit_name']] = {} yml_type[used]['kits'][info['kit_name']] = {}
yml_type[used]['kits'][info['kit_name']]['cost'] = info['cost_per_run'] yml_type[used]['kits'][info['kit_name']]['cost'] = info['cost_per_run']
yml_type[used]['kits'][info['kit_name']]['reagenttypes'] = reagents yml_type[used]['kits'][info['kit_name']]['reagenttypes'] = reagents
print(yml_type) logger.debug(yml_type)
create_kit_from_yaml(ctx=self.ctx, exp=yml_type) create_kit_from_yaml(ctx=self.ctx, exp=yml_type)
def extract_form_info(self, object): def extract_form_info(self, object):
@@ -265,7 +268,7 @@ class KitAdder(QWidget):
values = [] values = []
reagents = {} reagents = {}
for item in object.findChildren(QWidget): for item in object.findChildren(QWidget):
print(item.parentWidget()) logger.debug(item.parentWidget())
# if not isinstance(item.parentWidget(), ReagentTypeForm): # if not isinstance(item.parentWidget(), ReagentTypeForm):
match item: match item:
case QLabel(): case QLabel():
@@ -273,8 +276,8 @@ class KitAdder(QWidget):
case QLineEdit(): case QLineEdit():
# ad hoc check to prevent double reporting of qdatedit under lineedit for some reason # ad hoc check to prevent double reporting of qdatedit under lineedit for some reason
if not isinstance(prev_item, QDateEdit) and not isinstance(prev_item, QComboBox) and not isinstance(prev_item, QSpinBox) and not isinstance(prev_item, QScrollBar): if not isinstance(prev_item, QDateEdit) and not isinstance(prev_item, QComboBox) and not isinstance(prev_item, QSpinBox) and not isinstance(prev_item, QScrollBar):
print(f"Previous: {prev_item}") logger.debug(f"Previous: {prev_item}")
print(f"Item: {item}, {item.text()}") logger.debug(f"Item: {item}, {item.text()}")
values.append(item.text()) values.append(item.text())
case QComboBox(): case QComboBox():
values.append(item.currentText()) values.append(item.currentText())
@@ -286,7 +289,7 @@ class KitAdder(QWidget):
re_labels, re_values, _ = self.extract_form_info(item) re_labels, re_values, _ = self.extract_form_info(item)
reagent = {item[0]:item[1] for item in zip(re_labels, re_values)} reagent = {item[0]:item[1] for item in zip(re_labels, re_values)}
print(reagent) logger.debug(reagent)
# reagent = {reagent['name:']:{'eol':reagent['extension_of_life_(months):']}} # reagent = {reagent['name:']:{'eol':reagent['extension_of_life_(months):']}}
reagents[reagent['name']] = {'eol_ext':int(reagent['extension_of_life_(months)'])} reagents[reagent['name']] = {'eol_ext':int(reagent['extension_of_life_(months)'])}
prev_item = item prev_item = item

View File

@@ -5,7 +5,7 @@ from plotly.graph_objects import Figure
import logging import logging
from backend.excel import get_unique_values_in_df_column from backend.excel import get_unique_values_in_df_column
logger = logging.getLogger("controls.tools.vis_functions") logger = logging.getLogger(f"submissions.{__name__}")
def create_charts(ctx:dict, df:pd.DataFrame, ytitle:str|None=None) -> Figure: def create_charts(ctx:dict, df:pd.DataFrame, ytitle:str|None=None) -> Figure:
@@ -160,7 +160,7 @@ def construct_chart(ctx:dict, df:pd.DataFrame, modes:list, ytitle:str|None=None)
color_discrete_sequence=None color_discrete_sequence=None
else: else:
color = "target" color = "target"
print(get_unique_values_in_df_column(df, 'target')) # print(get_unique_values_in_df_column(df, 'target'))
match get_unique_values_in_df_column(df, 'target'): match get_unique_values_in_df_column(df, 'target'):
case ['Target']: case ['Target']:
color_discrete_sequence=["blue"] color_discrete_sequence=["blue"]