From 9c1321ca2c033353103155289b9c593bd4487bf6 Mon Sep 17 00:00:00 2001 From: deepaerial <nazarii.oleksiuk@gmail.com> Date: Tue, 31 May 2022 21:23:36 +0200 Subject: [PATCH 1/3] Added support for custom separator character for nedsted variables. --- confz/confz_source.py | 2 ++ confz/loaders/env_loader.py | 4 +++- confz/loaders/loader.py | 2 +- docs/source/usage/sources_loaders.rst | 2 +- tests/loaders/test_env_loader.py | 12 ++++++++++++ 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/confz/confz_source.py b/confz/confz_source.py index be0b85a..e555fc9 100644 --- a/confz/confz_source.py +++ b/confz/confz_source.py @@ -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 diff --git a/confz/loaders/env_loader.py b/confz/loaders/env_loader.py index 0374d6a..719e88d 100644 --- a/confz/loaders/env_loader.py +++ b/confz/loaders/env_loader.py @@ -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) diff --git a/confz/loaders/loader.py b/confz/loaders/loader.py index e2334be..1c3503b 100644 --- a/confz/loaders/loader.py +++ b/confz/loaders/loader.py @@ -42,7 +42,7 @@ def transform_nested_dicts( """ 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): diff --git a/docs/source/usage/sources_loaders.rst b/docs/source/usage/sources_loaders.rst index 1570b1c..e70202d 100644 --- a/docs/source/usage/sources_loaders.rst +++ b/docs/source/usage/sources_loaders.rst @@ -15,7 +15,7 @@ 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 diff --git a/tests/loaders/test_env_loader.py b/tests/loaders/test_env_loader.py index cba4af4..f2f3bda 100644 --- a/tests/loaders/test_env_loader.py +++ b/tests/loaders/test_env_loader.py @@ -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" From 314a93e0ab09a051ec26f7248d592b980b09dbbe Mon Sep 17 00:00:00 2001 From: deepaerial <nazarii.oleksiuk@gmail.com> Date: Mon, 6 Jun 2022 19:55:06 +0200 Subject: [PATCH 2/3] Added optional separator variable for CLI loader class --- confz/confz_source.py | 2 ++ confz/loaders/cl_arg_loader.py | 4 +++- confz/loaders/loader.py | 3 ++- docs/source/usage/sources_loaders.rst | 2 +- tests/loaders/test_cl_arg_loader.py | 17 +++++++++++++++++ 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/confz/confz_source.py b/confz/confz_source.py index e555fc9..5e145fa 100644 --- a/confz/confz_source.py +++ b/confz/confz_source.py @@ -88,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 environment variables.""" @dataclass diff --git a/confz/loaders/cl_arg_loader.py b/confz/loaders/cl_arg_loader.py index e32ce81..60a640e 100644 --- a/confz/loaders/cl_arg_loader.py +++ b/confz/loaders/cl_arg_loader.py @@ -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) diff --git a/confz/loaders/loader.py b/confz/loaders/loader.py index 1c3503b..052f49d 100644 --- a/confz/loaders/loader.py +++ b/confz/loaders/loader.py @@ -29,13 +29,14 @@ def update_dict_recursively(cls, original_dict: Dict, update_dict: Dict): @classmethod def transform_nested_dicts( - cls, dict_in: Dict[str, Any], separator: str = "." + cls, dict_in: Dict[str, Any], separator: str ) -> Dict[str, Any]: """Transform dictionaries into nested dictionaries, using a separator in the keys as hint. :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. diff --git a/docs/source/usage/sources_loaders.rst b/docs/source/usage/sources_loaders.rst index e70202d..e9ce5f1 100644 --- a/docs/source/usage/sources_loaders.rst +++ b/docs/source/usage/sources_loaders.rst @@ -18,7 +18,7 @@ There are multiple config sources which support a heterogeneous set of use-cases 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 variables 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`. diff --git a/tests/loaders/test_cl_arg_loader.py b/tests/loaders/test_cl_arg_loader.py index 9258c43..2368828 100644 --- a/tests/loaders/test_cl_arg_loader.py +++ b/tests/loaders/test_cl_arg_loader.py @@ -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 From a3740402997b2adf7884ab22c4de7d7a8981c7e5 Mon Sep 17 00:00:00 2001 From: deepaerial <nazarii.oleksiuk@gmail.com> Date: Tue, 7 Jun 2022 10:05:13 +0200 Subject: [PATCH 3/3] Small fixes in docstrings and documentation. --- confz/confz_source.py | 2 +- confz/loaders/loader.py | 2 +- docs/source/usage/sources_loaders.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/confz/confz_source.py b/confz/confz_source.py index 5e145fa..7da64b7 100644 --- a/confz/confz_source.py +++ b/confz/confz_source.py @@ -89,7 +89,7 @@ class ConfZCLArgSource(ConfZSource): """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 environment variables.""" + """Separator will be used in nested command line arguments.""" @dataclass diff --git a/confz/loaders/loader.py b/confz/loaders/loader.py index 052f49d..22c1a25 100644 --- a/confz/loaders/loader.py +++ b/confz/loaders/loader.py @@ -29,7 +29,7 @@ def update_dict_recursively(cls, original_dict: Dict, update_dict: Dict): @classmethod def transform_nested_dicts( - cls, dict_in: Dict[str, Any], separator: str + cls, dict_in: Dict[str, Any], separator: str = "." ) -> Dict[str, Any]: """Transform dictionaries into nested dictionaries, using a separator in the keys as hint. diff --git a/docs/source/usage/sources_loaders.rst b/docs/source/usage/sources_loaders.rst index e9ce5f1..b875f66 100644 --- a/docs/source/usage/sources_loaders.rst +++ b/docs/source/usage/sources_loaders.rst @@ -18,7 +18,7 @@ There are multiple config sources which support a heterogeneous set of use-cases 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. Optional custom separator for nested variables is also supported. 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`.