Minor bug fixes.

This commit is contained in:
Landon Wark
2023-12-07 12:50:03 -06:00
parent cbf36a5b0b
commit 0dd51827a0
22 changed files with 308 additions and 370 deletions

View File

@@ -4,9 +4,8 @@ All control related models.
from __future__ import annotations
from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey
from sqlalchemy.orm import relationship, Query
import logging
import logging, json
from operator import itemgetter
import json
from . import BaseClass
from tools import setup_lookup, query_return
from datetime import date, datetime
@@ -51,6 +50,26 @@ class ControlType(BaseClass):
pass
return query_return(query=query, limit=limit)
def get_subtypes(self, mode:str) -> List[str]:
"""
Get subtypes associated with this controltype
Args:
mode (str): analysis mode name
Returns:
List[str]: list of subtypes available
"""
outs = self.instances[0]
jsoner = json.loads(getattr(outs, mode))
logger.debug(f"JSON out: {jsoner.keys()}")
try:
genera = list(jsoner.keys())[0]
except IndexError:
return []
subtypes = [item for item in jsoner[genera] if "_hashes" not in item and "_ratio" not in item]
return subtypes
class Control(BaseClass):
"""
Base class of a control sample.
@@ -249,4 +268,3 @@ class Control(BaseClass):
def save(self):
self.__database_session__.add(self)
self.__database_session__.commit()

View File

@@ -183,15 +183,6 @@ class ReagentType(BaseClass):
# 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:
# """
# string representing this object
# Returns:
# str: string representing this object's name
# """
# return self.name
def __repr__(self):
return f"<ReagentType({self.name})>"
@@ -379,7 +370,17 @@ class Reagent(BaseClass):
name = Column(String(64)) #: reagent name
lot = Column(String(64)) #: lot number of reagent
expiry = Column(TIMESTAMP) #: expiry date - extended by eol_ext of parent programmatically
submissions = relationship("BasicSubmission", back_populates="reagents", uselist=True) #: submissions this reagent is used in
# submissions = relationship("BasicSubmission", back_populates="reagents", uselist=True) #: submissions this reagent is used in
reagent_submission_associations = relationship(
"SubmissionReagentAssociation",
back_populates="reagent",
cascade="all, delete-orphan",
) #: Relation to SubmissionSampleAssociation
# association proxy of "user_keyword_associations" collection
# to "keyword" attribute
submissions = association_proxy("reagent_submission_associations", "submission") #: Association proxy to SubmissionSampleAssociation.samples
def __repr__(self):
if self.name != None:
@@ -706,3 +707,69 @@ class SubmissionTypeKitTypeAssociation(BaseClass):
query = query.join(KitType).filter(KitType.id==kit_type)
limit = query.count()
return query_return(query=query, limit=limit)
class SubmissionReagentAssociation(BaseClass):
__tablename__ = "_reagents_submissions"
reagent_id = Column(INTEGER, ForeignKey("_reagents.id"), primary_key=True) #: id of associated sample
submission_id = Column(INTEGER, ForeignKey("_submissions.id"), primary_key=True)
comments = Column(String(1024))
submission = relationship("BasicSubmission", back_populates="submission_reagent_associations") #: associated submission
reagent = relationship(Reagent, back_populates="reagent_submission_associations")
def __repr__(self):
return f"<{self.submission.rsl_plate_num}&{self.reagent.lot}>"
def __init__(self, reagent=None, submission=None):
self.reagent = reagent
self.submission = submission
self.comments = ""
@classmethod
@setup_lookup
def query(cls,
submission:"BasicSubmission"|str|int|None=None,
reagent:Reagent|str|None=None,
limit:int=0) -> SubmissionReagentAssociation|List[SubmissionReagentAssociation]:
"""
Lookup SubmissionReagentAssociations of interest.
Args:
submission (BasicSubmission&quot; | str | int | None, optional): Identifier of joined submission. Defaults to None.
reagent (Reagent | str | None, optional): Identifier of joined reagent. Defaults to None.
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
Returns:
SubmissionReagentAssociation|List[SubmissionReagentAssociation]: SubmissionReagentAssociation(s) of interest
"""
from . import BasicSubmission
query: Query = cls.__database_session__.query(cls)
match reagent:
case Reagent():
query = query.filter(cls.reagent==reagent)
case str():
# logger.debug(f"Filtering query with reagent: {reagent}")
reagent = Reagent.query(lot_number=reagent)
query = query.filter(cls.reagent==reagent)
# logger.debug([item.reagent.lot for item in query.all()])
# query = query.join(Reagent).filter(Reagent.lot==reagent)
case _:
pass
# logger.debug(f"Result of query after reagent: {query.all()}")
match submission:
case BasicSubmission():
query = query.filter(cls.submission==submission)
case str():
query = query.join(BasicSubmission).filter(BasicSubmission.rsl_plate_num==submission)
case int():
query = query.join(BasicSubmission).filter(BasicSubmission.id==submission)
case _:
pass
# logger.debug(f"Result of query after submission: {query.all()}")
# limit = query.count()
return query_return(query=query, limit=limit)

