Skip to content

Commit 24b299e

Browse files
Swannbmjoke2kCopilot
authored
Feature/add choice parameter and raise an exception if fetched value is not within (#555)
* set new parameter in str to coerce value into a specific list of choices. Raise improperlyConfigured if the value is not within choices. * Fix indentation after rebase in Env.str Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Document Env.str choices option Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Daniele Faraglia <joke2k@users.noreply.github.com> Co-authored-by: joke2k <daniele.faraglia@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c441413 commit 24b299e

3 files changed

Lines changed: 53 additions & 14 deletions

File tree

docs/tips.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,29 @@ The following example demonstrates the above:
287287
print(env.str('ESCAPED_CERT', multiline=False))
288288
# ---BEGIN---\\n---END---
289289
290+
Restrict string values with choices
291+
===================================
292+
293+
You can restrict ``env.str()`` to an allowed list of values using
294+
``choices``. If the value is not in the provided list,
295+
``ImproperlyConfigured`` is raised.
296+
297+
.. code-block:: python
298+
299+
import environ
300+
from django.core.exceptions import ImproperlyConfigured
301+
302+
env = environ.Env()
303+
304+
# APP_ENV=prod
305+
env.str("APP_ENV", choices=("dev", "prod", "staging")) # "prod"
306+
307+
# APP_ENV=unknown
308+
try:
309+
env.str("APP_ENV", choices=("dev", "prod", "staging"))
310+
except ImproperlyConfigured:
311+
...
312+
290313
Proxy value
291314
===========
292315

environ/environ.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,20 @@ def str(
237237
self,
238238
var,
239239
default: Union[str, NoValue] = NOTSET,
240-
multiline=False) -> str:
240+
multiline=False,
241+
choices=NOTSET) -> str:
241242
"""
242243
:rtype: str
243244
"""
244245
value = self.get_value(var, cast=str, default=default)
245246
if multiline:
246247
return re.sub(r'(\\r)?\\n', r'\n', value)
248+
if choices is not self.NOTSET:
249+
# if choices is provided, check that the value is in choices
250+
if value not in choices:
251+
raise ImproperlyConfigured(
252+
f"Invalid value: {value} not in {choices}"
253+
)
247254
return value
248255

249256
def bytes(

tests/test_env.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -115,22 +115,31 @@ def test_contains(self):
115115
assert 'I_AM_NOT_A_VAR' not in self.env
116116

117117
@pytest.mark.parametrize(
118-
'var,val,multiline',
118+
'var,val,multiline,choices',
119119
[
120-
('STR_VAR', 'bar', False),
121-
('MULTILINE_STR_VAR', 'foo\\nbar', False),
122-
('MULTILINE_STR_VAR', 'foo\nbar', True),
123-
('MULTILINE_QUOTED_STR_VAR', '---BEGIN---\\r\\n---END---', False),
124-
('MULTILINE_QUOTED_STR_VAR', '---BEGIN---\n---END---', True),
125-
('MULTILINE_ESCAPED_STR_VAR', '---BEGIN---\\\\n---END---', False),
126-
('MULTILINE_ESCAPED_STR_VAR', '---BEGIN---\\\n---END---', True),
120+
('STR_VAR', 'bar', False, Env.NOTSET),
121+
('STR_VAR', 'bar', False, ['foo', 'bar']),
122+
('STR_VAR', 'bar', False, ['pow', 'foo']),
123+
('MULTILINE_STR_VAR', 'foo\\nbar', False, Env.NOTSET),
124+
('MULTILINE_STR_VAR', 'foo\\nbar', False, ['foo\\nbar', '***']),
125+
('MULTILINE_STR_VAR', 'foo\\nbar', False, ['***', '***']),
126+
('MULTILINE_STR_VAR', 'foo\nbar', True, Env.NOTSET),
127+
('MULTILINE_QUOTED_STR_VAR', '---BEGIN---\\r\\n---END---', False, Env.NOTSET),
128+
('MULTILINE_QUOTED_STR_VAR', '---BEGIN---\n---END---', True, Env.NOTSET),
129+
('MULTILINE_ESCAPED_STR_VAR', '---BEGIN---\\\\n---END---', False, Env.NOTSET),
130+
('MULTILINE_ESCAPED_STR_VAR', '---BEGIN---\\\n---END---', True, Env.NOTSET),
127131
],
128132
)
129-
def test_str(self, var, val, multiline):
130-
assert isinstance(self.env(var), str)
131-
if not multiline:
132-
assert self.env(var) == val
133-
assert self.env.str(var, multiline=multiline) == val
133+
def test_str(self, var, val, multiline, choices):
134+
if choices is Env.NOTSET or val in choices:
135+
assert isinstance(self.env(var), str)
136+
if not multiline:
137+
assert self.env(var) == val
138+
assert self.env.str(var, multiline=multiline) == val
139+
else:
140+
with pytest.raises(ImproperlyConfigured) as excinfo:
141+
self.env.str(var, multiline=multiline, choices=choices)
142+
assert str(excinfo.value) == f"Invalid value: {val} not in {choices}"
134143

135144
@pytest.mark.parametrize(
136145
'var,val,default',

0 commit comments

Comments
 (0)