Moments before disaster
This commit is contained in:
@@ -2,11 +2,7 @@
|
|||||||
Contains all models for sqlalchemy
|
Contains all models for sqlalchemy
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys, logging, json
|
import sys, logging, json
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
import sqlalchemy.exc
|
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -21,7 +17,6 @@ from pathlib import Path
|
|||||||
from sqlalchemy.orm.relationships import _RelationshipDeclared
|
from sqlalchemy.orm.relationships import _RelationshipDeclared
|
||||||
from tools import report_result, list_sort_dict
|
from tools import report_result, list_sort_dict
|
||||||
|
|
||||||
|
|
||||||
# NOTE: Load testing environment
|
# NOTE: Load testing environment
|
||||||
if 'pytest' in sys.modules:
|
if 'pytest' in sys.modules:
|
||||||
sys.path.append(Path(__file__).parents[4].absolute().joinpath("tests").__str__())
|
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
|
__table_args__ = {'extend_existing': True} #: NOTE Will only add new columns
|
||||||
|
|
||||||
singles = ['id']
|
singles = ['id']
|
||||||
omni_removes = ["id", 'run', "omnigui_class_dict", "omnigui_instance_dict"]
|
# omni_removes = ["id", 'run', "omnigui_class_dict", "omnigui_instance_dict"]
|
||||||
omni_sort = ["name"]
|
# omni_sort = ["name"]
|
||||||
omni_inheritable = []
|
# omni_inheritable = []
|
||||||
searchables = []
|
searchables = []
|
||||||
|
|
||||||
_misc_info = Column(JSON)
|
_misc_info = Column(JSON)
|
||||||
@@ -242,12 +237,13 @@ class BaseClass(Base):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def query_or_create(cls, **kwargs) -> Tuple[Any, bool]:
|
def query_or_create(cls, **kwargs) -> Tuple[Any, bool]:
|
||||||
new = False
|
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)]
|
# and not isinstance(v.property, _RelationshipDeclared)]
|
||||||
sanitized_kwargs = {k: v for k, v in kwargs.items() if k in allowed}
|
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}
|
outside_kwargs = {k: v for k, v in kwargs.items() if k not in allowed}
|
||||||
# logger.debug(f"Sanitized kwargs: {sanitized_kwargs}")
|
logger.debug(f"Sanitized kwargs: {sanitized_kwargs}")
|
||||||
instance = cls.query(**sanitized_kwargs)
|
instance = cls.query(limit=1, **sanitized_kwargs)
|
||||||
if not instance or isinstance(instance, list):
|
if not instance or isinstance(instance, list):
|
||||||
instance = cls()
|
instance = cls()
|
||||||
new = True
|
new = True
|
||||||
@@ -280,7 +276,8 @@ class BaseClass(Base):
|
|||||||
return cls.execute_query(**kwargs)
|
return cls.execute_query(**kwargs)
|
||||||
|
|
||||||
@classmethod
|
@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.
|
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")
|
output_date = datetime.combine(output_date, addition_time).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
return output_date
|
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
|
relevant = {k: v for k, v in self.__class__.__dict__.items() if
|
||||||
isinstance(v, InstrumentedAttribute) or isinstance(v, AssociationProxy)}
|
isinstance(v, InstrumentedAttribute) or isinstance(v, AssociationProxy)}
|
||||||
# output = OrderedDict()
|
# 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():
|
for k, v in relevant.items():
|
||||||
try:
|
try:
|
||||||
check = v.foreign_keys
|
check = v.foreign_keys
|
||||||
@@ -666,7 +663,7 @@ class BaseClass(Base):
|
|||||||
output[k] = value
|
output[k] = value
|
||||||
return output
|
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
|
from backend.validators import pydant
|
||||||
if not pyd_model_name:
|
if not pyd_model_name:
|
||||||
pyd_model_name = f"Pyd{self.__class__.__name__}"
|
pyd_model_name = f"Pyd{self.__class__.__name__}"
|
||||||
@@ -685,7 +682,7 @@ class BaseClass(Base):
|
|||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
pass
|
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"):
|
# if not hasattr(self, "template_file"):
|
||||||
# logger.error(f"Export not implemented for {self.__class__.__name__}")
|
# logger.error(f"Export not implemented for {self.__class__.__name__}")
|
||||||
# return
|
# return
|
||||||
|
|||||||
@@ -44,13 +44,13 @@ equipmentrole_equipment = Table(
|
|||||||
extend_existing=True
|
extend_existing=True
|
||||||
)
|
)
|
||||||
|
|
||||||
equipment_process = Table(
|
# equipment_process = Table(
|
||||||
"_equipment_process",
|
# "_equipment_process",
|
||||||
Base.metadata,
|
# Base.metadata,
|
||||||
Column("process_id", INTEGER, ForeignKey("_process.id")),
|
# Column("process_id", INTEGER, ForeignKey("_process.id")),
|
||||||
Column("equipment_id", INTEGER, ForeignKey("_equipment.id")),
|
# Column("equipment_id", INTEGER, ForeignKey("_equipment.id")),
|
||||||
extend_existing=True
|
# extend_existing=True
|
||||||
)
|
# )
|
||||||
|
|
||||||
equipmentrole_process = Table(
|
equipmentrole_process = Table(
|
||||||
"_equipmentrole_process",
|
"_equipmentrole_process",
|
||||||
@@ -68,15 +68,15 @@ equipmentrole_process = Table(
|
|||||||
# extend_existing=True
|
# extend_existing=True
|
||||||
# )
|
# )
|
||||||
|
|
||||||
tiprole_tips = Table(
|
# tiprole_tips = Table(
|
||||||
"_tiprole_tips",
|
# "_tiprole_tips",
|
||||||
Base.metadata,
|
# Base.metadata,
|
||||||
Column("tiprole_id", INTEGER, ForeignKey("_tiprole.id")),
|
# Column("tiprole_id", INTEGER, ForeignKey("_tiprole.id")),
|
||||||
Column("tips_id", INTEGER, ForeignKey("_tips.id")),
|
# Column("tips_id", INTEGER, ForeignKey("_tips.id")),
|
||||||
extend_existing=True
|
# extend_existing=True
|
||||||
)
|
# )
|
||||||
|
|
||||||
process_tiprole = Table(
|
process_tips = Table(
|
||||||
"_process_tiprole",
|
"_process_tiprole",
|
||||||
Base.metadata,
|
Base.metadata,
|
||||||
Column("process_id", INTEGER, ForeignKey("_process.id")),
|
Column("process_id", INTEGER, ForeignKey("_process.id")),
|
||||||
@@ -84,13 +84,13 @@ process_tiprole = Table(
|
|||||||
extend_existing=True
|
extend_existing=True
|
||||||
)
|
)
|
||||||
|
|
||||||
equipment_tips = Table(
|
# equipment_tips = Table(
|
||||||
"_equipment_tips",
|
# "_equipment_tips",
|
||||||
Base.metadata,
|
# Base.metadata,
|
||||||
Column("equipment_id", INTEGER, ForeignKey("_equipment.id")),
|
# Column("equipment_id", INTEGER, ForeignKey("_equipment.id")),
|
||||||
Column("tips_id", INTEGER, ForeignKey("_tips.id")),
|
# Column("tips_id", INTEGER, ForeignKey("_tips.id")),
|
||||||
extend_existing=True
|
# extend_existing=True
|
||||||
)
|
# )
|
||||||
|
|
||||||
# kittype_procedure = Table(
|
# kittype_procedure = Table(
|
||||||
# "_kittype_procedure",
|
# "_kittype_procedure",
|
||||||
@@ -100,13 +100,13 @@ equipment_tips = Table(
|
|||||||
# extend_existing=True
|
# extend_existing=True
|
||||||
# )
|
# )
|
||||||
|
|
||||||
proceduretype_process = Table(
|
# proceduretype_process = Table(
|
||||||
"_proceduretype_process",
|
# "_proceduretype_process",
|
||||||
Base.metadata,
|
# Base.metadata,
|
||||||
Column("process_id", INTEGER, ForeignKey("_process.id")),
|
# Column("process_id", INTEGER, ForeignKey("_process.id")),
|
||||||
Column("proceduretype_id", INTEGER, ForeignKey("_proceduretype.id")),
|
# Column("proceduretype_id", INTEGER, ForeignKey("_proceduretype.id")),
|
||||||
extend_existing=True
|
# extend_existing=True
|
||||||
)
|
# )
|
||||||
|
|
||||||
submissiontype_proceduretype = Table(
|
submissiontype_proceduretype = Table(
|
||||||
"_submissiontype_proceduretype",
|
"_submissiontype_proceduretype",
|
||||||
@@ -823,7 +823,7 @@ class Reagent(BaseClass, LogMixin):
|
|||||||
|
|
||||||
class ReagentLot(BaseClass):
|
class ReagentLot(BaseClass):
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
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
|
expiry = Column(TIMESTAMP) #: expiry date - extended by eol_ext of parent programmatically
|
||||||
reagent = relationship("Reagent") #: joined parent reagent type
|
reagent = relationship("Reagent") #: joined parent reagent type
|
||||||
reagent_id = Column(INTEGER, ForeignKey("_reagent.id", ondelete='SET NULL',
|
reagent_id = Column(INTEGER, ForeignKey("_reagent.id", ondelete='SET NULL',
|
||||||
@@ -843,6 +843,25 @@ class ReagentLot(BaseClass):
|
|||||||
def name(self):
|
def name(self):
|
||||||
return self.lot
|
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):
|
def __repr__(self):
|
||||||
return f"<Lot({self.lot}-{self.expiry}>"
|
return f"<Lot({self.lot}-{self.expiry}>"
|
||||||
|
|
||||||
@@ -1447,7 +1466,7 @@ class Procedure(BaseClass):
|
|||||||
) #: Relation to ProcedureReagentAssociation
|
) #: Relation to ProcedureReagentAssociation
|
||||||
|
|
||||||
reagentlot = association_proxy("procedurereagentlotassociation",
|
reagentlot = association_proxy("procedurereagentlotassociation",
|
||||||
"reagentlot", creator=lambda reg: ProcedureReagentLotAssociation(
|
"reagentlot", creator=lambda reg: ProcedureReagentLotAssociation(
|
||||||
reagent=reg)) #: Association proxy to RunReagentAssociation.reagent
|
reagent=reg)) #: Association proxy to RunReagentAssociation.reagent
|
||||||
|
|
||||||
procedureequipmentassociation = relationship(
|
procedureequipmentassociation = relationship(
|
||||||
@@ -1459,13 +1478,13 @@ class Procedure(BaseClass):
|
|||||||
equipment = association_proxy("procedureequipmentassociation",
|
equipment = association_proxy("procedureequipmentassociation",
|
||||||
"equipment") #: Association proxy to RunEquipmentAssociation.equipment
|
"equipment") #: Association proxy to RunEquipmentAssociation.equipment
|
||||||
|
|
||||||
proceduretipsassociation = relationship(
|
# proceduretipsassociation = relationship(
|
||||||
"ProcedureTipsAssociation",
|
# "ProcedureTipsAssociation",
|
||||||
back_populates="procedure",
|
# back_populates="procedure",
|
||||||
cascade="all, delete-orphan")
|
# cascade="all, delete-orphan")
|
||||||
|
#
|
||||||
tips = association_proxy("proceduretipsassociation",
|
# tips = association_proxy("proceduretipsassociation",
|
||||||
"tips")
|
# "tips")
|
||||||
|
|
||||||
@validates('repeat')
|
@validates('repeat')
|
||||||
def validate_repeat(self, key, value):
|
def validate_repeat(self, key, value):
|
||||||
@@ -1477,7 +1496,8 @@ class Procedure(BaseClass):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@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[
|
end_date: date | datetime | str | int | None = None, limit: int = 0, **kwargs) -> Procedure | List[
|
||||||
Procedure]:
|
Procedure]:
|
||||||
query: Query = cls.__database_session__.query(cls)
|
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['tips'] = [tips.details_dict() for tips in output['proceduretipsassociation']]
|
||||||
output['repeat'] = bool(output['repeat'])
|
output['repeat'] = bool(output['repeat'])
|
||||||
output['run'] = self.run.name
|
output['run'] = self.run.name
|
||||||
output['excluded'] += ['id', "results", "proceduresampleassociation", "sample",
|
output['excluded'] += self.get_default_info("details_ignore")
|
||||||
"procedurereagentlotassociation",
|
|
||||||
"procedureequipmentassociation", "proceduretipsassociation", "reagent", "equipment",
|
|
||||||
"tips", "control", "kittype"]
|
|
||||||
output['sample_count'] = len(active_samples)
|
output['sample_count'] = len(active_samples)
|
||||||
output['clientlab'] = self.run.clientsubmission.clientlab.name
|
output['clientlab'] = self.run.clientsubmission.clientlab.name
|
||||||
output['cost'] = 0.00
|
output['cost'] = 0.00
|
||||||
@@ -1670,11 +1687,17 @@ class Procedure(BaseClass):
|
|||||||
def get_default_info(cls, *args) -> dict | list | str:
|
def get_default_info(cls, *args) -> dict | list | str:
|
||||||
dicto = super().get_default_info()
|
dicto = super().get_default_info()
|
||||||
recover = ['filepath', 'sample', 'csv', 'comment', 'equipment']
|
recover = ['filepath', 'sample', 'csv', 'comment', 'equipment']
|
||||||
|
# ['id', "results", "proceduresampleassociation", "sample",
|
||||||
|
# "procedurereagentlotassociation",
|
||||||
|
# "procedureequipmentassociation", "proceduretipsassociation", "reagent", "equipment",
|
||||||
|
# "tips", "control", "kittype"]
|
||||||
dicto.update(dict(
|
dicto.update(dict(
|
||||||
details_ignore=['excluded', 'reagents', 'sample',
|
details_ignore=['excluded', 'reagents', 'sample', 'extraction_info', 'comment', 'barcode',
|
||||||
'extraction_info', 'comment', 'barcode',
|
'platemap', 'export_map', 'equipment', 'tips', 'custom', 'reagentlot', 'reagent_lot',
|
||||||
'platemap', 'export_map', 'equipment', 'tips', 'custom', 'reagentlot',
|
"results", "proceduresampleassociation", "sample",
|
||||||
'procedurereagentassociation'],
|
"procedurereagentlotassociation",
|
||||||
|
"procedureequipmentassociation", "proceduretipsassociation", "reagent", "equipment",
|
||||||
|
"tips", "control"],
|
||||||
# NOTE: Fields not placed in ui form
|
# NOTE: Fields not placed in ui form
|
||||||
form_ignore=['reagents', 'ctx', 'id', 'cost', 'extraction_info', 'signed_by', 'comment', 'namer',
|
form_ignore=['reagents', 'ctx', 'id', 'cost', 'extraction_info', 'signed_by', 'comment', 'namer',
|
||||||
'submission_object', "tips", 'contact_phone', 'custom', 'cost_centre', 'completed_date',
|
'submission_object', "tips", 'contact_phone', 'custom', 'cost_centre', 'completed_date',
|
||||||
@@ -1683,11 +1706,15 @@ class Procedure(BaseClass):
|
|||||||
form_recover=recover
|
form_recover=recover
|
||||||
))
|
))
|
||||||
if args:
|
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:
|
else:
|
||||||
output = {k: v for k, v in dicto.items()}
|
output = {k: v for k, v in dicto.items()}
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
# class ProcedureTypeKitTypeAssociation(BaseClass):
|
# class ProcedureTypeKitTypeAssociation(BaseClass):
|
||||||
# """
|
# """
|
||||||
# Abstract of relationship between kits and their procedure type.
|
# 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
|
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_removes = BaseClass.omni_removes + ["submission_type_id", "kits_id", "reagent_roles_id", "last_used"]
|
||||||
omni_sort = ["proceduretype", "kittype", "reagentrole", "required", "uses"]
|
# omni_sort = ["proceduretype", "kittype", "reagentrole", "required", "uses"]
|
||||||
omni_inheritable = ["proceduretype", "kittype"]
|
# omni_inheritable = ["proceduretype", "kittype"]
|
||||||
|
|
||||||
reagentrole_id = Column(INTEGER, ForeignKey("_reagentrole.id"),
|
reagentrole_id = Column(INTEGER, ForeignKey("_reagentrole.id"),
|
||||||
primary_key=True) #: id of associated reagent type
|
primary_key=True) #: id of associated reagent type
|
||||||
@@ -2076,7 +2103,7 @@ class ProcedureReagentLotAssociation(BaseClass):
|
|||||||
str: Representation of this RunReagentAssociation
|
str: Representation of this RunReagentAssociation
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return f"<ProcedureReagentAssociation({self.procedure.procedure.rsl_plate_number} & {self.reagent.lot})>"
|
return f"<ProcedureReagentLotAssociation({self.procedure.name} & {self.reagent.lot})>"
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
try:
|
try:
|
||||||
logger.error(f"Reagent {self.reagent.lot} procedure association {self.reagent_id} has no procedure!")
|
logger.error(f"Reagent {self.reagent.lot} procedure association {self.reagent_id} has no procedure!")
|
||||||
@@ -2584,189 +2611,12 @@ class EquipmentRole(BaseClass):
|
|||||||
return output
|
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):
|
class Process(BaseClass):
|
||||||
"""
|
"""
|
||||||
A Process is a method used by a piece of equipment.
|
A Process is a method used by a piece of equipment.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
level = 2
|
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: Process id, primary key
|
id = Column(INTEGER, primary_key=True) #: Process id, primary key
|
||||||
name = Column(String(64), unique=True) #: Process name
|
name = Column(String(64), unique=True) #: Process name
|
||||||
@@ -2885,20 +2735,20 @@ class Process(BaseClass):
|
|||||||
def details_dict(self, **kwargs):
|
def details_dict(self, **kwargs):
|
||||||
output = super().details_dict(**kwargs)
|
output = super().details_dict(**kwargs)
|
||||||
output['processversion'] = [item.details_dict() for item in self.processversion]
|
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
|
return output
|
||||||
|
|
||||||
def to_pydantic(self):
|
# def to_pydantic(self):
|
||||||
from backend.validators.pydant import PydProcess
|
# from backend.validators.pydant import PydProcess
|
||||||
output = {}
|
# output = {}
|
||||||
for k, v in self.details_dict().items():
|
# for k, v in self.details_dict().items():
|
||||||
if isinstance(v, list):
|
# if isinstance(v, list):
|
||||||
output[k] = [item.name if issubclass(item.__class__, BaseClass) else item for item in v]
|
# output[k] = [item.name if issubclass(item.__class__, BaseClass) else item for item in v]
|
||||||
elif issubclass(v.__class__, BaseClass):
|
# elif issubclass(v.__class__, BaseClass):
|
||||||
output[k] = v.name
|
# output[k] = v.name
|
||||||
else:
|
# else:
|
||||||
output[k] = v
|
# output[k] = v
|
||||||
return PydProcess(**output)
|
# return PydProcess(**output)
|
||||||
|
|
||||||
|
|
||||||
class ProcessVersion(BaseClass):
|
class ProcessVersion(BaseClass):
|
||||||
@@ -2923,26 +2773,51 @@ class ProcessVersion(BaseClass):
|
|||||||
output['project'] = ""
|
output['project'] = ""
|
||||||
return output
|
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
|
An abstract reagentrole that a tip fills during a process
|
||||||
"""
|
"""
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(64)) #: name of reagent type
|
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
|
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(
|
# tiproleproceduretypeassociation = relationship(
|
||||||
"ProcedureTypeTipRoleAssociation",
|
# "ProcedureTypeTipRoleAssociation",
|
||||||
back_populates="tiprole",
|
# back_populates="tiprole",
|
||||||
cascade="all, delete-orphan"
|
# cascade="all, delete-orphan"
|
||||||
) #: associated procedure
|
# ) #: associated procedure
|
||||||
|
#
|
||||||
proceduretype = association_proxy("tiproleproceduretypeassociation", "proceduretype",
|
# proceduretype = association_proxy("tiproleproceduretypeassociation", "proceduretype",
|
||||||
creator=lambda proceduretype: ProcedureTypeTipRoleAssociation(
|
# creator=lambda proceduretype: ProcedureTypeTipRoleAssociation(
|
||||||
proceduretype=proceduretype))
|
# proceduretype=proceduretype))
|
||||||
|
|
||||||
# @classmethod
|
# @classmethod
|
||||||
# def query_or_create(cls, **kwargs) -> Tuple[TipRole, bool]:
|
# 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.
|
A concrete instance of tips.
|
||||||
"""
|
"""
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
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
|
secondary=tiprole_tips) #: joined parent reagent type
|
||||||
tiprole_id = Column(INTEGER, ForeignKey("_tiprole.id", ondelete='SET NULL',
|
tips_id = Column(INTEGER, ForeignKey("_tips.id", ondelete='SET NULL',
|
||||||
name="fk_tip_role_id")) #: id of parent reagent type
|
name="fk_tips_id")) #: id of parent reagent type
|
||||||
manufacturer = Column(String(64))
|
lot = Column(String(64), unique=True)
|
||||||
capacity = Column(INTEGER)
|
expiry = Column(TIMESTAMP)
|
||||||
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
|
|
||||||
|
|
||||||
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
|
@property
|
||||||
def size(self) -> str:
|
def size(self) -> str:
|
||||||
return f"{self.capacity}ul"
|
return f"{self.capacity}ul"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name (self) -> str:
|
def name(self) -> str:
|
||||||
return f"{self.manufacturer}-{self.size}-{self.ref}"
|
return f"{self.manufacturer}-{self.size}-{self.ref}"
|
||||||
|
|
||||||
# @classmethod
|
# @classmethod
|
||||||
@@ -3121,6 +2998,187 @@ class Tips(BaseClass, LogMixin):
|
|||||||
return output
|
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):
|
class ProcedureTypeTipRoleAssociation(BaseClass):
|
||||||
"""
|
"""
|
||||||
Abstract association between SubmissionType and TipRole
|
Abstract association between SubmissionType and TipRole
|
||||||
@@ -3144,71 +3202,71 @@ class ProcedureTypeTipRoleAssociation(BaseClass):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ProcedureTipsAssociation(BaseClass):
|
# class ProcedureTipsAssociation(BaseClass):
|
||||||
"""
|
# """
|
||||||
Association between a concrete procedure instance and concrete tips
|
# Association between a concrete procedure instance and concrete tips
|
||||||
"""
|
# """
|
||||||
tips_id = Column(INTEGER, ForeignKey("_tips.id"), primary_key=True) #: id of associated equipment
|
# 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_id = Column(INTEGER, ForeignKey("_procedure.id"), primary_key=True) #: id of associated procedure
|
||||||
procedure = relationship("Procedure",
|
# procedure = relationship("Procedure",
|
||||||
back_populates="proceduretipsassociation") #: associated procedure
|
# back_populates="proceduretipsassociation") #: associated procedure
|
||||||
tips = relationship(Tips,
|
# tips = relationship(Tips,
|
||||||
back_populates="tipsprocedureassociation") #: associated equipment
|
# back_populates="tipsprocedureassociation") #: associated equipment
|
||||||
tiprole = Column(String(32), primary_key=True) #, ForeignKey("_tiprole.name"))
|
# tiprole = Column(String(32), primary_key=True) #, ForeignKey("_tiprole.name"))
|
||||||
|
#
|
||||||
def to_sub_dict(self) -> dict:
|
# def to_sub_dict(self) -> dict:
|
||||||
"""
|
# """
|
||||||
This item as a dictionary
|
# This item as a dictionary
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
dict: Values of this object
|
# dict: Values of this object
|
||||||
"""
|
# """
|
||||||
return dict(role=self.role_name, name=self.tips.name, lot=self.tips.lot)
|
# return dict(role=self.role_name, name=self.tips.name, lot=self.tips.lot)
|
||||||
|
#
|
||||||
@classmethod
|
# @classmethod
|
||||||
@setup_lookup
|
# @setup_lookup
|
||||||
def query(cls, tips: int | Tips, tiprole: str, procedure: int | Procedure | None = None, limit: int = 0, **kwargs) \
|
# def query(cls, tips: int | Tips, tiprole: str, procedure: int | Procedure | None = None, limit: int = 0, **kwargs) \
|
||||||
-> Any | List[Any]:
|
# -> Any | List[Any]:
|
||||||
query: Query = cls.__database_session__.query(cls)
|
# query: Query = cls.__database_session__.query(cls)
|
||||||
match tips:
|
# match tips:
|
||||||
case int():
|
# case int():
|
||||||
query = query.filter(cls.tips_id == tips)
|
# query = query.filter(cls.tips_id == tips)
|
||||||
case Tips():
|
# case Tips():
|
||||||
query = query.filter(cls.tips == tips)
|
# query = query.filter(cls.tips == tips)
|
||||||
case _:
|
# case _:
|
||||||
pass
|
# pass
|
||||||
match procedure:
|
# match procedure:
|
||||||
case int():
|
# case int():
|
||||||
query = query.filter(cls.procedure_id == procedure)
|
# query = query.filter(cls.procedure_id == procedure)
|
||||||
case Procedure():
|
# case Procedure():
|
||||||
query = query.filter(cls.procedure == procedure)
|
# query = query.filter(cls.procedure == procedure)
|
||||||
case _:
|
# case _:
|
||||||
pass
|
# pass
|
||||||
query = query.filter(cls.tiprole == tiprole)
|
# query = query.filter(cls.tiprole == tiprole)
|
||||||
return cls.execute_query(query=query, limit=limit, **kwargs)
|
# return cls.execute_query(query=query, limit=limit, **kwargs)
|
||||||
|
#
|
||||||
# TODO: fold this into the BaseClass.query_or_create ?
|
# # TODO: fold this into the BaseClass.query_or_create ?
|
||||||
# @classmethod
|
# # @classmethod
|
||||||
# def query_or_create(cls, tips, procedure, role: str, **kwargs):
|
# # def query_or_create(cls, tips, procedure, role: str, **kwargs):
|
||||||
# kwargs['limit'] = 1
|
# # kwargs['limit'] = 1
|
||||||
# instance = cls.query(tips_id=tips.id, role_name=role, procedure_id=procedure.id, **kwargs)
|
# # instance = cls.query(tips_id=tips.id, role_name=role, procedure_id=procedure.id, **kwargs)
|
||||||
# if instance is None:
|
# # if instance is None:
|
||||||
# instance = cls(procedure=procedure, tips=tips, role_name=role)
|
# # instance = cls(procedure=procedure, tips=tips, role_name=role)
|
||||||
# return instance
|
# # return instance
|
||||||
|
#
|
||||||
def to_pydantic(self):
|
# def to_pydantic(self):
|
||||||
from backend.validators import PydTips
|
# from backend.validators import PydTips
|
||||||
return PydTips(name=self.tips.name, lot=self.tips.lot, role=self.role_name)
|
# return PydTips(name=self.tips.name, lot=self.tips.lot, role=self.role_name)
|
||||||
|
#
|
||||||
def details_dict(self, **kwargs):
|
# def details_dict(self, **kwargs):
|
||||||
output = super().details_dict()
|
# output = super().details_dict()
|
||||||
# NOTE: Figure out how to merge the misc_info if doing .update instead.
|
# # 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']}
|
# relevant = {k: v for k, v in output.items() if k not in ['tips']}
|
||||||
output = output['tips'].details_dict()
|
# output = output['tips'].details_dict()
|
||||||
misc = output['misc_info']
|
# misc = output['misc_info']
|
||||||
output.update(relevant)
|
# output.update(relevant)
|
||||||
output['misc_info'] = misc
|
# output['misc_info'] = misc
|
||||||
return output
|
# return output
|
||||||
|
|
||||||
|
|
||||||
class Results(BaseClass):
|
class Results(BaseClass):
|
||||||
|
|||||||
@@ -1248,6 +1248,7 @@ class Run(BaseClass, LogMixin):
|
|||||||
dlg = ProcedureCreation(parent=obj, procedure=procedure_type.construct_dummy_procedure(run=self))
|
dlg = ProcedureCreation(parent=obj, procedure=procedure_type.construct_dummy_procedure(run=self))
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
sql, _ = dlg.return_sql(new=True)
|
sql, _ = dlg.return_sql(new=True)
|
||||||
|
# sys.exit(pformat(sql.__dict__))
|
||||||
sql.save()
|
sql.save()
|
||||||
obj.set_data()
|
obj.set_data()
|
||||||
|
|
||||||
@@ -1460,11 +1461,11 @@ class Run(BaseClass, LogMixin):
|
|||||||
return list(sorted(padded_list, key=itemgetter('submission_rank')))
|
return list(sorted(padded_list, key=itemgetter('submission_rank')))
|
||||||
|
|
||||||
|
|
||||||
class SampleType(BaseClass):
|
# class SampleType(BaseClass):
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
# id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(64), nullable=False, unique=True) #: identification from submitter
|
# name = Column(String(64), nullable=False, unique=True) #: identification from submitter
|
||||||
|
#
|
||||||
sample = relationship("Sample", back_populates="sampletype", uselist=True)
|
# sample = relationship("Sample", back_populates="sampletype", uselist=True)
|
||||||
|
|
||||||
|
|
||||||
# NOTE: Sample Classes
|
# NOTE: Sample Classes
|
||||||
@@ -1476,9 +1477,9 @@ class Sample(BaseClass, LogMixin):
|
|||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
sample_id = Column(String(64), nullable=False, unique=True) #: identification from submitter
|
sample_id = Column(String(64), nullable=False, unique=True) #: identification from submitter
|
||||||
sampletype_id = Column(INTEGER, ForeignKey("_sampletype.id", ondelete="SET NULL",
|
# sampletype_id = Column(INTEGER, ForeignKey("_sampletype.id", ondelete="SET NULL",
|
||||||
name="fk_SAMP_sampletype_id"))
|
# name="fk_SAMP_sampletype_id"))
|
||||||
sampletype = relationship("SampleType", back_populates="sample")
|
# sampletype = relationship("SampleType", back_populates="sample")
|
||||||
# misc_info = Column(JSON)
|
# misc_info = Column(JSON)
|
||||||
control = relationship("Control", back_populates="sample", uselist=False)
|
control = relationship("Control", back_populates="sample", uselist=False)
|
||||||
|
|
||||||
@@ -1512,10 +1513,7 @@ class Sample(BaseClass, LogMixin):
|
|||||||
return self.sample_id
|
return self.sample_id
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
try:
|
return f"<Sample({self.sample_id})>"
|
||||||
return f"<{self.sampletype.name.replace('_', ' ').title().replace(' ', '')}({self.sample_id})>"
|
|
||||||
except AttributeError:
|
|
||||||
return f"<Sample({self.sample_id})>"
|
|
||||||
|
|
||||||
@classproperty
|
@classproperty
|
||||||
def searchables(cls):
|
def searchables(cls):
|
||||||
@@ -1531,13 +1529,13 @@ class Sample(BaseClass, LogMixin):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: submitter id and sample type and linked procedure if full data
|
dict: submitter id and sample type and linked procedure if full data
|
||||||
"""
|
"""
|
||||||
try:
|
# try:
|
||||||
sample_type = self.sampletype.name
|
# sample_type = self.sampletype.name
|
||||||
except AttributeError:
|
# except AttributeError:
|
||||||
sample_type = "NA"
|
# sample_type = "NA"
|
||||||
sample = dict(
|
sample = dict(
|
||||||
sample_id=self.sample_id,
|
sample_id=self.sample_id
|
||||||
sampletype=sample_type
|
# sampletype=sample_type
|
||||||
)
|
)
|
||||||
if full_data:
|
if full_data:
|
||||||
sample['clientsubmission'] = sorted([item.to_sub_dict() for item in self.sampleclientsubmissionassociation],
|
sample['clientsubmission'] = sorted([item.to_sub_dict() for item in self.sampleclientsubmissionassociation],
|
||||||
@@ -1565,7 +1563,7 @@ class Sample(BaseClass, LogMixin):
|
|||||||
@setup_lookup
|
@setup_lookup
|
||||||
def query(cls,
|
def query(cls,
|
||||||
sample_id: str | None = None,
|
sample_id: str | None = None,
|
||||||
sampletype: str | SampleType | None = None,
|
# sampletype: str | SampleType | None = None,
|
||||||
limit: int = 0,
|
limit: int = 0,
|
||||||
**kwargs
|
**kwargs
|
||||||
) -> Sample | List[Sample]:
|
) -> Sample | List[Sample]:
|
||||||
@@ -1574,20 +1572,19 @@ class Sample(BaseClass, LogMixin):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
sample_id (str | None, optional): Name of the sample (limits results to 1). Defaults to None.
|
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.
|
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
models.Sample|List[models.Sample]: Sample(s) of interest.
|
models.Sample|List[models.Sample]: Sample(s) of interest.
|
||||||
"""
|
"""
|
||||||
query = cls.__database_session__.query(cls)
|
query = cls.__database_session__.query(cls)
|
||||||
match sampletype:
|
# match sampletype:
|
||||||
case str():
|
# case str():
|
||||||
query = query.join(SampleType).filter(SampleType.name == sampletype)
|
# query = query.join(SampleType).filter(SampleType.name == sampletype)
|
||||||
case SampleType():
|
# case SampleType():
|
||||||
query = query.filter(cls.sampletype == sampletype)
|
# query = query.filter(cls.sampletype == sampletype)
|
||||||
case _:
|
# case _:
|
||||||
pass
|
# pass
|
||||||
match sample_id:
|
match sample_id:
|
||||||
case str():
|
case str():
|
||||||
query = query.filter(cls.sample_id == sample_id)
|
query = query.filter(cls.sample_id == sample_id)
|
||||||
@@ -1596,37 +1593,37 @@ class Sample(BaseClass, LogMixin):
|
|||||||
pass
|
pass
|
||||||
return cls.execute_query(query=query, limit=limit, **kwargs)
|
return cls.execute_query(query=query, limit=limit, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
def fuzzy_search(cls,
|
# def fuzzy_search(cls,
|
||||||
sampletype: str | Sample | None = None,
|
# sampletype: str | Sample | None = None,
|
||||||
**kwargs
|
# **kwargs
|
||||||
) -> List[Sample]:
|
# ) -> List[Sample]:
|
||||||
"""
|
# """
|
||||||
Allows for fuzzy search of sample.
|
# Allows for fuzzy search of sample.
|
||||||
|
#
|
||||||
Args:
|
# Args:
|
||||||
sampletype (str | BasicSample | None, optional): Type of sample. Defaults to None.
|
# sampletype (str | BasicSample | None, optional): Type of sample. Defaults to None.
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
List[Sample]: List of sample that match kwarg search parameters.
|
# List[Sample]: List of sample that match kwarg search parameters.
|
||||||
"""
|
# """
|
||||||
query: Query = cls.__database_session__.query(cls)
|
# query: Query = cls.__database_session__.query(cls)
|
||||||
match sampletype:
|
# match sampletype:
|
||||||
case str():
|
# case str():
|
||||||
query = query.join(SampleType).filter(SampleType.name == sampletype)
|
# query = query.join(SampleType).filter(SampleType.name == sampletype)
|
||||||
case SampleType():
|
# case SampleType():
|
||||||
query = query.filter(cls.sampletype == sampletype)
|
# query = query.filter(cls.sampletype == sampletype)
|
||||||
case _:
|
# case _:
|
||||||
pass
|
# pass
|
||||||
for k, v in kwargs.items():
|
# for k, v in kwargs.items():
|
||||||
search = f"%{v}%"
|
# search = f"%{v}%"
|
||||||
try:
|
# try:
|
||||||
attr = getattr(cls, k)
|
# attr = getattr(cls, k)
|
||||||
# NOTE: the secret sauce is in attr.like
|
# # NOTE: the secret sauce is in attr.like
|
||||||
query = query.filter(attr.like(search))
|
# query = query.filter(attr.like(search))
|
||||||
except (ArgumentError, AttributeError) as e:
|
# except (ArgumentError, AttributeError) as e:
|
||||||
logger.error(f"Attribute {k} unavailable due to:\n\t{e}\nSkipping.")
|
# logger.error(f"Attribute {k} unavailable due to:\n\t{e}\nSkipping.")
|
||||||
return query.limit(50).all()
|
# return query.limit(50).all()
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
raise AttributeError(f"Delete not implemented for {self.__class__}")
|
raise AttributeError(f"Delete not implemented for {self.__class__}")
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class PydBaseClass(BaseModel, extra='allow', validate_assignment=True):
|
|||||||
@model_validator(mode="before")
|
@model_validator(mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
def prevalidate(cls, data):
|
def prevalidate(cls, data):
|
||||||
|
# logger.debug(f"Initial data for {cls.__name__}: {pformat(data)}")
|
||||||
sql_fields = [k for k, v in cls._sql_object.__dict__.items() if isinstance(v, InstrumentedAttribute)]
|
sql_fields = [k for k, v in cls._sql_object.__dict__.items() if isinstance(v, InstrumentedAttribute)]
|
||||||
output = {}
|
output = {}
|
||||||
try:
|
try:
|
||||||
@@ -72,7 +73,7 @@ class PydBaseClass(BaseModel, extra='allow', validate_assignment=True):
|
|||||||
def __init__(self, **data):
|
def __init__(self, **data):
|
||||||
# NOTE: Grab the sql model for validation purposes.
|
# NOTE: Grab the sql model for validation purposes.
|
||||||
# self.__class__._sql_object = getattr(models, self.__class__.__name__.replace("Pyd", ""))
|
# self.__class__._sql_object = getattr(models, self.__class__.__name__.replace("Pyd", ""))
|
||||||
logger.debug(f"Initial data: {data}")
|
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
|
|
||||||
def filter_field(self, key: str) -> Any:
|
def filter_field(self, key: str) -> Any:
|
||||||
@@ -284,7 +285,7 @@ class PydReagent(PydBaseClass):
|
|||||||
|
|
||||||
class PydSample(PydBaseClass):
|
class PydSample(PydBaseClass):
|
||||||
sample_id: str
|
sample_id: str
|
||||||
sampletype: str | None = Field(default=None)
|
# sampletype: str | None = Field(default=None)
|
||||||
submission_rank: int | List[int] | None = Field(default=0, validate_default=True)
|
submission_rank: int | List[int] | None = Field(default=0, validate_default=True)
|
||||||
enabled: bool = Field(default=True)
|
enabled: bool = Field(default=True)
|
||||||
row: int = Field(default=0)
|
row: int = Field(default=0)
|
||||||
@@ -327,7 +328,7 @@ class PydSample(PydBaseClass):
|
|||||||
def improved_dict(self, dictionaries: bool = True) -> dict:
|
def improved_dict(self, dictionaries: bool = True) -> dict:
|
||||||
output = super().improved_dict(dictionaries=dictionaries)
|
output = super().improved_dict(dictionaries=dictionaries)
|
||||||
output['name'] = self.sample_id
|
output['name'] = self.sample_id
|
||||||
del output['sampletype']
|
# del output['sampletype']
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def to_sql(self):
|
def to_sql(self):
|
||||||
@@ -399,22 +400,24 @@ class PydEquipment(PydBaseClass):
|
|||||||
|
|
||||||
@field_validator('process', mode='before')
|
@field_validator('process', mode='before')
|
||||||
@classmethod
|
@classmethod
|
||||||
def make_empty_list(cls, value):
|
def make_empty_list(cls, value, values):
|
||||||
# if isinstance(value, dict):
|
# if isinstance(value, dict):
|
||||||
# value = value['processes']
|
# value = value['processes']
|
||||||
if isinstance(value, GeneratorType):
|
if isinstance(value, GeneratorType):
|
||||||
value = [item for item in value]
|
value = [item for item in value]
|
||||||
value = convert_nans_to_nones(value)
|
value = convert_nans_to_nones(value)
|
||||||
if not value:
|
if not value:
|
||||||
value = ['']
|
value = []
|
||||||
# logger.debug(value)
|
logger.debug(value)
|
||||||
try:
|
try:
|
||||||
# value = [item.strip() for item in value]
|
# value = [item.strip() for item in value]
|
||||||
d = next((process for process in value), None)
|
d: Process = next((process for process in value if values.data['name'] in [item.name for item in process.equipment]), None)
|
||||||
logger.debug(f"Next process: {d.detail_dict()}")
|
# logger.debug(f"Next process: {d.details_dict()}")
|
||||||
value = PydProcess(d.details_dict())
|
value = d.to_pydantic()
|
||||||
|
# value = PydProcess(**d.details_dict())
|
||||||
# value = next((process.to_pydantic() for process in value))
|
# value = next((process.to_pydantic() for process in value))
|
||||||
except AttributeError:
|
except AttributeError as e:
|
||||||
|
logger.error(f"Process Validation error due to {e}")
|
||||||
pass
|
pass
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -1179,6 +1182,7 @@ class PydProcess(PydBaseClass, extra="allow"):
|
|||||||
@field_validator("proceduretype", "equipment", "equipmentrole", "tiprole", mode="before")
|
@field_validator("proceduretype", "equipment", "equipmentrole", "tiprole", mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
def enforce_list(cls, value):
|
def enforce_list(cls, value):
|
||||||
|
# logger.debug(f"Validating field: {value}")
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
value = [value]
|
value = [value]
|
||||||
output = []
|
output = []
|
||||||
@@ -1192,32 +1196,36 @@ class PydProcess(PydBaseClass, extra="allow"):
|
|||||||
@report_result
|
@report_result
|
||||||
def to_sql(self):
|
def to_sql(self):
|
||||||
report = Report()
|
report = Report()
|
||||||
instance = Process.query(name=self.name)
|
logger.debug(f"Query process: {self.name}, version = {self.version}")
|
||||||
|
# NOTE: can't use query_or_create due to name not being part of ProcessVersion
|
||||||
|
instance = ProcessVersion.query(name=self.name, version=self.version, limit=1)
|
||||||
|
logger.debug(f"Got instance: {instance}")
|
||||||
if not instance:
|
if not instance:
|
||||||
instance = Process()
|
instance = ProcessVersion()
|
||||||
fields = [item for item in self.model_fields]
|
# fields = [item for item in self.model_fields]
|
||||||
for field in fields:
|
# for field in fields:
|
||||||
logger.debug(f"Field: {field}")
|
# logger.debug(f"Field: {field}")
|
||||||
try:
|
# try:
|
||||||
field_type = getattr(instance.__class__, field).property
|
# field_type = getattr(instance.__class__, field).property
|
||||||
except AttributeError:
|
# except AttributeError:
|
||||||
logger.error(f"No attribute: {field} in {instance.__class__}")
|
# logger.error(f"No attribute: {field} in {instance.__class__}")
|
||||||
continue
|
# continue
|
||||||
match field_type:
|
# match field_type:
|
||||||
case _RelationshipDeclared():
|
# case _RelationshipDeclared():
|
||||||
logger.debug(f"{field} is a relationship with {field_type.entity.class_}")
|
# logger.debug(f"{field} is a relationship with {field_type.entity.class_}")
|
||||||
query_str = getattr(self, field)
|
# query_str = getattr(self, field)
|
||||||
if isinstance(query_str, list):
|
# if isinstance(query_str, list):
|
||||||
query_str = query_str[0]
|
# query_str = query_str[0]
|
||||||
if query_str in ["", " ", None]:
|
# if query_str in ["", " ", None]:
|
||||||
continue
|
# continue
|
||||||
logger.debug(f"Querying {field_type.entity.class_} with name {query_str}")
|
# logger.debug(f"Querying {field_type.entity.class_} with name {query_str}")
|
||||||
field_value = field_type.entity.class_.query(name=query_str)
|
# field_value = field_type.entity.class_.query(name=query_str)
|
||||||
logger.debug(f"{field} query result: {field_value}")
|
# logger.debug(f"{field} query result: {field_value}")
|
||||||
case ColumnProperty():
|
# case ColumnProperty():
|
||||||
logger.debug(f"{field} is a property.")
|
# logger.debug(f"{field} is a property.")
|
||||||
field_value = getattr(self, field)
|
# field_value = getattr(self, field)
|
||||||
instance.set_attribute(key=field, value=field_value)
|
# # instance.set_attribute(key=field, value=field_value)
|
||||||
|
# setattr(instance, field, field_value)
|
||||||
return instance, report
|
return instance, report
|
||||||
|
|
||||||
|
|
||||||
@@ -1469,7 +1477,7 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
idx = 0
|
idx = 0
|
||||||
insertable = PydReagent(reagentrole=reagentrole, name=name, lot=lot, expiry=expiry)
|
insertable = PydReagent(reagentrole=reagentrole, name=name, lot=lot, expiry=expiry)
|
||||||
self.reagent.insert(idx, insertable)
|
self.reagent.insert(idx, insertable)
|
||||||
logger.debug(self.reagent)
|
# logger.debug(self.reagent)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_new_reagents(cls, reagent: PydReagent):
|
def update_new_reagents(cls, reagent: PydReagent):
|
||||||
@@ -1506,9 +1514,9 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
sql.proceduretype = self.proceduretype
|
sql.proceduretype = self.proceduretype
|
||||||
# Note: convert any new reagents to sql and save
|
# Note: convert any new reagents to sql and save
|
||||||
# for reagentrole, reagents in self.reagentrole.items():
|
# for reagentrole, reagents in self.reagentrole.items():
|
||||||
for reagent in self.reagent:
|
# for reagent in self.reagent:
|
||||||
if not reagent.lot or reagent.name == "--New--":
|
# if not reagent.lot or reagent.name == "--New--":
|
||||||
continue
|
# continue
|
||||||
# self.update_new_reagents(reagent)
|
# self.update_new_reagents(reagent)
|
||||||
# NOTE: reset reagent associations.
|
# NOTE: reset reagent associations.
|
||||||
# sql.procedurereagentassociation = []
|
# sql.procedurereagentassociation = []
|
||||||
@@ -1532,7 +1540,7 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
r.delete()
|
r.delete()
|
||||||
else:
|
else:
|
||||||
removable.delete()
|
removable.delete()
|
||||||
# logger.debug(f"Adding {reagent} to {sql}")
|
logger.debug(f"Adding {reagent} to {sql}")
|
||||||
reagent_assoc = ProcedureReagentLotAssociation(reagentlot=reagent, procedure=sql, reagentrole=reagentrole)
|
reagent_assoc = ProcedureReagentLotAssociation(reagentlot=reagent, procedure=sql, reagentrole=reagentrole)
|
||||||
try:
|
try:
|
||||||
start_index = max([item.id for item in ProcedureSampleAssociation.query()]) + 1
|
start_index = max([item.id for item in ProcedureSampleAssociation.query()]) + 1
|
||||||
@@ -1556,14 +1564,18 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
proc_assoc = ProcedureSampleAssociation(new_id=assoc_id_range[iii], procedure=sql, sample=sample_sql,
|
proc_assoc = ProcedureSampleAssociation(new_id=assoc_id_range[iii], procedure=sql, sample=sample_sql,
|
||||||
row=sample.row, column=sample.column,
|
row=sample.row, column=sample.column,
|
||||||
procedure_rank=sample.procedure_rank)
|
procedure_rank=sample.procedure_rank)
|
||||||
sys.exit(pformat(self.equipment))
|
# sys.exit(pformat(self.equipment))
|
||||||
for equipment in self.equipment:
|
for equipment in self.equipment:
|
||||||
equip = Equipment.query(name=equipment.name)
|
logger.debug(f"Equipment: {equipment}")
|
||||||
|
# equip = Equipment.query(name=equipment.name)
|
||||||
|
equip, _ = equipment.to_sql()
|
||||||
|
logger.debug(f"Process: {equipment.process}")
|
||||||
if equip not in sql.equipment:
|
if equip not in sql.equipment:
|
||||||
equip_assoc = ProcedureEquipmentAssociation(equipment=equip, procedure=sql,
|
equip_assoc = ProcedureEquipmentAssociation(equipment=equip, procedure=sql,
|
||||||
equipmentrole=equip.equipmentrole[0])
|
equipmentrole=equip.equipmentrole[0])
|
||||||
process = equipment.process.to_sql()
|
process = equipment.process.to_sql()
|
||||||
equip_assoc.process = process
|
equip_assoc.processversion = process
|
||||||
|
# sys.exit(pformat([item.__dict__ for item in sql.procedureequipmentassociation]))
|
||||||
return sql, None
|
return sql, None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ class ProcedureCreation(QDialog):
|
|||||||
tips = next((tps for tps in equipment.tips if tps.name == tips), None)
|
tips = next((tps for tps in equipment.tips if tps.name == tips), None)
|
||||||
if tips:
|
if tips:
|
||||||
eoi.tips = tips.to_pydantic()
|
eoi.tips = tips.to_pydantic()
|
||||||
self.procedure.equipment.append(eoi)
|
self.procedure.equipment.append(eoi)
|
||||||
logger.debug(f"Updated equipment: {self.procedure.equipment}")
|
logger.debug(f"Updated equipment: {self.procedure.equipment}")
|
||||||
|
|
||||||
@pyqtSlot(str, str)
|
@pyqtSlot(str, str)
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class SubmissionDetails(QDialog):
|
|||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def process_details(self, process: str | Process):
|
def process_details(self, process: str | Process):
|
||||||
logger.debug(f"Equipment details")
|
logger.debug(f"Process details")
|
||||||
if isinstance(process, str):
|
if isinstance(process, str):
|
||||||
process = Process.query(name=process)
|
process = Process.query(name=process)
|
||||||
base_dict = process.to_sub_dict(full_data=True)
|
base_dict = process.to_sub_dict(full_data=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user