View File

@@ -6,13 +6,13 @@ from getpass import getuser
import math, json, logging, uuid, tempfile, re, yaml
from pprint import pformat
from . import Reagent, SubmissionType, KitType, Organization
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, Table, JSON, FLOAT, case
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case
from sqlalchemy.orm import relationship, validates, Query
from json.decoder import JSONDecodeError
from sqlalchemy.ext.associationproxy import association_proxy
import pandas as pd
from openpyxl import Workbook
from . import Base, BaseClass
from . import BaseClass
from tools import check_not_nan, row_map, query_return, setup_lookup
from datetime import datetime, date
from typing import List
@@ -24,15 +24,6 @@ from pathlib import Path
logger = logging.getLogger(f"submissions.{__name__}")
# table containing reagents/submission relationships
reagents_submissions = Table(
"_reagents_submissions",
Base.metadata,
Column("reagent_id", INTEGER, ForeignKey("_reagents.id")),
Column("submission_id", INTEGER, ForeignKey("_submissions.id")),
extend_existing = True
)
class BasicSubmission(BaseClass):
"""
Concrete of basic submission which polymorphs into BacterialCulture and Wastewater
@@ -51,7 +42,7 @@ class BasicSubmission(BaseClass):
submission_type_name = Column(String, ForeignKey("_submission_types.name", ondelete="SET NULL", name="fk_BS_subtype_name")) #: name of joined submission type
technician = Column(String(64)) #: initials of processing tech(s)
# Move this into custom types?
reagents = relationship("Reagent", back_populates="submissions", secondary=reagents_submissions) #: relationship to reagents
# reagents = relationship("Reagent", back_populates="submissions", secondary=reagents_submissions) #: relationship to reagents
reagents_id = Column(String, ForeignKey("_reagents.id", ondelete="SET NULL", name="fk_BS_reagents_id")) #: id of used reagents
extraction_info = Column(JSON) #: unstructured output from the extraction table logger.
pcr_info = Column(JSON) #: unstructured output from pcr table logger or user(Artic)
@@ -69,6 +60,15 @@ class BasicSubmission(BaseClass):
# to "keyword" attribute
samples = association_proxy("submission_sample_associations", "sample") #: Association proxy to SubmissionSampleAssociation.samples
submission_reagent_associations = relationship(
"SubmissionReagentAssociation",
back_populates="submission",
cascade="all, delete-orphan",
) #: Relation to SubmissionSampleAssociation
# association proxy of "user_keyword_associations" collection
# to "keyword" attribute
reagents = association_proxy("submission_reagent_associations", "reagent") #: Association proxy to SubmissionSampleAssociation.samples
# Allows for subclassing into ex. BacterialCulture, Wastewater, etc.
__mapper_args__ = {
"polymorphic_identity": "Basic Submission",
@@ -438,6 +438,22 @@ class BasicSubmission(BaseClass):
"""
return "{{ rsl_plate_num }}"
@classmethod
def submissions_to_df(cls, submission_type:str|None=None, limit:int=0) -> pd.DataFrame:
logger.debug(f"Querying Type: {submission_type}")
logger.debug(f"Using limit: {limit}")
# use lookup function to create list of dicts
subs = [item.to_dict() for item in cls.query(submission_type=submission_type, limit=limit)]
logger.debug(f"Got {len(subs)} submissions.")
df = pd.DataFrame.from_records(subs)
# Exclude sub information
for item in ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents']:
try:
df = df.drop(item, axis=1)
except:
logger.warning(f"Couldn't drop '{item}' column from submissionsheet df.")
return df
def set_attribute(self, key:str, value):
"""
Performs custom attribute setting based on values.
@@ -479,6 +495,11 @@ class BasicSubmission(BaseClass):
field_value = value
case "ctx" | "csv" | "filepath":
return
case "comment":
if value == "" or value == None or value == 'null':
field_value = None
else:
field_value = dict(name="submitter", text=value, time=datetime.now())
case _:
field_value = value
# insert into field
@@ -595,7 +616,8 @@ class BasicSubmission(BaseClass):
start_date:date|str|int|None=None,
end_date:date|str|int|None=None,
reagent:Reagent|str|None=None,
chronologic:bool=False, limit:int=0,
chronologic:bool=False,
limit:int=0,
**kwargs
) -> BasicSubmission | List[BasicSubmission]:
"""