Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

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

## [0.12.0] - 2020-02-28

Expand Down
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,23 @@ export S3_BUCKET=YOURS3BUCKET
export SECRET_KEY=YOURSECRETKEYGOESHERE
```

`.env` can interpolate variables using POSIX variable expansion,
variables are replaced from the environment first or from other values
in the `.env` file if the variable is not present in the environment.
Python-dotenv can interpolate variables using POSIX variable expansion.

The value of a variable is the first of the values defined in the following list:

- Value of that variable in the environment.
- Value of that variable in the `.env` file.
- Default value, if provided.
- Empty string.

Ensure that variables are surrounded with `{}` like `${HOME}` as bare
variables such as `$HOME` are not expanded.
(**Note**: Default Value Expansion is not supported as of yet, see
[\#30](https://github.com/theskumar/python-dotenv/pull/30#issuecomment-244036604).)

```shell
CONFIG_PATH=${HOME}/.config/foo
DOMAIN=example.org
EMAIL=admin@${DOMAIN}
DEBUG=${DEBUG:-false}
```

## Getting started
Expand Down
24 changes: 18 additions & 6 deletions src/dotenv/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,17 @@
else:
_StringIO = StringIO[Text]

__posix_variable = re.compile(r'\$\{[^\}]*\}') # type: Pattern[Text]
__posix_variable = re.compile(
r"""
\$\{
(?P<name>[^\}:]*)
(?::-
(?P<default>[^\}]*)
)?
\}
""",
re.VERBOSE,
) # type: Pattern[Text]


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

def resolve_nested_variables(values):
# type: (Dict[Text, Optional[Text]]) -> Dict[Text, Optional[Text]]
def _replacement(name):
# type: (Text) -> Text
def _replacement(name, default):
# type: (Text, Optional[Text]) -> Text
"""
get appropriate value for a variable name.
first search in environ, if not found,
then look into the dotenv variables
"""
ret = os.getenv(name, new_values.get(name, ""))
default = default if default is not None else ""
ret = os.getenv(name, new_values.get(name, default))
return ret # type: ignore

def _re_sub_callback(match_object):
def _re_sub_callback(match):
# type: (Match[Text]) -> Text
"""
From a match object gets the variable name and returns
the correct replacement
"""
return _replacement(match_object.group()[2:-1])
matches = match.groupdict()
return _replacement(name=matches["name"], default=matches["default"]) # type: ignore

new_values = {}

Expand Down
11 changes: 11 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,20 +304,31 @@ def test_dotenv_values_file(dotenv_file):
({"b": "c"}, "a=$b", True, {"a": "$b"}),
({"b": "c"}, "a=${b}", False, {"a": "${b}"}),
({"b": "c"}, "a=${b}", True, {"a": "c"}),
({"b": "c"}, "a=${b:-d}", False, {"a": "${b:-d}"}),
({"b": "c"}, "a=${b:-d}", True, {"a": "c"}),

# Defined in file
({}, "b=c\na=${b}", True, {"a": "c", "b": "c"}),

# Undefined
({}, "a=${b}", True, {"a": ""}),
({}, "a=${b:-d}", True, {"a": "d"}),

# With quotes
({"b": "c"}, 'a="${b}"', True, {"a": "c"}),
({"b": "c"}, "a='${b}'", True, {"a": "c"}),

# With surrounding text
({"b": "c"}, "a=x${b}y", True, {"a": "xcy"}),

# Self-referential
({"a": "b"}, "a=${a}", True, {"a": "b"}),
({}, "a=${a}", True, {"a": ""}),
({"a": "b"}, "a=${a:-c}", True, {"a": "b"}),
({}, "a=${a:-c}", True, {"a": "c"}),

# Reused
({"b": "c"}, "a=${b}${b}", True, {"a": "cc"}),
],
)
def test_dotenv_values_stream(env, string, interpolate, expected):
Expand Down