Moments before disaster
This commit is contained in:
@@ -2,11 +2,7 @@
|
||||
Contains all models for sqlalchemy
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys, logging, json
|
||||
from collections import OrderedDict
|
||||
|
||||
import sqlalchemy.exc
|
||||
from dateutil.parser import parse
|
||||
from pandas import DataFrame
|
||||
from pydantic import BaseModel
|
||||
@@ -21,7 +17,6 @@ from pathlib import Path
|
||||
from sqlalchemy.orm.relationships import _RelationshipDeclared
|
||||
from tools import report_result, list_sort_dict
|
||||
|
||||
|
||||
# NOTE: Load testing environment
|
||||
if 'pytest' in sys.modules:
|
||||
sys.path.append(Path(__file__).parents[4].absolute().joinpath("tests").__str__())
|
||||
@@ -41,9 +36,9 @@ class BaseClass(Base):
|
||||
__table_args__ = {'extend_existing': True} #: NOTE Will only add new columns
|
||||
|
||||
singles = ['id']
|
||||
omni_removes = ["id", 'run', "omnigui_class_dict", "omnigui_instance_dict"]
|
||||
omni_sort = ["name"]
|
||||
omni_inheritable = []
|
||||
# omni_removes = ["id", 'run', "omnigui_class_dict", "omnigui_instance_dict"]
|
||||
# omni_sort = ["name"]
|
||||
# omni_inheritable = []
|
||||
searchables = []
|
||||
|
||||
_misc_info = Column(JSON)
|
||||
@@ -242,12 +237,13 @@ class BaseClass(Base):
|
||||
@classmethod
|
||||
def query_or_create(cls, **kwargs) -> Tuple[Any, bool]:
|
||||
new = False
|
||||
allowed = [k for k, v in cls.__dict__.items() if isinstance(v, InstrumentedAttribute) or isinstance(v, hybrid_property)]
|
||||
allowed = [k for k, v in cls.__dict__.items() if
|
||||
isinstance(v, InstrumentedAttribute) or isinstance(v, hybrid_property)]
|
||||
# and not isinstance(v.property, _RelationshipDeclared)]
|
||||
sanitized_kwargs = {k: v for k, v in kwargs.items() if k in allowed}
|
||||
outside_kwargs = {k: v for k, v in kwargs.items() if k not in allowed}
|
||||
# logger.debug(f"Sanitized kwargs: {sanitized_kwargs}")
|
||||
instance = cls.query(**sanitized_kwargs)
|
||||
logger.debug(f"Sanitized kwargs: {sanitized_kwargs}")
|
||||
instance = cls.query(limit=1, **sanitized_kwargs)
|
||||
if not instance or isinstance(instance, list):
|
||||
instance = cls()
|
||||
new = True
|
||||
@@ -280,7 +276,8 @@ class BaseClass(Base):
|
||||
return cls.execute_query(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def execute_query(cls, query: Query = None, model=None, limit: int = 0, offset:int|None=None, **kwargs) -> Any | List[Any]:
|
||||
def execute_query(cls, query: Query = None, model=None, limit: int = 0, offset: int | None = None,
|
||||
**kwargs) -> Any | List[Any]:
|
||||
"""
|
||||
Execute sqlalchemy query with relevant defaults.
|
||||
|
||||
@@ -610,12 +607,12 @@ class BaseClass(Base):
|
||||
output_date = datetime.combine(output_date, addition_time).strftime("%Y-%m-%d %H:%M:%S")
|
||||
return output_date
|
||||
|
||||
def details_dict(self, **kwargs):
|
||||
def details_dict(self, **kwargs) -> dict:
|
||||
|
||||
relevant = {k: v for k, v in self.__class__.__dict__.items() if
|
||||
isinstance(v, InstrumentedAttribute) or isinstance(v, AssociationProxy)}
|
||||
# output = OrderedDict()
|
||||
output = dict(excluded = ["excluded", "misc_info", "_misc_info", "id"])
|
||||
output = dict(excluded=["excluded", "misc_info", "_misc_info", "id"])
|
||||
for k, v in relevant.items():
|
||||
try:
|
||||
check = v.foreign_keys
|
||||
@@ -666,7 +663,7 @@ class BaseClass(Base):
|
||||
output[k] = value
|
||||
return output
|
||||
|
||||
def to_pydantic(self, pyd_model_name:str|None=None, **kwargs):
|
||||
def to_pydantic(self, pyd_model_name: str | None = None, **kwargs):
|
||||
from backend.validators import pydant
|
||||
if not pyd_model_name:
|
||||
pyd_model_name = f"Pyd{self.__class__.__name__}"
|
||||
@@ -685,7 +682,7 @@ class BaseClass(Base):
|
||||
if dlg.exec():
|
||||
pass
|
||||
|
||||
def export(self, obj, output_filepath: str|Path|None=None):
|
||||
def export(self, obj, output_filepath: str | Path | None = None):
|
||||
# if not hasattr(self, "template_file"):
|
||||
# logger.error(f"Export not implemented for {self.__class__.__name__}")
|
||||
# return
|
||||
|
||||
@@ -44,13 +44,13 @@ equipmentrole_equipment = Table(
|
||||
extend_existing=True
|
||||
)
|
||||
|
||||
equipment_process = Table(
|
||||
"_equipment_process",
|
||||
Base.metadata,
|
||||
Column("process_id", INTEGER, ForeignKey("_process.id")),
|
||||
Column("equipment_id", INTEGER, ForeignKey("_equipment.id")),
|
||||
extend_existing=True
|
||||
)
|
||||
# equipment_process = Table(
|
||||
# "_equipment_process",
|
||||
# Base.metadata,
|
||||
# Column("process_id", INTEGER, ForeignKey("_process.id")),
|
||||
# Column("equipment_id", INTEGER, ForeignKey("_equipment.id")),
|
||||
# extend_existing=True
|
||||
# )
|
||||
|
||||
equipmentrole_process = Table(
|
||||
"_equipmentrole_process",
|
||||
@@ -68,15 +68,15 @@ equipmentrole_process = Table(
|
||||
# extend_existing=True
|
||||
# )
|
||||
|
||||
tiprole_tips = Table(
|
||||
"_tiprole_tips",
|
||||
Base.metadata,
|
||||
Column("tiprole_id", INTEGER, ForeignKey("_tiprole.id")),
|
||||
Column("tips_id", INTEGER, ForeignKey("_tips.id")),
|
||||
extend_existing=True
|
||||
)
|
||||
# tiprole_tips = Table(
|
||||
# "_tiprole_tips",
|
||||
# Base.metadata,
|
||||
# Column("tiprole_id", INTEGER, ForeignKey("_tiprole.id")),
|
||||
# Column("tips_id", INTEGER, ForeignKey("_tips.id")),
|
||||
# extend_existing=True
|
||||
# )
|
||||
|
||||
process_tiprole = Table(
|
||||
process_tips = Table(
|
||||
"_process_tiprole",
|
||||
Base.metadata,
|
||||
Column("process_id", INTEGER, ForeignKey("_process.id")),
|
||||
@@ -84,13 +84,13 @@ 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
|
||||
)
|
||||
# equipment_tips = Table(
|
||||
# "_equipment_tips",
|
||||
# Base.metadata,
|
||||
# Column("equipment_id", INTEGER, ForeignKey("_equipment.id")),
|
||||
# Column("tips_id", INTEGER, ForeignKey("_tips.id")),
|
||||
# extend_existing=True
|
||||
# )
|
||||
|
||||
# kittype_procedure = Table(
|
||||
# "_kittype_procedure",
|
||||
@@ -100,13 +100,13 @@ equipment_tips = Table(
|
||||
# extend_existing=True
|
||||
# )
|
||||
|
||||
proceduretype_process = Table(
|
||||
"_proceduretype_process",
|
||||
Base.metadata,
|
||||
Column("process_id", INTEGER, ForeignKey("_process.id")),
|
||||
Column("proceduretype_id", INTEGER, ForeignKey("_proceduretype.id")),
|
||||
extend_existing=True
|
||||
)
|
||||
# proceduretype_process = Table(
|
||||
# "_proceduretype_process",
|
||||
# Base.metadata,
|
||||
# Column("process_id", INTEGER, ForeignKey("_process.id")),
|
||||
# Column("proceduretype_id", INTEGER, ForeignKey("_proceduretype.id")),
|
||||
# extend_existing=True
|
||||
# )
|
||||
|
||||
submissiontype_proceduretype = Table(
|
||||
"_submissiontype_proceduretype",
|
||||
@@ -823,7 +823,7 @@ class Reagent(BaseClass, LogMixin):
|
||||
|
||||
class ReagentLot(BaseClass):
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
lot = Column(String(64)) #: lot number of reagent
|
||||
lot = Column(String(64), unique=True) #: lot number of reagent
|
||||
expiry = Column(TIMESTAMP) #: expiry date - extended by eol_ext of parent programmatically
|
||||
reagent = relationship("Reagent") #: joined parent reagent type
|
||||
reagent_id = Column(INTEGER, ForeignKey("_reagent.id", ondelete='SET NULL',
|
||||
@@ -843,6 +843,25 @@ class ReagentLot(BaseClass):
|
||||
def name(self):
|
||||
return self.lot
|
||||
|
||||
@classmethod
|
||||
def query(cls,
|
||||
lot: str | None = None,
|
||||
name: str | None = None,
|
||||
limit: int = 1,
|
||||
**kwargs) -> ReagentLot | List[ReagentLot]:
|
||||
query: Query = cls.__database_session__.query(cls)
|
||||
match lot:
|
||||
case str():
|
||||
query = query.filter(cls.lot == lot)
|
||||
case _:
|
||||
pass
|
||||
match name:
|
||||
case str():
|
||||
query = query.join(Reagent).filter(Reagent.name == name)
|
||||
case _:
|
||||
pass
|
||||
return cls.execute_query(query=query, limit=limit)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Lot({self.lot}-{self.expiry}>"
|
||||
|
||||
@@ -1447,7 +1466,7 @@ class Procedure(BaseClass):
|
||||
) #: Relation to ProcedureReagentAssociation
|
||||
|
||||
reagentlot = association_proxy("procedurereagentlotassociation",
|
||||
"reagentlot", creator=lambda reg: ProcedureReagentLotAssociation(
|
||||
"reagentlot", creator=lambda reg: ProcedureReagentLotAssociation(
|
||||
reagent=reg)) #: Association proxy to RunReagentAssociation.reagent
|
||||
|
||||
procedureequipmentassociation = relationship(
|
||||
@@ -1459,13 +1478,13 @@ class Procedure(BaseClass):
|
||||
equipment = association_proxy("procedureequipmentassociation",
|
||||
"equipment") #: Association proxy to RunEquipmentAssociation.equipment
|
||||
|
||||
proceduretipsassociation = relationship(
|
||||
"ProcedureTipsAssociation",
|
||||
back_populates="procedure",
|
||||
cascade="all, delete-orphan")
|
||||
|
||||
tips = association_proxy("proceduretipsassociation",
|
||||
"tips")
|
||||
# proceduretipsassociation = relationship(
|
||||
# "ProcedureTipsAssociation",
|
||||
# back_populates="procedure",
|
||||
# cascade="all, delete-orphan")
|
||||
#
|
||||
# tips = association_proxy("proceduretipsassociation",
|
||||
# "tips")
|
||||
|
||||
@validates('repeat')
|
||||
def validate_repeat(self, key, value):
|
||||
@@ -1477,7 +1496,8 @@ class Procedure(BaseClass):
|
||||
|
||||
@classmethod
|
||||
@setup_lookup
|
||||
def query(cls, id: int | None = None, name: str | None = None, start_date: date | datetime | str | int | None = None,
|
||||
def query(cls, id: int | None = None, name: str | None = None,
|
||||
start_date: date | datetime | str | int | None = None,
|
||||
end_date: date | datetime | str | int | None = None, limit: int = 0, **kwargs) -> Procedure | List[
|
||||
Procedure]:
|
||||
query: Query = cls.__database_session__.query(cls)
|
||||
@@ -1610,10 +1630,7 @@ class Procedure(BaseClass):
|
||||
output['tips'] = [tips.details_dict() for tips in output['proceduretipsassociation']]
|
||||
output['repeat'] = bool(output['repeat'])
|
||||
output['run'] = self.run.name
|
||||
output['excluded'] += ['id', "results", "proceduresampleassociation", "sample",
|
||||
"procedurereagentlotassociation",
|
||||
"procedureequipmentassociation", "proceduretipsassociation", "reagent", "equipment",
|
||||
"tips", "control", "kittype"]
|
||||
output['excluded'] += self.get_default_info("details_ignore")
|
||||
output['sample_count'] = len(active_samples)
|
||||
output['clientlab'] = self.run.clientsubmission.clientlab.name
|
||||
output['cost'] = 0.00
|
||||
@@ -1670,11 +1687,17 @@ class Procedure(BaseClass):
|
||||
def get_default_info(cls, *args) -> dict | list | str:
|
||||
dicto = super().get_default_info()
|
||||
recover = ['filepath', 'sample', 'csv', 'comment', 'equipment']
|
||||
# ['id', "results", "proceduresampleassociation", "sample",
|
||||
# "procedurereagentlotassociation",
|
||||
# "procedureequipmentassociation", "proceduretipsassociation", "reagent", "equipment",
|
||||
# "tips", "control", "kittype"]
|
||||
dicto.update(dict(
|
||||
details_ignore=['excluded', 'reagents', 'sample',
|
||||
'extraction_info', 'comment', 'barcode',
|
||||
'platemap', 'export_map', 'equipment', 'tips', 'custom', 'reagentlot',
|
||||
'procedurereagentassociation'],
|
||||
details_ignore=['excluded', 'reagents', 'sample', 'extraction_info', 'comment', 'barcode',
|
||||
'platemap', 'export_map', 'equipment', 'tips', 'custom', 'reagentlot', 'reagent_lot',
|
||||
"results", "proceduresampleassociation", "sample",
|
||||
"procedurereagentlotassociation",
|
||||
"procedureequipmentassociation", "proceduretipsassociation", "reagent", "equipment",
|
||||
"tips", "control"],
|
||||
# NOTE: Fields not placed in ui form
|
||||
form_ignore=['reagents', 'ctx', 'id', 'cost', 'extraction_info', 'signed_by', 'comment', 'namer',
|
||||
'submission_object', "tips", 'contact_phone', 'custom', 'cost_centre', 'completed_date',
|
||||
@@ -1683,11 +1706,15 @@ class Procedure(BaseClass):
|
||||
form_recover=recover
|
||||
))
|
||||
if args:
|
||||
output = {k: v for k, v in dicto.items() if k in args}
|
||||
if len(args) > 1:
|
||||
output = {k: v for k, v in dicto.items() if k in args}
|
||||
else:
|
||||
output = dicto[args[0]]
|
||||
else:
|
||||
output = {k: v for k, v in dicto.items()}
|
||||
return output
|
||||
|
||||
|
||||
# class ProcedureTypeKitTypeAssociation(BaseClass):
|
||||
# """
|
||||
# Abstract of relationship between kits and their procedure type.
|
||||
@@ -1824,9 +1851,9 @@ class ProcedureTypeReagentRoleAssociation(BaseClass):
|
||||
DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
||||
"""
|
||||
|
||||
omni_removes = BaseClass.omni_removes + ["submission_type_id", "kits_id", "reagent_roles_id", "last_used"]
|
||||
omni_sort = ["proceduretype", "kittype", "reagentrole", "required", "uses"]
|
||||
omni_inheritable = ["proceduretype", "kittype"]
|
||||
# omni_removes = BaseClass.omni_removes + ["submission_type_id", "kits_id", "reagent_roles_id", "last_used"]
|
||||
# omni_sort = ["proceduretype", "kittype", "reagentrole", "required", "uses"]
|
||||
# omni_inheritable = ["proceduretype", "kittype"]
|
||||
|
||||
reagentrole_id = Column(INTEGER, ForeignKey("_reagentrole.id"),
|
||||
primary_key=True) #: id of associated reagent type
|
||||
@@ -2076,7 +2103,7 @@ class ProcedureReagentLotAssociation(BaseClass):
|
||||
str: Representation of this RunReagentAssociation
|
||||
"""
|
||||
try:
|
||||
return f"<ProcedureReagentAssociation({self.procedure.procedure.rsl_plate_number} & {self.reagent.lot})>"
|
||||
return f"<ProcedureReagentLotAssociation({self.procedure.name} & {self.reagent.lot})>"
|
||||
except AttributeError:
|
||||
try:
|
||||
logger.error(f"Reagent {self.reagent.lot} procedure association {self.reagent_id} has no procedure!")
|
||||
@@ -2584,189 +2611,12 @@ class EquipmentRole(BaseClass):
|
||||
return output
|
||||
|
||||
|
||||
class ProcedureEquipmentAssociation(BaseClass):
|
||||
"""
|
||||
Abstract association between BasicRun and Equipment
|
||||
"""
|
||||
|
||||
equipment_id = Column(INTEGER, ForeignKey("_equipment.id"), primary_key=True) #: id of associated equipment
|
||||
procedure_id = Column(INTEGER, ForeignKey("_procedure.id"), primary_key=True) #: id of associated procedure
|
||||
equipmentrole = Column(String(64), primary_key=True) #: name of the reagentrole the equipment fills
|
||||
processversion_id = Column(INTEGER, ForeignKey("_processversion.id", ondelete="SET NULL",
|
||||
name="SEA_Process_id")) #: Foreign key of process id
|
||||
start_time = Column(TIMESTAMP) #: start time of equipment use
|
||||
end_time = Column(TIMESTAMP) #: end time of equipment use
|
||||
comments = Column(String(1024)) #: comments about equipment
|
||||
|
||||
procedure = relationship(Procedure,
|
||||
back_populates="procedureequipmentassociation") #: associated procedure
|
||||
|
||||
equipment = relationship(Equipment, back_populates="equipmentprocedureassociation") #: associated equipment
|
||||
|
||||
tips_id = Column(INTEGER, ForeignKey("_tips.id", ondelete="SET NULL",
|
||||
name="SEA_Process_id"))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
try:
|
||||
return f"<ProcedureEquipmentAssociation({self.name})>"
|
||||
except AttributeError:
|
||||
return "<ProcedureEquipmentAssociation(Unknown)>"
|
||||
|
||||
def __init__(self, procedure=None, equipment=None, procedure_id: int | None = None, equipment_id: int | None = None,
|
||||
equipmentrole: str = "None"):
|
||||
if not procedure:
|
||||
if procedure_id:
|
||||
procedure = Procedure.query(id=procedure_id)
|
||||
else:
|
||||
logger.error("Creation error")
|
||||
self.procedure = procedure
|
||||
if not equipment:
|
||||
if equipment_id:
|
||||
equipment = Equipment.query(id=equipment_id)
|
||||
else:
|
||||
logger.error("Creation error")
|
||||
self.equipment = equipment
|
||||
if isinstance(equipmentrole, list):
|
||||
equipmentrole = equipmentrole[0]
|
||||
if isinstance(equipmentrole, EquipmentRole):
|
||||
equipmentrole = equipmentrole.name
|
||||
self.equipmentrole = equipmentrole
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return f"{self.procedure.name} & {self.equipment.name}"
|
||||
|
||||
@property
|
||||
def process(self):
|
||||
return ProcessVersion.query(id=self.processversion_id)
|
||||
|
||||
@property
|
||||
def tips(self):
|
||||
try:
|
||||
return Tips.query(id=self.tips_id, limit=1)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def to_sub_dict(self) -> dict:
|
||||
"""
|
||||
This RunEquipmentAssociation as a dictionary
|
||||
|
||||
Returns:
|
||||
dict: This RunEquipmentAssociation as a dictionary
|
||||
"""
|
||||
try:
|
||||
process = self.process.name
|
||||
except AttributeError:
|
||||
process = "No process found"
|
||||
output = dict(name=self.equipment.name, asset_number=self.equipment.asset_number, comment=self.comments,
|
||||
processes=[process], role=self.equipmentrole, nickname=self.equipment.nickname)
|
||||
return output
|
||||
|
||||
def to_pydantic(self) -> "PydEquipment":
|
||||
"""
|
||||
Returns a pydantic model based on this object.
|
||||
|
||||
Returns:
|
||||
PydEquipment: pydantic equipment model
|
||||
"""
|
||||
from backend.validators import PydEquipment
|
||||
return PydEquipment(**self.details_dict())
|
||||
|
||||
@classmethod
|
||||
@setup_lookup
|
||||
def query(cls,
|
||||
equipment: int | Equipment | None = None,
|
||||
procedure: int | Procedure | None = None,
|
||||
equipmentrole: str | None = None,
|
||||
limit: int = 0, **kwargs) \
|
||||
-> Any | List[Any]:
|
||||
query: Query = cls.__database_session__.query(cls)
|
||||
match equipment:
|
||||
case int():
|
||||
query = query.filter(cls.equipment_id == equipment)
|
||||
case Equipment():
|
||||
query = query.filter(cls.equipment == equipment)
|
||||
case _:
|
||||
pass
|
||||
match procedure:
|
||||
case int():
|
||||
query = query.filter(cls.procedure_id == procedure)
|
||||
case Procedure():
|
||||
query = query.filter(cls.procedure == procedure)
|
||||
case _:
|
||||
pass
|
||||
if equipmentrole is not None:
|
||||
query = query.filter(cls.equipmentrole == equipmentrole)
|
||||
return cls.execute_query(query=query, limit=limit, **kwargs)
|
||||
|
||||
def details_dict(self, **kwargs):
|
||||
output = super().details_dict()
|
||||
# NOTE: Figure out how to merge the misc_info if doing .update instead.
|
||||
relevant = {k: v for k, v in output.items() if k not in ['equipment']}
|
||||
output = output['equipment'].details_dict()
|
||||
misc = output['misc_info']
|
||||
output.update(relevant)
|
||||
output['misc_info'] = misc
|
||||
output['equipment_role'] = self.equipmentrole
|
||||
output['process'] = self.process.details_dict()
|
||||
try:
|
||||
output['tips'] = self.tips.details_dict()
|
||||
except AttributeError:
|
||||
output['tips'] = None
|
||||
return output
|
||||
|
||||
|
||||
class ProcedureTypeEquipmentRoleAssociation(BaseClass):
|
||||
"""
|
||||
Abstract association between SubmissionType and EquipmentRole
|
||||
"""
|
||||
equipmentrole_id = Column(INTEGER, ForeignKey("_equipmentrole.id"), primary_key=True) #: id of associated equipment
|
||||
proceduretype_id = Column(INTEGER, ForeignKey("_proceduretype.id"),
|
||||
primary_key=True) #: id of associated procedure
|
||||
# kittype_id = Column(INTEGER, ForeignKey("_kittype.id"),
|
||||
# primary_key=True)
|
||||
uses = Column(JSON) #: locations of equipment on the procedure 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?
|
||||
|
||||
proceduretype = relationship(ProcedureType,
|
||||
back_populates="proceduretypeequipmentroleassociation") #: associated procedure
|
||||
|
||||
equipmentrole = relationship(EquipmentRole,
|
||||
back_populates="equipmentroleproceduretypeassociation") #: associated equipment
|
||||
|
||||
# kittype = relationship(KitType, back_populates="proceduretypeequipmentroleassociation")
|
||||
|
||||
@validates('static')
|
||||
def validate_static(self, key, value):
|
||||
"""
|
||||
Ensures only 1 & 0 used in 'static'
|
||||
|
||||
Args:
|
||||
key (str): name of attribute
|
||||
value (_type_): value of attribute
|
||||
|
||||
Raises:
|
||||
ValueError: Raised if bad value given
|
||||
|
||||
Returns:
|
||||
_type_: value
|
||||
"""
|
||||
if not 0 <= value < 2:
|
||||
raise ValueError(f'Invalid required value {value}. Must be 0 or 1.')
|
||||
return value
|
||||
|
||||
@check_authorization
|
||||
def save(self):
|
||||
super().save()
|
||||
|
||||
|
||||
class Process(BaseClass):
|
||||
"""
|
||||
A Process is a method used by a piece of equipment.
|
||||
"""
|
||||
|
||||
level = 2
|
||||
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: Process id, primary key
|
||||
name = Column(String(64), unique=True) #: Process name
|
||||
@@ -2885,20 +2735,20 @@ class Process(BaseClass):
|
||||
def details_dict(self, **kwargs):
|
||||
output = super().details_dict(**kwargs)
|
||||
output['processversion'] = [item.details_dict() for item in self.processversion]
|
||||
logger.debug(f"Process output dict: {pformat(output)}")
|
||||
# logger.debug(f"Process output dict: {pformat(output)}")
|
||||
return output
|
||||
|
||||
def to_pydantic(self):
|
||||
from backend.validators.pydant import PydProcess
|
||||
output = {}
|
||||
for k, v in self.details_dict().items():
|
||||
if isinstance(v, list):
|
||||
output[k] = [item.name if issubclass(item.__class__, BaseClass) else item for item in v]
|
||||
elif issubclass(v.__class__, BaseClass):
|
||||
output[k] = v.name
|
||||
else:
|
||||
output[k] = v
|
||||
return PydProcess(**output)
|
||||
# def to_pydantic(self):
|
||||
# from backend.validators.pydant import PydProcess
|
||||
# output = {}
|
||||
# for k, v in self.details_dict().items():
|
||||
# if isinstance(v, list):
|
||||
# output[k] = [item.name if issubclass(item.__class__, BaseClass) else item for item in v]
|
||||
# elif issubclass(v.__class__, BaseClass):
|
||||
# output[k] = v.name
|
||||
# else:
|
||||
# output[k] = v
|
||||
# return PydProcess(**output)
|
||||
|
||||
|
||||
class ProcessVersion(BaseClass):
|
||||
@@ -2923,26 +2773,51 @@ class ProcessVersion(BaseClass):
|
||||
output['project'] = ""
|
||||
return output
|
||||
|
||||
def set_attribute(self, key, value):
|
||||
setattr(self, key, value)
|
||||
|
||||
class TipRole(BaseClass):
|
||||
@classmethod
|
||||
def query(cls,
|
||||
version: str | None = None,
|
||||
name: str | None = None,
|
||||
limit: int = 0,
|
||||
**kwargs) -> ReagentLot | List[ReagentLot]:
|
||||
query: Query = cls.__database_session__.query(cls)
|
||||
match name:
|
||||
case str():
|
||||
query = query.join(Process).filter(Process.name == name)
|
||||
case _:
|
||||
pass
|
||||
match version:
|
||||
case str():
|
||||
query = query.filter(cls.version == version)
|
||||
case _:
|
||||
pass
|
||||
return cls.execute_query(query=query, limit=limit)
|
||||
|
||||
|
||||
class Tips(BaseClass):
|
||||
"""
|
||||
An abstract reagentrole that a tip fills during a process
|
||||
"""
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
name = Column(String(64)) #: name of reagent type
|
||||
tips = relationship("Tips", back_populates="tiprole",
|
||||
tipslot = relationship("TipsLot", back_populates="tips",
|
||||
secondary=tiprole_tips) #: concrete control of this reagent type
|
||||
process = relationship("Process", back_populates="tiprole", secondary=process_tiprole)
|
||||
manufacturer = Column(String(64))
|
||||
capacity = Column(INTEGER)
|
||||
ref = Column(String(64)) #: tip reference number
|
||||
process = relationship("Process", back_populates="tips", secondary=process_tiprole)
|
||||
|
||||
tiproleproceduretypeassociation = relationship(
|
||||
"ProcedureTypeTipRoleAssociation",
|
||||
back_populates="tiprole",
|
||||
cascade="all, delete-orphan"
|
||||
) #: associated procedure
|
||||
|
||||
proceduretype = association_proxy("tiproleproceduretypeassociation", "proceduretype",
|
||||
creator=lambda proceduretype: ProcedureTypeTipRoleAssociation(
|
||||
proceduretype=proceduretype))
|
||||
# tiproleproceduretypeassociation = relationship(
|
||||
# "ProcedureTypeTipRoleAssociation",
|
||||
# back_populates="tiprole",
|
||||
# cascade="all, delete-orphan"
|
||||
# ) #: associated procedure
|
||||
#
|
||||
# proceduretype = association_proxy("tiproleproceduretypeassociation", "proceduretype",
|
||||
# creator=lambda proceduretype: ProcedureTypeTipRoleAssociation(
|
||||
# proceduretype=proceduretype))
|
||||
|
||||
# @classmethod
|
||||
# def query_or_create(cls, **kwargs) -> Tuple[TipRole, bool]:
|
||||
@@ -2990,35 +2865,37 @@ class TipRole(BaseClass):
|
||||
)
|
||||
|
||||
|
||||
class Tips(BaseClass, LogMixin):
|
||||
class TipsLot(BaseClass, LogMixin):
|
||||
"""
|
||||
A concrete instance of tips.
|
||||
"""
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
tiprole = relationship("TipRole", back_populates="tips",
|
||||
tips = relationship("Tips", back_populates="tipslot",
|
||||
secondary=tiprole_tips) #: joined parent reagent type
|
||||
tiprole_id = Column(INTEGER, ForeignKey("_tiprole.id", ondelete='SET NULL',
|
||||
name="fk_tip_role_id")) #: id of parent reagent type
|
||||
manufacturer = Column(String(64))
|
||||
capacity = Column(INTEGER)
|
||||
ref = Column(String(64)) #: tip reference number
|
||||
# lot = Column(String(64)) #: lot number of tips
|
||||
equipment = relationship("Equipment", back_populates="tips",
|
||||
secondary=equipment_tips) #: associated procedure
|
||||
tipsprocedureassociation = relationship(
|
||||
"ProcedureTipsAssociation",
|
||||
back_populates="tips",
|
||||
cascade="all, delete-orphan"
|
||||
) #: associated procedure
|
||||
tips_id = Column(INTEGER, ForeignKey("_tips.id", ondelete='SET NULL',
|
||||
name="fk_tips_id")) #: id of parent reagent type
|
||||
lot = Column(String(64), unique=True)
|
||||
expiry = Column(TIMESTAMP)
|
||||
|
||||
procedure = association_proxy("tipsprocedureassociation", 'procedure')
|
||||
# lot = Column(String(64)) #: lot number of tips
|
||||
# equipment = relationship("Equipment", back_populates="tips",
|
||||
# secondary=equipment_tips) #: associated procedure
|
||||
# tipsprocedureassociation = relationship(
|
||||
# "ProcedureTipsAssociation",
|
||||
# back_populates="tips",
|
||||
# cascade="all, delete-orphan"
|
||||
# ) #: associated procedure
|
||||
#
|
||||
# procedure = association_proxy("tipsprocedureassociation", 'procedure')
|
||||
|
||||
procedureequipmentassociation =
|
||||
|
||||
@property
|
||||
def size(self) -> str:
|
||||
return f"{self.capacity}ul"
|
||||
|
||||
@property
|
||||
def name (self) -> str:
|
||||
def name(self) -> str:
|
||||
return f"{self.manufacturer}-{self.size}-{self.ref}"
|
||||
|
||||
# @classmethod
|
||||
@@ -3121,6 +2998,187 @@ class Tips(BaseClass, LogMixin):
|
||||
return output
|
||||
|
||||
|
||||
class ProcedureEquipmentAssociation(BaseClass):
|
||||
"""
|
||||
Abstract association between BasicRun and Equipment
|
||||
"""
|
||||
|
||||
equipment_id = Column(INTEGER, ForeignKey("_equipment.id"), primary_key=True) #: id of associated equipment
|
||||
procedure_id = Column(INTEGER, ForeignKey("_procedure.id"), primary_key=True) #: id of associated procedure
|
||||
equipmentrole = Column(String(64), primary_key=True) #: name of the reagentrole the equipment fills
|
||||
processversion_id = Column(INTEGER, ForeignKey("_processversion.id", ondelete="SET NULL",
|
||||
name="SEA_Process_id")) #: Foreign key of process id
|
||||
start_time = Column(TIMESTAMP) #: start time of equipment use
|
||||
end_time = Column(TIMESTAMP) #: end time of equipment use
|
||||
comments = Column(String(1024)) #: comments about equipment
|
||||
|
||||
procedure = relationship(Procedure,
|
||||
back_populates="procedureequipmentassociation") #: associated procedure
|
||||
|
||||
equipment = relationship(Equipment, back_populates="equipmentprocedureassociation") #: associated equipment
|
||||
|
||||
processversion = relationship(ProcessVersion)
|
||||
|
||||
tips_id = Column(INTEGER, ForeignKey("_tips.id", ondelete="SET NULL",
|
||||
name="SEA_Process_id"))
|
||||
|
||||
tips = relationship(Tips)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
try:
|
||||
return f"<ProcedureEquipmentAssociation({self.name})>"
|
||||
except AttributeError:
|
||||
return "<ProcedureEquipmentAssociation(Unknown)>"
|
||||
|
||||
def __init__(self, procedure=None, equipment=None, procedure_id: int | None = None, equipment_id: int | None = None,
|
||||
equipmentrole: str = "None"):
|
||||
if not procedure:
|
||||
if procedure_id:
|
||||
procedure = Procedure.query(id=procedure_id)
|
||||
else:
|
||||
logger.error("Creation error")
|
||||
self.procedure = procedure
|
||||
if not equipment:
|
||||
if equipment_id:
|
||||
equipment = Equipment.query(id=equipment_id)
|
||||
else:
|
||||
logger.error("Creation error")
|
||||
self.equipment = equipment
|
||||
if isinstance(equipmentrole, list):
|
||||
equipmentrole = equipmentrole[0]
|
||||
if isinstance(equipmentrole, EquipmentRole):
|
||||
equipmentrole = equipmentrole.name
|
||||
self.equipmentrole = equipmentrole
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return f"{self.procedure.name} & {self.equipment.name}"
|
||||
|
||||
@property
|
||||
def process(self):
|
||||
return ProcessVersion.query(id=self.processversion_id)
|
||||
|
||||
@property
|
||||
def tips(self):
|
||||
try:
|
||||
return Tips.query(id=self.tips_id, limit=1)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def to_sub_dict(self) -> dict:
|
||||
"""
|
||||
This RunEquipmentAssociation as a dictionary
|
||||
|
||||
Returns:
|
||||
dict: This RunEquipmentAssociation as a dictionary
|
||||
"""
|
||||
try:
|
||||
process = self.process.name
|
||||
except AttributeError:
|
||||
process = "No process found"
|
||||
output = dict(name=self.equipment.name, asset_number=self.equipment.asset_number, comment=self.comments,
|
||||
processes=[process], role=self.equipmentrole, nickname=self.equipment.nickname)
|
||||
return output
|
||||
|
||||
def to_pydantic(self) -> "PydEquipment":
|
||||
"""
|
||||
Returns a pydantic model based on this object.
|
||||
|
||||
Returns:
|
||||
PydEquipment: pydantic equipment model
|
||||
"""
|
||||
from backend.validators import PydEquipment
|
||||
return PydEquipment(**self.details_dict())
|
||||
|
||||
@classmethod
|
||||
@setup_lookup
|
||||
def query(cls,
|
||||
equipment: int | Equipment | None = None,
|
||||
procedure: int | Procedure | None = None,
|
||||
equipmentrole: str | None = None,
|
||||
limit: int = 0, **kwargs) \
|
||||
-> Any | List[Any]:
|
||||
query: Query = cls.__database_session__.query(cls)
|
||||
match equipment:
|
||||
case int():
|
||||
query = query.filter(cls.equipment_id == equipment)
|
||||
case Equipment():
|
||||
query = query.filter(cls.equipment == equipment)
|
||||
case _:
|
||||
pass
|
||||
match procedure:
|
||||
case int():
|
||||
query = query.filter(cls.procedure_id == procedure)
|
||||
case Procedure():
|
||||
query = query.filter(cls.procedure == procedure)
|
||||
case _:
|
||||
pass
|
||||
if equipmentrole is not None:
|
||||
query = query.filter(cls.equipmentrole == equipmentrole)
|
||||
return cls.execute_query(query=query, limit=limit, **kwargs)
|
||||
|
||||
def details_dict(self, **kwargs):
|
||||
output = super().details_dict()
|
||||
# NOTE: Figure out how to merge the misc_info if doing .update instead.
|
||||
relevant = {k: v for k, v in output.items() if k not in ['equipment']}
|
||||
output = output['equipment'].details_dict()
|
||||
misc = output['misc_info']
|
||||
output.update(relevant)
|
||||
output['misc_info'] = misc
|
||||
output['equipment_role'] = self.equipmentrole
|
||||
output['processversion'] = self.processversion.details_dict()
|
||||
try:
|
||||
output['tips'] = self.tips.details_dict()
|
||||
except AttributeError:
|
||||
output['tips'] = None
|
||||
return output
|
||||
|
||||
|
||||
class ProcedureTypeEquipmentRoleAssociation(BaseClass):
|
||||
"""
|
||||
Abstract association between SubmissionType and EquipmentRole
|
||||
"""
|
||||
equipmentrole_id = Column(INTEGER, ForeignKey("_equipmentrole.id"), primary_key=True) #: id of associated equipment
|
||||
proceduretype_id = Column(INTEGER, ForeignKey("_proceduretype.id"),
|
||||
primary_key=True) #: id of associated procedure
|
||||
# kittype_id = Column(INTEGER, ForeignKey("_kittype.id"),
|
||||
# primary_key=True)
|
||||
uses = Column(JSON) #: locations of equipment on the procedure 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?
|
||||
|
||||
proceduretype = relationship(ProcedureType,
|
||||
back_populates="proceduretypeequipmentroleassociation") #: associated procedure
|
||||
|
||||
equipmentrole = relationship(EquipmentRole,
|
||||
back_populates="equipmentroleproceduretypeassociation") #: associated equipment
|
||||
|
||||
|
||||
|
||||
@validates('static')
|
||||
def validate_static(self, key, value):
|
||||
"""
|
||||
Ensures only 1 & 0 used in 'static'
|
||||
|
||||
Args:
|
||||
key (str): name of attribute
|
||||
value (_type_): value of attribute
|
||||
|
||||
Raises:
|
||||
ValueError: Raised if bad value given
|
||||
|
||||
Returns:
|
||||
_type_: value
|
||||
"""
|
||||
if not 0 <= value < 2:
|
||||
raise ValueError(f'Invalid required value {value}. Must be 0 or 1.')
|
||||
return value
|
||||
|
||||
@check_authorization
|
||||
def save(self):
|
||||
super().save()
|
||||
|
||||
|
||||
class ProcedureTypeTipRoleAssociation(BaseClass):
|
||||
"""
|
||||
Abstract association between SubmissionType and TipRole
|
||||
@@ -3144,71 +3202,71 @@ class ProcedureTypeTipRoleAssociation(BaseClass):
|
||||
pass
|
||||
|
||||
|
||||
class ProcedureTipsAssociation(BaseClass):
|
||||
"""
|
||||
Association between a concrete procedure instance and concrete tips
|
||||
"""
|
||||
tips_id = Column(INTEGER, ForeignKey("_tips.id"), primary_key=True) #: id of associated equipment
|
||||
procedure_id = Column(INTEGER, ForeignKey("_procedure.id"), primary_key=True) #: id of associated procedure
|
||||
procedure = relationship("Procedure",
|
||||
back_populates="proceduretipsassociation") #: associated procedure
|
||||
tips = relationship(Tips,
|
||||
back_populates="tipsprocedureassociation") #: associated equipment
|
||||
tiprole = Column(String(32), primary_key=True) #, ForeignKey("_tiprole.name"))
|
||||
|
||||
def to_sub_dict(self) -> dict:
|
||||
"""
|
||||
This item as a dictionary
|
||||
|
||||
Returns:
|
||||
dict: Values of this object
|
||||
"""
|
||||
return dict(role=self.role_name, name=self.tips.name, lot=self.tips.lot)
|
||||
|
||||
@classmethod
|
||||
@setup_lookup
|
||||
def query(cls, tips: int | Tips, tiprole: str, procedure: int | Procedure | None = None, limit: int = 0, **kwargs) \
|
||||
-> Any | List[Any]:
|
||||
query: Query = cls.__database_session__.query(cls)
|
||||
match tips:
|
||||
case int():
|
||||
query = query.filter(cls.tips_id == tips)
|
||||
case Tips():
|
||||
query = query.filter(cls.tips == tips)
|
||||
case _:
|
||||
pass
|
||||
match procedure:
|
||||
case int():
|
||||
query = query.filter(cls.procedure_id == procedure)
|
||||
case Procedure():
|
||||
query = query.filter(cls.procedure == procedure)
|
||||
case _:
|
||||
pass
|
||||
query = query.filter(cls.tiprole == tiprole)
|
||||
return cls.execute_query(query=query, limit=limit, **kwargs)
|
||||
|
||||
# TODO: fold this into the BaseClass.query_or_create ?
|
||||
# @classmethod
|
||||
# def query_or_create(cls, tips, procedure, role: str, **kwargs):
|
||||
# kwargs['limit'] = 1
|
||||
# instance = cls.query(tips_id=tips.id, role_name=role, procedure_id=procedure.id, **kwargs)
|
||||
# if instance is None:
|
||||
# instance = cls(procedure=procedure, tips=tips, role_name=role)
|
||||
# return instance
|
||||
|
||||
def to_pydantic(self):
|
||||
from backend.validators import PydTips
|
||||
return PydTips(name=self.tips.name, lot=self.tips.lot, role=self.role_name)
|
||||
|
||||
def details_dict(self, **kwargs):
|
||||
output = super().details_dict()
|
||||
# NOTE: Figure out how to merge the misc_info if doing .update instead.
|
||||
relevant = {k: v for k, v in output.items() if k not in ['tips']}
|
||||
output = output['tips'].details_dict()
|
||||
misc = output['misc_info']
|
||||
output.update(relevant)
|
||||
output['misc_info'] = misc
|
||||
return output
|
||||
# class ProcedureTipsAssociation(BaseClass):
|
||||
# """
|
||||
# Association between a concrete procedure instance and concrete tips
|
||||
# """
|
||||
# tips_id = Column(INTEGER, ForeignKey("_tips.id"), primary_key=True) #: id of associated equipment
|
||||
# procedure_id = Column(INTEGER, ForeignKey("_procedure.id"), primary_key=True) #: id of associated procedure
|
||||
# procedure = relationship("Procedure",
|
||||
# back_populates="proceduretipsassociation") #: associated procedure
|
||||
# tips = relationship(Tips,
|
||||
# back_populates="tipsprocedureassociation") #: associated equipment
|
||||
# tiprole = Column(String(32), primary_key=True) #, ForeignKey("_tiprole.name"))
|
||||
#
|
||||
# def to_sub_dict(self) -> dict:
|
||||
# """
|
||||
# This item as a dictionary
|
||||
#
|
||||
# Returns:
|
||||
# dict: Values of this object
|
||||
# """
|
||||
# return dict(role=self.role_name, name=self.tips.name, lot=self.tips.lot)
|
||||
#
|
||||
# @classmethod
|
||||
# @setup_lookup
|
||||
# def query(cls, tips: int | Tips, tiprole: str, procedure: int | Procedure | None = None, limit: int = 0, **kwargs) \
|
||||
# -> Any | List[Any]:
|
||||
# query: Query = cls.__database_session__.query(cls)
|
||||
# match tips:
|
||||
# case int():
|
||||
# query = query.filter(cls.tips_id == tips)
|
||||
# case Tips():
|
||||
# query = query.filter(cls.tips == tips)
|
||||
# case _:
|
||||
# pass
|
||||
# match procedure:
|
||||
# case int():
|
||||
# query = query.filter(cls.procedure_id == procedure)
|
||||
# case Procedure():
|
||||
# query = query.filter(cls.procedure == procedure)
|
||||
# case _:
|
||||
# pass
|
||||
# query = query.filter(cls.tiprole == tiprole)
|
||||
# return cls.execute_query(query=query, limit=limit, **kwargs)
|
||||
#
|
||||
# # TODO: fold this into the BaseClass.query_or_create ?
|
||||
# # @classmethod
|
||||
# # def query_or_create(cls, tips, procedure, role: str, **kwargs):
|
||||
# # kwargs['limit'] = 1
|
||||
# # instance = cls.query(tips_id=tips.id, role_name=role, procedure_id=procedure.id, **kwargs)
|
||||
# # if instance is None:
|
||||
# # instance = cls(procedure=procedure, tips=tips, role_name=role)
|
||||
# # return instance
|
||||
#
|
||||
# def to_pydantic(self):
|
||||
# from backend.validators import PydTips
|
||||
# return PydTips(name=self.tips.name, lot=self.tips.lot, role=self.role_name)
|
||||
#
|
||||
# def details_dict(self, **kwargs):
|
||||
# output = super().details_dict()
|
||||
# # NOTE: Figure out how to merge the misc_info if doing .update instead.
|
||||
# relevant = {k: v for k, v in output.items() if k not in ['tips']}
|
||||
# output = output['tips'].details_dict()
|
||||
# misc = output['misc_info']
|
||||
# output.update(relevant)
|
||||
# output['misc_info'] = misc
|
||||
# return output
|
||||
|
||||
|
||||
class Results(BaseClass):
|
||||
|
||||
@@ -1248,6 +1248,7 @@ class Run(BaseClass, LogMixin):
|
||||
dlg = ProcedureCreation(parent=obj, procedure=procedure_type.construct_dummy_procedure(run=self))
|
||||
if dlg.exec():
|
||||
sql, _ = dlg.return_sql(new=True)
|
||||
# sys.exit(pformat(sql.__dict__))
|
||||
sql.save()
|
||||
obj.set_data()
|
||||
|
||||
@@ -1460,11 +1461,11 @@ class Run(BaseClass, LogMixin):
|
||||
return list(sorted(padded_list, key=itemgetter('submission_rank')))
|
||||
|
||||
|
||||
class SampleType(BaseClass):
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
name = Column(String(64), nullable=False, unique=True) #: identification from submitter
|
||||
|
||||
sample = relationship("Sample", back_populates="sampletype", uselist=True)
|
||||
# class SampleType(BaseClass):
|
||||
# id = Column(INTEGER, primary_key=True) #: primary key
|
||||
# name = Column(String(64), nullable=False, unique=True) #: identification from submitter
|
||||
#
|
||||
# sample = relationship("Sample", back_populates="sampletype", uselist=True)
|
||||
|
||||
|
||||
# NOTE: Sample Classes
|
||||
@@ -1476,9 +1477,9 @@ class Sample(BaseClass, LogMixin):
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
sample_id = Column(String(64), nullable=False, unique=True) #: identification from submitter
|
||||
sampletype_id = Column(INTEGER, ForeignKey("_sampletype.id", ondelete="SET NULL",
|
||||
name="fk_SAMP_sampletype_id"))
|
||||
sampletype = relationship("SampleType", back_populates="sample")
|
||||
# sampletype_id = Column(INTEGER, ForeignKey("_sampletype.id", ondelete="SET NULL",
|
||||
# name="fk_SAMP_sampletype_id"))
|
||||
# sampletype = relationship("SampleType", back_populates="sample")
|
||||
# misc_info = Column(JSON)
|
||||
control = relationship("Control", back_populates="sample", uselist=False)
|
||||
|
||||
@@ -1512,10 +1513,7 @@ class Sample(BaseClass, LogMixin):
|
||||
return self.sample_id
|
||||
|
||||
def __repr__(self) -> str:
|
||||
try:
|
||||
return f"<{self.sampletype.name.replace('_', ' ').title().replace(' ', '')}({self.sample_id})>"
|
||||
except AttributeError:
|
||||
return f"<Sample({self.sample_id})>"
|
||||
return f"<Sample({self.sample_id})>"
|
||||
|
||||
@classproperty
|
||||
def searchables(cls):
|
||||
@@ -1531,13 +1529,13 @@ class Sample(BaseClass, LogMixin):
|
||||
Returns:
|
||||
dict: submitter id and sample type and linked procedure if full data
|
||||
"""
|
||||
try:
|
||||
sample_type = self.sampletype.name
|
||||
except AttributeError:
|
||||
sample_type = "NA"
|
||||
# try:
|
||||
# sample_type = self.sampletype.name
|
||||
# except AttributeError:
|
||||
# sample_type = "NA"
|
||||
sample = dict(
|
||||
sample_id=self.sample_id,
|
||||
sampletype=sample_type
|
||||
sample_id=self.sample_id
|
||||
# sampletype=sample_type
|
||||
)
|
||||
if full_data:
|
||||
sample['clientsubmission'] = sorted([item.to_sub_dict() for item in self.sampleclientsubmissionassociation],
|
||||
@@ -1565,7 +1563,7 @@ class Sample(BaseClass, LogMixin):
|
||||
@setup_lookup
|
||||
def query(cls,
|
||||
sample_id: str | None = None,
|
||||
sampletype: str | SampleType | None = None,
|
||||
# sampletype: str | SampleType | None = None,
|
||||
limit: int = 0,
|
||||
**kwargs
|
||||
) -> Sample | List[Sample]:
|
||||
@@ -1574,20 +1572,19 @@ class Sample(BaseClass, LogMixin):
|
||||
|
||||
Args:
|
||||
sample_id (str | None, optional): Name of the sample (limits results to 1). Defaults to None.
|
||||
sampletype (str | None, optional): Sample type. Defaults to None.
|
||||
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
||||
|
||||
Returns:
|
||||
models.Sample|List[models.Sample]: Sample(s) of interest.
|
||||
"""
|
||||
query = cls.__database_session__.query(cls)
|
||||
match sampletype:
|
||||
case str():
|
||||
query = query.join(SampleType).filter(SampleType.name == sampletype)
|
||||
case SampleType():
|
||||
query = query.filter(cls.sampletype == sampletype)
|
||||
case _:
|
||||
pass
|
||||
# match sampletype:
|
||||
# case str():
|
||||
# query = query.join(SampleType).filter(SampleType.name == sampletype)
|
||||
# case SampleType():
|
||||
# query = query.filter(cls.sampletype == sampletype)
|
||||
# case _:
|
||||
# pass
|
||||
match sample_id:
|
||||
case str():
|
||||
query = query.filter(cls.sample_id == sample_id)
|
||||
@@ -1596,37 +1593,37 @@ class Sample(BaseClass, LogMixin):
|
||||
pass
|
||||
return cls.execute_query(query=query, limit=limit, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def fuzzy_search(cls,
|
||||
sampletype: str | Sample | None = None,
|
||||
**kwargs
|
||||
) -> List[Sample]:
|
||||
"""
|
||||
Allows for fuzzy search of sample.
|
||||
|
||||
Args:
|
||||
sampletype (str | BasicSample | None, optional): Type of sample. Defaults to None.
|
||||
|
||||
Returns:
|
||||
List[Sample]: List of sample that match kwarg search parameters.
|
||||
"""
|
||||
query: Query = cls.__database_session__.query(cls)
|
||||
match sampletype:
|
||||
case str():
|
||||
query = query.join(SampleType).filter(SampleType.name == sampletype)
|
||||
case SampleType():
|
||||
query = query.filter(cls.sampletype == sampletype)
|
||||
case _:
|
||||
pass
|
||||
for k, v in kwargs.items():
|
||||
search = f"%{v}%"
|
||||
try:
|
||||
attr = getattr(cls, k)
|
||||
# NOTE: the secret sauce is in attr.like
|
||||
query = query.filter(attr.like(search))
|
||||
except (ArgumentError, AttributeError) as e:
|
||||
logger.error(f"Attribute {k} unavailable due to:\n\t{e}\nSkipping.")
|
||||
return query.limit(50).all()
|
||||
# @classmethod
|
||||
# def fuzzy_search(cls,
|
||||
# sampletype: str | Sample | None = None,
|
||||
# **kwargs
|
||||
# ) -> List[Sample]:
|
||||
# """
|
||||
# Allows for fuzzy search of sample.
|
||||
#
|
||||
# Args:
|
||||
# sampletype (str | BasicSample | None, optional): Type of sample. Defaults to None.
|
||||
#
|
||||
# Returns:
|
||||
# List[Sample]: List of sample that match kwarg search parameters.
|
||||
# """
|
||||
# query: Query = cls.__database_session__.query(cls)
|
||||
# match sampletype:
|
||||
# case str():
|
||||
# query = query.join(SampleType).filter(SampleType.name == sampletype)
|
||||
# case SampleType():
|
||||
# query = query.filter(cls.sampletype == sampletype)
|
||||
# case _:
|
||||
# pass
|
||||
# for k, v in kwargs.items():
|
||||
# search = f"%{v}%"
|
||||
# try:
|
||||
# attr = getattr(cls, k)
|
||||
# # NOTE: the secret sauce is in attr.like
|
||||
# query = query.filter(attr.like(search))
|
||||
# except (ArgumentError, AttributeError) as e:
|
||||
# logger.error(f"Attribute {k} unavailable due to:\n\t{e}\nSkipping.")
|
||||
# return query.limit(50).all()
|
||||
|
||||
def delete(self):
|
||||
raise AttributeError(f"Delete not implemented for {self.__class__}")
|
||||
|
||||
Reference in New Issue
Block a user