Sanity checking.

This commit is contained in:
lwark
2024-05-16 14:07:18 -05:00
parent 84fac23890
commit bbcbd35127
6 changed files with 466 additions and 365 deletions

View File

@@ -1,3 +1,9 @@
## 202405.03
- Settings can now pull values from the db.
- Improved generic and WW specific PCR parsers.
- Various bug fixes.
## 202405.01 ## 202405.01
- New Excel writers - New Excel writers

View File

@@ -55,10 +55,10 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
# are written from script.py.mako # are written from script.py.mako
# output_encoding = utf-8 # output_encoding = utf-8
sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db ; sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db
;sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\Submissions_app_backups\DB_backups\submissions-demo.db ; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\Submissions_app_backups\DB_backups\submissions-demo.db
;sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\Submissions_app_backups\DB_backups\submissions-prototypes.db ;sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\Submissions_app_backups\DB_backups\submissions-prototypes.db
;sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\python\submissions\mytests\test_assets\submissions-test.db sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\python\submissions\mytests\test_assets\submissions-test.db
[post_write_hooks] [post_write_hooks]

View File

@@ -3,6 +3,8 @@ Contains all models for sqlalchemy
''' '''
from __future__ import annotations from __future__ import annotations
import sys, logging import sys, logging
from sqlalchemy import Column, INTEGER, String, JSON
from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query, Session from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query, Session
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.exc import ArgumentError from sqlalchemy.exc import ArgumentError
@@ -11,8 +13,6 @@ from pathlib import Path
# Load testing environment # Load testing environment
if 'pytest' in sys.modules: if 'pytest' in sys.modules:
from pathlib import Path
sys.path.append(Path(__file__).parents[4].absolute().joinpath("tests").__str__()) sys.path.append(Path(__file__).parents[4].absolute().joinpath("tests").__str__())
Base: DeclarativeMeta = declarative_base() Base: DeclarativeMeta = declarative_base()
@@ -46,7 +46,7 @@ class BaseClass(Base):
Returns: Returns:
Session: DB session from ctx settings. Session: DB session from ctx settings.
""" """
if not 'pytest' in sys.modules: if 'pytest' not in sys.modules:
from tools import ctx from tools import ctx
else: else:
from test_settings import ctx from test_settings import ctx
@@ -60,7 +60,7 @@ class BaseClass(Base):
Returns: Returns:
Path: Location of the Submissions directory in Settings object Path: Location of the Submissions directory in Settings object
""" """
if not 'pytest' in sys.modules: if 'pytest' not in sys.modules:
from tools import ctx from tools import ctx
else: else:
from test_settings import ctx from test_settings import ctx
@@ -74,7 +74,7 @@ class BaseClass(Base):
Returns: Returns:
Path: Location of the Submissions backup directory in Settings object Path: Location of the Submissions backup directory in Settings object
""" """
if not 'pytest' in sys.modules: if 'pytest' not in sys.modules:
from tools import ctx from tools import ctx
else: else:
from test_settings import ctx from test_settings import ctx
@@ -104,8 +104,9 @@ class BaseClass(Base):
Execute sqlalchemy query. Execute sqlalchemy query.
Args: Args:
query (Query): input query object model (Any, optional): model to be queried. Defaults to None
limit (int): Maximum number of results. (0 = all) query (Query, optional): input query object. Defaults to None
limit (int): Maximum number of results. (0 = all). Defaults to 0
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.
@@ -153,6 +154,19 @@ class BaseClass(Base):
self.__database_session__.rollback() self.__database_session__.rollback()
class ConfigItem(BaseClass):
id = Column(INTEGER, primary_key=True)
key = Column(String(32))
value = Column(JSON)
def __repr__(self):
return f"ConfigItem({self.key} : {self.value})"
@classmethod
def get_config_items(cls):
return cls.__database_session__.query(cls).all()
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 *

View File

