Skip to content

Commit

Permalink
Add option to disable env var interpolation in configs
Browse files Browse the repository at this point in the history
  • Loading branch information
ZipFile committed Feb 23, 2025
1 parent 2330122 commit 6c60245
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 45 deletions.
13 changes: 13 additions & 0 deletions docs/providers/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------------------------

Expand Down
6 changes: 3 additions & 3 deletions src/dependency_injector/providers.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
75 changes: 33 additions & 42 deletions src/dependency_injector/providers.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import sys
import threading
import types
import warnings
from configparser import ConfigParser as IniConfigParser

try:
import contextvars
Expand All @@ -38,11 +39,6 @@ else:
else:
_is_coroutine_marker = True

try:
import ConfigParser as iniconfigparser
except ImportError:
import configparser as iniconfigparser

try:
import yaml
except ImportError:
Expand Down Expand Up @@ -99,7 +95,7 @@ config_env_marker_pattern = re.compile(
r"\${(?P<name>[^}^{:]+)(?P<separator>:?)(?P<default>.*?)}",
)

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))

Expand All @@ -118,28 +114,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()

with open(filepath) as config_file:
config_string = config_file.read()

def _parse_ini_file(filepath, envs_required=False):
parser = iniconfigparser.ConfigParser()
with open(filepath) as config_file:
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:
Expand Down Expand Up @@ -1713,7 +1700,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 \
Expand Down Expand Up @@ -1772,10 +1759,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__()
Expand Down Expand Up @@ -1810,10 +1798,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__()
Expand Down Expand Up @@ -2266,7 +2255,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 \
Expand Down Expand Up @@ -2325,10 +2314,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__()
Expand Down Expand Up @@ -2363,10 +2353,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__()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@
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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@
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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@
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)

Expand Down

0 comments on commit 6c60245

Please sign in to comment.