[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__ = [
|
||||
"register_datagrabber",
|
||||
"register_datareader",
|
||||
"register_preprocessor",
|
||||
"register_marker",
|
||||
"register_preprocessor",
|
||||
"register_storage",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ from ..typing import DataGrabberLike, MarkerLike, PreprocessorLike, StorageLike
|
|||
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:
|
||||
|
|
|
|||
|
|
@ -29,19 +29,19 @@ from .utils import (
|
|||
|
||||
|
||||
__all__ = [
|
||||
"cli",
|
||||
"run",
|
||||
"collect",
|
||||
"queue",
|
||||
"wtf",
|
||||
"selftest",
|
||||
"reset",
|
||||
"list_elements",
|
||||
"setup",
|
||||
"afni_docker",
|
||||
"fsl_docker",
|
||||
"ants_docker",
|
||||
"cli",
|
||||
"collect",
|
||||
"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
|
||||
|
||||
|
||||
__all__ = ["parse_yaml", "parse_elements"]
|
||||
__all__ = ["parse_elements", "parse_yaml"]
|
||||
|
||||
|
||||
def parse_yaml(filepath: Union[str, Path]) -> dict: # noqa: C901
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
__all__ = [
|
||||
"deregister_data",
|
||||
"get_data",
|
||||
"list_data",
|
||||
"load_data",
|
||||
"register_data",
|
||||
"deregister_data",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ if TYPE_CHECKING:
|
|||
from nibabel.nifti1 import Nifti1Image
|
||||
|
||||
|
||||
__all__ = ["compute_brain_mask", "MaskRegistry"]
|
||||
__all__ = ["MaskRegistry", "compute_brain_mask"]
|
||||
|
||||
|
||||
# Path to the masks
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from ..utils import logger, raise_error
|
|||
from .utils import closest_resolution
|
||||
|
||||
|
||||
__all__ = ["get_xfm", "get_template"]
|
||||
__all__ = ["get_template", "get_xfm"]
|
||||
|
||||
|
||||
def get_xfm(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from scipy.stats.mstats import winsorize
|
|||
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(
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ from ..utils.logging import logger, raise_error
|
|||
|
||||
|
||||
__all__ = [
|
||||
"get_dependency_version",
|
||||
"process_meta",
|
||||
"element_to_prefix",
|
||||
"store_matrix_checks",
|
||||
"get_dependency_version",
|
||||
"matrix_to_vector",
|
||||
"process_meta",
|
||||
"store_matrix_checks",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ from ..datagrabber.base import BaseDataGrabber
|
|||
|
||||
__all__ = [
|
||||
"OasisVBMTestingDataGrabber",
|
||||
"SPMAuditoryTestingDataGrabber",
|
||||
"PartlyCloudyTestingDataGrabber",
|
||||
"SPMAuditoryTestingDataGrabber",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ __all__ = [
|
|||
"ExternalDependencies",
|
||||
"MarkerInOutMappings",
|
||||
"DataGrabberPatterns",
|
||||
"ConfigVal",
|
||||
]
|
||||
|
||||
from ._typing import (
|
||||
|
|
@ -22,4 +23,5 @@ from ._typing import (
|
|||
ExternalDependencies,
|
||||
MarkerInOutMappings,
|
||||
DataGrabberPatterns,
|
||||
ConfigVal,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,16 +19,17 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
__all__ = [
|
||||
"DataGrabberLike",
|
||||
"PreprocessorLike",
|
||||
"MarkerLike",
|
||||
"StorageLike",
|
||||
"PipelineComponent",
|
||||
"Dependencies",
|
||||
"ConditionalDependencies",
|
||||
"ConfigVal",
|
||||
"DataGrabberLike",
|
||||
"DataGrabberPatterns",
|
||||
"Dependencies",
|
||||
"ExternalDependencies",
|
||||
"MarkerInOutMappings",
|
||||
"DataGrabberPatterns",
|
||||
"MarkerLike",
|
||||
"PipelineComponent",
|
||||
"PreprocessorLike",
|
||||
"StorageLike",
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -60,3 +61,4 @@ MarkerInOutMappings = MutableMapping[str, MutableMapping[str, str]]
|
|||
DataGrabberPatterns = dict[
|
||||
str, Union[dict[str, str], Sequence[dict[str, str]]]
|
||||
]
|
||||
ConfigVal = Union[bool, int, float]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
__all__ = [
|
||||
"make_executable",
|
||||
"configure_logging",
|
||||
"config",
|
||||
"logger",
|
||||
"raise_error",
|
||||
"warn_with_log",
|
||||
"run_ext_cmd",
|
||||
"deep_update",
|
||||
"yaml",
|
||||
"ConfigManager",
|
||||
]
|
||||
|
||||
from .fs import make_executable
|
||||
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 ._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
|
||||
|
||||
|
||||
__all__ = ["run_ext_cmd", "deep_update"]
|
||||
__all__ = ["deep_update", "run_ext_cmd"]
|
||||
|
||||
|
||||
def run_ext_cmd(name: str, cmd: list[str]) -> None:
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import datalad
|
|||
|
||||
__all__ = [
|
||||
"WrapStdOut",
|
||||
"configure_logging",
|
||||
"get_versions",
|
||||
"log_versions",
|
||||
"configure_logging",
|
||||
"raise_error",
|
||||
"warn_with_log",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from abc import ABCMeta
|
|||
from typing import Any, ClassVar
|
||||
|
||||
|
||||
__all__ = ["Singleton", "ABCSingleton"]
|
||||
__all__ = ["ABCSingleton", "Singleton"]
|
||||
|
||||
|
||||
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