Improvements to submission querying.
This commit is contained in:
4
TODO.md
4
TODO.md
@@ -1,3 +1,7 @@
|
|||||||
|
- [ ] Make reporting better.
|
||||||
|
- [ ] Build master query method?
|
||||||
|
- Obviously there will need to be extensions, but I feel the attr method I have in Submissions could work.
|
||||||
|
- [x] Fix Artic RSLNamer
|
||||||
- [x] Put "Not applicable" reagents in to_dict() method.
|
- [x] Put "Not applicable" reagents in to_dict() method.
|
||||||
- Currently in to_pydantic().
|
- Currently in to_pydantic().
|
||||||
- [x] Critical: Convert Json lits to dicts so I can have them update properly without using crashy Sqlalchemy-json
|
- [x] Critical: Convert Json lits to dicts so I can have them update properly without using crashy Sqlalchemy-json
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
Contains all models for sqlalchemy
|
Contains all models for sqlalchemy
|
||||||
'''
|
'''
|
||||||
import sys
|
import sys
|
||||||
from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query
|
from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query, Session
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
|
from typing import Any, List
|
||||||
|
from pathlib import Path
|
||||||
# Load testing environment
|
# Load testing environment
|
||||||
if 'pytest' in sys.modules:
|
if 'pytest' in sys.modules:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -11,28 +13,32 @@ if 'pytest' in sys.modules:
|
|||||||
|
|
||||||
Base: DeclarativeMeta = declarative_base()
|
Base: DeclarativeMeta = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
Args:
|
|
||||||
Base (DeclarativeMeta): Declarative base for metadata.
|
|
||||||
"""
|
"""
|
||||||
__abstract__ = True
|
__abstract__ = True #: Will not be added to DB
|
||||||
|
|
||||||
__table_args__ = {'extend_existing': True}
|
__table_args__ = {'extend_existing': True} #: Will only add new columns
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def __tablename__(cls):
|
def __tablename__(cls) -> str:
|
||||||
"""
|
"""
|
||||||
Set tablename to lowercase class name
|
Sets table name to lower case class name.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: lower case class name
|
||||||
"""
|
"""
|
||||||
return f"_{cls.__name__.lower()}"
|
return f"_{cls.__name__.lower()}"
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def __database_session__(cls):
|
def __database_session__(cls) -> Session:
|
||||||
"""
|
"""
|
||||||
Pull db session from ctx
|
Pull db session from ctx to be used in operations
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Session: DB session from ctx settings.
|
||||||
"""
|
"""
|
||||||
if not 'pytest' in sys.modules:
|
if not 'pytest' in sys.modules:
|
||||||
from tools import ctx
|
from tools import ctx
|
||||||
@@ -41,9 +47,12 @@ class BaseClass(Base):
|
|||||||
return ctx.database_session
|
return ctx.database_session
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def __directory_path__(cls):
|
def __directory_path__(cls) -> Path:
|
||||||
"""
|
"""
|
||||||
Pull submission directory from ctx
|
Pull directory path from ctx to be used in operations.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path: Location of the Submissions directory in Settings object
|
||||||
"""
|
"""
|
||||||
if not 'pytest' in sys.modules:
|
if not 'pytest' in sys.modules:
|
||||||
from tools import ctx
|
from tools import ctx
|
||||||
@@ -52,9 +61,12 @@ class BaseClass(Base):
|
|||||||
return ctx.directory_path
|
return ctx.directory_path
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def __backup_path__(cls):
|
def __backup_path__(cls) -> Path:
|
||||||
"""
|
"""
|
||||||
Pull backup directory from ctx
|
Pull backup directory path from ctx to be used in operations.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path: Location of the Submissions backup directory in Settings object
|
||||||
"""
|
"""
|
||||||
if not 'pytest' in sys.modules:
|
if not 'pytest' in sys.modules:
|
||||||
from tools import ctx
|
from tools import ctx
|
||||||
@@ -62,16 +74,17 @@ class BaseClass(Base):
|
|||||||
from test_settings import ctx
|
from test_settings import ctx
|
||||||
return ctx.backup_path
|
return ctx.backup_path
|
||||||
|
|
||||||
def query_return(query:Query, limit:int=0):
|
@classmethod
|
||||||
|
def execute_query(cls, query: Query, limit: int = 0) -> Any | List[Any]:
|
||||||
"""
|
"""
|
||||||
Execute sqlalchemy query.
|
Execute sqlalchemy query.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query (Query): Query object
|
query (Query): input query object
|
||||||
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
limit (int): Maximum number of results. (0 = all)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
_type_: Query result.
|
Any | List[Any]: Single result if limit = 1 or List if other.
|
||||||
"""
|
"""
|
||||||
with query.session.no_autoflush:
|
with query.session.no_autoflush:
|
||||||
match limit:
|
match limit:
|
||||||
@@ -94,6 +107,7 @@ class BaseClass(Base):
|
|||||||
logger.critical(f"Problem saving object: {e}")
|
logger.critical(f"Problem saving object: {e}")
|
||||||
self.__database_session__.rollback()
|
self.__database_session__.rollback()
|
||||||
|
|
||||||
|
|
||||||
from .controls import *
|
from .controls import *
|
||||||
# import order must go: orgs, kit, subs due to circular import issues
|
# import order must go: orgs, kit, subs due to circular import issues
|
||||||
from .organizations import *
|
from .organizations import *
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
'''
|
"""
|
||||||
All control related models.
|
All control related models.
|
||||||
'''
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey
|
from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey
|
||||||
from sqlalchemy.orm import relationship, Query
|
from sqlalchemy.orm import relationship, Query
|
||||||
import logging, re, sys
|
import logging, re
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from . import BaseClass
|
from . import BaseClass
|
||||||
from tools import setup_lookup
|
from tools import setup_lookup
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from typing import List
|
from typing import List
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
|
from re import Pattern
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
|
||||||
class ControlType(BaseClass):
|
class ControlType(BaseClass):
|
||||||
"""
|
"""
|
||||||
Base class of a control archetype.
|
Base class of a control archetype.
|
||||||
"""
|
"""
|
||||||
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
name = Column(String(255), unique=True) #: controltype name (e.g. MCS)
|
||||||
name = Column(String(255), unique=True) #: controltype name (e.g. MCS)
|
targets = Column(JSON) #: organisms checked for
|
||||||
targets = Column(JSON) #: organisms checked for
|
instances = relationship("Control", back_populates="controltype") #: control samples created of this type.
|
||||||
instances = relationship("Control", back_populates="controltype") #: control samples created of this type.
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<ControlType({self.name})>"
|
return f"<ControlType({self.name})>"
|
||||||
@@ -31,29 +31,29 @@ class ControlType(BaseClass):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@setup_lookup
|
||||||
def query(cls,
|
def query(cls,
|
||||||
name:str=None,
|
name: str = None,
|
||||||
limit:int=0
|
limit: int = 0
|
||||||
) -> ControlType|List[ControlType]:
|
) -> ControlType | List[ControlType]:
|
||||||
"""
|
"""
|
||||||
Lookup control archetypes in the database
|
Lookup control archetypes in the database
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str, optional): Control type name (limits results to 1). Defaults to None.
|
name (str, optional): Name of the desired controltype. Defaults to None.
|
||||||
limit (int, optional): Maximum number of results to return. Defaults to 0.
|
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
models.ControlType|List[models.ControlType]: ControlType(s) of interest.
|
ControlType | List[ControlType]: Single result if the limit = 1, else a list.
|
||||||
"""
|
"""
|
||||||
query = cls.__database_session__.query(cls)
|
query = cls.__database_session__.query(cls)
|
||||||
match name:
|
match name:
|
||||||
case str():
|
case str():
|
||||||
query = query.filter(cls.name==name)
|
query = query.filter(cls.name == name)
|
||||||
limit = 1
|
limit = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
def get_subtypes(self, mode:str) -> List[str]:
|
def get_subtypes(self, mode: str) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Get subtypes associated with this controltype
|
Get subtypes associated with this controltype
|
||||||
|
|
||||||
@@ -64,54 +64,66 @@ class ControlType(BaseClass):
|
|||||||
List[str]: list of subtypes available
|
List[str]: list of subtypes available
|
||||||
"""
|
"""
|
||||||
# Get first instance since all should have same subtypes
|
# Get first instance since all should have same subtypes
|
||||||
# outs = self.instances[0]
|
|
||||||
# Get mode of instance
|
# Get mode of instance
|
||||||
# jsoner = json.loads(getattr(outs, mode))
|
|
||||||
jsoner = getattr(self.instances[0], mode)
|
jsoner = getattr(self.instances[0], mode)
|
||||||
logger.debug(f"JSON out: {jsoner.keys()}")
|
# logger.debug(f"JSON out: {jsoner.keys()}")
|
||||||
try:
|
try:
|
||||||
# Pick genera (all should have same subtypes)
|
# Pick genera (all should have same subtypes)
|
||||||
genera = list(jsoner.keys())[0]
|
genera = list(jsoner.keys())[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return []
|
return []
|
||||||
|
# remove items that don't have relevant data
|
||||||
subtypes = [item for item in jsoner[genera] if "_hashes" not in item and "_ratio" not in item]
|
subtypes = [item for item in jsoner[genera] if "_hashes" not in item and "_ratio" not in item]
|
||||||
return subtypes
|
return subtypes
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_positive_control_types(cls):
|
def get_positive_control_types(cls) -> List[ControlType]:
|
||||||
|
"""
|
||||||
|
Gets list of Control types if they have targets
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[ControlType]: Control types that have targets
|
||||||
|
"""
|
||||||
return [item for item in cls.query() if item.targets != []]
|
return [item for item in cls.query() if item.targets != []]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build_positive_regex(cls):
|
def build_positive_regex(cls) -> Pattern:
|
||||||
|
"""
|
||||||
|
Creates a re.Pattern that will look for positive control types
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Pattern: Constructed pattern
|
||||||
|
"""
|
||||||
strings = list(set([item.name.split("-")[0] for item in cls.get_positive_control_types()]))
|
strings = list(set([item.name.split("-")[0] for item in cls.get_positive_control_types()]))
|
||||||
return re.compile(rf"(^{'|^'.join(strings)})-.*", flags=re.IGNORECASE)
|
return re.compile(rf"(^{'|^'.join(strings)})-.*", flags=re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
class Control(BaseClass):
|
class Control(BaseClass):
|
||||||
"""
|
"""
|
||||||
Base class of a control sample.
|
Base class of a control sample.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
parent_id = Column(String, ForeignKey("_controltype.id", name="fk_control_parent_id")) #: primary key of control type
|
parent_id = Column(String,
|
||||||
controltype = relationship("ControlType", back_populates="instances", foreign_keys=[parent_id]) #: reference to parent control type
|
ForeignKey("_controltype.id", name="fk_control_parent_id")) #: primary key of control type
|
||||||
name = Column(String(255), unique=True) #: Sample ID
|
controltype = relationship("ControlType", back_populates="instances",
|
||||||
submitted_date = Column(TIMESTAMP) #: Date submitted to Robotics
|
foreign_keys=[parent_id]) #: reference to parent control type
|
||||||
contains = Column(JSON) #: unstructured hashes in contains.tsv for each organism
|
name = Column(String(255), unique=True) #: Sample ID
|
||||||
matches = Column(JSON) #: unstructured hashes in matches.tsv for each organism
|
submitted_date = Column(TIMESTAMP) #: Date submitted to Robotics
|
||||||
kraken = Column(JSON) #: unstructured output from kraken_report
|
contains = Column(JSON) #: unstructured hashes in contains.tsv for each organism
|
||||||
submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id")) #: parent submission id
|
matches = Column(JSON) #: unstructured hashes in matches.tsv for each organism
|
||||||
submission = relationship("BacterialCulture", back_populates="controls", foreign_keys=[submission_id]) #: parent submission
|
kraken = Column(JSON) #: unstructured output from kraken_report
|
||||||
refseq_version = Column(String(16)) #: version of refseq used in fastq parsing
|
submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id")) #: parent submission id
|
||||||
kraken2_version = Column(String(16)) #: version of kraken2 used in fastq parsing
|
submission = relationship("BacterialCulture", back_populates="controls",
|
||||||
kraken2_db_version = Column(String(32)) #: folder name of kraken2 db
|
foreign_keys=[submission_id]) #: parent submission
|
||||||
sample = relationship("BacterialCultureSample", back_populates="control") #: This control's submission sample
|
refseq_version = Column(String(16)) #: version of refseq used in fastq parsing
|
||||||
sample_id = Column(INTEGER, ForeignKey("_basicsample.id", ondelete="SET NULL", name="cont_BCS_id")) #: sample id key
|
kraken2_version = Column(String(16)) #: version of kraken2 used in fastq parsing
|
||||||
|
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,
|
||||||
|
ForeignKey("_basicsample.id", ondelete="SET NULL", name="cont_BCS_id")) #: sample id key
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
"""
|
|
||||||
Returns:
|
|
||||||
str: Representation of self
|
|
||||||
"""
|
|
||||||
return f"<Control({self.name})>"
|
return f"<Control({self.name})>"
|
||||||
|
|
||||||
def to_sub_dict(self) -> dict:
|
def to_sub_dict(self) -> dict:
|
||||||
@@ -133,7 +145,8 @@ class Control(BaseClass):
|
|||||||
for item in kraken:
|
for item in kraken:
|
||||||
# logger.debug("calculating kraken percent (overwrites what's already been scraped)")
|
# logger.debug("calculating kraken percent (overwrites what's already been scraped)")
|
||||||
kraken_percent = kraken[item]['kraken_count'] / kraken_cnt_total
|
kraken_percent = kraken[item]['kraken_count'] / kraken_cnt_total
|
||||||
new_kraken.append({'name': item, 'kraken_count':kraken[item]['kraken_count'], 'kraken_percent':"{0:.0%}".format(kraken_percent)})
|
new_kraken.append({'name': item, 'kraken_count': kraken[item]['kraken_count'],
|
||||||
|
'kraken_percent': "{0:.0%}".format(kraken_percent)})
|
||||||
new_kraken = sorted(new_kraken, key=itemgetter('kraken_count'), reverse=True)
|
new_kraken = sorted(new_kraken, key=itemgetter('kraken_count'), reverse=True)
|
||||||
# logger.debug("setting targets")
|
# logger.debug("setting targets")
|
||||||
if self.controltype.targets == []:
|
if self.controltype.targets == []:
|
||||||
@@ -142,14 +155,14 @@ class Control(BaseClass):
|
|||||||
targets = self.controltype.targets
|
targets = self.controltype.targets
|
||||||
# logger.debug("constructing output dictionary")
|
# logger.debug("constructing output dictionary")
|
||||||
output = {
|
output = {
|
||||||
"name" : self.name,
|
"name": self.name,
|
||||||
"type" : self.controltype.name,
|
"type": self.controltype.name,
|
||||||
"targets" : ", ".join(targets),
|
"targets": ", ".join(targets),
|
||||||
"kraken" : new_kraken[0:5]
|
"kraken": new_kraken[0:5]
|
||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def convert_by_mode(self, mode:str) -> list[dict]:
|
def convert_by_mode(self, mode: str) -> list[dict]:
|
||||||
"""
|
"""
|
||||||
split this instance into analysis types for controls graphs
|
split this instance into analysis types for controls graphs
|
||||||
|
|
||||||
@@ -203,12 +216,12 @@ class Control(BaseClass):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@setup_lookup
|
||||||
def query(cls,
|
def query(cls,
|
||||||
control_type:ControlType|str|None=None,
|
control_type: ControlType | str | None = None,
|
||||||
start_date:date|str|int|None=None,
|
start_date: date | str | int | None = None,
|
||||||
end_date:date|str|int|None=None,
|
end_date: date | str | int | None = None,
|
||||||
control_name:str|None=None,
|
control_name: str | None = None,
|
||||||
limit:int=0
|
limit: int = 0
|
||||||
) -> Control|List[Control]:
|
) -> Control | List[Control]:
|
||||||
"""
|
"""
|
||||||
Lookup control objects in the database based on a number of parameters.
|
Lookup control objects in the database based on a number of parameters.
|
||||||
|
|
||||||
@@ -227,10 +240,10 @@ class Control(BaseClass):
|
|||||||
match control_type:
|
match control_type:
|
||||||
case ControlType():
|
case ControlType():
|
||||||
# logger.debug(f"Looking up control by control type: {control_type}")
|
# logger.debug(f"Looking up control by control type: {control_type}")
|
||||||
query = query.filter(cls.controltype==control_type)
|
query = query.filter(cls.controltype == control_type)
|
||||||
case str():
|
case str():
|
||||||
# logger.debug(f"Looking up control by control type: {control_type}")
|
# logger.debug(f"Looking up control by control type: {control_type}")
|
||||||
query = query.join(ControlType).filter(ControlType.name==control_type)
|
query = query.join(ControlType).filter(ControlType.name == control_type)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
# by date range
|
# by date range
|
||||||
@@ -247,7 +260,8 @@ class Control(BaseClass):
|
|||||||
start_date = start_date.strftime("%Y-%m-%d")
|
start_date = start_date.strftime("%Y-%m-%d")
|
||||||
case int():
|
case int():
|
||||||
# logger.debug(f"Lookup control by ordinal start date {start_date}")
|
# logger.debug(f"Lookup control by ordinal start date {start_date}")
|
||||||
start_date = datetime.fromordinal(datetime(1900, 1, 1).toordinal() + start_date - 2).date().strftime("%Y-%m-%d")
|
start_date = datetime.fromordinal(
|
||||||
|
datetime(1900, 1, 1).toordinal() + start_date - 2).date().strftime("%Y-%m-%d")
|
||||||
case _:
|
case _:
|
||||||
# logger.debug(f"Lookup control with parsed start date {start_date}")
|
# logger.debug(f"Lookup control with parsed start date {start_date}")
|
||||||
start_date = parse(start_date).strftime("%Y-%m-%d")
|
start_date = parse(start_date).strftime("%Y-%m-%d")
|
||||||
@@ -257,7 +271,8 @@ class Control(BaseClass):
|
|||||||
end_date = end_date.strftime("%Y-%m-%d")
|
end_date = end_date.strftime("%Y-%m-%d")
|
||||||
case int():
|
case int():
|
||||||
# logger.debug(f"Lookup control by ordinal end date {end_date}")
|
# logger.debug(f"Lookup control by ordinal end date {end_date}")
|
||||||
end_date = datetime.fromordinal(datetime(1900, 1, 1).toordinal() + end_date - 2).date().strftime("%Y-%m-%d")
|
end_date = datetime.fromordinal(datetime(1900, 1, 1).toordinal() + end_date - 2).date().strftime(
|
||||||
|
"%Y-%m-%d")
|
||||||
case _:
|
case _:
|
||||||
# logger.debug(f"Lookup control with parsed end date {end_date}")
|
# logger.debug(f"Lookup control with parsed end date {end_date}")
|
||||||
end_date = parse(end_date).strftime("%Y-%m-%d")
|
end_date = parse(end_date).strftime("%Y-%m-%d")
|
||||||
@@ -270,5 +285,4 @@ class Control(BaseClass):
|
|||||||
limit = 1
|
limit = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ class KitType(BaseClass):
|
|||||||
limit = 1
|
limit = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
@check_authorization
|
@check_authorization
|
||||||
def save(self):
|
def save(self):
|
||||||
@@ -303,7 +303,7 @@ class ReagentType(BaseClass):
|
|||||||
limit = 1
|
limit = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
def to_pydantic(self) -> "PydReagent":
|
def to_pydantic(self) -> "PydReagent":
|
||||||
"""
|
"""
|
||||||
@@ -464,7 +464,7 @@ class Reagent(BaseClass):
|
|||||||
limit = 1
|
limit = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
class Discount(BaseClass):
|
class Discount(BaseClass):
|
||||||
"""
|
"""
|
||||||
@@ -533,7 +533,7 @@ class Discount(BaseClass):
|
|||||||
case _:
|
case _:
|
||||||
# raise ValueError(f"Invalid value for kit type: {kit_type}")
|
# raise ValueError(f"Invalid value for kit type: {kit_type}")
|
||||||
pass
|
pass
|
||||||
return cls.query_return(query=query)
|
return cls.execute_query(query=query)
|
||||||
|
|
||||||
@check_authorization
|
@check_authorization
|
||||||
def save(self):
|
def save(self):
|
||||||
@@ -702,7 +702,7 @@ class SubmissionType(BaseClass):
|
|||||||
query = query.filter(cls.info_map.op('->')(key)!=None)
|
query = query.filter(cls.info_map.op('->')(key)!=None)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
@check_authorization
|
@check_authorization
|
||||||
def save(self):
|
def save(self):
|
||||||
@@ -781,7 +781,7 @@ class SubmissionTypeKitTypeAssociation(BaseClass):
|
|||||||
# logger.debug(f"Looking up {cls.__name__} by id {kit_type}")
|
# logger.debug(f"Looking up {cls.__name__} by id {kit_type}")
|
||||||
query = query.join(KitType).filter(KitType.id==kit_type)
|
query = query.join(KitType).filter(KitType.id==kit_type)
|
||||||
limit = query.count()
|
limit = query.count()
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
class KitTypeReagentTypeAssociation(BaseClass):
|
class KitTypeReagentTypeAssociation(BaseClass):
|
||||||
"""
|
"""
|
||||||
@@ -889,7 +889,7 @@ class KitTypeReagentTypeAssociation(BaseClass):
|
|||||||
pass
|
pass
|
||||||
if kit_type != None and reagent_type != None:
|
if kit_type != None and reagent_type != None:
|
||||||
limit = 1
|
limit = 1
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
class SubmissionReagentAssociation(BaseClass):
|
class SubmissionReagentAssociation(BaseClass):
|
||||||
"""
|
"""
|
||||||
@@ -956,7 +956,7 @@ class SubmissionReagentAssociation(BaseClass):
|
|||||||
query = query.join(BasicSubmission).filter(BasicSubmission.id==submission)
|
query = query.join(BasicSubmission).filter(BasicSubmission.id==submission)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
def to_sub_dict(self, extraction_kit) -> dict:
|
def to_sub_dict(self, extraction_kit) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -1083,7 +1083,7 @@ class Equipment(BaseClass):
|
|||||||
limit = 1
|
limit = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
def to_pydantic(self, submission_type:SubmissionType, extraction_kit:str|KitType|None=None) -> "PydEquipment":
|
def to_pydantic(self, submission_type:SubmissionType, extraction_kit:str|KitType|None=None) -> "PydEquipment":
|
||||||
"""
|
"""
|
||||||
@@ -1206,7 +1206,7 @@ class EquipmentRole(BaseClass):
|
|||||||
limit = 1
|
limit = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
def get_processes(self, submission_type:str|SubmissionType|None, extraction_kit:str|KitType|None=None) -> List[Process]:
|
def get_processes(self, submission_type:str|SubmissionType|None, extraction_kit:str|KitType|None=None) -> List[Process]:
|
||||||
"""
|
"""
|
||||||
@@ -1382,5 +1382,5 @@ class Process(BaseClass):
|
|||||||
limit = 1
|
limit = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
|
|||||||
@@ -33,10 +33,6 @@ class Organization(BaseClass):
|
|||||||
contacts = relationship("Contact", back_populates="organization", secondary=orgs_contacts) #: contacts involved with this org
|
contacts = relationship("Contact", back_populates="organization", secondary=orgs_contacts) #: contacts involved with this org
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
"""
|
|
||||||
Returns:
|
|
||||||
str: Representation of this Organization
|
|
||||||
"""
|
|
||||||
return f"<Organization({self.name})>"
|
return f"<Organization({self.name})>"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -70,7 +66,7 @@ class Organization(BaseClass):
|
|||||||
limit = 1
|
limit = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
@check_authorization
|
@check_authorization
|
||||||
def save(self):
|
def save(self):
|
||||||
@@ -137,5 +133,5 @@ class Contact(BaseClass):
|
|||||||
limit = 1
|
limit = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
@@ -47,7 +47,6 @@ class BasicSubmission(BaseClass):
|
|||||||
submission_type_name = Column(String, ForeignKey("_submissiontype.name", ondelete="SET NULL", name="fk_BS_subtype_name")) #: name of joined submission type
|
submission_type_name = Column(String, ForeignKey("_submissiontype.name", ondelete="SET NULL", name="fk_BS_subtype_name")) #: name of joined submission type
|
||||||
technician = Column(String(64)) #: initials of processing tech(s)
|
technician = Column(String(64)) #: initials of processing tech(s)
|
||||||
# Move this into custom types?
|
# Move this into custom types?
|
||||||
# reagents = relationship("Reagent", back_populates="submissions", secondary=reagents_submissions) #: relationship to reagents
|
|
||||||
reagents_id = Column(String, ForeignKey("_reagent.id", ondelete="SET NULL", name="fk_BS_reagents_id")) #: id of used reagents
|
reagents_id = Column(String, ForeignKey("_reagent.id", ondelete="SET NULL", name="fk_BS_reagents_id")) #: id of used reagents
|
||||||
extraction_info = Column(JSON) #: unstructured output from the extraction table logger.
|
extraction_info = Column(JSON) #: unstructured output from the extraction table logger.
|
||||||
run_cost = Column(FLOAT(2)) #: total cost of running the plate. Set from constant and mutable kit costs at time of creation.
|
run_cost = Column(FLOAT(2)) #: total cost of running the plate. Set from constant and mutable kit costs at time of creation.
|
||||||
@@ -127,13 +126,13 @@ class BasicSubmission(BaseClass):
|
|||||||
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:
|
||||||
logger.debug(f"Don't want {k}")
|
# logger.debug(f"Don't want {k}")
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
output[k] = v
|
output[k] = v
|
||||||
for k,v in st.defaults.items():
|
for k,v in st.defaults.items():
|
||||||
if len(args) > 0 and k not in args:
|
if len(args) > 0 and k not in args:
|
||||||
logger.debug(f"Don't want {k}")
|
# logger.debug(f"Don't want {k}")
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
match v:
|
match v:
|
||||||
@@ -410,7 +409,7 @@ class BasicSubmission(BaseClass):
|
|||||||
case item if item in self.jsons():
|
case item if item in self.jsons():
|
||||||
logger.debug(f"Setting JSON attribute.")
|
logger.debug(f"Setting JSON attribute.")
|
||||||
existing = self.__getattribute__(key)
|
existing = self.__getattribute__(key)
|
||||||
if value == "" or value is None or value == 'null':
|
if value is None or value in ['', 'null']:
|
||||||
logger.error(f"No value given, not setting.")
|
logger.error(f"No value given, not setting.")
|
||||||
return
|
return
|
||||||
if existing is None:
|
if existing is None:
|
||||||
@@ -422,7 +421,8 @@ class BasicSubmission(BaseClass):
|
|||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
existing += value
|
existing += value
|
||||||
else:
|
else:
|
||||||
existing.append(value)
|
if value is not None:
|
||||||
|
existing.append(value)
|
||||||
self.__setattr__(key, existing)
|
self.__setattr__(key, existing)
|
||||||
flag_modified(self, key)
|
flag_modified(self, key)
|
||||||
return
|
return
|
||||||
@@ -890,7 +890,7 @@ class BasicSubmission(BaseClass):
|
|||||||
# limit = 1
|
# limit = 1
|
||||||
if chronologic:
|
if chronologic:
|
||||||
query.order_by(cls.submitted_date)
|
query.order_by(cls.submitted_date)
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def query_or_create(cls, submission_type:str|SubmissionType|None=None, **kwargs) -> BasicSubmission:
|
def query_or_create(cls, submission_type:str|SubmissionType|None=None, **kwargs) -> BasicSubmission:
|
||||||
@@ -1421,7 +1421,7 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def enforce_name(cls, instr:str, data:dict|None={}) -> str:
|
def enforce_name(cls, instr:str, data:dict={}) -> str:
|
||||||
"""
|
"""
|
||||||
Extends parent
|
Extends parent
|
||||||
"""
|
"""
|
||||||
@@ -1430,16 +1430,12 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
instr = re.sub(r"Artic", "", instr, flags=re.IGNORECASE)
|
instr = re.sub(r"Artic", "", instr, flags=re.IGNORECASE)
|
||||||
except (AttributeError, TypeError) as e:
|
except (AttributeError, TypeError) as e:
|
||||||
logger.error(f"Problem using regex: {e}")
|
logger.error(f"Problem using regex: {e}")
|
||||||
# try:
|
# logger.debug(f"Before RSL addition: {instr}")
|
||||||
# check = instr.startswith("RSL")
|
instr = instr.replace("-", "")
|
||||||
# except AttributeError:
|
instr = re.sub(r"^(\d{6})", f"RSL-AR-\\1", instr)
|
||||||
# check = False
|
# logger.debug(f"name coming out of Artic namer: {instr}")
|
||||||
# if not check:
|
|
||||||
# try:
|
|
||||||
# instr = "RSL" + instr
|
|
||||||
# except TypeError:
|
|
||||||
# instr = "RSL"
|
|
||||||
outstr = super().enforce_name(instr=instr, data=data)
|
outstr = super().enforce_name(instr=instr, data=data)
|
||||||
|
|
||||||
return outstr
|
return outstr
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1922,7 +1918,7 @@ class BasicSample(BaseClass):
|
|||||||
query = query.filter(attr==v)
|
query = query.filter(attr==v)
|
||||||
if len(kwargs) > 0:
|
if len(kwargs) > 0:
|
||||||
limit = 1
|
limit = 1
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def query_or_create(cls, sample_type:str|None=None, **kwargs) -> BasicSample:
|
def query_or_create(cls, sample_type:str|None=None, **kwargs) -> BasicSample:
|
||||||
@@ -2259,7 +2255,7 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
query = query.order_by(BasicSubmission.submitted_date.desc())
|
query = query.order_by(BasicSubmission.submitted_date.desc())
|
||||||
else:
|
else:
|
||||||
query = query.order_by(BasicSubmission.submitted_date)
|
query = query.order_by(BasicSubmission.submitted_date)
|
||||||
return cls.query_return(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def query_or_create(cls,
|
def query_or_create(cls,
|
||||||
|
|||||||
@@ -482,12 +482,16 @@ def setup_lookup(func):
|
|||||||
func (_type_): _description_
|
func (_type_): _description_
|
||||||
"""
|
"""
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
for k, v in locals().items():
|
sanitized_kwargs = {}
|
||||||
if k == "kwargs":
|
for k, v in locals()['kwargs'].items():
|
||||||
continue
|
|
||||||
if isinstance(v, dict):
|
if isinstance(v, dict):
|
||||||
raise ValueError("Cannot use dictionary in query. Make sure you parse it first.")
|
try:
|
||||||
return func(*args, **kwargs)
|
sanitized_kwargs[k] = v['value']
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError("Could not sanitize dictionary in query. Make sure you parse it first.")
|
||||||
|
elif v is not None:
|
||||||
|
sanitized_kwargs[k] = v
|
||||||
|
return func(*args, **sanitized_kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
class Result(BaseModel):
|
class Result(BaseModel):
|
||||||
|
|||||||
Reference in New Issue
Block a user