diff --git a/docs/providers/configuration.rst b/docs/providers/configuration.rst index 582c0cc1..66c299f3 100644 --- a/docs/providers/configuration.rst +++ b/docs/providers/configuration.rst @@ -366,6 +366,19 @@ See also: :ref:`configuration-strict-mode`. assert container.config.section.option() is None +If you want to disable environment variables interpolation, pass ``envs_required=None``: + +.. code-block:: yaml + :caption: templates.yml + + template_string: 'Hello, ${name}!' + +.. code-block:: python + + >>> container.config.from_yaml("templates.yml", envs_required=None) + >>> container.config.template_string() + 'Hello, ${name}!' + Mandatory and optional sources ------------------------------ diff --git a/src/dependency_injector/providers.pyi b/src/dependency_injector/providers.pyi index 32534043..e4d62506 100644 --- a/src/dependency_injector/providers.pyi +++ b/src/dependency_injector/providers.pyi @@ -225,20 +225,20 @@ class ConfigurationOption(Provider[Any]): self, filepath: Union[Path, str], required: bool = False, - envs_required: bool = False, + envs_required: Optional[bool] = False, ) -> None: ... def from_yaml( self, filepath: Union[Path, str], required: bool = False, loader: Optional[Any] = None, - envs_required: bool = False, + envs_required: Optional[bool] = False, ) -> None: ... def from_json( self, filepath: Union[Path, str], required: bool = False, - envs_required: bool = False, + envs_required: Optional[bool] = False, ) -> None: ... def from_pydantic( self, settings: PydanticSettings, required: bool = False, **kwargs: Any diff --git a/src/dependency_injector/providers.pyx b/src/dependency_injector/providers.pyx index 71b048a1..a3620350 100644 --- a/src/dependency_injector/providers.pyx +++ b/src/dependency_injector/providers.pyx @@ -15,6 +15,7 @@ import sys import threading import types import warnings +from configparser import ConfigParser as IniConfigParser try: import contextvars @@ -41,11 +42,6 @@ try: except ImportError: _is_coroutine = True -try: - import ConfigParser as iniconfigparser -except ImportError: - import configparser as iniconfigparser - try: import yaml except ImportError: @@ -102,7 +98,7 @@ config_env_marker_pattern = re.compile( r"\${(?P[^}^{:]+)(?P:?)(?P.*?)}", ) -def _resolve_config_env_markers(config_content, envs_required=False): +cdef str _resolve_config_env_markers(config_content: str, envs_required: bool): """Replace environment variable markers with their values.""" findings = list(config_env_marker_pattern.finditer(config_content)) @@ -121,28 +117,19 @@ def _resolve_config_env_markers(config_content, envs_required=False): return config_content -if sys.version_info[0] == 3: - def _parse_ini_file(filepath, envs_required=False): - parser = iniconfigparser.ConfigParser() - with open(filepath) as config_file: - config_string = _resolve_config_env_markers( - config_file.read(), - envs_required=envs_required, - ) - parser.read_string(config_string) - return parser -else: - import StringIO +cdef object _parse_ini_file(filepath, envs_required: bool | None): + parser = IniConfigParser() - def _parse_ini_file(filepath, envs_required=False): - parser = iniconfigparser.ConfigParser() - with open(filepath) as config_file: + with open(filepath) as config_file: + config_string = config_file.read() + + if envs_required is not None: config_string = _resolve_config_env_markers( - config_file.read(), + config_string, envs_required=envs_required, ) - parser.readfp(StringIO.StringIO(config_string)) - return parser + parser.read_string(config_string) + return parser if yaml: @@ -1717,7 +1704,7 @@ cdef class ConfigurationOption(Provider): try: parser = _parse_ini_file( filepath, - envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(), + envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(), ) except IOError as exception: if required is not False \ @@ -1776,10 +1763,11 @@ cdef class ConfigurationOption(Provider): raise return - config_content = _resolve_config_env_markers( - config_content, - envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(), - ) + if envs_required is not None: + config_content = _resolve_config_env_markers( + config_content, + envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(), + ) config = yaml.load(config_content, loader) current_config = self.__call__() @@ -1814,10 +1802,11 @@ cdef class ConfigurationOption(Provider): raise return - config_content = _resolve_config_env_markers( - config_content, - envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(), - ) + if envs_required is not None: + config_content = _resolve_config_env_markers( + config_content, + envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(), + ) config = json.loads(config_content) current_config = self.__call__() @@ -2270,7 +2259,7 @@ cdef class Configuration(Object): try: parser = _parse_ini_file( filepath, - envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(), + envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(), ) except IOError as exception: if required is not False \ @@ -2329,10 +2318,11 @@ cdef class Configuration(Object): raise return - config_content = _resolve_config_env_markers( - config_content, - envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(), - ) + if envs_required is not None: + config_content = _resolve_config_env_markers( + config_content, + envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(), + ) config = yaml.load(config_content, loader) current_config = self.__call__() @@ -2367,10 +2357,11 @@ cdef class Configuration(Object): raise return - config_content = _resolve_config_env_markers( - config_content, - envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(), - ) + if envs_required is not None: + config_content = _resolve_config_env_markers( + config_content, + envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(), + ) config = json.loads(config_content) current_config = self.__call__() diff --git a/tests/unit/providers/configuration/test_from_ini_with_env_py2_py3.py b/tests/unit/providers/configuration/test_from_ini_with_env_py2_py3.py index e669ce48..96949a67 100644 --- a/tests/unit/providers/configuration/test_from_ini_with_env_py2_py3.py +++ b/tests/unit/providers/configuration/test_from_ini_with_env_py2_py3.py @@ -5,6 +5,23 @@ import os from pytest import mark, raises +def test_no_env_variable_interpolation(config, ini_config_file_3): + config.from_ini(ini_config_file_3, envs_required=None) + + assert config() == { + "section1": { + "value1": "${CONFIG_TEST_ENV}", + "value2": "${CONFIG_TEST_PATH}/path", + }, + } + assert config.section1() == { + "value1": "${CONFIG_TEST_ENV}", + "value2": "${CONFIG_TEST_PATH}/path", + } + assert config.section1.value1() == "${CONFIG_TEST_ENV}" + assert config.section1.value2() == "${CONFIG_TEST_PATH}/path" + + def test_env_variable_interpolation(config, ini_config_file_3): config.from_ini(ini_config_file_3) diff --git a/tests/unit/providers/configuration/test_from_json_with_env_py2_py3.py b/tests/unit/providers/configuration/test_from_json_with_env_py2_py3.py index 2bd8f9aa..4ec7c4ea 100644 --- a/tests/unit/providers/configuration/test_from_json_with_env_py2_py3.py +++ b/tests/unit/providers/configuration/test_from_json_with_env_py2_py3.py @@ -6,6 +6,23 @@ import os from pytest import mark, raises +def test_no_env_variable_interpolation(config, json_config_file_3): + config.from_json(json_config_file_3, envs_required=None) + + assert config() == { + "section1": { + "value1": "${CONFIG_TEST_ENV}", + "value2": "${CONFIG_TEST_PATH}/path", + }, + } + assert config.section1() == { + "value1": "${CONFIG_TEST_ENV}", + "value2": "${CONFIG_TEST_PATH}/path", + } + assert config.section1.value1() == "${CONFIG_TEST_ENV}" + assert config.section1.value2() == "${CONFIG_TEST_PATH}/path" + + def test_env_variable_interpolation(config, json_config_file_3): config.from_json(json_config_file_3) diff --git a/tests/unit/providers/configuration/test_from_yaml_with_env_py2_py3.py b/tests/unit/providers/configuration/test_from_yaml_with_env_py2_py3.py index 8e6e1c0d..c047659e 100644 --- a/tests/unit/providers/configuration/test_from_yaml_with_env_py2_py3.py +++ b/tests/unit/providers/configuration/test_from_yaml_with_env_py2_py3.py @@ -6,6 +6,23 @@ import yaml from pytest import mark, raises +def test_no_env_variable_interpolation(config, yaml_config_file_3): + config.from_yaml(yaml_config_file_3, envs_required=None) + + assert config() == { + "section1": { + "value1": "${CONFIG_TEST_ENV}", + "value2": "${CONFIG_TEST_PATH}/path", + }, + } + assert config.section1() == { + "value1": "${CONFIG_TEST_ENV}", + "value2": "${CONFIG_TEST_PATH}/path", + } + assert config.section1.value1() == "${CONFIG_TEST_ENV}" + assert config.section1.value2() == "${CONFIG_TEST_PATH}/path" + + def test_env_variable_interpolation(config, yaml_config_file_3): config.from_yaml(yaml_config_file_3)