From 5f05ebc702e78798687a3bf5e96908f7ab271d48 Mon Sep 17 00:00:00 2001 From: Darren Kennedy Date: Sat, 22 Nov 2025 00:12:58 -0700 Subject: [PATCH 1/3] bugfix: Return `None` for inaccessible GCP Secret Manager secrets, update tests to reflect this behavior, and add a test for `list_secrets` error handling. --- pydantic_settings/sources/providers/gcp.py | 3 +- tests/test_source_gcp_secret_manager.py | 40 ++++++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/pydantic_settings/sources/providers/gcp.py b/pydantic_settings/sources/providers/gcp.py index b40117e3..859919b5 100644 --- a/pydantic_settings/sources/providers/gcp.py +++ b/pydantic_settings/sources/providers/gcp.py @@ -75,7 +75,8 @@ def __getitem__(self, key: str) -> str | None: name=self._secret_version_path(key) ).payload.data.decode('UTF-8') except Exception: - raise KeyError(key) + # If we can't access the secret, we return None + self._loaded_secrets[key] = None return self._loaded_secrets[key] diff --git a/tests/test_source_gcp_secret_manager.py b/tests/test_source_gcp_secret_manager.py index e43c45ad..83b7790d 100644 --- a/tests/test_source_gcp_secret_manager.py +++ b/tests/test_source_gcp_secret_manager.py @@ -112,8 +112,7 @@ def test_secret_manager_mapping_getitem_access_error(self, secret_manager_mappin side_effect=Exception('Access denied') ) - with pytest.raises(KeyError): - _ = secret_manager_mapping['test-secret'] + assert secret_manager_mapping['test-secret'] is None def test_secret_manager_mapping_iter(self, secret_manager_mapping): assert list(secret_manager_mapping) == ['test-secret'] @@ -210,3 +209,40 @@ def settings_customise_sources( with pytest.raises(ValidationError): _ = Settings() + + def test_pydantic_base_settings_with_default_value(self, mock_secret_client): + class Settings(BaseSettings): + my_field: str | None = Field(default='foo') + + @classmethod + def settings_customise_sources( + cls, + settings_cls: type[BaseSettings], + init_settings: PydanticBaseSettingsSource, + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + ) -> tuple[PydanticBaseSettingsSource, ...]: + google_secret_manager_settings = GoogleSecretManagerSettingsSource( + settings_cls, secret_client=mock_secret_client + ) + return ( + init_settings, + env_settings, + dotenv_settings, + file_secret_settings, + google_secret_manager_settings, + ) + + settings = Settings() + assert settings.my_field == 'foo' + + def test_secret_manager_mapping_list_secrets_error(self, secret_manager_mapping, mocker): + from pydantic_settings.exceptions import SettingsError + + secret_manager_mapping._secret_client.list_secrets = mocker.Mock( + side_effect=Exception('Permission denied') + ) + + with pytest.raises(Exception, match='Permission denied'): + _ = secret_manager_mapping._secret_names From aed9aee86dd2863d55055b1204bac1bfca0e428f Mon Sep 17 00:00:00 2001 From: Darren Kennedy Date: Sat, 22 Nov 2025 00:17:56 -0700 Subject: [PATCH 2/3] Fix lint error --- tests/test_source_gcp_secret_manager.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_source_gcp_secret_manager.py b/tests/test_source_gcp_secret_manager.py index 83b7790d..c8da5744 100644 --- a/tests/test_source_gcp_secret_manager.py +++ b/tests/test_source_gcp_secret_manager.py @@ -238,8 +238,6 @@ def settings_customise_sources( assert settings.my_field == 'foo' def test_secret_manager_mapping_list_secrets_error(self, secret_manager_mapping, mocker): - from pydantic_settings.exceptions import SettingsError - secret_manager_mapping._secret_client.list_secrets = mocker.Mock( side_effect=Exception('Permission denied') ) From de5486e7d22ebeac65dcefbd37df8e171e50ea21 Mon Sep 17 00:00:00 2001 From: Darren Kennedy Date: Sat, 22 Nov 2025 00:20:12 -0700 Subject: [PATCH 3/3] Ruff format --- tests/test_source_gcp_secret_manager.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_source_gcp_secret_manager.py b/tests/test_source_gcp_secret_manager.py index c8da5744..1ed76468 100644 --- a/tests/test_source_gcp_secret_manager.py +++ b/tests/test_source_gcp_secret_manager.py @@ -238,9 +238,7 @@ def settings_customise_sources( assert settings.my_field == 'foo' def test_secret_manager_mapping_list_secrets_error(self, secret_manager_mapping, mocker): - secret_manager_mapping._secret_client.list_secrets = mocker.Mock( - side_effect=Exception('Permission denied') - ) + secret_manager_mapping._secret_client.list_secrets = mocker.Mock(side_effect=Exception('Permission denied')) with pytest.raises(Exception, match='Permission denied'): _ = secret_manager_mapping._secret_names