[ENH]: Introduce ConfigManager #401
19 changed files with 204 additions and 27 deletions
1
docs/changes/newsfragments/401.feature
Normal file
1
docs/changes/newsfragments/401.feature
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Introduce :class:`junifer.utils.ConfigManager` singleton class to manage global configuration by `Fede Raimondo`_
|
||||||
|
|
@ -13,8 +13,8 @@ from ..typing import DataGrabberLike, MarkerLike, PreprocessorLike, StorageLike
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"register_datagrabber",
|
"register_datagrabber",
|
||||||
"register_datareader",
|
"register_datareader",
|
||||||
"register_preprocessor",
|
|
||||||
"register_marker",
|
"register_marker",
|
||||||
|
"register_preprocessor",
|
||||||
"register_storage",
|
"register_storage",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ from ..typing import DataGrabberLike, MarkerLike, PreprocessorLike, StorageLike
|
||||||
from ..utils import logger, raise_error, yaml
|
from ..utils import logger, raise_error, yaml
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["run", "collect", "queue", "reset", "list_elements"]
|
__all__ = ["collect", "list_elements", "queue", "reset", "run"]
|
||||||
|
|
||||||
|
|
||||||
def _get_datagrabber(datagrabber_config: dict) -> DataGrabberLike:
|
def _get_datagrabber(datagrabber_config: dict) -> DataGrabberLike:
|
||||||
|
|
|
||||||
|
|
@ -29,19 +29,19 @@ from .utils import (
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"cli",
|
|
||||||
"run",
|
|
||||||
"collect",
|
|
||||||
"queue",
|
|
||||||
"wtf",
|
|
||||||
"selftest",
|
|
||||||
"reset",
|
|
||||||
"list_elements",
|
|
||||||
"setup",
|
|
||||||
"afni_docker",
|
"afni_docker",
|
||||||
"fsl_docker",
|
|
||||||
"ants_docker",
|
"ants_docker",
|
||||||
|
"cli",
|
||||||
|
"collect",
|
||||||
"freesurfer_docker",
|
"freesurfer_docker",
|
||||||
|
"fsl_docker",
|
||||||
|
"list_elements",
|
||||||
|
"queue",
|
||||||
|
"reset",
|
||||||
|
"run",
|
||||||
|
"selftest",
|
||||||
|
"setup",
|
||||||
|
"wtf",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import pandas as pd
|
||||||
from ..utils import logger, raise_error, warn_with_log, yaml
|
from ..utils import logger, raise_error, warn_with_log, yaml
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["parse_yaml", "parse_elements"]
|
__all__ = ["parse_elements", "parse_yaml"]
|
||||||
|
|
||||||
|
|
||||||
def parse_yaml(filepath: Union[str, Path]) -> dict: # noqa: C901
|
def parse_yaml(filepath: Union[str, Path]) -> dict: # noqa: C901
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,11 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"deregister_data",
|
||||||
"get_data",
|
"get_data",
|
||||||
"list_data",
|
"list_data",
|
||||||
"load_data",
|
"load_data",
|
||||||
"register_data",
|
"register_data",
|
||||||
"deregister_data",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ if TYPE_CHECKING:
|
||||||
from nibabel.nifti1 import Nifti1Image
|
from nibabel.nifti1 import Nifti1Image
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["compute_brain_mask", "MaskRegistry"]
|
__all__ = ["MaskRegistry", "compute_brain_mask"]
|
||||||
|
|
||||||
|
|
||||||
# Path to the masks
|
# Path to the masks
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ from ..utils import logger, raise_error
|
||||||
from .utils import closest_resolution
|
from .utils import closest_resolution
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["get_xfm", "get_template"]
|
__all__ = ["get_template", "get_xfm"]
|
||||||
|
|
||||||
|
|
||||||
def get_xfm(
|
def get_xfm(
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from scipy.stats.mstats import winsorize
|
||||||
from .utils import logger, raise_error
|
from .utils import logger, raise_error
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["get_aggfunc_by_name", "count", "winsorized_mean", "select"]
|
__all__ = ["count", "get_aggfunc_by_name", "select", "winsorized_mean"]
|
||||||
|
|
||||||
|
|
||||||
def get_aggfunc_by_name(
|
def get_aggfunc_by_name(
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@ from ..utils.logging import logger, raise_error
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"get_dependency_version",
|
|
||||||
"process_meta",
|
|
||||||
"element_to_prefix",
|
"element_to_prefix",
|
||||||
"store_matrix_checks",
|
"get_dependency_version",
|
||||||
"matrix_to_vector",
|
"matrix_to_vector",
|
||||||
|
"process_meta",
|
||||||
|
"store_matrix_checks",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ from ..datagrabber.base import BaseDataGrabber
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"OasisVBMTestingDataGrabber",
|
"OasisVBMTestingDataGrabber",
|
||||||
"SPMAuditoryTestingDataGrabber",
|
|
||||||
"PartlyCloudyTestingDataGrabber",
|
"PartlyCloudyTestingDataGrabber",
|
||||||
|
"SPMAuditoryTestingDataGrabber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ __all__ = [
|
||||||
"ExternalDependencies",
|
"ExternalDependencies",
|
||||||
"MarkerInOutMappings",
|
"MarkerInOutMappings",
|
||||||
"DataGrabberPatterns",
|
"DataGrabberPatterns",
|
||||||
|
"ConfigVal",
|
||||||
]
|
]
|
||||||
|
|
||||||
from ._typing import (
|
from ._typing import (
|
||||||
|
|
@ -22,4 +23,5 @@ from ._typing import (
|
||||||
ExternalDependencies,
|
ExternalDependencies,
|
||||||
MarkerInOutMappings,
|
MarkerInOutMappings,
|
||||||
DataGrabberPatterns,
|
DataGrabberPatterns,
|
||||||
|
ConfigVal,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,17 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"DataGrabberLike",
|
|
||||||
"PreprocessorLike",
|
|
||||||
"MarkerLike",
|
|
||||||
"StorageLike",
|
|
||||||
"PipelineComponent",
|
|
||||||
"Dependencies",
|
|
||||||
"ConditionalDependencies",
|
"ConditionalDependencies",
|
||||||
|
"ConfigVal",
|
||||||
|
"DataGrabberLike",
|
||||||
|
"DataGrabberPatterns",
|
||||||
|
"Dependencies",
|
||||||
"ExternalDependencies",
|
"ExternalDependencies",
|
||||||
"MarkerInOutMappings",
|
"MarkerInOutMappings",
|
||||||
"DataGrabberPatterns",
|
"MarkerLike",
|
||||||
|
"PipelineComponent",
|
||||||
|
"PreprocessorLike",
|
||||||
|
"StorageLike",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -60,3 +61,4 @@ MarkerInOutMappings = MutableMapping[str, MutableMapping[str, str]]
|
||||||
DataGrabberPatterns = dict[
|
DataGrabberPatterns = dict[
|
||||||
str, Union[dict[str, str], Sequence[dict[str, str]]]
|
str, Union[dict[str, str], Sequence[dict[str, str]]]
|
||||||
]
|
]
|
||||||
|
ConfigVal = Union[bool, int, float]
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"make_executable",
|
"make_executable",
|
||||||
"configure_logging",
|
"configure_logging",
|
||||||
|
"config",
|
||||||
"logger",
|
"logger",
|
||||||
"raise_error",
|
"raise_error",
|
||||||
"warn_with_log",
|
"warn_with_log",
|
||||||
"run_ext_cmd",
|
"run_ext_cmd",
|
||||||
"deep_update",
|
"deep_update",
|
||||||
"yaml",
|
"yaml",
|
||||||
|
"ConfigManager",
|
||||||
]
|
]
|
||||||
|
|
||||||
from .fs import make_executable
|
from .fs import make_executable
|
||||||
from .logging import configure_logging, logger, raise_error, warn_with_log
|
from .logging import configure_logging, logger, raise_error, warn_with_log
|
||||||
|
from ._config import config, ConfigManager
|
||||||
from .helpers import run_ext_cmd, deep_update
|
from .helpers import run_ext_cmd, deep_update
|
||||||
from ._yaml import yaml
|
from ._yaml import yaml
|
||||||
|
|
|
||||||
110
junifer/utils/_config.py
Normal file
110
junifer/utils/_config.py
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
"""Provide junifer global configuration."""
|
||||||
|
|
||||||
|
# Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
|
||||||
|
# Synchon Mandal <s.mandal@fz-juelich.de>
|
||||||
|
# License: AGPL
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from ..typing import ConfigVal
|
||||||
|
from .logging import logger
|
||||||
|
from .singleton import Singleton
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["ConfigManager", "config"]
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigManager(metaclass=Singleton):
|
||||||
|
"""Manage configuration parameters.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
_config : dict
|
||||||
|
Configuration parameters.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""Initialize the class."""
|
||||||
|
self._config = {}
|
||||||
|
# Initial setup from process env
|
||||||
|
self._reload()
|
||||||
|
|
||||||
|
def _reload(self) -> None:
|
||||||
|
"""Reload env vars."""
|
||||||
|
for t_var in os.environ:
|
||||||
|
if t_var.startswith("JUNIFER_"):
|
||||||
|
# Set correct type
|
||||||
|
var_value = os.environ[t_var]
|
||||||
|
# bool
|
||||||
|
if var_value.lower() == "true":
|
||||||
|
var_value = True
|
||||||
|
elif var_value.lower() == "false":
|
||||||
|
var_value = False
|
||||||
|
# numeric
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
var_value = int(var_value)
|
||||||
|
except ValueError:
|
||||||
|
try:
|
||||||
|
var_value = float(var_value)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
# Set value
|
||||||
|
var_name = (
|
||||||
|
t_var.replace("JUNIFER_", "").lower().replace("_", ".")
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
f"Setting `{var_name}` from environment to "
|
||||||
|
f"`{var_value}` (type: {type(var_value)})"
|
||||||
|
)
|
||||||
|
self._config[var_name] = var_value
|
||||||
|
|
||||||
|
def get(self, key: str, default: Optional[ConfigVal] = None) -> ConfigVal:
|
||||||
|
"""Get configuration parameter.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
key : str
|
||||||
|
The configuration key to get.
|
||||||
|
default : bool or int or float or None, optional
|
||||||
|
The default value to return if the key is not found (default None).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool or int or float
|
||||||
|
The configuration value.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self._config.get(key, default)
|
||||||
|
|
||||||
|
def set(self, key: str, val: ConfigVal) -> None:
|
||||||
|
"""Set configuration parameter.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
key : str
|
||||||
|
The configuration key to set.
|
||||||
|
val : bool or int or float
|
||||||
|
The value to set ``key`` to.
|
||||||
|
|
||||||
|
"""
|
||||||
|
logger.debug(f"Setting `{key}` to `{val}` (type: {type(val)})")
|
||||||
|
self._config[key] = val
|
||||||
|
|
||||||
|
def delete(self, key: str) -> None:
|
||||||
|
"""Delete configuration parameter.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
key : str
|
||||||
|
The configuration key to delete.
|
||||||
|
|
||||||
|
"""
|
||||||
|
logger.debug(f"Deleting `{key}` from config")
|
||||||
|
_ = self._config.pop(key)
|
||||||
|
|
||||||
|
|
||||||
|
# Initialize here to access from anywhere
|
||||||
|
config = ConfigManager()
|
||||||
|
|
@ -10,7 +10,7 @@ import sys
|
||||||
from .logging import logger, raise_error
|
from .logging import logger, raise_error
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["run_ext_cmd", "deep_update"]
|
__all__ = ["deep_update", "run_ext_cmd"]
|
||||||
|
|
||||||
|
|
||||||
def run_ext_cmd(name: str, cmd: list[str]) -> None:
|
def run_ext_cmd(name: str, cmd: list[str]) -> None:
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@ import datalad
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"WrapStdOut",
|
"WrapStdOut",
|
||||||
|
"configure_logging",
|
||||||
"get_versions",
|
"get_versions",
|
||||||
"log_versions",
|
"log_versions",
|
||||||
"configure_logging",
|
|
||||||
"raise_error",
|
"raise_error",
|
||||||
"warn_with_log",
|
"warn_with_log",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from abc import ABCMeta
|
||||||
from typing import Any, ClassVar
|
from typing import Any, ClassVar
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["Singleton", "ABCSingleton"]
|
__all__ = ["ABCSingleton", "Singleton"]
|
||||||
|
|
||||||
|
|
||||||
class Singleton(type):
|
class Singleton(type):
|
||||||
|
|
|
||||||
59
junifer/utils/tests/test_config.py
Normal file
59
junifer/utils/tests/test_config.py
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
"""Provide tests for ConfigManager."""
|
||||||
|
|
||||||
|
# Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
|
||||||
|
# Synchon Mandal <s.mandal@fz-juelich.de>
|
||||||
|
# License: AGPL
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from junifer.typing import ConfigVal
|
||||||
|
from junifer.utils import config
|
||||||
|
from junifer.utils._config import ConfigManager
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_manager_singleton() -> None:
|
||||||
|
"""Test that ConfigManager is a singleton."""
|
||||||
|
config_mgr_1 = ConfigManager()
|
||||||
|
config_mgr_2 = ConfigManager()
|
||||||
|
assert id(config_mgr_1) == id(config_mgr_2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_manager() -> None:
|
||||||
|
"""Test config operations for ConfigManager."""
|
||||||
|
# Get non-existing with default
|
||||||
|
assert config.get(key="scooby") is None
|
||||||
|
# Set
|
||||||
|
config.set(key="scooby", val=True)
|
||||||
|
# Get existing
|
||||||
|
assert config.get("scooby")
|
||||||
|
# Delete
|
||||||
|
config.delete("scooby")
|
||||||
|
# Get non-existing with default
|
||||||
|
assert config.get(key="scooby") is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"val, expected_val",
|
||||||
|
[("TRUE", True), ("FALSE", False), ("1", 1), ("0.0", 0.0)],
|
||||||
|
)
|
||||||
|
def test_config_manager_env_reload(val: str, expected_val: ConfigVal) -> None:
|
||||||
|
"""Test config parsing from env reload.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
val : str
|
||||||
|
The parametrized values.
|
||||||
|
expected_val : bool or int or float
|
||||||
|
The parametrized expected value.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Set env var
|
||||||
|
os.environ["JUNIFER_TESTME"] = val
|
||||||
|
# Check
|
||||||
|
config._reload()
|
||||||
|
assert config.get("testme") == expected_val
|
||||||
|
# Cleanup
|
||||||
|
del os.environ["JUNIFER_TESTME"]
|
||||||
|
config.delete("testme")
|
||||||
Loading…
Reference in a new issue