Skip to content

Commit eaefca8

Browse files
committed
Add support for Bash-like default values
When interpolation is active, variable expansion can use a default value, like in Bash.
1 parent b491bbc commit eaefca8

File tree

4 files changed

+40
-12
lines changed

4 files changed

+40
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased]
99

10-
*No unreleased change at this time.*
10+
- Add support for a Bash-like default value in variable expansion (#248 by [@bbc2]).
1111

1212
## [0.12.0] - 2020-02-28
1313

README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,23 @@ export S3_BUCKET=YOURS3BUCKET
3939
export SECRET_KEY=YOURSECRETKEYGOESHERE
4040
```
4141

42-
`.env` can interpolate variables using POSIX variable expansion,
43-
variables are replaced from the environment first or from other values
44-
in the `.env` file if the variable is not present in the environment.
42+
Python-dotenv can interpolate variables using POSIX variable expansion.
43+
44+
The value of a variable is the first of the values defined in the following list:
45+
46+
- Value of that variable in the environment.
47+
- Value of that variable in the `.env` file.
48+
- Default value, if provided.
49+
- Empty string.
50+
4551
Ensure that variables are surrounded with `{}` like `${HOME}` as bare
4652
variables such as `$HOME` are not expanded.
47-
(**Note**: Default Value Expansion is not supported as of yet, see
48-
[\#30](https://github.com/theskumar/python-dotenv/pull/30#issuecomment-244036604).)
4953

5054
```shell
5155
CONFIG_PATH=${HOME}/.config/foo
5256
DOMAIN=example.org
5357
EMAIL=admin@${DOMAIN}
58+
DEBUG=${DEBUG:-false}
5459
```
5560

5661
## Getting started

src/dotenv/main.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,17 @@
3030
else:
3131
_StringIO = StringIO[Text]
3232

33-
__posix_variable = re.compile(r'\$\{[^\}]*\}') # type: Pattern[Text]
33+
__posix_variable = re.compile(
34+
r"""
35+
\$\{
36+
(?P<name>[^\}:]*)
37+
(?::-
38+
(?P<default>[^\}]*)
39+
)?
40+
\}
41+
""",
42+
re.VERBOSE,
43+
) # type: Pattern[Text]
3444

3545

3646
def with_warn_for_invalid_lines(mappings):
@@ -202,23 +212,25 @@ def unset_key(dotenv_path, key_to_unset, quote_mode="always"):
202212

203213
def resolve_nested_variables(values):
204214
# type: (Dict[Text, Optional[Text]]) -> Dict[Text, Optional[Text]]
205-
def _replacement(name):
206-
# type: (Text) -> Text
215+
def _replacement(name, default):
216+
# type: (Text, Optional[Text]) -> Text
207217
"""
208218
get appropriate value for a variable name.
209219
first search in environ, if not found,
210220
then look into the dotenv variables
211221
"""
212-
ret = os.getenv(name, new_values.get(name, ""))
222+
default = default if default is not None else ""
223+
ret = os.getenv(name, new_values.get(name, default))
213224
return ret # type: ignore
214225

215-
def _re_sub_callback(match_object):
226+
def _re_sub_callback(match):
216227
# type: (Match[Text]) -> Text
217228
"""
218229
From a match object gets the variable name and returns
219230
the correct replacement
220231
"""
221-
return _replacement(match_object.group()[2:-1])
232+
matches = match.groupdict()
233+
return _replacement(name=matches["name"], default=matches["default"]) # type: ignore
222234

223235
new_values = {}
224236

tests/test_main.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,20 +304,31 @@ def test_dotenv_values_file(dotenv_file):
304304
({"b": "c"}, "a=$b", True, {"a": "$b"}),
305305
({"b": "c"}, "a=${b}", False, {"a": "${b}"}),
306306
({"b": "c"}, "a=${b}", True, {"a": "c"}),
307+
({"b": "c"}, "a=${b:-d}", False, {"a": "${b:-d}"}),
308+
({"b": "c"}, "a=${b:-d}", True, {"a": "c"}),
307309
308310
# Defined in file
309311
({}, "b=c\na=${b}", True, {"a": "c", "b": "c"}),
310312
311313
# Undefined
312314
({}, "a=${b}", True, {"a": ""}),
315+
({}, "a=${b:-d}", True, {"a": "d"}),
313316
314317
# With quotes
315318
({"b": "c"}, 'a="${b}"', True, {"a": "c"}),
316319
({"b": "c"}, "a='${b}'", True, {"a": "c"}),
317320
321+
# With surrounding text
322+
({"b": "c"}, "a=x${b}y", True, {"a": "xcy"}),
323+
318324
# Self-referential
319325
({"a": "b"}, "a=${a}", True, {"a": "b"}),
320326
({}, "a=${a}", True, {"a": ""}),
327+
({"a": "b"}, "a=${a:-c}", True, {"a": "b"}),
328+
({}, "a=${a:-c}", True, {"a": "c"}),
329+
330+
# Reused
331+
({"b": "c"}, "a=${b}${b}", True, {"a": "cc"}),
321332
],
322333
)
323334
def test_dotenv_values_stream(env, string, interpolate, expected):

0 commit comments

Comments
 (0)