Pre-removal of constructors module.
This commit is contained in:
@@ -87,5 +87,5 @@ def store_object(ctx:Settings, object) -> dict|None:
|
||||
return None
|
||||
|
||||
from .lookups import *
|
||||
from .constructions import *
|
||||
# from .constructions import *
|
||||
from .misc import *
|
||||
|
||||
@@ -11,266 +11,270 @@ from dateutil.parser import parse
|
||||
from typing import Tuple
|
||||
from sqlalchemy.exc import IntegrityError, SAWarning
|
||||
from . import store_object
|
||||
from backend.validators import RSLNamer
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
def construct_reagent(ctx:Settings, info_dict:dict) -> models.Reagent:
|
||||
"""
|
||||
Construct reagent object from dictionary
|
||||
# def construct_reagent(ctx:Settings, info_dict:dict) -> models.Reagent:
|
||||
# """
|
||||
# Construct reagent object from dictionary
|
||||
# NOTE: Depreciated in favour of Pydantic model .toSQL method
|
||||
|
||||
Args:
|
||||
ctx (Settings): settings object passed down from gui
|
||||
info_dict (dict): dictionary to be converted
|
||||
# Args:
|
||||
# ctx (Settings): settings object passed down from gui
|
||||
# info_dict (dict): dictionary to be converted
|
||||
|
||||
Returns:
|
||||
models.Reagent: Constructed reagent object
|
||||
"""
|
||||
reagent = models.Reagent()
|
||||
for item in info_dict:
|
||||
logger.debug(f"Reagent info item for {item}: {info_dict[item]}")
|
||||
# set fields based on keys in dictionary
|
||||
match item:
|
||||
case "lot":
|
||||
reagent.lot = info_dict[item].upper()
|
||||
case "expiry":
|
||||
if isinstance(info_dict[item], date):
|
||||
reagent.expiry = info_dict[item]
|
||||
else:
|
||||
reagent.expiry = parse(info_dict[item]).date()
|
||||
case "type":
|
||||
reagent_type = lookup_reagent_types(ctx=ctx, name=info_dict[item])
|
||||
if reagent_type != None:
|
||||
reagent.type.append(reagent_type)
|
||||
case "name":
|
||||
if item == None:
|
||||
reagent.name = reagent.type.name
|
||||
else:
|
||||
reagent.name = info_dict[item]
|
||||
# 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
|
||||
# Returns:
|
||||
# models.Reagent: Constructed reagent object
|
||||
# """
|
||||
# reagent = models.Reagent()
|
||||
# for item in info_dict:
|
||||
# logger.debug(f"Reagent info item for {item}: {info_dict[item]}")
|
||||
# # set fields based on keys in dictionary
|
||||
# match item:
|
||||
# case "lot":
|
||||
# reagent.lot = info_dict[item].upper()
|
||||
# case "expiry":
|
||||
# if isinstance(info_dict[item], date):
|
||||
# reagent.expiry = info_dict[item]
|
||||
# else:
|
||||
# reagent.expiry = parse(info_dict[item]).date()
|
||||
# case "type":
|
||||
# reagent_type = lookup_reagent_types(ctx=ctx, name=info_dict[item])
|
||||
# if reagent_type != None:
|
||||
# reagent.type.append(reagent_type)
|
||||
# case "name":
|
||||
# if item == None:
|
||||
# reagent.name = reagent.type.name
|
||||
# else:
|
||||
# reagent.name = info_dict[item]
|
||||
# # 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
|
||||
|
||||
def construct_submission_info(ctx:Settings, info_dict:dict) -> Tuple[models.BasicSubmission, dict]:
|
||||
"""
|
||||
Construct submission object from dictionary pulled from gui form
|
||||
# def construct_submission_info(ctx:Settings, info_dict:dict) -> Tuple[models.BasicSubmission, dict]:
|
||||
# """
|
||||
# Construct submission object from dictionary pulled from gui form
|
||||
# NOTE: Depreciated in favour of Pydantic model .toSQL method
|
||||
|
||||
Args:
|
||||
ctx (Settings): settings object passed down from gui
|
||||
info_dict (dict): dictionary to be transformed
|
||||
# Args:
|
||||
# ctx (Settings): settings object passed down from gui
|
||||
# info_dict (dict): dictionary to be transformed
|
||||
|
||||
Returns:
|
||||
models.BasicSubmission: Constructed submission object
|
||||
"""
|
||||
# convert submission type into model name
|
||||
# model = get_polymorphic_subclass(polymorphic_identity=info_dict['submission_type'])
|
||||
model = models.BasicSubmission.find_polymorphic_subclass(polymorphic_identity=info_dict['submission_type'])
|
||||
logger.debug(f"We've got the model: {type(model)}")
|
||||
# Ensure an rsl plate number exists for the plate
|
||||
if not check_regex_match("^RSL", info_dict["rsl_plate_num"]):
|
||||
instance = None
|
||||
msg = "A proper RSL plate number is required."
|
||||
return instance, {'code': 2, 'message': "A proper RSL plate number is required."}
|
||||
# else:
|
||||
# # enforce conventions on the rsl plate number from the form
|
||||
# # info_dict['rsl_plate_num'] = RSLNamer(ctx=ctx, instr=info_dict["rsl_plate_num"]).parsed_name
|
||||
# info_dict['rsl_plate_num'] = RSLNamer(ctx=ctx, instr=info_dict["rsl_plate_num"], sub_type=info_dict['submission_type']).parsed_name
|
||||
# check database for existing object
|
||||
instance = lookup_submissions(ctx=ctx, rsl_number=info_dict['rsl_plate_num'])
|
||||
# get model based on submission type converted above
|
||||
# logger.debug(f"Looking at models for submission type: {query}")
|
||||
# Returns:
|
||||
# models.BasicSubmission: Constructed submission object
|
||||
# """
|
||||
# # convert submission type into model name
|
||||
# # model = get_polymorphic_subclass(polymorphic_identity=info_dict['submission_type'])
|
||||
# model = models.BasicSubmission.find_polymorphic_subclass(polymorphic_identity=info_dict['submission_type'])
|
||||
# logger.debug(f"We've got the model: {type(model)}")
|
||||
# # Ensure an rsl plate number exists for the plate
|
||||
# if not check_regex_match("^RSL", info_dict["rsl_plate_num"]):
|
||||
# instance = None
|
||||
# msg = "A proper RSL plate number is required."
|
||||
# return instance, {'code': 2, 'message': "A proper RSL plate number is required."}
|
||||
# else:
|
||||
# # # enforce conventions on the rsl plate number from the form
|
||||
# # # info_dict['rsl_plate_num'] = RSLNamer(ctx=ctx, instr=info_dict["rsl_plate_num"]).parsed_name
|
||||
# info_dict['rsl_plate_num'] = RSLNamer(ctx=ctx, instr=info_dict["rsl_plate_num"], sub_type=info_dict['submission_type']).parsed_name
|
||||
# # check database for existing object
|
||||
# instance = lookup_submissions(ctx=ctx, rsl_number=info_dict['rsl_plate_num'])
|
||||
# # get model based on submission type converted above
|
||||
# # logger.debug(f"Looking at models for submission type: {query}")
|
||||
|
||||
# if query return nothing, ie doesn't already exist in db
|
||||
if instance == None:
|
||||
instance = model()
|
||||
logger.debug(f"Submission doesn't exist yet, creating new instance: {instance}")
|
||||
msg = None
|
||||
code = 0
|
||||
else:
|
||||
code = 1
|
||||
msg = "This submission already exists.\nWould you like to overwrite?"
|
||||
for item in info_dict:
|
||||
value = info_dict[item]
|
||||
logger.debug(f"Setting {item} to {value}")
|
||||
# set fields based on keys in dictionary
|
||||
match item:
|
||||
case "extraction_kit":
|
||||
logger.debug(f"Looking up kit {value}")
|
||||
field_value = lookup_kit_types(ctx=ctx, name=value)
|
||||
logger.debug(f"Got {field_value} for kit {value}")
|
||||
case "submitting_lab":
|
||||
logger.debug(f"Looking up organization: {value}")
|
||||
field_value = lookup_organizations(ctx=ctx, name=value)
|
||||
logger.debug(f"Got {field_value} for organization {value}")
|
||||
case "submitter_plate_num":
|
||||
logger.debug(f"Submitter plate id: {value}")
|
||||
field_value = value
|
||||
case "samples":
|
||||
instance = construct_samples(ctx=ctx, instance=instance, samples=value)
|
||||
continue
|
||||
case "submission_type":
|
||||
field_value = lookup_submission_type(ctx=ctx, name=value)
|
||||
case _:
|
||||
field_value = value
|
||||
# insert into field
|
||||
try:
|
||||
setattr(instance, item, field_value)
|
||||
except AttributeError:
|
||||
logger.debug(f"Could not set attribute: {item} to {info_dict[item]}")
|
||||
continue
|
||||
except KeyError:
|
||||
continue
|
||||
# calculate cost of the run: immutable cost + mutable times number of columns
|
||||
# This is now attached to submission upon creation to preserve at-run costs incase of cost increase in the future.
|
||||
try:
|
||||
logger.debug(f"Calculating costs for procedure...")
|
||||
instance.calculate_base_cost()
|
||||
except (TypeError, AttributeError) as e:
|
||||
logger.debug(f"Looks like that kit doesn't have cost breakdown yet due to: {e}, using full plate cost.")
|
||||
instance.run_cost = instance.extraction_kit.cost_per_run
|
||||
logger.debug(f"Calculated base run cost of: {instance.run_cost}")
|
||||
# Apply any discounts that are applicable for client and kit.
|
||||
try:
|
||||
logger.debug("Checking and applying discounts...")
|
||||
discounts = [item.amount for item in lookup_discounts(ctx=ctx, kit_type=instance.extraction_kit, organization=instance.submitting_lab)]
|
||||
logger.debug(f"We got discounts: {discounts}")
|
||||
if len(discounts) > 0:
|
||||
discounts = sum(discounts)
|
||||
instance.run_cost = instance.run_cost - discounts
|
||||
except Exception as e:
|
||||
logger.error(f"An unknown exception occurred when calculating discounts: {e}")
|
||||
# We need to make sure there's a proper rsl plate number
|
||||
logger.debug(f"We've got a total cost of {instance.run_cost}")
|
||||
try:
|
||||
logger.debug(f"Constructed instance: {instance.to_string()}")
|
||||
except AttributeError as e:
|
||||
logger.debug(f"Something went wrong constructing instance {info_dict['rsl_plate_num']}: {e}")
|
||||
logger.debug(f"Constructed submissions message: {msg}")
|
||||
return instance, {'code':code, 'message':msg}
|
||||
# # if query return nothing, ie doesn't already exist in db
|
||||
# if instance == None:
|
||||
# instance = model()
|
||||
# logger.debug(f"Submission doesn't exist yet, creating new instance: {instance}")
|
||||
# msg = None
|
||||
# code = 0
|
||||
# else:
|
||||
# code = 1
|
||||
# msg = "This submission already exists.\nWould you like to overwrite?"
|
||||
# for item in info_dict:
|
||||
# value = info_dict[item]
|
||||
# logger.debug(f"Setting {item} to {value}")
|
||||
# # set fields based on keys in dictionary
|
||||
# match item:
|
||||
# case "extraction_kit":
|
||||
# logger.debug(f"Looking up kit {value}")
|
||||
# field_value = lookup_kit_types(ctx=ctx, name=value)
|
||||
# logger.debug(f"Got {field_value} for kit {value}")
|
||||
# case "submitting_lab":
|
||||
# logger.debug(f"Looking up organization: {value}")
|
||||
# field_value = lookup_organizations(ctx=ctx, name=value)
|
||||
# logger.debug(f"Got {field_value} for organization {value}")
|
||||
# case "submitter_plate_num":
|
||||
# logger.debug(f"Submitter plate id: {value}")
|
||||
# field_value = value
|
||||
# case "samples":
|
||||
# instance = construct_samples(ctx=ctx, instance=instance, samples=value)
|
||||
# continue
|
||||
# case "submission_type":
|
||||
# field_value = lookup_submission_type(ctx=ctx, name=value)
|
||||
# case _:
|
||||
# field_value = value
|
||||
# # insert into field
|
||||
# try:
|
||||
# setattr(instance, item, field_value)
|
||||
# except AttributeError:
|
||||
# logger.debug(f"Could not set attribute: {item} to {info_dict[item]}")
|
||||
# continue
|
||||
# except KeyError:
|
||||
# continue
|
||||
# # calculate cost of the run: immutable cost + mutable times number of columns
|
||||
# # This is now attached to submission upon creation to preserve at-run costs incase of cost increase in the future.
|
||||
# try:
|
||||
# logger.debug(f"Calculating costs for procedure...")
|
||||
# instance.calculate_base_cost()
|
||||
# except (TypeError, AttributeError) as e:
|
||||
# logger.debug(f"Looks like that kit doesn't have cost breakdown yet due to: {e}, using full plate cost.")
|
||||
# instance.run_cost = instance.extraction_kit.cost_per_run
|
||||
# logger.debug(f"Calculated base run cost of: {instance.run_cost}")
|
||||
# # Apply any discounts that are applicable for client and kit.
|
||||
# try:
|
||||
# logger.debug("Checking and applying discounts...")
|
||||
# discounts = [item.amount for item in lookup_discounts(ctx=ctx, kit_type=instance.extraction_kit, organization=instance.submitting_lab)]
|
||||
# logger.debug(f"We got discounts: {discounts}")
|
||||
# if len(discounts) > 0:
|
||||
# discounts = sum(discounts)
|
||||
# instance.run_cost = instance.run_cost - discounts
|
||||
# except Exception as e:
|
||||
# logger.error(f"An unknown exception occurred when calculating discounts: {e}")
|
||||
# # We need to make sure there's a proper rsl plate number
|
||||
# logger.debug(f"We've got a total cost of {instance.run_cost}")
|
||||
# try:
|
||||
# logger.debug(f"Constructed instance: {instance.to_string()}")
|
||||
# except AttributeError as e:
|
||||
# logger.debug(f"Something went wrong constructing instance {info_dict['rsl_plate_num']}: {e}")
|
||||
# logger.debug(f"Constructed submissions message: {msg}")
|
||||
# return instance, {'code':code, 'message':msg}
|
||||
|
||||
def construct_samples(ctx:Settings, instance:models.BasicSubmission, samples:List[dict]) -> models.BasicSubmission:
|
||||
"""
|
||||
constructs sample objects and adds to submission
|
||||
# def construct_samples(ctx:Settings, instance:models.BasicSubmission, samples:List[dict]) -> models.BasicSubmission:
|
||||
# """
|
||||
# constructs sample objects and adds to submission
|
||||
# NOTE: Depreciated in favour of Pydantic model .toSQL method
|
||||
|
||||
Args:
|
||||
ctx (Settings): settings passed down from gui
|
||||
instance (models.BasicSubmission): Submission samples scraped from.
|
||||
samples (List[dict]): List of parsed samples
|
||||
# Args:
|
||||
# ctx (Settings): settings passed down from gui
|
||||
# instance (models.BasicSubmission): Submission samples scraped from.
|
||||
# samples (List[dict]): List of parsed samples
|
||||
|
||||
Returns:
|
||||
models.BasicSubmission: Updated submission object.
|
||||
"""
|
||||
for sample in samples:
|
||||
sample_instance = lookup_samples(ctx=ctx, submitter_id=str(sample['sample'].submitter_id))
|
||||
if sample_instance == None:
|
||||
sample_instance = sample['sample']
|
||||
else:
|
||||
logger.warning(f"Sample {sample} already exists, creating association.")
|
||||
logger.debug(f"Adding {sample_instance.__dict__}")
|
||||
if sample_instance in instance.samples:
|
||||
logger.error(f"Looks like there's a duplicate sample on this plate: {sample_instance.submitter_id}!")
|
||||
continue
|
||||
try:
|
||||
with ctx.database_session.no_autoflush:
|
||||
try:
|
||||
sample_query = sample_instance.sample_type.replace('Sample', '').strip()
|
||||
logger.debug(f"Here is the sample instance type: {sample_instance}")
|
||||
try:
|
||||
assoc = getattr(models, f"{sample_query}Association")
|
||||
except AttributeError as e:
|
||||
logger.error(f"Couldn't get type specific association using {sample_instance.sample_type.replace('Sample', '').strip()}. Getting generic.")
|
||||
assoc = models.SubmissionSampleAssociation
|
||||
assoc = assoc(submission=instance, sample=sample_instance, row=sample['row'], column=sample['column'])
|
||||
instance.submission_sample_associations.append(assoc)
|
||||
except IntegrityError:
|
||||
logger.error(f"Hit integrity error for: {sample}")
|
||||
continue
|
||||
except SAWarning:
|
||||
logger.error(f"Looks like the association already exists for submission: {instance} and sample: {sample_instance}")
|
||||
continue
|
||||
except IntegrityError as e:
|
||||
logger.critical(e)
|
||||
continue
|
||||
return instance
|
||||
# Returns:
|
||||
# models.BasicSubmission: Updated submission object.
|
||||
# """
|
||||
# for sample in samples:
|
||||
# sample_instance = lookup_samples(ctx=ctx, submitter_id=str(sample['sample'].submitter_id))
|
||||
# if sample_instance == None:
|
||||
# sample_instance = sample['sample']
|
||||
# else:
|
||||
# logger.warning(f"Sample {sample} already exists, creating association.")
|
||||
# logger.debug(f"Adding {sample_instance.__dict__}")
|
||||
# if sample_instance in instance.samples:
|
||||
# logger.error(f"Looks like there's a duplicate sample on this plate: {sample_instance.submitter_id}!")
|
||||
# continue
|
||||
# try:
|
||||
# with ctx.database_session.no_autoflush:
|
||||
# try:
|
||||
# sample_query = sample_instance.sample_type.replace('Sample', '').strip()
|
||||
# logger.debug(f"Here is the sample instance type: {sample_instance}")
|
||||
# try:
|
||||
# assoc = getattr(models, f"{sample_query}Association")
|
||||
# except AttributeError as e:
|
||||
# logger.error(f"Couldn't get type specific association using {sample_instance.sample_type.replace('Sample', '').strip()}. Getting generic.")
|
||||
# assoc = models.SubmissionSampleAssociation
|
||||
# assoc = assoc(submission=instance, sample=sample_instance, row=sample['row'], column=sample['column'])
|
||||
# instance.submission_sample_associations.append(assoc)
|
||||
# except IntegrityError:
|
||||
# logger.error(f"Hit integrity error for: {sample}")
|
||||
# continue
|
||||
# except SAWarning:
|
||||
# logger.error(f"Looks like the association already exists for submission: {instance} and sample: {sample_instance}")
|
||||
# continue
|
||||
# except IntegrityError as e:
|
||||
# logger.critical(e)
|
||||
# continue
|
||||
# return instance
|
||||
|
||||
@check_authorization
|
||||
def construct_kit_from_yaml(ctx:Settings, kit_dict:dict) -> dict:
|
||||
"""
|
||||
Create and store a new kit in the database based on a .yml file
|
||||
TODO: split into create and store functions
|
||||
# @check_authorization
|
||||
# def construct_kit_from_yaml(ctx:Settings, kit_dict:dict) -> dict:
|
||||
# """
|
||||
# Create and store a new kit in the database based on a .yml file
|
||||
# TODO: split into create and store functions
|
||||
|
||||
Args:
|
||||
ctx (Settings): Context object passed down from frontend
|
||||
kit_dict (dict): Experiment dictionary created from yaml file
|
||||
# Args:
|
||||
# ctx (Settings): Context object passed down from frontend
|
||||
# kit_dict (dict): Experiment dictionary created from yaml file
|
||||
|
||||
Returns:
|
||||
dict: a dictionary containing results of db addition
|
||||
"""
|
||||
# from tools import check_is_power_user, massage_common_reagents
|
||||
# Don't want just anyone adding kits
|
||||
# if not check_is_power_user(ctx=ctx):
|
||||
# logger.debug(f"{getuser()} does not have permission to add kits.")
|
||||
# return {'code':1, 'message':"This user does not have permission to add kits.", "status":"warning"}
|
||||
submission_type = lookup_submission_type(ctx=ctx, name=kit_dict['used_for'])
|
||||
logger.debug(f"Looked up submission type: {kit_dict['used_for']} and got {submission_type}")
|
||||
kit = models.KitType(name=kit_dict["kit_name"])
|
||||
kt_st_assoc = models.SubmissionTypeKitTypeAssociation(kit_type=kit, submission_type=submission_type)
|
||||
for k,v in kit_dict.items():
|
||||
if k not in ["reagent_types", "kit_name", "used_for"]:
|
||||
kt_st_assoc.set_attrib(k, v)
|
||||
kit.kit_submissiontype_associations.append(kt_st_assoc)
|
||||
# A kit contains multiple reagent types.
|
||||
for r in kit_dict['reagent_types']:
|
||||
logger.debug(f"Constructing reagent type: {r}")
|
||||
rtname = massage_common_reagents(r['rtname'])
|
||||
look_up = lookup_reagent_types(name=rtname)
|
||||
if look_up == None:
|
||||
rt = models.ReagentType(name=rtname.strip(), eol_ext=timedelta(30*r['eol']))
|
||||
else:
|
||||
rt = look_up
|
||||
uses = {kit_dict['used_for']:{k:v for k,v in r.items() if k not in ['eol']}}
|
||||
assoc = models.KitTypeReagentTypeAssociation(kit_type=kit, reagent_type=rt, uses=uses)
|
||||
# ctx.database_session.add(rt)
|
||||
store_object(ctx=ctx, object=rt)
|
||||
kit.kit_reagenttype_associations.append(assoc)
|
||||
logger.debug(f"Kit construction reagent type: {rt.__dict__}")
|
||||
logger.debug(f"Kit construction kit: {kit.__dict__}")
|
||||
store_object(ctx=ctx, object=kit)
|
||||
return {'code':0, 'message':'Kit has been added', 'status': 'information'}
|
||||
# Returns:
|
||||
# dict: a dictionary containing results of db addition
|
||||
# """
|
||||
# # from tools import check_is_power_user, massage_common_reagents
|
||||
# # Don't want just anyone adding kits
|
||||
# # if not check_is_power_user(ctx=ctx):
|
||||
# # logger.debug(f"{getuser()} does not have permission to add kits.")
|
||||
# # return {'code':1, 'message':"This user does not have permission to add kits.", "status":"warning"}
|
||||
# submission_type = lookup_submission_type(ctx=ctx, name=kit_dict['used_for'])
|
||||
# logger.debug(f"Looked up submission type: {kit_dict['used_for']} and got {submission_type}")
|
||||
# kit = models.KitType(name=kit_dict["kit_name"])
|
||||
# kt_st_assoc = models.SubmissionTypeKitTypeAssociation(kit_type=kit, submission_type=submission_type)
|
||||
# for k,v in kit_dict.items():
|
||||
# if k not in ["reagent_types", "kit_name", "used_for"]:
|
||||
# kt_st_assoc.set_attrib(k, v)
|
||||
# kit.kit_submissiontype_associations.append(kt_st_assoc)
|
||||
# # A kit contains multiple reagent types.
|
||||
# for r in kit_dict['reagent_types']:
|
||||
# logger.debug(f"Constructing reagent type: {r}")
|
||||
# rtname = massage_common_reagents(r['rtname'])
|
||||
# look_up = lookup_reagent_types(name=rtname)
|
||||
# if look_up == None:
|
||||
# rt = models.ReagentType(name=rtname.strip(), eol_ext=timedelta(30*r['eol']))
|
||||
# else:
|
||||
# rt = look_up
|
||||
# uses = {kit_dict['used_for']:{k:v for k,v in r.items() if k not in ['eol']}}
|
||||
# assoc = models.KitTypeReagentTypeAssociation(kit_type=kit, reagent_type=rt, uses=uses)
|
||||
# # ctx.database_session.add(rt)
|
||||
# store_object(ctx=ctx, object=rt)
|
||||
# kit.kit_reagenttype_associations.append(assoc)
|
||||
# logger.debug(f"Kit construction reagent type: {rt.__dict__}")
|
||||
# logger.debug(f"Kit construction kit: {kit.__dict__}")
|
||||
# store_object(ctx=ctx, object=kit)
|
||||
# return {'code':0, 'message':'Kit has been added', 'status': 'information'}
|
||||
|
||||
@check_authorization
|
||||
def construct_org_from_yaml(ctx:Settings, org:dict) -> dict:
|
||||
"""
|
||||
Create and store a new organization based on a .yml file
|
||||
# @check_authorization
|
||||
# def construct_org_from_yaml(ctx:Settings, org:dict) -> dict:
|
||||
# """
|
||||
# Create and store a new organization based on a .yml file
|
||||
|
||||
Args:
|
||||
ctx (Settings): Context object passed down from frontend
|
||||
org (dict): Dictionary containing organization info.
|
||||
# Args:
|
||||
# ctx (Settings): Context object passed down from frontend
|
||||
# org (dict): Dictionary containing organization info.
|
||||
|
||||
Returns:
|
||||
dict: dictionary containing results of db addition
|
||||
"""
|
||||
# from tools import check_is_power_user
|
||||
# # Don't want just anyone adding in clients
|
||||
# if not check_is_power_user(ctx=ctx):
|
||||
# logger.debug(f"{getuser()} does not have permission to add kits.")
|
||||
# return {'code':1, 'message':"This user does not have permission to add organizations."}
|
||||
# the yml can contain multiple clients
|
||||
for client in org:
|
||||
cli_org = models.Organization(name=client.replace(" ", "_").lower(), cost_centre=org[client]['cost centre'])
|
||||
# a client can contain multiple contacts
|
||||
for contact in org[client]['contacts']:
|
||||
cont_name = list(contact.keys())[0]
|
||||
# check if contact already exists
|
||||
look_up = ctx.database_session.query(models.Contact).filter(models.Contact.name==cont_name).first()
|
||||
if look_up == None:
|
||||
cli_cont = models.Contact(name=cont_name, phone=contact[cont_name]['phone'], email=contact[cont_name]['email'], organization=[cli_org])
|
||||
else:
|
||||
cli_cont = look_up
|
||||
cli_cont.organization.append(cli_org)
|
||||
ctx.database_session.add(cli_cont)
|
||||
logger.debug(f"Client creation contact: {cli_cont.__dict__}")
|
||||
logger.debug(f"Client creation client: {cli_org.__dict__}")
|
||||
ctx.database_session.add(cli_org)
|
||||
ctx.database_session.commit()
|
||||
return {"code":0, "message":"Organization has been added."}
|
||||
# Returns:
|
||||
# dict: dictionary containing results of db addition
|
||||
# """
|
||||
# # from tools import check_is_power_user
|
||||
# # # Don't want just anyone adding in clients
|
||||
# # if not check_is_power_user(ctx=ctx):
|
||||
# # logger.debug(f"{getuser()} does not have permission to add kits.")
|
||||
# # return {'code':1, 'message':"This user does not have permission to add organizations."}
|
||||
# # the yml can contain multiple clients
|
||||
# for client in org:
|
||||
# cli_org = models.Organization(name=client.replace(" ", "_").lower(), cost_centre=org[client]['cost centre'])
|
||||
# # a client can contain multiple contacts
|
||||
# for contact in org[client]['contacts']:
|
||||
# cont_name = list(contact.keys())[0]
|
||||
# # check if contact already exists
|
||||
# look_up = ctx.database_session.query(models.Contact).filter(models.Contact.name==cont_name).first()
|
||||
# if look_up == None:
|
||||
# cli_cont = models.Contact(name=cont_name, phone=contact[cont_name]['phone'], email=contact[cont_name]['email'], organization=[cli_org])
|
||||
# else:
|
||||
# cli_cont = look_up
|
||||
# cli_cont.organization.append(cli_org)
|
||||
# ctx.database_session.add(cli_cont)
|
||||
# logger.debug(f"Client creation contact: {cli_cont.__dict__}")
|
||||
# logger.debug(f"Client creation client: {cli_org.__dict__}")
|
||||
# ctx.database_session.add(cli_org)
|
||||
# ctx.database_session.commit()
|
||||
# return {"code":0, "message":"Organization has been added."}
|
||||
|
||||
|
||||
@@ -135,7 +135,13 @@ def lookup_reagent_types(ctx:Settings,
|
||||
reagent = lookup_reagents(ctx=ctx, lot_number=reagent)
|
||||
case _:
|
||||
pass
|
||||
return list(set(kit_type.reagent_types).intersection(reagent.type))[0]
|
||||
assert reagent.type != []
|
||||
logger.debug(f"Looking up reagent type for {type(kit_type)} {kit_type} and {type(reagent)} {reagent}")
|
||||
logger.debug(f"Kit reagent types: {kit_type.reagent_types}")
|
||||
logger.debug(f"Reagent reagent types: {reagent._sa_instance_state}")
|
||||
result = list(set(kit_type.reagent_types).intersection(reagent.type))
|
||||
logger.debug(f"Result: {result}")
|
||||
return result[0]
|
||||
match name:
|
||||
case str():
|
||||
logger.debug(f"Looking up reagent type by name: {name}")
|
||||
@@ -420,6 +426,8 @@ def lookup_reagenttype_kittype_association(ctx:Settings,
|
||||
def lookup_submission_sample_association(ctx:Settings,
|
||||
submission:models.BasicSubmission|str|None=None,
|
||||
sample:models.BasicSample|str|None=None,
|
||||
row:int=0,
|
||||
column:int=0,
|
||||
limit:int=0,
|
||||
chronologic:bool=False
|
||||
) -> models.SubmissionSampleAssociation|List[models.SubmissionSampleAssociation]:
|
||||
@@ -438,10 +446,14 @@ def lookup_submission_sample_association(ctx:Settings,
|
||||
query = query.join(models.BasicSample).filter(models.BasicSample.submitter_id==sample)
|
||||
case _:
|
||||
pass
|
||||
if row > 0:
|
||||
query = query.filter(models.SubmissionSampleAssociation.row==row)
|
||||
if column > 0:
|
||||
query = query.filter(models.SubmissionSampleAssociation.column==column)
|
||||
logger.debug(f"Query count: {query.count()}")
|
||||
if chronologic:
|
||||
query.join(models.BasicSubmission).order_by(models.BasicSubmission.submitted_date)
|
||||
if query.count() == 1:
|
||||
if query.count() <= 1:
|
||||
limit = 1
|
||||
return query_return(query=query, limit=limit)
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ def update_last_used(ctx:Settings, reagent:models.Reagent, kit:models.KitType):
|
||||
kit (models.KitType): kit to be used for lookup
|
||||
"""
|
||||
# rt = list(set(reagent.type).intersection(kit.reagent_types))[0]
|
||||
logger.debug(f"Attempting update of reagent type at intersection of ({reagent}), ({kit})")
|
||||
rt = lookup_reagent_types(ctx=ctx, kit_type=kit, reagent=reagent)
|
||||
if rt != None:
|
||||
assoc = lookup_reagenttype_kittype_association(ctx=ctx, kit_type=kit, reagent_type=rt)
|
||||
|
||||
@@ -1,43 +1,42 @@
|
||||
'''
|
||||
Contains all models for sqlalchemy
|
||||
'''
|
||||
from typing import Any
|
||||
from sqlalchemy.orm import declarative_base, DeclarativeMeta
|
||||
import logging
|
||||
from pprint import pformat
|
||||
|
||||
Base: DeclarativeMeta = declarative_base()
|
||||
metadata = Base.metadata
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
def find_subclasses(parent:Any, attrs:dict|None=None, rsl_number:str|None=None) -> Any:
|
||||
"""
|
||||
Finds subclasses of a parent that does contain all
|
||||
attributes if the parent does not.
|
||||
# def find_subclasses(parent:Any, attrs:dict|None=None, rsl_number:str|None=None) -> Any:
|
||||
# """
|
||||
# Finds subclasses of a parent that does contain all
|
||||
# attributes if the parent does not.
|
||||
# NOTE: Depreciated, moved to classmethods in individual base models.
|
||||
|
||||
Args:
|
||||
parent (_type_): Parent class.
|
||||
attrs (dict): Key:Value dictionary of attributes
|
||||
# Args:
|
||||
# parent (_type_): Parent class.
|
||||
# attrs (dict): Key:Value dictionary of attributes
|
||||
|
||||
Raises:
|
||||
AttributeError: Raised if no subclass is found.
|
||||
# Raises:
|
||||
# AttributeError: Raised if no subclass is found.
|
||||
|
||||
Returns:
|
||||
_type_: Parent or subclass.
|
||||
"""
|
||||
if len(attrs) == 0 or attrs == None:
|
||||
return parent
|
||||
if any([not hasattr(parent, attr) for attr in attrs]):
|
||||
# looks for first model that has all included kwargs
|
||||
try:
|
||||
model = [subclass for subclass in parent.__subclasses__() if all([hasattr(subclass, attr) for attr in attrs])][0]
|
||||
except IndexError as e:
|
||||
raise AttributeError(f"Couldn't find existing class/subclass of {parent} with all attributes:\n{pformat(attrs)}")
|
||||
else:
|
||||
model = parent
|
||||
logger.debug(f"Using model: {model}")
|
||||
return model
|
||||
# Returns:
|
||||
# _type_: Parent or subclass.
|
||||
# """
|
||||
# if len(attrs) == 0 or attrs == None:
|
||||
# return parent
|
||||
# if any([not hasattr(parent, attr) for attr in attrs]):
|
||||
# # looks for first model that has all included kwargs
|
||||
# try:
|
||||
# model = [subclass for subclass in parent.__subclasses__() if all([hasattr(subclass, attr) for attr in attrs])][0]
|
||||
# except IndexError as e:
|
||||
# raise AttributeError(f"Couldn't find existing class/subclass of {parent} with all attributes:\n{pformat(attrs)}")
|
||||
# else:
|
||||
# model = parent
|
||||
# logger.debug(f"Using model: {model}")
|
||||
# return model
|
||||
|
||||
from .controls import Control, ControlType
|
||||
from .kits import KitType, ReagentType, Reagent, Discount, KitTypeReagentTypeAssociation, SubmissionType, SubmissionTypeKitTypeAssociation
|
||||
|
||||
@@ -31,7 +31,8 @@ class KitType(Base):
|
||||
|
||||
# association proxy of "user_keyword_associations" collection
|
||||
# to "keyword" attribute
|
||||
reagent_types = association_proxy("kit_reagenttype_associations", "reagent_type")
|
||||
# creator function: https://stackoverflow.com/questions/11091491/keyerror-when-adding-objects-to-sqlalchemy-association-object/11116291#11116291
|
||||
reagent_types = association_proxy("kit_reagenttype_associations", "reagent_type", creator=lambda RT: KitTypeReagentTypeAssociation(reagent_type=RT))
|
||||
|
||||
kit_submissiontype_associations = relationship(
|
||||
"SubmissionTypeKitTypeAssociation",
|
||||
@@ -118,7 +119,8 @@ class ReagentType(Base):
|
||||
|
||||
# association proxy of "user_keyword_associations" collection
|
||||
# to "keyword" attribute
|
||||
kit_types = association_proxy("reagenttype_kit_associations", "kit_type")
|
||||
# creator function: https://stackoverflow.com/questions/11091491/keyerror-when-adding-objects-to-sqlalchemy-association-object/11116291#11116291
|
||||
kit_types = association_proxy("reagenttype_kit_associations", "kit_type", creator=lambda kit: KitTypeReagentTypeAssociation(kit_type=kit))
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
@@ -150,6 +152,7 @@ class KitTypeReagentTypeAssociation(Base):
|
||||
reagent_type = relationship(ReagentType, back_populates="reagenttype_kit_associations")
|
||||
|
||||
def __init__(self, kit_type=None, reagent_type=None, uses=None, required=1):
|
||||
logger.debug(f"Parameters: Kit={kit_type}, RT={reagent_type}, Uses={uses}, Required={required}")
|
||||
self.kit_type = kit_type
|
||||
self.reagent_type = reagent_type
|
||||
self.uses = uses
|
||||
@@ -186,9 +189,9 @@ class Reagent(Base):
|
||||
|
||||
def __repr__(self):
|
||||
if self.name != None:
|
||||
return f"Reagent({self.name}-{self.lot})"
|
||||
return f"<Reagent({self.name}-{self.lot})>"
|
||||
else:
|
||||
return f"Reagent({self.type.name}-{self.lot})"
|
||||
return f"<Reagent({self.type.name}-{self.lot})>"
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
@@ -32,6 +32,13 @@ class Organization(Base):
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Organization({self.name})>"
|
||||
|
||||
def save(self, ctx):
|
||||
ctx.database_session.add(self)
|
||||
ctx.database_session.commit()
|
||||
|
||||
def set_attribute(self, name:str, value):
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class Contact(Base):
|
||||
|
||||
@@ -13,7 +13,6 @@ from json.decoder import JSONDecodeError
|
||||
from math import ceil
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
import uuid
|
||||
from pandas import Timestamp
|
||||
from dateutil.parser import parse
|
||||
import re
|
||||
import pandas as pd
|
||||
@@ -301,6 +300,7 @@ class BasicSubmission(Base):
|
||||
@classmethod
|
||||
def enforce_name(cls, ctx:Settings, instr:str) -> str:
|
||||
logger.debug(f"Hello from {cls.__mapper_args__['polymorphic_identity']} Enforcer!")
|
||||
logger.debug(f"Attempting enforcement on {instr}")
|
||||
return instr
|
||||
|
||||
@classmethod
|
||||
@@ -344,6 +344,11 @@ class BasicSubmission(Base):
|
||||
logger.debug(f"Hello from {cls.__mapper_args__['polymorphic_identity']} PCR parser!")
|
||||
return []
|
||||
|
||||
def save(self, ctx:Settings):
|
||||
self.uploaded_by = getuser()
|
||||
ctx.database_session.add(self)
|
||||
ctx.database_session.commit()
|
||||
|
||||
# Below are the custom submission types
|
||||
|
||||
class BacterialCulture(BasicSubmission):
|
||||
@@ -536,6 +541,8 @@ class Wastewater(BasicSubmission):
|
||||
def construct():
|
||||
today = datetime.now()
|
||||
return f"RSL-WW-{today.year}{str(today.month).zfill(2)}{str(today.day).zfill(2)}"
|
||||
if outstr == None:
|
||||
outstr = construct()
|
||||
try:
|
||||
outstr = re.sub(r"PCR(-|_)", "", outstr)
|
||||
except AttributeError as e:
|
||||
@@ -743,6 +750,11 @@ class BasicSample(Base):
|
||||
logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}")
|
||||
return cls
|
||||
|
||||
@classmethod
|
||||
def parse_sample(cls, input_dict:dict) -> dict:
|
||||
logger.debug(f"Called {cls.__name__} sample parser")
|
||||
return input_dict
|
||||
|
||||
class WastewaterSample(BasicSample):
|
||||
"""
|
||||
Derivative wastewater sample
|
||||
@@ -757,51 +769,51 @@ class WastewaterSample(BasicSample):
|
||||
__mapper_args__ = {"polymorphic_identity": "Wastewater Sample", "polymorphic_load": "inline"}
|
||||
|
||||
|
||||
@validates("collected-date")
|
||||
def convert_cdate_time(self, key, value):
|
||||
logger.debug(f"Validating {key}: {value}")
|
||||
if isinstance(value, Timestamp):
|
||||
return value.date()
|
||||
if isinstance(value, str):
|
||||
return parse(value)
|
||||
return value
|
||||
# @validates("collected-date")
|
||||
# def convert_cdate_time(self, key, value):
|
||||
# logger.debug(f"Validating {key}: {value}")
|
||||
# if isinstance(value, Timestamp):
|
||||
# return value.date()
|
||||
# if isinstance(value, str):
|
||||
# return parse(value)
|
||||
# return value
|
||||
|
||||
@validates("rsl_number")
|
||||
def use_submitter_id(self, key, value):
|
||||
logger.debug(f"Validating {key}: {value}")
|
||||
return value or self.submitter_id
|
||||
# @validates("rsl_number")
|
||||
# def use_submitter_id(self, key, value):
|
||||
# logger.debug(f"Validating {key}: {value}")
|
||||
# return value or self.submitter_id
|
||||
|
||||
def set_attribute(self, name:str, value):
|
||||
"""
|
||||
Set an attribute of this object. Extends parent.
|
||||
# def set_attribute(self, name:str, value):
|
||||
# """
|
||||
# Set an attribute of this object. Extends parent.
|
||||
|
||||
Args:
|
||||
name (str): name of the attribute
|
||||
value (_type_): value to be set
|
||||
"""
|
||||
# Due to the plate map being populated with RSL numbers, we have to do some shuffling.
|
||||
match name:
|
||||
case "submitter_id":
|
||||
# If submitter_id already has a value, stop
|
||||
if self.submitter_id != None:
|
||||
return
|
||||
# otherwise also set rsl_number to the same value
|
||||
else:
|
||||
super().set_attribute("rsl_number", value)
|
||||
case "ww_full_sample_id":
|
||||
# If value present, set ww_full_sample_id and make this the submitter_id
|
||||
if value != None:
|
||||
super().set_attribute(name, value)
|
||||
name = "submitter_id"
|
||||
case 'collection_date':
|
||||
# If this is a string use dateutils to parse into date()
|
||||
if isinstance(value, str):
|
||||
logger.debug(f"collection_date {value} is a string. Attempting parse...")
|
||||
value = parse(value)
|
||||
case "rsl_number":
|
||||
if value == None:
|
||||
value = self.submitter_id
|
||||
super().set_attribute(name, value)
|
||||
# Args:
|
||||
# name (str): name of the attribute
|
||||
# value (_type_): value to be set
|
||||
# """
|
||||
# # Due to the plate map being populated with RSL numbers, we have to do some shuffling.
|
||||
# match name:
|
||||
# case "submitter_id":
|
||||
# # If submitter_id already has a value, stop
|
||||
# if self.submitter_id != None:
|
||||
# return
|
||||
# # otherwise also set rsl_number to the same value
|
||||
# else:
|
||||
# super().set_attribute("rsl_number", value)
|
||||
# case "ww_full_sample_id":
|
||||
# # If value present, set ww_full_sample_id and make this the submitter_id
|
||||
# if value != None:
|
||||
# super().set_attribute(name, value)
|
||||
# name = "submitter_id"
|
||||
# case 'collection_date':
|
||||
# # If this is a string use dateutils to parse into date()
|
||||
# if isinstance(value, str):
|
||||
# logger.debug(f"collection_date {value} is a string. Attempting parse...")
|
||||
# value = parse(value)
|
||||
# case "rsl_number":
|
||||
# if value == None:
|
||||
# value = self.submitter_id
|
||||
# super().set_attribute(name, value)
|
||||
|
||||
def to_hitpick(self, submission_rsl:str) -> dict|None:
|
||||
"""
|
||||
@@ -832,6 +844,16 @@ class WastewaterSample(BasicSample):
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def parse_sample(cls, input_dict: dict) -> dict:
|
||||
output_dict = super().parse_sample(input_dict)
|
||||
if output_dict['rsl_number'] == None:
|
||||
output_dict['rsl_number'] = output_dict['submitter_id']
|
||||
if output_dict['ww_full_sample_id'] != None:
|
||||
output_dict["submitter_id"] = output_dict['ww_full_sample_id']
|
||||
return output_dict
|
||||
|
||||
|
||||
class BacterialCultureSample(BasicSample):
|
||||
"""
|
||||
base of bacterial culture sample
|
||||
@@ -873,7 +895,7 @@ class SubmissionSampleAssociation(Base):
|
||||
# Refers to the type of parent.
|
||||
# Hooooooo boy, polymorphic association type, now we're getting into the weeds!
|
||||
__mapper_args__ = {
|
||||
"polymorphic_identity": "basic_association",
|
||||
"polymorphic_identity": "Basic Association",
|
||||
"polymorphic_on": base_sub_type,
|
||||
"with_polymorphic": "*",
|
||||
}
|
||||
@@ -886,6 +908,19 @@ class SubmissionSampleAssociation(Base):
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<SubmissionSampleAssociation({self.submission.rsl_plate_num} & {self.sample.submitter_id})"
|
||||
|
||||
@classmethod
|
||||
def find_polymorphic_subclass(cls, polymorphic_identity:str|None=None):
|
||||
if isinstance(polymorphic_identity, dict):
|
||||
polymorphic_identity = polymorphic_identity['value']
|
||||
if polymorphic_identity == None:
|
||||
return cls
|
||||
else:
|
||||
try:
|
||||
return [item for item in cls.__subclasses__() if item.__mapper_args__['polymorphic_identity']==polymorphic_identity][0]
|
||||
except Exception as e:
|
||||
logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}")
|
||||
return cls
|
||||
|
||||
class WastewaterAssociation(SubmissionSampleAssociation):
|
||||
"""
|
||||
@@ -897,5 +932,5 @@ class WastewaterAssociation(SubmissionSampleAssociation):
|
||||
n2_status = Column(String(32)) #: positive or negative for N2
|
||||
pcr_results = Column(JSON) #: imported PCR status from QuantStudio
|
||||
|
||||
__mapper_args__ = {"polymorphic_identity": "wastewater", "polymorphic_load": "inline"}
|
||||
__mapper_args__ = {"polymorphic_identity": "Wastewater Association", "polymorphic_load": "inline"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user