From 1c2afbaa3b0210e37026e44b0509bee9a27aeb7c Mon Sep 17 00:00:00 2001 From: Gaston Avila Date: Tue, 8 Aug 2023 22:38:07 -0300 Subject: [PATCH 1/2] Demo FileAwareEnv --- environs/__init__.py | 30 ++++++++++++++++++++++++++++++ tests/test_environs.py | 20 ++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/environs/__init__.py b/environs/__init__.py index ef13d64..98034bf 100644 --- a/environs/__init__.py +++ b/environs/__init__.py @@ -535,3 +535,33 @@ def _expand_vars(self, parsed_key, value): def _get_key(self, key: _StrType, *, omit_prefix: _BoolType = False) -> _StrType: return self._prefix + key if self._prefix and not omit_prefix else key + + +class FileAwareEnv(Env): + def _get_from_environ( + self, key: _StrType, default: typing.Any, *, proxied: _BoolType = False + ) -> typing.Tuple[_StrType, typing.Any, typing.Optional[_StrType]]: + env_key = self._get_key(key, omit_prefix=proxied) + file_key = key + "_FILE" + if file_location := os.environ.get(file_key, None): + value = Path(file_location).read_text() + else: + value = os.environ.get(env_key, default) + if hasattr(value, "strip"): + expand_match = self.expand_vars and _EXPANDED_VAR_PATTERN.match(value) + if expand_match: # Full match expand_vars - special case keep default + proxied_key: _StrType = expand_match.groups()[0] + subs_default: typing.Optional[_StrType] = expand_match.groups()[1] + if subs_default is not None: + default = subs_default[2:] + elif value == default: # if we have used default, don't use it recursively + default = ma.missing + return (key, self._get_from_environ(proxied_key, default, proxied=True)[1], proxied_key) + expand_search = self.expand_vars and _EXPANDED_VAR_PATTERN.search(value) + if expand_search: # Multiple or in text match expand_vars - General case - default lost + return self._expand_vars(env_key, value) + # Remove escaped $ + if self.expand_vars and r"\$" in value: + value = value.replace(r"\$", "$") + return env_key, value, None + diff --git a/tests/test_environs.py b/tests/test_environs.py index 70f3034..f1cd821 100644 --- a/tests/test_environs.py +++ b/tests/test_environs.py @@ -33,6 +33,11 @@ def env(): return environs.Env() +@pytest.fixture(scope="function") +def file_aware_env(): + return environs.FileAwareEnv() + + class FauxTestException(Exception): pass @@ -837,3 +842,18 @@ def test_composite_types(self, env, set_env): "foo": "bar", "wget_params": '--header="Referer: https://radiocut.fm/"', } + + +class TestFileAwareEnv: + @pytest.fixture + def env(self): + return environs.FileAwareEnv(expand_vars=True) + + def test_full_expand_vars(self, env, set_env): + set_env( + { + "MAIN": "legacy_value", + "MAIN_FILE": HERE / "subfolder/.another.env", + } + ) + assert env.str("MAIN") == "CUSTOM_STRING=bar\n" From 79051e752450c7b13b67b06b83938cf9f232fa4b Mon Sep 17 00:00:00 2001 From: Gaston Avila Date: Tue, 8 Aug 2023 22:45:51 -0300 Subject: [PATCH 2/2] Cleanup --- environs/__init__.py | 37 ++++++++++++------------------------- tests/test_environs.py | 5 ----- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/environs/__init__.py b/environs/__init__.py index 98034bf..ffb025b 100644 --- a/environs/__init__.py +++ b/environs/__init__.py @@ -495,7 +495,7 @@ def _get_from_environ( proxy env key. """ env_key = self._get_key(key, omit_prefix=proxied) - value = os.environ.get(env_key, default) + value = self._get_value(env_key, default) if hasattr(value, "strip"): expand_match = self.expand_vars and _EXPANDED_VAR_PATTERN.match(value) if expand_match: # Full match expand_vars - special case keep default @@ -536,32 +536,19 @@ def _expand_vars(self, parsed_key, value): def _get_key(self, key: _StrType, *, omit_prefix: _BoolType = False) -> _StrType: return self._prefix + key if self._prefix and not omit_prefix else key + @staticmethod + def _get_value(env_key, default): + return os.environ.get(env_key, default) + class FileAwareEnv(Env): - def _get_from_environ( - self, key: _StrType, default: typing.Any, *, proxied: _BoolType = False - ) -> typing.Tuple[_StrType, typing.Any, typing.Optional[_StrType]]: - env_key = self._get_key(key, omit_prefix=proxied) - file_key = key + "_FILE" + """An environment variable reader that supports reading values from files.""" + + @staticmethod + def _get_value(env_key, default): + file_key = env_key + "_FILE" if file_location := os.environ.get(file_key, None): - value = Path(file_location).read_text() + return Path(file_location).read_text() else: - value = os.environ.get(env_key, default) - if hasattr(value, "strip"): - expand_match = self.expand_vars and _EXPANDED_VAR_PATTERN.match(value) - if expand_match: # Full match expand_vars - special case keep default - proxied_key: _StrType = expand_match.groups()[0] - subs_default: typing.Optional[_StrType] = expand_match.groups()[1] - if subs_default is not None: - default = subs_default[2:] - elif value == default: # if we have used default, don't use it recursively - default = ma.missing - return (key, self._get_from_environ(proxied_key, default, proxied=True)[1], proxied_key) - expand_search = self.expand_vars and _EXPANDED_VAR_PATTERN.search(value) - if expand_search: # Multiple or in text match expand_vars - General case - default lost - return self._expand_vars(env_key, value) - # Remove escaped $ - if self.expand_vars and r"\$" in value: - value = value.replace(r"\$", "$") - return env_key, value, None + return os.environ.get(env_key, default) diff --git a/tests/test_environs.py b/tests/test_environs.py index f1cd821..8eb0cca 100644 --- a/tests/test_environs.py +++ b/tests/test_environs.py @@ -33,11 +33,6 @@ def env(): return environs.Env() -@pytest.fixture(scope="function") -def file_aware_env(): - return environs.FileAwareEnv() - - class FauxTestException(Exception): pass