Skip to content

Commit

Permalink
Merge pull request #401 from juaml/enh/config_manager
Browse files Browse the repository at this point in the history
[ENH]: Introduce `ConfigManager`
  • Loading branch information
synchon authored Nov 26, 2024
2 parents 1944462 + 8b7b599 commit 654f409
Show file tree
Hide file tree
Showing 19 changed files with 204 additions and 27 deletions.
1 change: 1 addition & 0 deletions docs/changes/newsfragments/401.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Introduce :class:`junifer.utils.ConfigManager` singleton class to manage global configuration by `Fede Raimondo`_
2 changes: 1 addition & 1 deletion junifer/api/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
__all__ = [
"register_datagrabber",
"register_datareader",
"register_preprocessor",
"register_marker",
"register_preprocessor",
"register_storage",
]

Expand Down
2 changes: 1 addition & 1 deletion junifer/api/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
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:
Expand Down
16 changes: 8 additions & 8 deletions junifer/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@


__all__ = [
"afni_docker",
"ants_docker",
"cli",
"run",
"collect",
"freesurfer_docker",
"fsl_docker",
"list_elements",
"queue",
"wtf",
"selftest",
"reset",
"list_elements",
"run",
"selftest",
"setup",
"afni_docker",
"fsl_docker",
"ants_docker",
"freesurfer_docker",
"wtf",
]


Expand Down
2 changes: 1 addition & 1 deletion junifer/cli/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
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
Expand Down
2 changes: 1 addition & 1 deletion junifer/data/_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@


__all__ = [
"deregister_data",
"get_data",
"list_data",
"load_data",
"register_data",
"deregister_data",
]


Expand Down
2 changes: 1 addition & 1 deletion junifer/data/masks/_masks.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from nibabel.nifti1 import Nifti1Image


__all__ = ["compute_brain_mask", "MaskRegistry"]
__all__ = ["MaskRegistry", "compute_brain_mask"]


# Path to the masks
Expand Down
2 changes: 1 addition & 1 deletion junifer/data/template_spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from .utils import closest_resolution


__all__ = ["get_xfm", "get_template"]
__all__ = ["get_template", "get_xfm"]


def get_xfm(
Expand Down
2 changes: 1 addition & 1 deletion junifer/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
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(
Expand Down
4 changes: 2 additions & 2 deletions junifer/storage/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@


__all__ = [
"element_to_prefix",
"get_dependency_version",
"matrix_to_vector",
"process_meta",
"element_to_prefix",
"store_matrix_checks",
"matrix_to_vector",
]


Expand Down
2 changes: 1 addition & 1 deletion junifer/testing/datagrabbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@

__all__ = [
"OasisVBMTestingDataGrabber",
"SPMAuditoryTestingDataGrabber",
"PartlyCloudyTestingDataGrabber",
"SPMAuditoryTestingDataGrabber",
]


Expand Down
2 changes: 2 additions & 0 deletions junifer/typing/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ __all__ = [
"ExternalDependencies",
"MarkerInOutMappings",
"DataGrabberPatterns",
"ConfigVal",
]

from ._typing import (
Expand All @@ -22,4 +23,5 @@ from ._typing import (
ExternalDependencies,
MarkerInOutMappings,
DataGrabberPatterns,
ConfigVal,
)
14 changes: 8 additions & 6 deletions junifer/typing/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@


__all__ = [
"ConditionalDependencies",
"ConfigVal",
"DataGrabberLike",
"PreprocessorLike",
"MarkerLike",
"StorageLike",
"PipelineComponent",
"DataGrabberPatterns",
"Dependencies",
"ConditionalDependencies",
"ExternalDependencies",
"MarkerInOutMappings",
"DataGrabberPatterns",
"MarkerLike",
"PipelineComponent",
"PreprocessorLike",
"StorageLike",
]


Expand Down Expand Up @@ -60,3 +61,4 @@
DataGrabberPatterns = dict[
str, Union[dict[str, str], Sequence[dict[str, str]]]
]
ConfigVal = Union[bool, int, float]
3 changes: 3 additions & 0 deletions junifer/utils/__init__.pyi
Original file line number Diff line number Diff line change
@@ -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 changes: 110 additions & 0 deletions junifer/utils/_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""Provide junifer global configuration."""

# Authors: Federico Raimondo <[email protected]>
# Synchon Mandal <[email protected]>
# 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()
2 changes: 1 addition & 1 deletion junifer/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
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:
Expand Down
2 changes: 1 addition & 1 deletion junifer/utils/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@

__all__ = [
"WrapStdOut",
"configure_logging",
"get_versions",
"log_versions",
"configure_logging",
"raise_error",
"warn_with_log",
]
Expand Down
2 changes: 1 addition & 1 deletion junifer/utils/singleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Any, ClassVar


__all__ = ["Singleton", "ABCSingleton"]
__all__ = ["ABCSingleton", "Singleton"]


class Singleton(type):
Expand Down
59 changes: 59 additions & 0 deletions junifer/utils/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Provide tests for ConfigManager."""

# Authors: Federico Raimondo <[email protected]>
# Synchon Mandal <[email protected]>
# 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")

0 comments on commit 654f409

Please sign in to comment.