Skip to content

Commit

Permalink
Fix empty expanded value for duplicate key
Browse files Browse the repository at this point in the history
Example problematic file:

```bash
hello=hi
greetings=${hello}
goodbye=bye
greetings=${goodbye}
```

It would result in `greetings` being associated with the empty string
instead of `"bye"`.

The problem came from the fact that bindings were converted to a dict,
and so deduplicated by key, before being interpolated.  The dict would
be `{"hello": "hi", "greetings": "${goodbye}", "goodbye": "bye"}` in the
earlier example, which shows why interpolation wouldn't work: `goodbye`
would not be defined when `greetings` was interpolated.

This commit fixes that by passing all values in order, even if there are
duplicated keys.
  • Loading branch information
bbc2 committed Jul 16, 2020
1 parent e4bbb8a commit 4b43436
Show file tree
Hide file tree
Showing 3 changed files with 15 additions and 7 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Expand Up @@ -7,7 +7,9 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

*No unreleased change at this time.*
### Fixed

- Fix potentially empty expanded value for duplicate key (#260 by [@bbc]).

## [0.14.0] - 2020-07-03

Expand Down
16 changes: 10 additions & 6 deletions src/dotenv/main.py
Expand Up @@ -18,7 +18,7 @@

if IS_TYPE_CHECKING:
from typing import (
Dict, Iterator, Match, Optional, Pattern, Union, Text, IO, Tuple
Dict, Iterable, Iterator, Match, Optional, Pattern, Union, Text, IO, Tuple
)
if sys.version_info >= (3, 6):
_PathLike = os.PathLike
Expand Down Expand Up @@ -83,9 +83,13 @@ def dict(self):
if self._dict:
return self._dict

values = OrderedDict(self.parse())
self._dict = resolve_nested_variables(values) if self.interpolate else values
return self._dict
if self.interpolate:
values = resolve_nested_variables(self.parse())
else:
values = OrderedDict(self.parse())

self._dict = values
return values

def parse(self):
# type: () -> Iterator[Tuple[Text, Optional[Text]]]
Expand Down Expand Up @@ -211,7 +215,7 @@ 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]]
# type: (Iterable[Tuple[Text, Optional[Text]]]) -> Dict[Text, Optional[Text]]
def _replacement(name, default):
# type: (Text, Optional[Text]) -> Text
default = default if default is not None else ""
Expand All @@ -229,7 +233,7 @@ def _re_sub_callback(match):

new_values = {}

for k, v in values.items():
for (k, v) in values:
new_values[k] = __posix_variable.sub(_re_sub_callback, v) if v is not None else None

return new_values
Expand Down
2 changes: 2 additions & 0 deletions tests/test_main.py
Expand Up @@ -337,6 +337,8 @@ def test_dotenv_values_file(dotenv_file):
# Re-defined and used in file
({"b": "c"}, "b=d\na=${b}", True, {"a": "d", "b": "d"}),
({}, "a=b\na=c\nd=${a}", True, {"a": "c", "d": "c"}),
({}, "a=b\nc=${a}\nd=e\nc=${d}", True, {"a": "b", "c": "e", "d": "e"}),
],
)
def test_dotenv_values_stream(env, string, interpolate, expected):
Expand Down

0 comments on commit 4b43436

Please sign in to comment.