Skip to content

Commit

Permalink
Merge pull request #44 from deepaerial/feature/env-custom-separator
Browse files Browse the repository at this point in the history
Added support for custom separator character for nedsted variables.
  • Loading branch information
silvanmelchior authored Jun 9, 2022
2 parents 3434d98 + a374040 commit d5866c6
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 5 deletions.
4 changes: 4 additions & 0 deletions confz/confz_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ class ConfZEnvSource(ConfZSource):
file: Optional[Path] = None
"""Built in .env file loading with lower than environment precedence. Uses UTF-8
for decoding."""
nested_separator: str = "."
"""Separator will be used in nested environment variables."""


@dataclass
Expand All @@ -86,6 +88,8 @@ class ConfZCLArgSource(ConfZSource):
remap: Optional[Dict[str, str]] = None
"""Certain command line arguments can be mapped to config arguments with a different
name. The map does not need to include the two dashes at the beginning."""
nested_separator: str = "."
"""Separator will be used in nested command line arguments."""


@dataclass
Expand Down
4 changes: 3 additions & 1 deletion confz/loaders/cl_arg_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ def populate_config(cls, config: dict, confz_source: ConfZCLArgSource):

cl_args[cl_name] = cl_value

cl_args = cls.transform_nested_dicts(cl_args)
cl_args = cls.transform_nested_dicts(
cl_args, separator=confz_source.nested_separator
)
cls.update_dict_recursively(config, cl_args)
4 changes: 3 additions & 1 deletion confz/loaders/env_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,7 @@ def populate_config(cls, config: dict, confz_source: ConfZEnvSource):

env_vars[var_name] = origin_env_vars[env_var]

env_vars = cls.transform_nested_dicts(env_vars)
env_vars = cls.transform_nested_dicts(
env_vars, separator=confz_source.nested_separator
)
cls.update_dict_recursively(config, env_vars)
3 changes: 2 additions & 1 deletion confz/loaders/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ def transform_nested_dicts(
:param dict_in: A dictionary with string-keys.
:param separator: The string used to separate dict keys.
Default value will no longer be set in a future release.
:return: The transformed dictionary, splitting keys at the separator and
creating a new dictionary out of it.
:raises ConfZUpdateException: If dict keys contradict each other.
"""
dict_out: Dict[str, Any] = {}
for key, value in dict_in.items():
if separator in key:
if separator in key and not key.startswith(separator):
inner_keys = key.split(separator)
dict_inner = dict_out
for idx, inner_key in enumerate(inner_keys):
Expand Down
4 changes: 2 additions & 2 deletions docs/source/usage/sources_loaders.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ There are multiple config sources which support a heterogeneous set of use-cases
The filename can either be passed directly, via environment variable or via command line argument. The latter cases
allow to easily configure multiple environments by having a separate file for each environment.
- :class:`~confz.ConfZEnvSource` allows to load config data from environment variables and .env files. It supports to
select the corresponding variables with allow- and deny-lists and with an optional prefix. The variable names are
select the corresponding variables with allow- and deny-lists and with an optional prefix and optional custom separator for nested variables. The variable names are
either inferred from the config name or can be explicitly mapped.
- :class:`~confz.ConfZCLArgSource` allows to load config data from command line arguments. An optional prefix allows
to select only parts of the arguments. The argument names are either inferred from the config name or can be
to select only parts of the arguments. Optional custom separator for nested command line arguments is also supported. The argument names are either inferred from the config name or can be
explicitly mapped.
- :class:`~confz.ConfZDataSource` allows to define constant config data. This can be very useful for unit tests, see
:ref:`context_manager`.
Expand Down
17 changes: 17 additions & 0 deletions tests/loaders/test_cl_arg_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,20 @@ def test_remap(monkeypatch):
config = OuterConfig(config_sources=ConfZCLArgSource(remap={"val1": "inner.attr1"}))
assert config.inner.attr1 == 1
assert config.attr2 == 2


def test_nested_separator(monkeypatch):
argv = sys.argv.copy() + [
"--conf_inner__attr1",
"1",
"--conf_attr2",
"2",
"--attr1",
"100",
]
monkeypatch.setattr(sys, "argv", argv)
config = OuterConfig(
config_sources=ConfZCLArgSource(prefix="conf_", nested_separator="__")
)
assert config.inner.attr1 == 1
assert config.attr2 == 2
12 changes: 12 additions & 0 deletions tests/loaders/test_env_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,15 @@ def test_dotenv_loading(monkeypatch):
assert config.attr2 == 1
assert config.inner.attr1_name == 21
assert config.inner.attr_override == "2002"


def test_separator(monkeypatch):
monkeypatch.setenv("INNER__ATTR1_NAME", "21")
monkeypatch.setenv("INNER__ATTR-OVERRIDE", "2002")
monkeypatch.setenv("ATTR2", "1")
config = OuterConfig(
config_sources=ConfZEnvSource(allow_all=True, nested_separator="__")
)
assert config.attr2 == 1
assert config.inner.attr1_name == 21
assert config.inner.attr_override == "2002"

0 comments on commit d5866c6

Please sign in to comment.