Skip to content

Commit

Permalink
Fix #3807: matching characters in nested env delimeter and env prefix (
Browse files Browse the repository at this point in the history
…#3975)

* fix nested env delimeter with matching prefix

* Update env_settings and add change readme

* fix change and add comment

Co-authored-by: Samuel Colvin <s@muelcolvin.com>
  • Loading branch information
arsenron and samuelcolvin committed Aug 11, 2022
1 parent 02a2a8b commit 6650edf
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 3 deletions.
1 change: 1 addition & 0 deletions changes/3975-arsenron.md
@@ -0,0 +1 @@
Remove undefined behaviour when `env_prefix` had characters in common with `env_nested_delimiter`
14 changes: 11 additions & 3 deletions pydantic/env_settings.py
Expand Up @@ -63,6 +63,7 @@ def _build_values(
env_nested_delimiter=(
_env_nested_delimiter if _env_nested_delimiter is not None else self.__config__.env_nested_delimiter
),
env_prefix_len=len(self.__config__.env_prefix),
)
file_secret_settings = SecretsSettingsSource(secrets_dir=_secrets_dir or self.__config__.secrets_dir)
# Provide a hook to set built-in sources priority and add / remove sources
Expand Down Expand Up @@ -142,14 +143,19 @@ def __repr__(self) -> str:


class EnvSettingsSource:
__slots__ = ('env_file', 'env_file_encoding', 'env_nested_delimiter')
__slots__ = ('env_file', 'env_file_encoding', 'env_nested_delimiter', 'env_prefix_len')

def __init__(
self, env_file: Optional[StrPath], env_file_encoding: Optional[str], env_nested_delimiter: Optional[str] = None
self,
env_file: Optional[StrPath],
env_file_encoding: Optional[str],
env_nested_delimiter: Optional[str] = None,
env_prefix_len: int = 0,
):
self.env_file: Optional[StrPath] = env_file
self.env_file_encoding: Optional[str] = env_file_encoding
self.env_nested_delimiter: Optional[str] = env_nested_delimiter
self.env_prefix_len: int = env_prefix_len

def __call__(self, settings: BaseSettings) -> Dict[str, Any]: # noqa C901
"""
Expand Down Expand Up @@ -228,7 +234,9 @@ def explode_env_vars(self, field: ModelField, env_vars: Mapping[str, Optional[st
for env_name, env_val in env_vars.items():
if not any(env_name.startswith(prefix) for prefix in prefixes):
continue
_, *keys, last_key = env_name.split(self.env_nested_delimiter)
# we remove the prefix before splitting in case the prefix has characters in common with the delimiter
env_name_without_prefix = env_name[self.env_prefix_len :]
_, *keys, last_key = env_name_without_prefix.split(self.env_nested_delimiter)
env_var = result
for key in keys:
env_var = env_var.setdefault(key, {})
Expand Down
27 changes: 27 additions & 0 deletions tests/test_settings.py
Expand Up @@ -134,6 +134,33 @@ class Config:
}


def test_nested_env_delimiter_with_prefix(env):
class Subsettings(BaseSettings):
banana: str

class Settings(BaseSettings):
subsettings: Subsettings

class Config:
env_nested_delimiter = '_'
env_prefix = 'myprefix_'

env.set('myprefix_subsettings_banana', 'banana')
s = Settings()
assert s.subsettings.banana == 'banana'

class Settings(BaseSettings):
subsettings: Subsettings

class Config:
env_nested_delimiter = '_'
env_prefix = 'myprefix__'

env.set('myprefix__subsettings_banana', 'banana')
s = Settings()
assert s.subsettings.banana == 'banana'


def test_nested_env_delimiter_complex_required(env):
class Cfg(BaseSettings):
v: str = 'default'
Expand Down

0 comments on commit 6650edf

Please sign in to comment.