From 8e44f2547f2a3ea67c3605a2ec6009bf81fe912c Mon Sep 17 00:00:00 2001 From: John Bergvall Date: Fri, 13 Aug 2021 17:24:16 +0200 Subject: [PATCH 1/2] Add argument to interpolate variables from a user provided dict instead of os.environ --- CHANGELOG.md | 7 +++++++ README.md | 21 +++++++++++++++++++++ src/dotenv/main.py | 15 +++++++++++---- tests/test_main.py | 9 +++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1305894..ba13ab39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- Add support to use a custom dict instead of os.environ for variable + interpolating when calling `dotenv_values` (by [@johnbergvall]) + ## [0.19.0] - 2021-07-24 ### Changed diff --git a/README.md b/README.md index 9b56b546..ed6b53ca 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,27 @@ config = { } ``` +Further advanced use by overriding os.environ with a user defined dict: + +```python +import os +import subprocess +from dotenv import dotenv_values + +deploy_env = { + 'FALLBACK_DOMAIN': 'example.org', + 'VERSION': '1.5', +} +env = dotenv_values('.env.deployment01', base_env={ + **dotenv_values('.env.base', base_env=deploy_env), + **deploy_env, +}) +subprocess.call( + ['/usr/bin/docker', 'stack', 'deploy', '-c', 'docker-compose.yml', 'myproject'], + env={**deploy_env, **env}, +) +``` + ### Parse configuration as a stream `load_dotenv` and `dotenv_values` accept [streams][python_streams] via their `stream` diff --git a/src/dotenv/main.py b/src/dotenv/main.py index b8d0a4e0..fbda7f40 100644 --- a/src/dotenv/main.py +++ b/src/dotenv/main.py @@ -39,6 +39,7 @@ def __init__( encoding: Union[None, str] = None, interpolate: bool = True, override: bool = True, + base_env: Mapping[str, Optional[str]] = os.environ ) -> None: self.dotenv_path = dotenv_path # type: Optional[Union[str, _PathLike]] self.stream = stream # type: Optional[IO[str]] @@ -47,6 +48,7 @@ def __init__( self.encoding = encoding # type: Union[None, str] self.interpolate = interpolate # type: bool self.override = override # type: bool + self.base_env = base_env # type: Mapping[str, Optional[str]] @contextmanager def _get_stream(self) -> Iterator[IO[str]]: @@ -71,7 +73,9 @@ def dict(self) -> Dict[str, Optional[str]]: raw_values = self.parse() if self.interpolate: - self._dict = OrderedDict(resolve_variables(raw_values, override=self.override)) + self._dict = OrderedDict( + resolve_variables(raw_values, override=self.override, base_env=self.base_env) + ) else: self._dict = OrderedDict(raw_values) @@ -212,6 +216,7 @@ def unset_key( def resolve_variables( values: Iterable[Tuple[str, Optional[str]]], override: bool, + base_env: Mapping[str, Optional[str]] = os.environ, ) -> Mapping[str, Optional[str]]: new_values = {} # type: Dict[str, Optional[str]] @@ -222,11 +227,11 @@ def resolve_variables( atoms = parse_variables(value) env = {} # type: Dict[str, Optional[str]] if override: - env.update(os.environ) # type: ignore + env.update(base_env) # type: ignore env.update(new_values) else: env.update(new_values) - env.update(os.environ) # type: ignore + env.update(base_env) # type: ignore result = "".join(atom.resolve(env) for atom in atoms) new_values[name] = result @@ -334,6 +339,7 @@ def dotenv_values( verbose: bool = False, interpolate: bool = True, encoding: Optional[str] = "utf-8", + base_env: Mapping[str, Optional[str]] = os.environ, ) -> Dict[str, Optional[str]]: """ Parse a .env file and return its content as a dict. @@ -342,8 +348,8 @@ def dotenv_values( - *stream*: `StringIO` object with .env content, used if `dotenv_path` is `None`. - *verbose*: whether to output a warning the .env file is missing. Defaults to `False`. - in `.env` file. Defaults to `False`. - *encoding*: encoding to be used to read the file. + - *base_env*: dict with initial environment. Defaults to os.environ If both `dotenv_path` and `stream`, `find_dotenv()` is used to find the .env file. """ @@ -357,4 +363,5 @@ def dotenv_values( interpolate=interpolate, override=True, encoding=encoding, + base_env=base_env, ).dict() diff --git a/tests/test_main.py b/tests/test_main.py index 13e2791c..0c534925 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -325,6 +325,15 @@ def test_dotenv_values_file(dotenv_file): assert result == {"a": "b"} +def test_dotenv_values_file_base_env(dotenv_file): + with open(dotenv_file, "w") as f: + f.write("a=${var}") + + result = dotenv.dotenv_values(dotenv_file, base_env={'var': 'b'}) + + assert result == {"a": "b"} + + @pytest.mark.parametrize( "env,string,interpolate,expected", [ From cd21e3b00cda22ba4ea66cf2a4223389cae57af8 Mon Sep 17 00:00:00 2001 From: John Bergvall Date: Fri, 13 Aug 2021 16:44:39 +0200 Subject: [PATCH 2/2] Add override-flag to dotenv_values to allow for more advanced chaining --- CHANGELOG.md | 2 ++ README.md | 3 ++- src/dotenv/main.py | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba13ab39..b022ac46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Add support to use a custom dict instead of os.environ for variable interpolating when calling `dotenv_values` (by [@johnbergvall]) +- Add override-flag to `dotenv_values` to allow for more advanced + chaining of env-files (#73 #186 by [@johnbergvall]) ## [0.19.0] - 2021-07-24 diff --git a/README.md b/README.md index ed6b53ca..88bbb0e9 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,8 @@ deploy_env = { 'VERSION': '1.5', } env = dotenv_values('.env.deployment01', base_env={ - **dotenv_values('.env.base', base_env=deploy_env), + # override=False to ignore local file overrides in interpolations: + **dotenv_values('.env.base', override=False, base_env=deploy_env), **deploy_env, }) subprocess.call( diff --git a/src/dotenv/main.py b/src/dotenv/main.py index fbda7f40..08b6ae2b 100644 --- a/src/dotenv/main.py +++ b/src/dotenv/main.py @@ -337,6 +337,7 @@ def dotenv_values( dotenv_path: Union[str, _PathLike, None] = None, stream: Optional[IO[str]] = None, verbose: bool = False, + override: bool = True, interpolate: bool = True, encoding: Optional[str] = "utf-8", base_env: Mapping[str, Optional[str]] = os.environ, @@ -348,6 +349,8 @@ def dotenv_values( - *stream*: `StringIO` object with .env content, used if `dotenv_path` is `None`. - *verbose*: whether to output a warning the .env file is missing. Defaults to `False`. + - *override*: whether to override the system environment/`base_env` variables with + the variables in `.env` file. Defaults to `True` as opposed to `load_dotenv`. - *encoding*: encoding to be used to read the file. - *base_env*: dict with initial environment. Defaults to os.environ @@ -361,7 +364,7 @@ def dotenv_values( stream=stream, verbose=verbose, interpolate=interpolate, - override=True, + override=override, encoding=encoding, base_env=base_env, ).dict()