''' Bindings for the 'core' HPI configuration ''' from __future__ import annotations import re from collections.abc import Sequence from dataclasses import dataclass from pathlib import Path from . import warnings try: from my.config import core as user_config # type: ignore[attr-defined] except Exception as e: try: from my.config import common as user_config # type: ignore[attr-defined] warnings.high("'common' config section is deprecated. Please rename it to 'core'.") except Exception as e2: # make it defensive, because it's pretty commonly used and would be annoying if it breaks hpi doctor etc. # this way it'll at least use the defaults # todo actually not sure if needs a warning? Perhaps it's okay without it, because the defaults are reasonable enough user_config = object _HPI_CACHE_DIR_DEFAULT = '' @dataclass class Config(user_config): ''' Config for the HPI itself. To override, add to your config file something like class config: cache_dir = '/your/custom/cache/path' ''' cache_dir: Path | str | None = _HPI_CACHE_DIR_DEFAULT ''' Base directory for cachew. - if None , means cache is disabled - if '' (empty string), use user cache dir (see https://github.com/ActiveState/appdirs for more info). This is the default. - otherwise , use the specified directory as base cache directory NOTE: you shouldn't use this attribute in HPI modules directly, use Config.get_cache_dir()/cachew.cache_dir() instead ''' tmp_dir: Path | str | None = None ''' Path to a temporary directory. This can be used temporarily while extracting zipfiles etc... - if None , uses default determined by tempfile.gettempdir + 'HPI' - otherwise , use the specified directory as the base temporary directory ''' enabled_modules: Sequence[str] | None = None ''' list of regexes/globs - None means 'rely on disabled_modules' ''' disabled_modules: Sequence[str] | None = None ''' list of regexes/globs - None means 'rely on enabled_modules' ''' def get_cache_dir(self) -> Path | None: cdir = self.cache_dir if cdir is None: return None if cdir == _HPI_CACHE_DIR_DEFAULT: from .cachew import _appdirs_cache_dir return _appdirs_cache_dir() else: return Path(cdir).expanduser() def get_tmp_dir(self) -> Path: tdir: Path | str | None = self.tmp_dir tpath: Path # use tempfile if unset if tdir is None: import tempfile tpath = Path(tempfile.gettempdir()) / 'HPI' else: tpath = Path(tdir) tpath = tpath.expanduser() tpath.mkdir(parents=True, exist_ok=True) return tpath def _is_module_active(self, module: str) -> bool | None: # None means the config doesn't specify anything # todo might be nice to return the 'reason' too? e.g. which option has matched def matches(specs: Sequence[str]) -> str | None: for spec in specs: # not sure because . (packages separate) matches anything, but I guess unlikely to clash if re.match(spec, module): return spec return None on = matches(self.enabled_modules or []) off = matches(self.disabled_modules or []) if on is None: if off is None: # user is indifferent return None else: return False else: # not None if off is None: return True else: # not None # fallback onto the 'enable everything', then the user will notice warnings.medium(f"[module]: conflicting regexes '{on}' and '{off}' are set in the config. Please only use one of them.") return True from .cfg import make_config config = make_config(Config) ### tests start from collections.abc import Iterator from contextlib import contextmanager as ctx @ctx def _reset_config() -> Iterator[Config]: # todo maybe have this decorator for the whole of my.config? from .cfg import _override_config with _override_config(config) as cc: cc.enabled_modules = None cc.disabled_modules = None cc.cache_dir = None yield cc def test_active_modules() -> None: import pytest reset = _reset_config with reset() as cc: assert cc._is_module_active('my.whatever' ) is None assert cc._is_module_active('my.core' ) is None assert cc._is_module_active('my.body.exercise') is None with reset() as cc: cc.enabled_modules = ['my.whatever'] cc.disabled_modules = ['my.body.*'] assert cc._is_module_active('my.whatever' ) is True assert cc._is_module_active('my.core' ) is None assert cc._is_module_active('my.body.exercise') is False with reset() as cc: # if both are set, enable all cc.disabled_modules = ['my.body.*'] cc.enabled_modules = ['my.body.exercise'] assert cc._is_module_active('my.whatever' ) is None assert cc._is_module_active('my.core' ) is None with pytest.warns(UserWarning, match=r"conflicting regexes") as record_warnings: assert cc._is_module_active("my.body.exercise") is True assert len(record_warnings) == 1 ### tests end