Skip to content

Commit 6c60245

Browse files
committed
Add option to disable env var interpolation in configs
1 parent 2330122 commit 6c60245

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
@@ -14,6 +14,7 @@ import sys
1414
import threading
1515
import types
1616
import warnings
17+
from configparser import ConfigParser as IniConfigParser
1718

1819
try:
1920
import contextvars
@@ -38,11 +39,6 @@ else:
3839
else:
3940
_is_coroutine_marker = True
4041

41-
try:
42-
import ConfigParser as iniconfigparser
43-
except ImportError:
44-
import configparser as iniconfigparser
45-
4642
try:
4743
import yaml
4844
except ImportError:
@@ -99,7 +95,7 @@ config_env_marker_pattern = re.compile(
9995
r"\${(?P<name>[^}^{:]+)(?P<separator>:?)(?P<default>.*?)}",
10096
)
10197

102-
def _resolve_config_env_markers(config_content, envs_required=False):
98+
cdef str _resolve_config_env_markers(config_content: str, envs_required: bool):
10399
"""Replace environment variable markers with their values."""
104100
findings = list(config_env_marker_pattern.finditer(config_content))
105101

@@ -118,28 +114,19 @@ def _resolve_config_env_markers(config_content, envs_required=False):
118114
return config_content
119115

120116

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

134-
def _parse_ini_file(filepath, envs_required=False):
135-
parser = iniconfigparser.ConfigParser()
136-
with open(filepath) as config_file:
123+
if envs_required is not None:
137124
config_string = _resolve_config_env_markers(
138-
config_file.read(),
125+
config_string,
139126
envs_required=envs_required,
140127
)
141-
parser.readfp(StringIO.StringIO(config_string))
142-
return parser
128+
parser.read_string(config_string)
129+
return parser
143130

144131

145132
if yaml:
@@ -1713,7 +1700,7 @@ cdef class ConfigurationOption(Provider):
17131700
try:
17141701
parser = _parse_ini_file(
17151702
filepath,
1716-
envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
1703+
envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
17171704
)
17181705
except IOError as exception:
17191706
if required is not False \
@@ -1772,10 +1759,11 @@ cdef class ConfigurationOption(Provider):
17721759
raise
17731760
return
17741761

1775-
config_content = _resolve_config_env_markers(
1776-
config_content,
1777-
envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
1778-
)
1762+
if envs_required is not None:
1763+
config_content = _resolve_config_env_markers(
1764+
config_content,
1765+
envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
1766+
)
17791767
config = yaml.load(config_content, loader)
17801768

17811769
current_config = self.__call__()
@@ -1810,10 +1798,11 @@ cdef class ConfigurationOption(Provider):
18101798
raise
18111799
return
18121800

1813-
config_content = _resolve_config_env_markers(
1814-
config_content,
1815-
envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
1816-
)
1801+
if envs_required is not None:
1802+
config_content = _resolve_config_env_markers(
1803+
config_content,
1804+
envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
1805+
)
18171806
config = json.loads(config_content)
18181807

18191808
current_config = self.__call__()
@@ -2266,7 +2255,7 @@ cdef class Configuration(Object):
22662255
try:
22672256
parser = _parse_ini_file(
22682257
filepath,
2269-
envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
2258+
envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
22702259
)
22712260
except IOError as exception:
22722261
if required is not False \
@@ -2325,10 +2314,11 @@ cdef class Configuration(Object):
23252314
raise
23262315
return
23272316

2328-
config_content = _resolve_config_env_markers(
2329-
config_content,
2330-
envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
2331-
)
2317+
if envs_required is not None:
2318+
config_content = _resolve_config_env_markers(
2319+
config_content,
2320+
envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
2321+
)
23322322
config = yaml.load(config_content, loader)
23332323

23342324
current_config = self.__call__()
@@ -2363,10 +2353,11 @@ cdef class Configuration(Object):
23632353
raise
23642354
return
23652355

2366-
config_content = _resolve_config_env_markers(
2367-
config_content,
2368-
envs_required=envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
2369-
)
2356+
if envs_required is not None:
2357+
config_content = _resolve_config_env_markers(
2358+
config_content,
2359+
envs_required if envs_required is not UNDEFINED else self._is_strict_mode_enabled(),
2360+
)
23702361
config = json.loads(config_content)
23712362

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