diff --git a/changes/2917-davidmreed.md b/changes/2917-davidmreed.md new file mode 100644 index 0000000..243781b --- /dev/null +++ b/changes/2917-davidmreed.md @@ -0,0 +1 @@ +Make `SecretsSettingsSource` parse values being assigned to fields of complex types when sourced from a secrets file, just as when sourced from environment variables. diff --git a/pydantic/env_settings.py b/pydantic/env_settings.py index 248d5dd..b68aba8 100644 --- a/pydantic/env_settings.py +++ b/pydantic/env_settings.py @@ -216,7 +216,14 @@ class SecretsSettingsSource: for env_name in field.field_info.extra['env_names']: path = secrets_path / env_name if path.is_file(): - secrets[field.alias] = path.read_text().strip() + secret_value = path.read_text().strip() + if field.is_complex(): + try: + secret_value = settings.__config__.json_loads(secret_value) + except ValueError as e: + raise SettingsError(f'error parsing JSON for "{env_name}"') from e + + secrets[field.alias] = secret_value elif path.exists(): warnings.warn( f'attempted to load secret file "{path}" but found a {path_type(path)} instead.', diff --git a/tests/test_settings.py b/tests/test_settings.py index 5fa2f54..11f0519 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -830,6 +830,33 @@ def test_secrets_path_url(tmp_path): assert Settings().dict() == {'foo': 'http://www.example.com', 'bar': SecretStr('snap')} +def test_secrets_path_json(tmp_path): + p = tmp_path / 'foo' + p.write_text('{"a": "b"}') + + class Settings(BaseSettings): + foo: Dict[str, str] + + class Config: + secrets_dir = tmp_path + + assert Settings().dict() == {'foo': {'a': 'b'}} + + +def test_secrets_path_invalid_json(tmp_path): + p = tmp_path / 'foo' + p.write_text('{"a": "b"') + + class Settings(BaseSettings): + foo: Dict[str, str] + + class Config: + secrets_dir = tmp_path + + with pytest.raises(SettingsError, match='error parsing JSON for "foo"'): + Settings() + + def test_secrets_missing(tmp_path): class Settings(BaseSettings): foo: str