First working example.
This commit is contained in:
@@ -24,28 +24,6 @@ Base: DeclarativeMeta = declarative_base()
|
|||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
|
||||||
class LogMixin(Base):
|
|
||||||
|
|
||||||
tracking_exclusion: ClassVar = ['artic_technician', 'submission_sample_associations',
|
|
||||||
'submission_reagent_associations', 'submission_equipment_associations',
|
|
||||||
'submission_tips_associations', 'contact_id', 'gel_info', 'gel_controls',
|
|
||||||
'source_plates']
|
|
||||||
|
|
||||||
__abstract__ = True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def truncated_name(self):
|
|
||||||
name = str(self)
|
|
||||||
if len(name) > 64:
|
|
||||||
name = name.replace("<", "").replace(">", "")
|
|
||||||
if len(name) > 64:
|
|
||||||
# NOTE: As if re'agent'
|
|
||||||
name = name.replace("agent", "")
|
|
||||||
if len(name) > 64:
|
|
||||||
name = f"...{name[-61:]}"
|
|
||||||
return name
|
|
||||||
|
|
||||||
|
|
||||||
class BaseClass(Base):
|
class BaseClass(Base):
|
||||||
"""
|
"""
|
||||||
Abstract class to pass ctx values to all SQLAlchemy objects.
|
Abstract class to pass ctx values to all SQLAlchemy objects.
|
||||||
@@ -243,8 +221,10 @@ class BaseClass(Base):
|
|||||||
Returns:
|
Returns:
|
||||||
Any | List[Any]: Single result if limit = 1 or List if other.
|
Any | List[Any]: Single result if limit = 1 or List if other.
|
||||||
"""
|
"""
|
||||||
|
logger.debug(f"Kwargs: {kwargs}")
|
||||||
if model is None:
|
if model is None:
|
||||||
model = cls
|
model = cls
|
||||||
|
logger.debug(f"Model: {model}")
|
||||||
if query is None:
|
if query is None:
|
||||||
query: Query = cls.__database_session__.query(model)
|
query: Query = cls.__database_session__.query(model)
|
||||||
singles = model.get_default_info('singles')
|
singles = model.get_default_info('singles')
|
||||||
@@ -477,6 +457,28 @@ class BaseClass(Base):
|
|||||||
return output_date
|
return output_date
|
||||||
|
|
||||||
|
|
||||||
|
class LogMixin(Base):
|
||||||
|
|
||||||
|
tracking_exclusion: ClassVar = ['artic_technician', 'submission_sample_associations',
|
||||||
|
'submission_reagent_associations', 'submission_equipment_associations',
|
||||||
|
'submission_tips_associations', 'contact_id', 'gel_info', 'gel_controls',
|
||||||
|
'source_plates']
|
||||||
|
|
||||||
|
__abstract__ = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def truncated_name(self):
|
||||||
|
name = str(self)
|
||||||
|
if len(name) > 64:
|
||||||
|
name = name.replace("<", "").replace(">", "")
|
||||||
|
if len(name) > 64:
|
||||||
|
# NOTE: As if re'agent'
|
||||||
|
name = name.replace("agent", "")
|
||||||
|
if len(name) > 64:
|
||||||
|
name = f"...{name[-61:]}"
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
class ConfigItem(BaseClass):
|
class ConfigItem(BaseClass):
|
||||||
"""
|
"""
|
||||||
Key:JSON objects to store config settings in database.
|
Key:JSON objects to store config settings in database.
|
||||||
|
|||||||
@@ -123,6 +123,9 @@ class Control(BaseClass):
|
|||||||
controltype = relationship("ControlType", back_populates="instances",
|
controltype = relationship("ControlType", back_populates="instances",
|
||||||
foreign_keys=[controltype_name]) #: reference to parent control type
|
foreign_keys=[controltype_name]) #: reference to parent control type
|
||||||
name = Column(String(255), unique=True) #: Sample ID
|
name = Column(String(255), unique=True) #: Sample ID
|
||||||
|
sample_id = Column(String, ForeignKey("_basicsample.id", ondelete="SET NULL",
|
||||||
|
name="fk_Cont_sample_id")) #: name of joined submission type
|
||||||
|
sample = relationship("BasicSample", back_populates="control") #: This control's submission sample
|
||||||
submitted_date = Column(TIMESTAMP) #: Date submitted to Robotics
|
submitted_date = Column(TIMESTAMP) #: Date submitted to Robotics
|
||||||
submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id")) #: parent submission id
|
submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id")) #: parent submission id
|
||||||
submission = relationship("BasicSubmission", back_populates="controls",
|
submission = relationship("BasicSubmission", back_populates="controls",
|
||||||
@@ -362,7 +365,6 @@ class IridaControl(Control):
|
|||||||
refseq_version = Column(String(16)) #: version of refseq used in fastq parsing
|
refseq_version = Column(String(16)) #: version of refseq used in fastq parsing
|
||||||
kraken2_version = Column(String(16)) #: version of kraken2 used in fastq parsing
|
kraken2_version = Column(String(16)) #: version of kraken2 used in fastq parsing
|
||||||
kraken2_db_version = Column(String(32)) #: folder name of kraken2 db
|
kraken2_db_version = Column(String(32)) #: folder name of kraken2 db
|
||||||
sample = relationship("BacterialCultureSample", back_populates="control") #: This control's submission sample
|
|
||||||
sample_id = Column(INTEGER,
|
sample_id = Column(INTEGER,
|
||||||
ForeignKey("_basicsample.id", ondelete="SET NULL", name="cont_BCS_id")) #: sample id key
|
ForeignKey("_basicsample.id", ondelete="SET NULL", name="cont_BCS_id")) #: sample id key
|
||||||
|
|
||||||
|
|||||||
@@ -848,7 +848,7 @@ class SubmissionType(BaseClass):
|
|||||||
name = Column(String(128), unique=True) #: name of submission type
|
name = Column(String(128), unique=True) #: name of submission type
|
||||||
info_map = Column(JSON) #: Where parsable information is found in the excel workbook corresponding to this type.
|
info_map = Column(JSON) #: Where parsable information is found in the excel workbook corresponding to this type.
|
||||||
defaults = Column(JSON) #: Basic information about this submission type
|
defaults = Column(JSON) #: Basic information about this submission type
|
||||||
instances = relationship("BasicSubmission") #: Concrete instances of this type.
|
instances = relationship("ClientSubmission", back_populates="submission_type") #: Concrete instances of this type.
|
||||||
template_file = Column(BLOB) #: Blank form for this type stored as binary.
|
template_file = Column(BLOB) #: Blank form for this type stored as binary.
|
||||||
processes = relationship("Process", back_populates="submission_types",
|
processes = relationship("Process", back_populates="submission_types",
|
||||||
secondary=submissiontypes_processes) #: Relation to equipment processes used for this type.
|
secondary=submissiontypes_processes) #: Relation to equipment processes used for this type.
|
||||||
@@ -1048,9 +1048,9 @@ class SubmissionType(BaseClass):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
runtype_kit_associations = relationship(
|
submissiontype_kit_associations = relationship(
|
||||||
"RunTypeKitTypeAssociation",
|
"SubmissionTypeKitTypeAssociation",
|
||||||
back_populates="runtype",
|
back_populates="submission_type",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
) #: Association of kittypes
|
) #: Association of kittypes
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ from operator import itemgetter
|
|||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
from sqlalchemy.ext.hybrid import hybrid_property
|
from sqlalchemy.ext.hybrid import hybrid_property
|
||||||
from . import Base, BaseClass, Reagent, SubmissionType, KitType, Organization, Contact, LogMixin, \
|
from . import Base, BaseClass, Reagent, SubmissionType, KitType, Organization, Contact, \
|
||||||
SubmissionReagentAssociation
|
SubmissionReagentAssociation, LogMixin
|
||||||
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case, func, Table
|
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case, func, Table
|
||||||
from sqlalchemy.orm import relationship, validates, Query
|
from sqlalchemy.orm import relationship, validates, Query
|
||||||
from sqlalchemy.orm.attributes import flag_modified
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
@@ -55,14 +55,15 @@ class ClientSubmission(BaseClass, LogMixin):
|
|||||||
String(64)) #: ["Research", "Diagnostic", "Surveillance", "Validation"], else defaults to submission_type_name
|
String(64)) #: ["Research", "Diagnostic", "Surveillance", "Validation"], else defaults to submission_type_name
|
||||||
sample_count = Column(INTEGER) #: Number of samples in the submission
|
sample_count = Column(INTEGER) #: Number of samples in the submission
|
||||||
|
|
||||||
runs = relationship("BasicRun", back_populates="client_submission") #: many-to-one relationship
|
runs = relationship("BasicSubmission", back_populates="client_submission") #: many-to-one relationship
|
||||||
|
|
||||||
contact = relationship("Contact", back_populates="submissions") #: client org
|
contact = relationship("Contact", back_populates="submissions") #: client org
|
||||||
contact_id = Column(INTEGER, ForeignKey("_contact.id", ondelete="SET NULL",
|
contact_id = Column(INTEGER, ForeignKey("_contact.id", ondelete="SET NULL",
|
||||||
name="fk_BS_contact_id")) #: client lab id from _organizations
|
name="fk_BS_contact_id")) #: client lab id from _organizations
|
||||||
submission_type = relationship("SubmissionType", back_populates="instances") #: archetype of this submission
|
|
||||||
submission_type_name = Column(String, ForeignKey("_submissiontype.name", ondelete="SET NULL",
|
submission_type_name = Column(String, ForeignKey("_submissiontype.name", ondelete="SET NULL",
|
||||||
name="fk_BS_subtype_name")) #: name of joined submission type
|
name="fk_BS_subtype_name")) #: name of joined submission type
|
||||||
|
submission_type = relationship("SubmissionType", back_populates="instances") #: archetype of this submission
|
||||||
|
|
||||||
|
|
||||||
cost_centre = Column(
|
cost_centre = Column(
|
||||||
String(64)) #: Permanent storage of used cost centre in case organization field changed in the future.
|
String(64)) #: Permanent storage of used cost centre in case organization field changed in the future.
|
||||||
@@ -98,7 +99,9 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
rsl_plate_number = Column(String(32), unique=True, nullable=False) #: RSL name (e.g. RSL-22-0012)
|
rsl_plate_num = Column(String(32), unique=True, nullable=False) #: RSL name (e.g. RSL-22-0012)
|
||||||
|
client_submission_id = Column(INTEGER, ForeignKey("_clientsubmission.id", ondelete="SET NULL",
|
||||||
|
name="fk_BS_clientsub_id")) #: client lab id from _organizations)
|
||||||
client_submission = relationship("ClientSubmission", back_populates="runs")
|
client_submission = relationship("ClientSubmission", back_populates="runs")
|
||||||
# submitter_plate_num = Column(String(127), unique=True) #: The number given to the submission by the submitting lab
|
# submitter_plate_num = Column(String(127), unique=True) #: The number given to the submission by the submitting lab
|
||||||
started_date = Column(TIMESTAMP) #: Date this run was started.
|
started_date = Column(TIMESTAMP) #: Date this run was started.
|
||||||
@@ -304,7 +307,8 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
case SubmissionType():
|
case SubmissionType():
|
||||||
return sub_type
|
return sub_type
|
||||||
case _:
|
case _:
|
||||||
return SubmissionType.query(cls.__mapper_args__['polymorphic_identity'])
|
# return SubmissionType.query(cls.__mapper_args__['polymorphic_identity'])
|
||||||
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def construct_info_map(cls, submission_type: SubmissionType | None = None,
|
def construct_info_map(cls, submission_type: SubmissionType | None = None,
|
||||||
@@ -356,7 +360,7 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
"""
|
"""
|
||||||
# NOTE: get lab from nested organization object
|
# NOTE: get lab from nested organization object
|
||||||
try:
|
try:
|
||||||
sub_lab = self.submitting_lab.name
|
sub_lab = self.client_submission.submitting_lab.name
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
sub_lab = None
|
sub_lab = None
|
||||||
try:
|
try:
|
||||||
@@ -364,24 +368,24 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
# NOTE: get extraction kit name from nested kit object
|
# NOTE: get extraction kit name from nested kit object
|
||||||
try:
|
# try:
|
||||||
ext_kit = self.extraction_kit.name
|
# ext_kit = self.extraction_kit.name
|
||||||
except AttributeError:
|
# except AttributeError:
|
||||||
ext_kit = None
|
# ext_kit = None
|
||||||
# NOTE: load scraped extraction info
|
# NOTE: load scraped extraction info
|
||||||
try:
|
# try:
|
||||||
ext_info = self.extraction_info
|
# ext_info = self.extraction_info
|
||||||
except TypeError:
|
# except TypeError:
|
||||||
ext_info = None
|
# ext_info = None
|
||||||
output = {
|
output = {
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"plate_number": self.rsl_plate_num,
|
"plate_number": self.rsl_plate_num,
|
||||||
"submission_type": self.submission_type_name,
|
"submission_type": self.client_submission.submission_type_name,
|
||||||
"submitter_plate_number": self.submitter_plate_num,
|
"submitter_plate_number": self.client_submission.submitter_plate_num,
|
||||||
"submitted_date": self.submitted_date.strftime("%Y-%m-%d"),
|
"submitted_date": self.client_submission.submitted_date.strftime("%Y-%m-%d"),
|
||||||
"submitting_lab": sub_lab,
|
"submitting_lab": sub_lab,
|
||||||
"sample_count": self.sample_count,
|
"sample_count": self.client_submission.sample_count,
|
||||||
"extraction_kit": ext_kit,
|
"extraction_kit": "Change submissions.py line 388",
|
||||||
"cost": self.run_cost
|
"cost": self.run_cost
|
||||||
}
|
}
|
||||||
if report:
|
if report:
|
||||||
@@ -433,11 +437,11 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
contact_phone = self.contact.phone
|
contact_phone = self.contact.phone
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
contact_phone = "NA"
|
contact_phone = "NA"
|
||||||
output["submission_category"] = self.submission_category
|
output["submission_category"] = self.client_submission.submission_category
|
||||||
output["technician"] = self.technician
|
output["technician"] = self.technician
|
||||||
output["reagents"] = reagents
|
output["reagents"] = reagents
|
||||||
output["samples"] = samples
|
output["samples"] = samples
|
||||||
output["extraction_info"] = ext_info
|
# output["extraction_info"] = ext_info
|
||||||
output["comment"] = comments
|
output["comment"] = comments
|
||||||
output["equipment"] = equipment
|
output["equipment"] = equipment
|
||||||
output["tips"] = tips
|
output["tips"] = tips
|
||||||
@@ -1236,7 +1240,8 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
# # else:
|
# # else:
|
||||||
start_date = cls.rectify_query_date(start_date)
|
start_date = cls.rectify_query_date(start_date)
|
||||||
end_date = cls.rectify_query_date(end_date, eod=True)
|
end_date = cls.rectify_query_date(end_date, eod=True)
|
||||||
query = query.filter(model.submitted_date.between(start_date, end_date))
|
logger.debug(f"Start date: {start_date}, end date: {end_date}")
|
||||||
|
query = query.join(ClientSubmission).filter(ClientSubmission.submitted_date.between(start_date, end_date))
|
||||||
# NOTE: by reagent (for some reason)
|
# NOTE: by reagent (for some reason)
|
||||||
match reagent:
|
match reagent:
|
||||||
case str():
|
case str():
|
||||||
@@ -1256,7 +1261,9 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
pass
|
pass
|
||||||
match submission_type_name:
|
match submission_type_name:
|
||||||
case str():
|
case str():
|
||||||
query = query.filter(model.submission_type_name == submission_type_name)
|
if not start_date:
|
||||||
|
query = query.join(ClientSubmission)
|
||||||
|
query = query.filter(ClientSubmission.submission_type_name == submission_type_name)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
# NOTE: by id (returns only a single value)
|
# NOTE: by id (returns only a single value)
|
||||||
@@ -1269,7 +1276,7 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
limit = 1
|
limit = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
query = query.order_by(cls.submitted_date.desc())
|
# query = query.order_by(cls.submitted_date.desc())
|
||||||
# NOTE: Split query results into pages of size {page_size}
|
# NOTE: Split query results into pages of size {page_size}
|
||||||
if page_size > 0:
|
if page_size > 0:
|
||||||
query = query.limit(page_size)
|
query = query.limit(page_size)
|
||||||
@@ -1471,7 +1478,7 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
completed = self.completed_date.date()
|
completed = self.completed_date.date()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
completed = None
|
completed = None
|
||||||
return self.calculate_turnaround(start_date=self.submitted_date.date(), end_date=completed)
|
return self.calculate_turnaround(start_date=self.client_submission.submitted_date.date(), end_date=completed)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def calculate_turnaround(cls, start_date: date | None = None, end_date: date | None = None) -> int:
|
def calculate_turnaround(cls, start_date: date | None = None, end_date: date | None = None) -> int:
|
||||||
@@ -2459,6 +2466,7 @@ class BasicSample(BaseClass, LogMixin):
|
|||||||
submitter_id = Column(String(64), nullable=False, unique=True) #: identification from submitter
|
submitter_id = Column(String(64), nullable=False, unique=True) #: identification from submitter
|
||||||
sample_type = Column(String(32)) #: mode_sub_type of sample
|
sample_type = Column(String(32)) #: mode_sub_type of sample
|
||||||
misc_info = Column(JSON)
|
misc_info = Column(JSON)
|
||||||
|
control = relationship("Control", back_populates="sample", uselist=False)
|
||||||
|
|
||||||
sample_submission_associations = relationship(
|
sample_submission_associations = relationship(
|
||||||
"SubmissionSampleAssociation",
|
"SubmissionSampleAssociation",
|
||||||
@@ -2775,140 +2783,140 @@ class BasicSample(BaseClass, LogMixin):
|
|||||||
|
|
||||||
# NOTE: Below are the custom sample types
|
# NOTE: Below are the custom sample types
|
||||||
|
|
||||||
class WastewaterSample(BasicSample):
|
# class WastewaterSample(BasicSample):
|
||||||
"""
|
# """
|
||||||
Derivative wastewater sample
|
# Derivative wastewater sample
|
||||||
"""
|
# """
|
||||||
|
#
|
||||||
id = Column(INTEGER, ForeignKey('_basicsample.id'), primary_key=True)
|
# id = Column(INTEGER, ForeignKey('_basicsample.id'), primary_key=True)
|
||||||
ww_processing_num = Column(String(64)) #: wastewater processing number
|
# ww_processing_num = Column(String(64)) #: wastewater processing number
|
||||||
ww_full_sample_id = Column(String(64)) #: full id given by entrics
|
# ww_full_sample_id = Column(String(64)) #: full id given by entrics
|
||||||
rsl_number = Column(String(64)) #: rsl plate identification number
|
# rsl_number = Column(String(64)) #: rsl plate identification number
|
||||||
collection_date = Column(TIMESTAMP) #: Date sample collected
|
# collection_date = Column(TIMESTAMP) #: Date sample collected
|
||||||
received_date = Column(TIMESTAMP) #: Date sample received
|
# received_date = Column(TIMESTAMP) #: Date sample received
|
||||||
notes = Column(String(2000)) #: notes from submission form
|
# notes = Column(String(2000)) #: notes from submission form
|
||||||
sample_location = Column(String(8)) #: location on 24 well plate
|
# sample_location = Column(String(8)) #: location on 24 well plate
|
||||||
__mapper_args__ = dict(polymorphic_identity="Wastewater Sample",
|
# __mapper_args__ = dict(polymorphic_identity="Wastewater Sample",
|
||||||
polymorphic_load="inline",
|
# polymorphic_load="inline",
|
||||||
inherit_condition=(id == BasicSample.id))
|
# inherit_condition=(id == BasicSample.id))
|
||||||
|
#
|
||||||
@classmethod
|
# @classmethod
|
||||||
def get_default_info(cls, *args):
|
# def get_default_info(cls, *args):
|
||||||
"""
|
# """
|
||||||
Returns default info for a model. Extends BaseClass method.
|
# Returns default info for a model. Extends BaseClass method.
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
dict | list | str: Output of key:value dict or single (list, str) desired variable
|
# dict | list | str: Output of key:value dict or single (list, str) desired variable
|
||||||
"""
|
# """
|
||||||
dicto = super().get_default_info(*args)
|
# dicto = super().get_default_info(*args)
|
||||||
match dicto:
|
# match dicto:
|
||||||
case dict():
|
# case dict():
|
||||||
dicto['singles'] += ['ww_processing_num']
|
# dicto['singles'] += ['ww_processing_num']
|
||||||
output = {}
|
# output = {}
|
||||||
for k, v in dicto.items():
|
# for k, v in dicto.items():
|
||||||
if len(args) > 0 and k not in args:
|
# if len(args) > 0 and k not in args:
|
||||||
continue
|
# continue
|
||||||
else:
|
# else:
|
||||||
output[k] = v
|
# output[k] = v
|
||||||
if len(args) == 1:
|
# if len(args) == 1:
|
||||||
return output[args[0]]
|
# return output[args[0]]
|
||||||
case list():
|
# case list():
|
||||||
if "singles" in args:
|
# if "singles" in args:
|
||||||
dicto += ['ww_processing_num']
|
# dicto += ['ww_processing_num']
|
||||||
return dicto
|
# return dicto
|
||||||
case _:
|
# case _:
|
||||||
pass
|
# pass
|
||||||
|
#
|
||||||
def to_sub_dict(self, full_data: bool = False) -> dict:
|
# def to_sub_dict(self, full_data: bool = False) -> dict:
|
||||||
"""
|
# """
|
||||||
gui friendly dictionary, extends parent method.
|
# gui friendly dictionary, extends parent method.
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
dict: sample id, type, received date, collection date
|
# dict: sample id, type, received date, collection date
|
||||||
"""
|
# """
|
||||||
sample = super().to_sub_dict(full_data=full_data)
|
# sample = super().to_sub_dict(full_data=full_data)
|
||||||
sample['ww_processing_num'] = self.ww_processing_num
|
# sample['ww_processing_num'] = self.ww_processing_num
|
||||||
sample['sample_location'] = self.sample_location
|
# sample['sample_location'] = self.sample_location
|
||||||
sample['received_date'] = self.received_date
|
# sample['received_date'] = self.received_date
|
||||||
sample['collection_date'] = self.collection_date
|
# sample['collection_date'] = self.collection_date
|
||||||
return sample
|
# return sample
|
||||||
|
#
|
||||||
@classmethod
|
# @classmethod
|
||||||
def parse_sample(cls, input_dict: dict) -> dict:
|
# def parse_sample(cls, input_dict: dict) -> dict:
|
||||||
"""
|
# """
|
||||||
Custom sample parser. Extends parent
|
# Custom sample parser. Extends parent
|
||||||
|
#
|
||||||
Args:
|
# Args:
|
||||||
input_dict (dict): Basic parser results for this sample.
|
# input_dict (dict): Basic parser results for this sample.
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
dict: Updated parser results.
|
# dict: Updated parser results.
|
||||||
"""
|
# """
|
||||||
output_dict = super().parse_sample(input_dict)
|
# output_dict = super().parse_sample(input_dict)
|
||||||
disallowed = ["", None, "None"]
|
# disallowed = ["", None, "None"]
|
||||||
try:
|
# try:
|
||||||
check = output_dict['rsl_number'] in disallowed
|
# check = output_dict['rsl_number'] in disallowed
|
||||||
except KeyError:
|
# except KeyError:
|
||||||
check = True
|
# check = True
|
||||||
if check:
|
# if check:
|
||||||
output_dict['rsl_number'] = "RSL-WW-" + output_dict['ww_processing_num']
|
# output_dict['rsl_number'] = "RSL-WW-" + output_dict['ww_processing_num']
|
||||||
if output_dict['ww_full_sample_id'] is not None and output_dict["submitter_id"] in disallowed:
|
# if output_dict['ww_full_sample_id'] is not None and output_dict["submitter_id"] in disallowed:
|
||||||
output_dict["submitter_id"] = output_dict['ww_full_sample_id']
|
# output_dict["submitter_id"] = output_dict['ww_full_sample_id']
|
||||||
# check = check_key_or_attr("rsl_number", output_dict, check_none=True)
|
# # check = check_key_or_attr("rsl_number", output_dict, check_none=True)
|
||||||
return output_dict
|
# return output_dict
|
||||||
|
#
|
||||||
@classproperty
|
# @classproperty
|
||||||
def searchables(cls) -> List[dict]:
|
# def searchables(cls) -> List[dict]:
|
||||||
"""
|
# """
|
||||||
Delivers a list of fields that can be used in fuzzy search. Extends parent.
|
# Delivers a list of fields that can be used in fuzzy search. Extends parent.
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
List[str]: List of fields.
|
# List[str]: List of fields.
|
||||||
"""
|
# """
|
||||||
searchables = deepcopy(super().searchables)
|
# searchables = deepcopy(super().searchables)
|
||||||
for item in ["ww_processing_num", "ww_full_sample_id", "rsl_number"]:
|
# for item in ["ww_processing_num", "ww_full_sample_id", "rsl_number"]:
|
||||||
label = item.strip("ww_").replace("_", " ").replace("rsl", "RSL").title()
|
# label = item.strip("ww_").replace("_", " ").replace("rsl", "RSL").title()
|
||||||
searchables.append(dict(label=label, field=item))
|
# searchables.append(dict(label=label, field=item))
|
||||||
return searchables
|
# return searchables
|
||||||
|
#
|
||||||
|
#
|
||||||
class BacterialCultureSample(BasicSample):
|
# class BacterialCultureSample(BasicSample):
|
||||||
"""
|
# """
|
||||||
base of bacterial culture sample
|
# base of bacterial culture sample
|
||||||
"""
|
# """
|
||||||
id = Column(INTEGER, ForeignKey('_basicsample.id'), primary_key=True)
|
# id = Column(INTEGER, ForeignKey('_basicsample.id'), primary_key=True)
|
||||||
organism = Column(String(64)) #: bacterial specimen
|
# organism = Column(String(64)) #: bacterial specimen
|
||||||
concentration = Column(String(16)) #: sample concentration
|
# concentration = Column(String(16)) #: sample concentration
|
||||||
control = relationship("IridaControl", back_populates="sample", uselist=False)
|
# control = relationship("IridaControl", back_populates="sample", uselist=False)
|
||||||
__mapper_args__ = dict(polymorphic_identity="Bacterial Culture Sample",
|
# __mapper_args__ = dict(polymorphic_identity="Bacterial Culture Sample",
|
||||||
polymorphic_load="inline",
|
# polymorphic_load="inline",
|
||||||
inherit_condition=(id == BasicSample.id))
|
# inherit_condition=(id == BasicSample.id))
|
||||||
|
#
|
||||||
def to_sub_dict(self, full_data: bool = False) -> dict:
|
# def to_sub_dict(self, full_data: bool = False) -> dict:
|
||||||
"""
|
# """
|
||||||
gui friendly dictionary, extends parent method.
|
# gui friendly dictionary, extends parent method.
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above
|
# dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above
|
||||||
"""
|
# """
|
||||||
sample = super().to_sub_dict(full_data=full_data)
|
# sample = super().to_sub_dict(full_data=full_data)
|
||||||
sample['name'] = self.submitter_id
|
# sample['name'] = self.submitter_id
|
||||||
sample['organism'] = self.organism
|
# sample['organism'] = self.organism
|
||||||
try:
|
# try:
|
||||||
sample['concentration'] = f"{float(self.concentration):.2f}"
|
# sample['concentration'] = f"{float(self.concentration):.2f}"
|
||||||
except (TypeError, ValueError):
|
# except (TypeError, ValueError):
|
||||||
sample['concentration'] = 0.0
|
# sample['concentration'] = 0.0
|
||||||
if self.control is not None:
|
# if self.control is not None:
|
||||||
sample['colour'] = [0, 128, 0]
|
# sample['colour'] = [0, 128, 0]
|
||||||
target = next((v for k, v in self.control.controltype.targets.items() if k == self.control.subtype),
|
# target = next((v for k, v in self.control.controltype.targets.items() if k == self.control.subtype),
|
||||||
"Not Available")
|
# "Not Available")
|
||||||
try:
|
# try:
|
||||||
target = ", ".join(target)
|
# target = ", ".join(target)
|
||||||
except:
|
# except:
|
||||||
target = "None"
|
# target = "None"
|
||||||
sample['tooltip'] = f"\nControl: {self.control.controltype.name} - {target}"
|
# sample['tooltip'] = f"\nControl: {self.control.controltype.name} - {target}"
|
||||||
return sample
|
# return sample
|
||||||
|
#
|
||||||
|
|
||||||
# # NOTE: Submission to Sample Associations
|
# # NOTE: Submission to Sample Associations
|
||||||
|
|
||||||
@@ -2920,26 +2928,27 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
|
|
||||||
id = Column(INTEGER, unique=True, nullable=False) #: id to be used for inheriting purposes
|
id = Column(INTEGER, unique=True, nullable=False) #: id to be used for inheriting purposes
|
||||||
sample_id = Column(INTEGER, ForeignKey("_basicsample.id"), nullable=False) #: id of associated sample
|
sample_id = Column(INTEGER, ForeignKey("_basicsample.id"), nullable=False) #: id of associated sample
|
||||||
submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id"), primary_key=True) #: id of associated submission
|
submission_id = Column(INTEGER, ForeignKey("_clientsubmission.id"), primary_key=True) #: id of associated submission
|
||||||
row = Column(INTEGER, primary_key=True) #: row on the 96 well plate
|
row = Column(INTEGER, primary_key=True) #: row on the 96 well plate
|
||||||
column = Column(INTEGER, primary_key=True) #: column on the 96 well plate
|
column = Column(INTEGER, primary_key=True) #: column on the 96 well plate
|
||||||
submission_rank = Column(INTEGER, nullable=False, default=0) #: Location in sample list
|
submission_rank = Column(INTEGER, nullable=False, default=0) #: Location in sample list
|
||||||
|
misc_info = Column(JSON)
|
||||||
|
|
||||||
# NOTE: reference to the Submission object
|
# NOTE: reference to the Submission object
|
||||||
submission = relationship(BasicSubmission,
|
submission = relationship(ClientSubmission,
|
||||||
back_populates="submission_sample_associations") #: associated submission
|
back_populates="submission_sample_associations") #: associated submission
|
||||||
|
|
||||||
# NOTE: reference to the Sample object
|
# NOTE: reference to the Sample object
|
||||||
sample = relationship(BasicSample, back_populates="sample_submission_associations") #: associated sample
|
sample = relationship(BasicSample, back_populates="sample_submission_associations") #: associated sample
|
||||||
|
|
||||||
base_sub_type = Column(String) #: string of mode_sub_type name
|
# base_sub_type = Column(String) #: string of mode_sub_type name
|
||||||
|
|
||||||
# NOTE: Refers to the type of parent.
|
# NOTE: Refers to the type of parent.
|
||||||
__mapper_args__ = {
|
# __mapper_args__ = {
|
||||||
"polymorphic_identity": "Basic Association",
|
# "polymorphic_identity": "Basic Association",
|
||||||
"polymorphic_on": base_sub_type,
|
# "polymorphic_on": base_sub_type,
|
||||||
"with_polymorphic": "*",
|
# "with_polymorphic": "*",
|
||||||
}
|
# }
|
||||||
|
|
||||||
def __init__(self, submission: BasicSubmission = None, sample: BasicSample = None, row: int = 1, column: int = 1,
|
def __init__(self, submission: BasicSubmission = None, sample: BasicSample = None, row: int = 1, column: int = 1,
|
||||||
id: int | None = None, submission_rank: int = 0, **kwargs):
|
id: int | None = None, submission_rank: int = 0, **kwargs):
|
||||||
@@ -3183,96 +3192,96 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
raise AttributeError(f"Delete not implemented for {self.__class__}")
|
raise AttributeError(f"Delete not implemented for {self.__class__}")
|
||||||
|
|
||||||
|
|
||||||
class WastewaterAssociation(SubmissionSampleAssociation):
|
# class WastewaterAssociation(SubmissionSampleAssociation):
|
||||||
"""
|
# """
|
||||||
table containing wastewater specific submission/sample associations
|
# table containing wastewater specific submission/sample associations
|
||||||
DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
# DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
||||||
"""
|
# """
|
||||||
id = Column(INTEGER, ForeignKey("_submissionsampleassociation.id"), primary_key=True)
|
# id = Column(INTEGER, ForeignKey("_submissionsampleassociation.id"), primary_key=True)
|
||||||
ct_n1 = Column(FLOAT(2)) #: AKA ct for N1
|
# ct_n1 = Column(FLOAT(2)) #: AKA ct for N1
|
||||||
ct_n2 = Column(FLOAT(2)) #: AKA ct for N2
|
# ct_n2 = Column(FLOAT(2)) #: AKA ct for N2
|
||||||
n1_status = Column(String(32)) #: positive or negative for N1
|
# n1_status = Column(String(32)) #: positive or negative for N1
|
||||||
n2_status = Column(String(32)) #: positive or negative for N2
|
# n2_status = Column(String(32)) #: positive or negative for N2
|
||||||
pcr_results = Column(JSON) #: imported PCR status from QuantStudio
|
# pcr_results = Column(JSON) #: imported PCR status from QuantStudio
|
||||||
|
#
|
||||||
__mapper_args__ = dict(polymorphic_identity="Wastewater Association",
|
# __mapper_args__ = dict(polymorphic_identity="Wastewater Association",
|
||||||
polymorphic_load="inline",
|
# polymorphic_load="inline",
|
||||||
inherit_condition=(id == SubmissionSampleAssociation.id))
|
# inherit_condition=(id == SubmissionSampleAssociation.id))
|
||||||
|
#
|
||||||
def to_sub_dict(self) -> dict:
|
# def to_sub_dict(self) -> dict:
|
||||||
"""
|
# """
|
||||||
Returns a sample dictionary updated with instance information. Extends parent
|
# Returns a sample dictionary updated with instance information. Extends parent
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
dict: Updated dictionary with row, column and well updated
|
# dict: Updated dictionary with row, column and well updated
|
||||||
"""
|
# """
|
||||||
|
#
|
||||||
sample = super().to_sub_dict()
|
# sample = super().to_sub_dict()
|
||||||
sample['ct'] = f"({self.ct_n1}, {self.ct_n2})"
|
# sample['ct'] = f"({self.ct_n1}, {self.ct_n2})"
|
||||||
try:
|
# try:
|
||||||
sample['source_row'] = row_keys[self.sample.sample_location[0]]
|
# sample['source_row'] = row_keys[self.sample.sample_location[0]]
|
||||||
sample['source_column'] = int(self.sample.sample_location[1:])
|
# sample['source_column'] = int(self.sample.sample_location[1:])
|
||||||
except (TypeError, AttributeError) as e:
|
# except (TypeError, AttributeError) as e:
|
||||||
logger.error(f"Couldn't set sources for {self.sample.rsl_number}. Looks like there isn't data.")
|
# logger.error(f"Couldn't set sources for {self.sample.rsl_number}. Looks like there isn't data.")
|
||||||
try:
|
# try:
|
||||||
sample['positive'] = any(["positive" in item for item in [self.n1_status, self.n2_status]])
|
# sample['positive'] = any(["positive" in item for item in [self.n1_status, self.n2_status]])
|
||||||
except (TypeError, AttributeError) as e:
|
# except (TypeError, AttributeError) as e:
|
||||||
logger.error(f"Couldn't check positives for {self.sample.rsl_number}. Looks like there isn't PCR data.")
|
# logger.error(f"Couldn't check positives for {self.sample.rsl_number}. Looks like there isn't PCR data.")
|
||||||
return sample
|
# return sample
|
||||||
|
#
|
||||||
@property
|
# @property
|
||||||
def hitpicked(self) -> dict | None:
|
# def hitpicked(self) -> dict | None:
|
||||||
"""
|
# """
|
||||||
Outputs a dictionary usable for html plate maps. Extends parent
|
# Outputs a dictionary usable for html plate maps. Extends parent
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
dict: dictionary of sample id, row and column in elution plate
|
# dict: dictionary of sample id, row and column in elution plate
|
||||||
"""
|
# """
|
||||||
sample = super().hitpicked
|
# sample = super().hitpicked
|
||||||
try:
|
# try:
|
||||||
scaler = max([self.ct_n1, self.ct_n2])
|
# scaler = max([self.ct_n1, self.ct_n2])
|
||||||
except TypeError:
|
# except TypeError:
|
||||||
scaler = 0.0
|
# scaler = 0.0
|
||||||
if scaler == 0.0:
|
# if scaler == 0.0:
|
||||||
scaler = 45
|
# scaler = 45
|
||||||
bg = (45 - scaler) * 17
|
# bg = (45 - scaler) * 17
|
||||||
red = min([64 + bg, 255])
|
# red = min([64 + bg, 255])
|
||||||
grn = max([255 - bg, 0])
|
# grn = max([255 - bg, 0])
|
||||||
blu = 128
|
# blu = 128
|
||||||
sample['background_color'] = f"rgb({red}, {grn}, {blu})"
|
# sample['background_color'] = f"rgb({red}, {grn}, {blu})"
|
||||||
try:
|
# try:
|
||||||
sample[
|
# sample[
|
||||||
'tooltip'] += f"<br>- ct N1: {'{:.2f}'.format(self.ct_n1)}<br>- ct N2: {'{:.2f}'.format(self.ct_n2)}"
|
# 'tooltip'] += f"<br>- ct N1: {'{:.2f}'.format(self.ct_n1)}<br>- ct N2: {'{:.2f}'.format(self.ct_n2)}"
|
||||||
except (TypeError, AttributeError) as e:
|
# except (TypeError, AttributeError) as e:
|
||||||
logger.error(f"Couldn't set tooltip for {self.sample.rsl_number}. Looks like there isn't PCR data.")
|
# logger.error(f"Couldn't set tooltip for {self.sample.rsl_number}. Looks like there isn't PCR data.")
|
||||||
return sample
|
# return sample
|
||||||
|
#
|
||||||
|
#
|
||||||
class WastewaterArticAssociation(SubmissionSampleAssociation):
|
# class WastewaterArticAssociation(SubmissionSampleAssociation):
|
||||||
"""
|
# """
|
||||||
table containing wastewater artic specific submission/sample associations
|
# table containing wastewater artic specific submission/sample associations
|
||||||
DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
# DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
||||||
"""
|
# """
|
||||||
id = Column(INTEGER, ForeignKey("_submissionsampleassociation.id"), primary_key=True)
|
# id = Column(INTEGER, ForeignKey("_submissionsampleassociation.id"), primary_key=True)
|
||||||
source_plate = Column(String(32))
|
# source_plate = Column(String(32))
|
||||||
source_plate_number = Column(INTEGER)
|
# source_plate_number = Column(INTEGER)
|
||||||
source_well = Column(String(8))
|
# source_well = Column(String(8))
|
||||||
ct = Column(String(8)) #: AKA ct for N1
|
# ct = Column(String(8)) #: AKA ct for N1
|
||||||
|
#
|
||||||
__mapper_args__ = dict(polymorphic_identity="Wastewater Artic Association",
|
# __mapper_args__ = dict(polymorphic_identity="Wastewater Artic Association",
|
||||||
polymorphic_load="inline",
|
# polymorphic_load="inline",
|
||||||
inherit_condition=(id == SubmissionSampleAssociation.id))
|
# inherit_condition=(id == SubmissionSampleAssociation.id))
|
||||||
|
#
|
||||||
def to_sub_dict(self) -> dict:
|
# def to_sub_dict(self) -> dict:
|
||||||
"""
|
# """
|
||||||
Returns a sample dictionary updated with instance information. Extends parent
|
# Returns a sample dictionary updated with instance information. Extends parent
|
||||||
|
#
|
||||||
Returns:
|
# Returns:
|
||||||
dict: Updated dictionary with row, column and well updated
|
# dict: Updated dictionary with row, column and well updated
|
||||||
"""
|
# """
|
||||||
sample = super().to_sub_dict()
|
# sample = super().to_sub_dict()
|
||||||
sample['ct'] = self.ct
|
# sample['ct'] = self.ct
|
||||||
sample['source_plate'] = self.source_plate
|
# sample['source_plate'] = self.source_plate
|
||||||
sample['source_plate_number'] = self.source_plate_number
|
# sample['source_plate_number'] = self.source_plate_number
|
||||||
sample['source_well'] = self.source_well
|
# sample['source_well'] = self.source_well
|
||||||
return sample
|
# return sample
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class ReportMaker(object):
|
|||||||
# NOTE: Set page size to zero to override limiting query size.
|
# NOTE: Set page size to zero to override limiting query size.
|
||||||
self.subs = BasicSubmission.query(start_date=start_date, end_date=end_date, page_size=0)
|
self.subs = BasicSubmission.query(start_date=start_date, end_date=end_date, page_size=0)
|
||||||
if organizations is not None:
|
if organizations is not None:
|
||||||
self.subs = [sub for sub in self.subs if sub.submitting_lab.name in organizations]
|
self.subs = [sub for sub in self.subs if sub.client_submission.submitting_lab.name in organizations]
|
||||||
self.detailed_df, self.summary_df = self.make_report_xlsx()
|
self.detailed_df, self.summary_df = self.make_report_xlsx()
|
||||||
self.html = self.make_report_html(df=self.summary_df)
|
self.html = self.make_report_html(df=self.summary_df)
|
||||||
|
|
||||||
@@ -183,7 +183,10 @@ class TurnaroundMaker(ReportArchetype):
|
|||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
tat = None
|
tat = None
|
||||||
if not tat:
|
if not tat:
|
||||||
|
try:
|
||||||
tat = ctx.TaT_threshold
|
tat = ctx.TaT_threshold
|
||||||
|
except AttributeError:
|
||||||
|
tat = 3
|
||||||
try:
|
try:
|
||||||
tat_ok = days <= tat
|
tat_ok = days <= tat
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import logging, numpy as np
|
|||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import Tuple, List
|
from typing import Tuple, List
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from backend.db.models import WastewaterArtic
|
from backend.db.models import BasicSubmission
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ logger = logging.getLogger(f"submissions.{__name__}")
|
|||||||
# Main window class
|
# Main window class
|
||||||
class GelBox(QDialog):
|
class GelBox(QDialog):
|
||||||
|
|
||||||
def __init__(self, parent, img_path: str | Path, submission: WastewaterArtic):
|
def __init__(self, parent, img_path: str | Path, submission: BasicSubmission):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
# NOTE: setting title
|
# NOTE: setting title
|
||||||
self.setWindowTitle(f"Gel - {img_path}")
|
self.setWindowTitle(f"Gel - {img_path}")
|
||||||
|
|||||||
@@ -1195,16 +1195,23 @@ class Settings(BaseSettings, extra="allow"):
|
|||||||
func = function[1]
|
func = function[1]
|
||||||
# NOTE: assign function based on its name being in config: startup/teardown
|
# NOTE: assign function based on its name being in config: startup/teardown
|
||||||
# NOTE: scripts must be registered using {name: Null} in the database
|
# NOTE: scripts must be registered using {name: Null} in the database
|
||||||
|
try:
|
||||||
if name in self.startup_scripts.keys():
|
if name in self.startup_scripts.keys():
|
||||||
self.startup_scripts[name] = func
|
self.startup_scripts[name] = func
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
if name in self.teardown_scripts.keys():
|
if name in self.teardown_scripts.keys():
|
||||||
self.teardown_scripts[name] = func
|
self.teardown_scripts[name] = func
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
@timer
|
@timer
|
||||||
def run_startup(self):
|
def run_startup(self):
|
||||||
"""
|
"""
|
||||||
Runs startup scripts.
|
Runs startup scripts.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
for script in self.startup_scripts.values():
|
for script in self.startup_scripts.values():
|
||||||
try:
|
try:
|
||||||
logger.info(f"Running startup script: {script.__name__}")
|
logger.info(f"Running startup script: {script.__name__}")
|
||||||
@@ -1212,12 +1219,15 @@ class Settings(BaseSettings, extra="allow"):
|
|||||||
thread.start()
|
thread.start()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.error(f"Couldn't run startup script: {script}")
|
logger.error(f"Couldn't run startup script: {script}")
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
@timer
|
@timer
|
||||||
def run_teardown(self):
|
def run_teardown(self):
|
||||||
"""
|
"""
|
||||||
Runs teardown scripts.
|
Runs teardown scripts.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
for script in self.teardown_scripts.values():
|
for script in self.teardown_scripts.values():
|
||||||
try:
|
try:
|
||||||
logger.info(f"Running teardown script: {script.__name__}")
|
logger.info(f"Running teardown script: {script.__name__}")
|
||||||
@@ -1225,6 +1235,8 @@ class Settings(BaseSettings, extra="allow"):
|
|||||||
thread.start()
|
thread.start()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.error(f"Couldn't run teardown script: {script}")
|
logger.error(f"Couldn't run teardown script: {script}")
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_alembic_db_path(cls, alembic_path, mode=Literal['path', 'schema', 'user', 'pass']) -> Path | str:
|
def get_alembic_db_path(cls, alembic_path, mode=Literal['path', 'schema', 'user', 'pass']) -> Path | str:
|
||||||
|
|||||||
Reference in New Issue
Block a user