Source code for watts.database

# SPDX-FileCopyrightText: 2022-2023 UChicago Argonne, LLC
# SPDX-License-Identifier: MIT

from collections.abc import Sequence
from pathlib import Path
import pprint
import shutil
from typing import Union
from warnings import warn

import platformdirs

from .results import Results


[docs] class Database(Sequence): """Database of simulation results Parameters ---------- path Path to database directory Attributes ---------- default_path Path used by default when creating instances if no path is specified job_id Integer ID assigned to new results path Base path for the database directory results List of simulation results in database """ _default_path = platformdirs.user_data_path('watts') _instances = {} def __new__(cls, path=None): # If no path specified, use global default if path is None: path = cls._default_path # If this class has already been instantiated before, return the # corresponding instance abs_path = Path(path).resolve() if abs_path in Database._instances: return Database._instances[abs_path] else: return super().__new__(cls) def __init__(self, path=None): # If no path specified, use global default if path is None: path = self._default_path # If instance has already been created, no need to perform setup logic if path in Database._instances: return # Create database directory if it doesn't already path = Path(path) path.mkdir(parents=True, exist_ok=True) self._path = path # Read previous results self._results = [] for dir in sorted(self.path.iterdir(), key=lambda x: x.stat().st_ctime): try: self._results.append(Results.from_pickle(dir / ".result_info.pkl")) except Exception: warn(f"Could not read results from {dir}") # Determine unique job ID based on what has already been used used_job_ids = set() for result in self: job_id = getattr(result, 'job_id', None) if job_id is not None: used_job_ids.add(job_id) self.job_id = max(used_job_ids, default=-1) + 1 # Add instance to class-wide dictionary Database._instances[path.resolve()] = self def __repr__(self): return pprint.pformat(self._results) def __getitem__(self, index): return self._results[index] def __len__(self): return len(self._results) @property def path(self) -> Path: return self._path @property def default_path(self) -> Path: return self.get_default_path() @default_path.setter def default_path(self, path): self.set_default_path(path)
[docs] @classmethod def set_default_path(cls, path: Union[str, Path]): """Set the default path used when instances are created Parameters ---------- path Default path to use """ cls._default_path = Path(path).resolve()
[docs] @classmethod def get_default_path(cls) -> Path: """Get the default path used when instances are created Returns ------- Default path """ return cls._default_path
[docs] def add_result(self, result: Results): """Add a result to the database Parameters ---------- result Simulation results to add """ self._results.append(result) # Save result info that can be recreated result.save(result.base_path / ".result_info.pkl")
[docs] def clear(self): """Remove all results from database""" for dir in self.path.iterdir(): shutil.rmtree(dir) self._results.clear()
[docs] def remove(self, result: Results): """Remove a single result from the database Parameters ---------- result Result to remove from the database """ self._results.remove(result) shutil.rmtree(result.base_path)
[docs] def show_summary(self): """Show a summary of results in database""" for result in self._results: rel_path = result.base_path.relative_to(self.path) print(result.time, result.plugin, str(rel_path), f"({len(result.inputs)} inputs)", f"({len(result.outputs)} outputs)")