From 5293adb3d3d1b6fcdb4e1ffc188a560fe601260c Mon Sep 17 00:00:00 2001 From: Jean Date: Thu, 4 Aug 2022 18:07:41 +0200 Subject: [PATCH] fix(secret): uppercase in filename on linux (#3304) * fix(secret): uppercase in filename * test(uppercase): fix test * refactor(secret): apply feedbacks * refactor(path): apply feedbacks * fix(test): fix path in test * cleanup and correct tests Co-authored-by: Jean Arhancetebehere Co-authored-by: Samuel Colvin --- changes/3273-JeanArhancet.md | 1 + pydantic/env_settings.py | 21 ++++++++++++++++++--- tests/test_settings.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 changes/3273-JeanArhancet.md diff --git a/changes/3273-JeanArhancet.md b/changes/3273-JeanArhancet.md new file mode 100644 index 0000000..2d8b23b --- /dev/null +++ b/changes/3273-JeanArhancet.md @@ -0,0 +1 @@ +Update `SecretsSettingsSource` to respect `config.case_sensitive` \ No newline at end of file diff --git a/pydantic/env_settings.py b/pydantic/env_settings.py index 35b9697..73945e3 100644 --- a/pydantic/env_settings.py +++ b/pydantic/env_settings.py @@ -269,7 +269,11 @@ class SecretsSettingsSource: for field in settings.__fields__.values(): for env_name in field.field_info.extra['env_names']: - path = secrets_path / env_name + path = find_case_path(secrets_path, env_name, settings.__config__.case_sensitive) + if not path: + # path does not exist, we curently don't return a warning for this + continue + if path.is_file(): secret_value = path.read_text().strip() if field.is_complex(): @@ -279,12 +283,11 @@ class SecretsSettingsSource: raise SettingsError(f'error parsing JSON for "{env_name}"') from e secrets[field.alias] = secret_value - elif path.exists(): + else: warnings.warn( f'attempted to load secret file "{path}" but found a {path_type(path)} instead.', stacklevel=4, ) - return secrets def __repr__(self) -> str: @@ -304,3 +307,15 @@ def read_env_file( return {k.lower(): v for k, v in file_vars.items()} else: return file_vars + + +def find_case_path(dir_path: Path, file_name: str, case_sensitive: bool) -> Optional[Path]: + """ + Find a file within path's directory matching filename, optionally ignoring case. + """ + for f in dir_path.iterdir(): + if f.name == file_name: + return f + elif not case_sensitive and f.name.lower() == file_name.lower(): + return f + return None diff --git a/tests/test_settings.py b/tests/test_settings.py index c11cdbb..1e85a54 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -890,6 +890,33 @@ def test_secrets_path(tmp_path): assert Settings().dict() == {'foo': 'foo_secret_value_str'} +def test_secrets_case_sensitive(tmp_path): + (tmp_path / 'SECRET_VAR').write_text('foo_env_value_str') + + class Settings(BaseSettings): + secret_var: Optional[str] + + class Config: + secrets_dir = tmp_path + case_sensitive = True + + assert Settings().dict() == {'secret_var': None} + + +def test_secrets_case_insensitive(tmp_path): + (tmp_path / 'SECRET_VAR').write_text('foo_env_value_str') + + class Settings(BaseSettings): + secret_var: Optional[str] + + class Config: + secrets_dir = tmp_path + case_sensitive = False + + settings = Settings().dict() + assert settings == {'secret_var': 'foo_env_value_str'} + + def test_secrets_path_url(tmp_path): (tmp_path / 'foo').write_text('http://www.example.com') (tmp_path / 'bar').write_text('snap') @@ -940,6 +967,7 @@ def test_secrets_missing(tmp_path): with pytest.raises(ValidationError) as exc_info: Settings() + assert exc_info.value.errors() == [{'loc': ('foo',), 'msg': 'field required', 'type': 'value_error.missing'}]