Tips parser complete.
This commit is contained in:
@@ -8,13 +8,12 @@ from sqlalchemy.ext.associationproxy import association_proxy
|
||||
from datetime import date
|
||||
import logging, re
|
||||
from tools import check_authorization, setup_lookup, Report, Result
|
||||
from typing import List, Literal
|
||||
from typing import List, Literal, Any
|
||||
from pandas import ExcelFile
|
||||
from pathlib import Path
|
||||
from . import Base, BaseClass, Organization
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
logger = logging.getLogger(f'submissions.{__name__}')
|
||||
|
||||
# logger.debug("Table for ReagentType/Reagent relations")
|
||||
@@ -79,14 +78,6 @@ tiproles_tips = Table(
|
||||
extend_existing=True
|
||||
)
|
||||
|
||||
submissions_tips = Table(
|
||||
"_submissions_tips",
|
||||
Base.metadata,
|
||||
Column("submission_id", INTEGER, ForeignKey("_basicsubmissions.id")),
|
||||
Column("tips_id", INTEGER, ForeignKey("_tips.id")),
|
||||
extend_existing=True
|
||||
)
|
||||
|
||||
process_tiprole = Table(
|
||||
"_process_tiprole",
|
||||
Base.metadata,
|
||||
@@ -95,6 +86,15 @@ process_tiprole = Table(
|
||||
extend_existing=True
|
||||
)
|
||||
|
||||
equipment_tips = Table(
|
||||
"_equipment_tips",
|
||||
Base.metadata,
|
||||
Column("equipment_id", INTEGER, ForeignKey("_equipment.id")),
|
||||
Column("tips_id", INTEGER, ForeignKey("_tips.id")),
|
||||
extend_existing=True
|
||||
)
|
||||
|
||||
|
||||
class KitType(BaseClass):
|
||||
"""
|
||||
Base of kits used in submission processing
|
||||
@@ -133,7 +133,8 @@ class KitType(BaseClass):
|
||||
"""
|
||||
return f"<KitType({self.name})>"
|
||||
|
||||
def get_reagents(self, required: bool = False, submission_type: str | SubmissionType | None = None) -> List[ReagentRole]:
|
||||
def get_reagents(self, required: bool = False, submission_type: str | SubmissionType | None = None) -> List[
|
||||
ReagentRole]:
|
||||
"""
|
||||
Return ReagentTypes linked to kit through KitTypeReagentTypeAssociation.
|
||||
|
||||
@@ -143,7 +144,7 @@ class KitType(BaseClass):
|
||||
|
||||
Returns:
|
||||
List[ReagentRole]: List of reagents linked to this kit.
|
||||
"""
|
||||
"""
|
||||
match submission_type:
|
||||
case SubmissionType():
|
||||
# logger.debug(f"Getting reagents by SubmissionType {submission_type}")
|
||||
@@ -609,6 +610,12 @@ class SubmissionType(BaseClass):
|
||||
cascade="all, delete-orphan"
|
||||
) #: triple association of KitTypes, ReagentTypes, SubmissionTypes
|
||||
|
||||
submissiontype_tiprole_associations = relationship(
|
||||
"SubmissionTypeTipRoleAssociation",
|
||||
back_populates="submission_type",
|
||||
cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Returns:
|
||||
@@ -656,7 +663,7 @@ class SubmissionType(BaseClass):
|
||||
|
||||
Returns:
|
||||
dict: Map of locations
|
||||
"""
|
||||
"""
|
||||
info = self.info_map
|
||||
# logger.debug(f"Info map: {info}")
|
||||
output = {}
|
||||
@@ -674,7 +681,7 @@ class SubmissionType(BaseClass):
|
||||
|
||||
Returns:
|
||||
dict: sample location map
|
||||
"""
|
||||
"""
|
||||
return self.sample_map
|
||||
|
||||
def construct_equipment_map(self) -> dict:
|
||||
@@ -693,6 +700,15 @@ class SubmissionType(BaseClass):
|
||||
output[item.equipment_role.name] = emap
|
||||
return output
|
||||
|
||||
def construct_tips_map(self):
|
||||
output = {}
|
||||
for item in self.submissiontype_tiprole_associations:
|
||||
tmap = item.uses
|
||||
if tmap is None:
|
||||
tmap = {}
|
||||
output[item.tip_role.name] = tmap
|
||||
return output
|
||||
|
||||
def get_equipment(self, extraction_kit: str | KitType | None = None) -> List['PydEquipmentRole']:
|
||||
"""
|
||||
Returns PydEquipmentRole of all equipment associated with this SubmissionType
|
||||
@@ -735,7 +751,7 @@ class SubmissionType(BaseClass):
|
||||
|
||||
Returns:
|
||||
BasicSubmission: Submission class
|
||||
"""
|
||||
"""
|
||||
from .submissions import BasicSubmission
|
||||
return BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.name)
|
||||
|
||||
@@ -1071,6 +1087,8 @@ class Equipment(BaseClass):
|
||||
secondary=equipmentroles_equipment) #: relation to EquipmentRoles
|
||||
processes = relationship("Process", back_populates="equipment",
|
||||
secondary=equipment_processes) #: relation to Processes
|
||||
tips = relationship("Tips", back_populates="equipment",
|
||||
secondary=equipment_tips) #: relation to Processes
|
||||
equipment_submission_associations = relationship(
|
||||
"SubmissionEquipmentAssociation",
|
||||
back_populates="equipment",
|
||||
@@ -1467,7 +1485,7 @@ class Process(BaseClass):
|
||||
backref='process') #: relation to SubmissionEquipmentAssociation
|
||||
kit_types = relationship("KitType", back_populates='processes',
|
||||
secondary=kittypes_processes) #: relation to KitType
|
||||
tip_roles = relationship("TipRoles", back_populates='processes',
|
||||
tip_roles = relationship("TipRole", back_populates='processes',
|
||||
secondary=process_tiprole) #: relation to KitType
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -1502,19 +1520,25 @@ class Process(BaseClass):
|
||||
|
||||
|
||||
class TipRole(BaseClass):
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
name = Column(String(64)) #: name of reagent type
|
||||
instances = relationship("Tips", back_populates="role",
|
||||
secondary=tiproles_tips) #: concrete instances of this reagent type
|
||||
processes = relationship("Process", back_populates="tip_roles", secondary=process_tiprole)
|
||||
|
||||
tiprole_submissiontype_associations = relationship(
|
||||
"SubmissionTypeTipRoleAssociation",
|
||||
back_populates="tip_role",
|
||||
cascade="all, delete-orphan"
|
||||
) #: associated submission
|
||||
|
||||
submission_types = association_proxy("tiprole_submissiontype_associations", "submission_type")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<TipRole({self.name})>"
|
||||
|
||||
class Tips(BaseClass):
|
||||
|
||||
class Tips(BaseClass):
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
role = relationship("TipRole", back_populates="instances",
|
||||
secondary=tiproles_tips) #: joined parent reagent type
|
||||
@@ -1522,10 +1546,64 @@ class Tips(BaseClass):
|
||||
name="fk_tip_role_id")) #: id of parent reagent type
|
||||
name = Column(String(64)) #: tip common name
|
||||
lot = Column(String(64)) #: lot number of tips
|
||||
submissions = relationship("BasicSubmission", back_populates="tips",
|
||||
secondary=submissions_tips) #: associated submission
|
||||
equipment = relationship("Equipment", back_populates="tips",
|
||||
secondary=equipment_tips) #: associated submission
|
||||
tips_submission_associations = relationship(
|
||||
"SubmissionTipsAssociation",
|
||||
back_populates="tips",
|
||||
cascade="all, delete-orphan"
|
||||
) #: associated submission
|
||||
|
||||
submissions = association_proxy("tips_submission_associations", 'submission')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Tips({self.name})>"
|
||||
|
||||
@classmethod
|
||||
def query(cls, name: str | None = None, lot: str | None = None, limit: int = 0, **kwargs) -> Any | List[Any]:
|
||||
query = cls.__database_session__.query(cls)
|
||||
match name:
|
||||
case str():
|
||||
# logger.debug(f"Lookup Equipment by name str {name}")
|
||||
query = query.filter(cls.name == name)
|
||||
case _:
|
||||
pass
|
||||
match lot:
|
||||
case str():
|
||||
# logger.debug(f"Lookup Equipment by nickname str {nickname}")
|
||||
query = query.filter(cls.lot == lot)
|
||||
limit = 1
|
||||
case _:
|
||||
pass
|
||||
return cls.execute_query(query=query, limit=limit)
|
||||
|
||||
|
||||
class SubmissionTypeTipRoleAssociation(BaseClass):
|
||||
"""
|
||||
Abstract association between SubmissionType and TipRole
|
||||
"""
|
||||
tiprole_id = Column(INTEGER, ForeignKey("_tiprole.id"), primary_key=True) #: id of associated equipment
|
||||
submissiontype_id = Column(INTEGER, ForeignKey("_submissiontype.id"),
|
||||
primary_key=True) #: id of associated submission
|
||||
uses = Column(JSON) #: locations of equipment on the submission type excel sheet.
|
||||
static = Column(INTEGER,
|
||||
default=1) #: if 1 this piece of equipment will always be used, otherwise it will need to be selected from list?
|
||||
submission_type = relationship(SubmissionType,
|
||||
back_populates="submissiontype_tiprole_associations") #: associated submission
|
||||
tip_role = relationship(TipRole,
|
||||
back_populates="tiprole_submissiontype_associations") #: associated equipment
|
||||
|
||||
|
||||
class SubmissionTipsAssociation(BaseClass):
|
||||
tip_id = Column(INTEGER, ForeignKey("_tips.id"), primary_key=True) #: id of associated equipment
|
||||
submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id"), primary_key=True) #: id of associated submission
|
||||
submission = relationship("BasicSubmission",
|
||||
back_populates="submission_tips_associations") #: associated submission
|
||||
tips = relationship(Tips,
|
||||
back_populates="tips_submission_associations") #: associated equipment
|
||||
role_name = Column(String(32)) #, ForeignKey("_tiprole.name"))
|
||||
|
||||
# role = relationship(TipRole)
|
||||
|
||||
def to_sub_dict(self):
|
||||
return dict(role=self.role_name, name=self.tips.name, lot=self.tips.lot)
|
||||
|
||||
@@ -10,7 +10,7 @@ from zipfile import ZipFile
|
||||
from tempfile import TemporaryDirectory
|
||||
from operator import attrgetter, itemgetter
|
||||
from pprint import pformat
|
||||
from . import BaseClass, Reagent, SubmissionType, KitType, Organization, Contact
|
||||
from . import BaseClass, Reagent, SubmissionType, KitType, Organization, Contact, Tips, TipRole, SubmissionTipsAssociation
|
||||
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case
|
||||
from sqlalchemy.orm import relationship, validates, Query
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
@@ -96,6 +96,14 @@ class BasicSubmission(BaseClass):
|
||||
equipment = association_proxy("submission_equipment_associations",
|
||||
"equipment") #: Association proxy to SubmissionEquipmentAssociation.equipment
|
||||
|
||||
submission_tips_associations = relationship(
|
||||
"SubmissionTipsAssociation",
|
||||
back_populates="submission",
|
||||
cascade="all, delete-orphan")
|
||||
|
||||
tips = association_proxy("submission_tips_associations",
|
||||
"tips")
|
||||
|
||||
# NOTE: Allows for subclassing into ex. BacterialCulture, Wastewater, etc.
|
||||
__mapper_args__ = {
|
||||
"polymorphic_identity": "Basic Submission",
|
||||
@@ -248,7 +256,6 @@ class BasicSubmission(BaseClass):
|
||||
ext_info = self.extraction_info
|
||||
except TypeError:
|
||||
ext_info = None
|
||||
|
||||
output = {
|
||||
"id": self.id,
|
||||
"plate_number": self.rsl_plate_num,
|
||||
@@ -282,16 +289,24 @@ class BasicSubmission(BaseClass):
|
||||
# logger.debug("Running equipment")
|
||||
try:
|
||||
equipment = [item.to_sub_dict() for item in self.submission_equipment_associations]
|
||||
if len(equipment) == 0:
|
||||
if not equipment:
|
||||
equipment = None
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting equipment: {e}")
|
||||
equipment = None
|
||||
try:
|
||||
tips = [item.to_sub_dict() for item in self.submission_tips_associations]
|
||||
if not tips:
|
||||
tips = None
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting tips: {e}")
|
||||
tips = None
|
||||
cost_centre = self.cost_centre
|
||||
else:
|
||||
reagents = None
|
||||
samples = None
|
||||
equipment = None
|
||||
tips = None
|
||||
cost_centre = None
|
||||
# logger.debug("Getting comments")
|
||||
try:
|
||||
@@ -315,6 +330,7 @@ class BasicSubmission(BaseClass):
|
||||
output["extraction_info"] = ext_info
|
||||
output["comment"] = comments
|
||||
output["equipment"] = equipment
|
||||
output["tips"] = tips
|
||||
output["cost_centre"] = cost_centre
|
||||
output["signed_by"] = self.signed_by
|
||||
# logger.debug(f"Setting contact to: {contact} of type: {type(contact)}")
|
||||
@@ -440,7 +456,7 @@ class BasicSubmission(BaseClass):
|
||||
excluded = ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents',
|
||||
'equipment', 'gel_info', 'gel_image', 'dna_core_submission_number', 'gel_controls',
|
||||
'source_plates', 'pcr_technician', 'ext_technician', 'artic_technician', 'cost_centre',
|
||||
'signed_by', 'artic_date', 'gel_barcode', 'gel_date', 'ngs_date', 'contact_phone', 'contact']
|
||||
'signed_by', 'artic_date', 'gel_barcode', 'gel_date', 'ngs_date', 'contact_phone', 'contact', 'tips']
|
||||
for item in excluded:
|
||||
try:
|
||||
df = df.drop(item, axis=1)
|
||||
@@ -1110,6 +1126,12 @@ class BasicSubmission(BaseClass):
|
||||
_, assoc = equip.toSQL(submission=self)
|
||||
# logger.debug(f"Appending SubmissionEquipmentAssociation: {assoc}")
|
||||
assoc.save()
|
||||
if equip.tips:
|
||||
logger.debug("We have tips in this equipment")
|
||||
for tips in equip.tips:
|
||||
tassoc = tips.to_sql(submission=self)
|
||||
tassoc.save()
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user