Source code for httpstan.cache

"""Cache management.

Functions in this module manage the Stan model cache and related caches.
"""
import logging
import shutil
import typing
from importlib.machinery import EXTENSION_SUFFIXES
from pathlib import Path

import appdirs

import httpstan

logger = logging.getLogger("httpstan")


[docs] def cache_directory() -> Path: """Get httpstan cache path.""" return Path(appdirs.user_cache_dir("httpstan", version=httpstan.__version__))
[docs] def model_directory(model_name: str) -> Path: """Get the path to a model's directory. Directory may not exist.""" model_id = model_name.split("/")[1] return cache_directory() / "models" / model_id
[docs] def fit_path(fit_name: str) -> Path: """Get the path to a fit file. File may not exist.""" # fit_name structure: cache / models / model_id / fit_id fit_directory, fit_id = fit_name.rsplit("/", maxsplit=1) fit_filename = fit_id + ".jsonlines.gz" return cache_directory() / fit_directory / fit_filename
[docs] def delete_model_directory(model_name: str) -> None: """Delete the directory in which a model and associated fits are stored.""" shutil.rmtree(model_directory(model_name), ignore_errors=True)
[docs] def dump_services_extension_module_compiler_output(compiler_output: str, model_name: str) -> None: """Dump compiler output from building a model-specific stan::services extension module.""" model_directory_ = model_directory(model_name) model_directory_.mkdir(parents=True, exist_ok=True) with (model_directory_ / "stderr.log").open("w") as fh: fh.write(compiler_output)
[docs] def load_services_extension_module_compiler_output(model_name: str) -> str: """Load compiler output from building a model-specific stan::services extension module.""" # may raise KeyError model_directory_ = model_directory(model_name) if not model_directory_.exists(): raise KeyError(f"Directory for `{model_name}` at `{model_directory}` does not exist.") with (model_directory_ / "stderr.log").open() as fh: return fh.read()
[docs] def list_model_names() -> typing.List[str]: """Return model names (e.g., `models/dyeicfn2`) for models in cache.""" models_directory = cache_directory() / "models" if not models_directory.exists(): return [] def has_extension_suffix(path: Path) -> bool: return path.suffix in EXTENSION_SUFFIXES model_names = [] for item in models_directory.iterdir(): if not item.is_dir(): continue # look for a compiled extension module, file with a suffix in EXTENSION_SUFFIXES if any(map(has_extension_suffix, item.iterdir())): model_names.append(f"models/{item.name}") return model_names
[docs] def dump_stanc_warnings(stanc_warnings: str, model_name: str) -> None: """Dump stanc warnings associated with a model.""" model_directory_ = model_directory(model_name) model_directory_.mkdir(parents=True, exist_ok=True) with (model_directory_ / "stanc.log").open("w") as fh: fh.write(stanc_warnings)
[docs] def load_stanc_warnings(model_name: str) -> str: """Load stanc output associated with a model.""" # may raise KeyError model_directory_ = model_directory(model_name) if not model_directory_.exists(): raise KeyError(f"Directory for `{model_name}` at `{model_directory}` does not exist.") with (model_directory_ / "stanc.log").open() as fh: return fh.read()
[docs] def dump_fit(fit_bytes: bytes, name: str) -> None: """Store Stan fit in filesystem-based cache. The Stan fit is passed via ``fit_bytes``. The content must already be compressed. Arguments: name: Stan fit name fit_bytes: gzip-compressed messages associated with Stan fit. """ # fits are stored under their "parent" models path = fit_path(name) path.parent.mkdir(parents=True, exist_ok=True) with path.open("wb") as fh: fh.write(fit_bytes)
[docs] def load_fit(name: str) -> bytes: """Load Stan fit from the filesystem-based cache. Arguments: name: Stan fit name model_name: Stan model name Returns gzip-compressed messages associated with Stan fit. """ # fits are stored under their "parent" models path = fit_path(name) try: with path.open("rb") as fh: return fh.read() except FileNotFoundError: raise KeyError(f"Fit `{name}` not found.")
[docs] def delete_fit(name: str) -> None: """Delete Stan fit from the filesystem-based cache. Arguments: name: Stan fit name """ path = fit_path(name) try: path.unlink() except FileNotFoundError: raise KeyError(f"Fit `{name}` not found.")