From cda2a03967b985621ab688d7e5c558f3507ac2a4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Mar 2018 11:27:50 -0500 Subject: [PATCH 1/4] Extract fixture for entered password --- tests/test_utils.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 8d70a51d..57bc6d68 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -222,8 +222,11 @@ def my_import(name, *args, **kwargs): monkeypatch.setattr(builtins, '__import__', my_import) -def test_get_password_keyring_missing_prompts(monkeypatch, keyring_missing): +@pytest.fixture +def entered_password(monkeypatch): monkeypatch.setattr(utils, 'password_prompt', lambda prompt: 'entered pw') - pw = utils.get_password('system', 'user', None, {}) - assert pw == 'entered pw' + +def test_get_password_keyring_missing_prompts( + entered_password, keyring_missing): + assert utils.get_password('system', 'user', None, {}) == 'entered pw' From 98a9033372ac335cb21901dd0d48532b71ff8182 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Mar 2018 11:54:54 -0500 Subject: [PATCH 2/4] Add test capturing undesirable behavior. Ref #315. --- tests/test_utils.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_utils.py b/tests/test_utils.py index 57bc6d68..baa340c7 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -230,3 +230,22 @@ def entered_password(monkeypatch): def test_get_password_keyring_missing_prompts( entered_password, keyring_missing): assert utils.get_password('system', 'user', None, {}) == 'entered pw' + + +@pytest.fixture +def keyring_no_backends(monkeypatch): + """ + Simulate that keyring has no available backends. When keyring + has no backends for the system, the backend will be a + fail.Keyring, which raises RuntimeError on get_password. + """ + class FailKeyring(object): + @staticmethod + def get_password(system, username): + raise RuntimeError("fail!") + monkeypatch.setitem(sys.modules, 'keyring', FailKeyring()) + + +def test_get_password_runtime_error_suppressed( + entered_password, keyring_no_backends): + assert utils.get_password('system', 'user', None, {}) == 'entered pw' From bc079c98c76f42be1c132988a85101b81b8b9054 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Mar 2018 11:59:58 -0500 Subject: [PATCH 3/4] Simply suppress the exception. Fixes #315. --- twine/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/twine/utils.py b/twine/utils.py index 7318c992..abbe0ff3 100644 --- a/twine/utils.py +++ b/twine/utils.py @@ -196,7 +196,10 @@ def get_password_from_keyring(system, username): except ImportError: return - return keyring.get_password(system, username) + try: + return keyring.get_password(system, username) + except Exception: + pass def password_from_keyring_or_prompt(system, username): From 3c939af7545e7f0ab6623f0394ff8ab6ba417341 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Mar 2018 12:04:40 -0500 Subject: [PATCH 4/4] Generate a UserWarning with the exception string. Ref #315. --- tests/test_utils.py | 5 ++++- twine/utils.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index baa340c7..3d58ddf6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -247,5 +247,8 @@ def get_password(system, username): def test_get_password_runtime_error_suppressed( - entered_password, keyring_no_backends): + entered_password, keyring_no_backends, recwarn): assert utils.get_password('system', 'user', None, {}) == 'entered pw' + assert len(recwarn) == 1 + warning = recwarn.pop(UserWarning) + assert 'fail!' in str(warning) diff --git a/twine/utils.py b/twine/utils.py index abbe0ff3..218a37ad 100644 --- a/twine/utils.py +++ b/twine/utils.py @@ -20,7 +20,7 @@ import getpass import sys import argparse - +import warnings try: import configparser @@ -198,8 +198,8 @@ def get_password_from_keyring(system, username): try: return keyring.get_password(system, username) - except Exception: - pass + except Exception as exc: + warnings.warn(str(exc)) def password_from_keyring_or_prompt(system, username):