Skip to content

Commit 7d4ebec

Browse files
authored
Add option to disable env var interpolation in configs (#861)
1 parent 09efbff commit 7d4ebec

File tree

6 files changed

+100
-45
lines changed

6 files changed

+100
-45
lines changed

docs/providers/configuration.rst

+13
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,19 @@ See also: :ref:`configuration-strict-mode`.
366366
367367
assert container.config.section.option() is None
368368
369+
If you want to disable environment variables interpolation, pass ``envs_required=None``:
370+
371+
.. code-block:: yaml
372+
:caption: templates.yml
373+
374+
template_string: 'Hello, ${name}!'
375+
376+
.. code-block:: python
377+
378+
>>> container.config.from_yaml("templates.yml", envs_required=None)
379+
>>> container.config.template_string()
380+
'Hello, ${name}!'
381+
369382
Mandatory and optional sources
370383
------------------------------
371384

src/dependency_injector/providers.pyi

+3-3
Original file line numberDiff line numberDiff line change
@@ -225,20 +225,20 @@ class ConfigurationOption(Provider[Any]):
225225
self,
226226
filepath: Union[Path, str],
227227
required: bool = False,
228-
envs_required: bool = False,
228+
envs_required: Optional[bool] = False,
229229
) -> None: ...
230230
def from_yaml(
231231
self,
232232
filepath: Union[Path, str],
233233
required: bool = False,
234234
loader: Optional[Any] = None,
235-
envs_required: bool = False,
235+
envs_required: Optional[bool] = False,
236236
) -> None: ...
237237
def from_json(
238238
self,
239239
filepath: Union[Path, str],
240240
required: bool = False,
241-
envs_required: bool = False,
241+
envs_required: Optional[bool] = False,
242242
) -> None: ...
243243
def from_pydantic(
244244
self, settings: PydanticSettings, required: bool = False, **kwargs: Any

src/dependency_injector/providers.pyx

+33-42
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import sys
1515
import threading
1616
import types
1717
import warnings
18+
from configparser import ConfigParser as IniConfigParser
1819

1920
try:
2021
import contextvars
@@ -41,11 +42,6 @@ try:
4142
except ImportError:
4243
_is_coroutine = True
4344

44-
try:
45-
import ConfigParser as iniconfigparser
46-
except ImportError:
47-
import configparser as iniconfigparser
48-
4945
try:
5046
import yaml
5147
except ImportError:
@@ -102,7 +98,7 @@ config_env_marker_pattern = re.compile(
10298
r"\${(?P<name>[^}^{:]+)(?P<separator>:?)(?P<default>.*?)}",
10399
)
104100

105-
def _resolve_config_env_markers(config_content, envs_required=False):
101+
cdef str _resolve_config_env_markers(config_content: str, envs_required: bool):
106102
"""Replace environment variable markers with their values."""
107103
findings = list(config_env_marker_pattern.finditer(config_content))
108104

@@ -121,28 +117,19 @@ def _resolve_config_env_markers(config_content, envs_required=False):
121117
return config_content
122118

123119

124-
if sys.version_info[0] == 3:
125-
def _parse_ini_file(filepath, envs_required=False):
126-
parser = iniconfigparser.ConfigParser()
127-
with open(filepath) as config_file:
128-
config_string = _resolve_config_env_markers(
129-
config_file.read(),
130-
envs_required=envs_required,
131-
)
132-
parser.read_string(config_string)
133-
return parser
134-
else:
135-
import StringIO
120+
cdef object _parse_ini_file(filepath, envs_required: bool | None):
121+
parser = IniConfigParser()
122+
123+
with open(filepath) as config_file:
124+
config_string = config_file.read()
136125

137-
def _parse_ini_file(filepath, envs_required=False):
138-
parser = iniconfigparser.ConfigParser()
139-
with open(filepath) as config_file:
126+
if envs_required is not None:
140127
config_string = _resolve_config_env_markers(
141-
config_file.read(),
128+
config_string,
142129
envs_required=envs_required,
143130
)
144-
parser.readfp(StringIO.StringIO(config_string))
145-
return parser
131+
parser.read_string(config_string)
132+
return parser
146133

147134

148135
if yaml:
@@ -1717,7 +1704,7 @@ cdef class ConfigurationOption(Provider):
17171704
try:
17181705
parser = _parse_ini_file(
17191706
filepath,
1720-
envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
1707+
envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
17211708
)
17221709
except IOError as exception:
17231710
if required is not False \
@@ -1776,10 +1763,11 @@ cdef class ConfigurationOption(Provider):
17761763
raise
17771764
return
17781765

1779-
config_content = _resolve_config_env_markers(
1780-
config_content,
1781-
envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
1782-
)
1766+
if envs_required is not None:
1767+
config_content = _resolve_config_env_markers(
1768+
config_content,
1769+
envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
1770+
)
17831771
config = yaml.load(config_content, loader)
17841772

17851773
current_config = self.__call__()
@@ -1814,10 +1802,11 @@ cdef class ConfigurationOption(Provider):
18141802
raise
18151803
return
18161804

1817-
config_content = _resolve_config_env_markers(
1818-
config_content,
1819-
envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
1820-
)
1805+
if envs_required is not None:
1806+
config_content = _resolve_config_env_markers(
1807+
config_content,
1808+
envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
1809+
)
18211810
config = json.loads(config_content)
18221811

18231812
current_config = self.__call__()
@@ -2270,7 +2259,7 @@ cdef class Configuration(Object):
22702259
try:
22712260
parser = _parse_ini_file(
22722261
filepath,
2273-
envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
2262+
envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
22742263
)
22752264
except IOError as exception:
22762265
if required is not False \
@@ -2329,10 +2318,11 @@ cdef class Configuration(Object):
23292318
raise
23302319
return
23312320

2332-
config_content = _resolve_config_env_markers(
2333-
config_content,
2334-
envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
2335-
)
2321+
if envs_required is not None:
2322+
config_content = _resolve_config_env_markers(
2323+
config_content,
2324+
envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
2325+
)
23362326
config = yaml.load(config_content, loader)
23372327

23382328
current_config = self.__call__()
@@ -2367,10 +2357,11 @@ cdef class Configuration(Object):
23672357
raise
23682358
return
23692359

2370-
config_content = _resolve_config_env_markers(
2371-
config_content,
2372-
envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
2373-
)
2360+
if envs_required is not None:
2361+
config_content = _resolve_config_env_markers(
2362+
config_content,
2363+
envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
2364+
)
23742365
config = json.loads(config_content)
23752366

23762367
current_config = self.__call__()

tests/unit/providers/configuration/test_from_ini_with_env_py2_py3.py

+17
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@
55
from pytest import mark, raises
66

77

8+
def test_no_env_variable_interpolation(config, ini_config_file_3):
9+
config.from_ini(ini_config_file_3, envs_required=None)
10+
11+
assert config() == {
12+
"section1": {
13+
"value1": "${CONFIG_TEST_ENV}",
14+
"value2": "${CONFIG_TEST_PATH}/path",
15+
},
16+
}
17+
assert config.section1() == {
18+
"value1": "${CONFIG_TEST_ENV}",
19+
"value2": "${CONFIG_TEST_PATH}/path",
20+
}
21+
assert config.section1.value1() == "${CONFIG_TEST_ENV}"
22+
assert config.section1.value2() == "${CONFIG_TEST_PATH}/path"
23+
24+
825
def test_env_variable_interpolation(config, ini_config_file_3):
926
config.from_ini(ini_config_file_3)
1027

tests/unit/providers/configuration/test_from_json_with_env_py2_py3.py

+17
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@
66
from pytest import mark, raises
77

88

9+
def test_no_env_variable_interpolation(config, json_config_file_3):
10+
config.from_json(json_config_file_3, envs_required=None)
11+
12+
assert config() == {
13+
"section1": {
14+
"value1": "${CONFIG_TEST_ENV}",
15+
"value2": "${CONFIG_TEST_PATH}/path",
16+
},
17+
}
18+
assert config.section1() == {
19+
"value1": "${CONFIG_TEST_ENV}",
20+
"value2": "${CONFIG_TEST_PATH}/path",
21+
}
22+
assert config.section1.value1() == "${CONFIG_TEST_ENV}"
23+
assert config.section1.value2() == "${CONFIG_TEST_PATH}/path"
24+
25+
926
def test_env_variable_interpolation(config, json_config_file_3):
1027
config.from_json(json_config_file_3)
1128

tests/unit/providers/configuration/test_from_yaml_with_env_py2_py3.py

+17
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@
66
from pytest import mark, raises
77

88

9+
def test_no_env_variable_interpolation(config, yaml_config_file_3):
10+
config.from_yaml(yaml_config_file_3, envs_required=None)
11+
12+
assert config() == {
13+
"section1": {
14+
"value1": "${CONFIG_TEST_ENV}",
15+
"value2": "${CONFIG_TEST_PATH}/path",
16+
},
17+
}
18+
assert config.section1() == {
19+
"value1": "${CONFIG_TEST_ENV}",
20+
"value2": "${CONFIG_TEST_PATH}/path",
21+
}
22+
assert config.section1.value1() == "${CONFIG_TEST_ENV}"
23+
assert config.section1.value2() == "${CONFIG_TEST_PATH}/path"
24+
25+
926
def test_env_variable_interpolation(config, yaml_config_file_3):
1027
config.from_yaml(yaml_config_file_3)
1128

0 commit comments

Comments
 (0)