@@ -175,19 +175,19 @@ class Control(BaseClass):
output = [] output = []
# logger.debug("load json string for mode (i.e. contains, matches, kraken2)") # logger.debug("load json string for mode (i.e. contains, matches, kraken2)")
try: try:
# data = json.loads(getattr(self, mode))
data = self.__getattribute__(mode) data = self.__getattribute__(mode)
except TypeError: except TypeError:
data = {} data = {}
logger.debug(f"Length of data: {len(data)}") logger.debug(f"Length of data: {len(data)}")
# logger.debug("dict keys are genera of bacteria, e.g. 'Streptococcus'") # logger.debug("dict keys are genera of bacteria, e.g. 'Streptococcus'")
for genus in data: for genus in data:
_dict = {} _dict = dict(
_dict['name'] = self.name name=self.name,
_dict['submitted_date'] = self.submitted_date submitted_date=self.submitted_date,
_dict['genus'] = genus genus=genus,
target='Target' if genus.strip("*") in self.controltype.targets else "Off-target"
)
# logger.debug("get Target or Off-target of genus") # logger.debug("get Target or Off-target of genus")
_dict['target'] = 'Target' if genus.strip("*") in self.controltype.targets else "Off-target"
# logger.debug("set 'contains_hashes', etc for genus") # logger.debug("set 'contains_hashes', etc for genus")
for key in data[genus]: for key in data[genus]:
_dict[key] = data[genus][key] _dict[key] = data[genus][key]
@@ -247,13 +247,13 @@ class Control(BaseClass):
case _: case _:
pass pass
# by date range # by date range
if start_date != None and end_date == None: if start_date is not None and end_date is None:
logger.warning(f"Start date with no end date, using today.") logger.warning(f"Start date with no end date, using today.")
end_date = date.today() end_date = date.today()
if end_date != None and start_date == None: if end_date is not None and start_date is None:
logger.warning(f"End date with no start date, using Jan 1, 2023") logger.warning(f"End date with no start date, using Jan 1, 2023")
start_date = date(2023, 1, 1) start_date = date(2023, 1, 1)
if start_date != None: if start_date is not None:
match start_date: match start_date:
case date(): case date():
# logger.debug(f"Lookup control by start date({start_date})") # logger.debug(f"Lookup control by start date({start_date})")

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,8 @@
Contains miscellaenous functions used by both frontend and backend. Contains miscellaenous functions used by both frontend and backend.
''' '''
from __future__ import annotations from __future__ import annotations
import json
from pathlib import Path from pathlib import Path
import numpy as np import numpy as np
import logging, re, yaml, sys, os, stat, platform, getpass, inspect, csv import logging, re, yaml, sys, os, stat, platform, getpass, inspect, csv
@@ -10,7 +12,7 @@ from jinja2 import Environment, FileSystemLoader
from logging import handlers from logging import handlers
from pathlib import Path from pathlib import Path
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import create_engine from sqlalchemy import create_engine, text
from pydantic import field_validator, BaseModel, Field from pydantic import field_validator, BaseModel, Field
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Any, Tuple, Literal, List from typing import Any, Tuple, Literal, List
@@ -137,7 +139,7 @@ def get_first_blank_df_row(df:pd.DataFrame) -> int:
# Settings # Settings
class Settings(BaseSettings): class Settings(BaseSettings, extra="allow"):
""" """
Pydantic model to hold settings Pydantic model to hold settings
@@ -147,21 +149,26 @@ class Settings(BaseSettings):
""" """
directory_path: Path directory_path: Path
database_path: Path|str|None = None database_path: Path|str|None = None
backup_path: Path backup_path: Path|str|None = None
super_users: list|None = None # super_users: list|None = None
power_users: list|None = None # power_users: list|None = None
rerun_regex: str # rerun_regex: str
submission_types: dict|None = None submission_types: dict|None = None
database_session: Session|None = None database_session: Session|None = None
package: Any|None = None package: Any|None = None
model_config = SettingsConfigDict(env_file_encoding='utf-8') model_config = SettingsConfigDict(env_file_encoding='utf-8')
@field_validator('backup_path') @field_validator('backup_path', mode="before")
@classmethod @classmethod
def set_backup_path(cls, value): def set_backup_path(cls, value, values):
if isinstance(value, str): match value:
case str():
value = Path(value) value = Path(value)
case None:
value = values.data['directory_path'].joinpath("Database backups")
if not value.exists():
value.mkdir(parents=True)
# metadata.backup_path = value # metadata.backup_path = value
return value return value
@@ -177,11 +184,14 @@ class Settings(BaseSettings):
@field_validator('database_path', mode="before") @field_validator('database_path', mode="before")
@classmethod @classmethod
def ensure_database_exists(cls, value): def ensure_database_exists(cls, value, values):
if value == ":memory:": if value == ":memory:":
return value return value
if isinstance(value, str): match value:
case str():
value = Path(value) value = Path(value)
case None:
value = values.data['directory_path'].joinpath("submissions.db")
if value.exists(): if value.exists():
return value return value
else: else:
@@ -225,6 +235,20 @@ class Settings(BaseSettings):
if value == None: if value == None:
return package return package
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# self.set_from_db(db_path=kwargs['database_path'])
def set_from_db(self, db_path:Path):
session = Session(create_engine(f"sqlite:///{db_path}"))
config_items = session.execute(text("SELECT * FROM _configitem")).all()
session.close()
config_items = {item[1]:json.loads(item[2]) for item in config_items}
for k, v in config_items.items():
if not hasattr(self, k):
self.__setattr__(k, v)
def get_config(settings_path: Path|str|None=None) -> Settings: def get_config(settings_path: Path|str|None=None) -> Settings:
""" """
Get configuration settings from path or default if blank. Get configuration settings from path or default if blank.