diff --git a/commit_msg.txt b/commit_msg.txt new file mode 100644 index 00000000..3bf66cc1 --- /dev/null +++ b/commit_msg.txt @@ -0,0 +1,14 @@ +Suppress PasswordDeleteError in ChainerBackend.delete_password + +When deleting a password through ChainerBackend, if the +highest-priority backend raises PasswordDeleteError (because it +doesn't hold that particular password), the chainer now catches +the error and continues to the next backend instead of +propagating immediately. + +This matches the existing pattern for NotImplementedError and +fixes the case where a password exists in a lower-priority +backend but cannot be deleted because a higher-priority backend +fails first. + +Fixes #697 diff --git a/keyring/backends/chainer.py b/keyring/backends/chainer.py index 6bc711f1..38563d7a 100644 --- a/keyring/backends/chainer.py +++ b/keyring/backends/chainer.py @@ -5,6 +5,7 @@ from .. import backend from ..compat import properties +from ..errors import PasswordDeleteError from . import fail @@ -61,7 +62,7 @@ def delete_password(self, service, username): for keyring in self.backends: try: return keyring.delete_password(service, username) - except NotImplementedError: + except (NotImplementedError, PasswordDeleteError): pass def get_credential(self, service, username): diff --git a/tests/backends/test_chainer.py b/tests/backends/test_chainer.py index 11dc6865..9b746b4b 100644 --- a/tests/backends/test_chainer.py +++ b/tests/backends/test_chainer.py @@ -2,6 +2,7 @@ import keyring.backends.chainer from keyring import backend +from keyring.errors import PasswordDeleteError @pytest.fixture @@ -16,6 +17,9 @@ def get_password(self, system, user): def set_password(self, system, user, password): pass + def delete_password(self, system, user): + pass + class Keyring2(backend.KeyringBackend): priority = 2 @@ -25,6 +29,9 @@ def get_password(self, system, user): def set_password(self, system, user, password): raise NotImplementedError() + def delete_password(self, system, user): + raise PasswordDeleteError("Password not found") + return Keyring1(), Keyring2() monkeypatch.setattr('keyring.backend.get_all_keyring', get_two) @@ -45,3 +52,13 @@ def test_chainer_defers_to_fail(self, monkeypatch): assert keyring.backend.by_priority( keyring.backends.chainer.ChainerBackend ) < keyring.backend.by_priority(keyring.backends.fail.Keyring) + + def test_chainer_delete_skips_missing(self, two_keyrings): + """ + When a higher-priority backend raises PasswordDeleteError, + the chainer should fall through to the next backend. + """ + chainer = keyring.backends.chainer.ChainerBackend() + # Should not raise even though Keyring2 (higher priority) + # raises PasswordDeleteError + chainer.delete_password('alpha', 'bravo')