From 4601a8d83f7fd9c300c07f2e423682ebbb56a484 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Tue, 30 Jul 2019 10:16:57 +0530 Subject: [PATCH 1/5] Simplify the handling of "typing.cast" --- src/pip/_internal/utils/misc.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index abb95979abc..61f74dc8c1b 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -48,21 +48,18 @@ if MYPY_CHECK_RUNNING: from typing import ( Any, AnyStr, Container, Iterable, List, Mapping, Match, Optional, Text, - Union, + Tuple, Union, cast, ) from pip._vendor.pkg_resources import Distribution from pip._internal.models.link import Link from pip._internal.utils.ui import SpinnerInterface -try: - from typing import cast, Tuple VersionInfo = Tuple[int, int, int] -except ImportError: - # typing's cast() isn't supported in code comments, so we need to - # define a dummy, no-op version. - def cast(typ, val): - return val - VersionInfo = None +else: + # typing's cast() is needed at runtime, but we don't want to import typing. + # Thus, we use a dummy no-op version, which we tell mypy to ignore. + def cast(type_, value): # type: ignore + return value __all__ = ['rmtree', 'display_path', 'backup_dir', @@ -135,7 +132,7 @@ def normalize_version_info(py_version_info): elif len(py_version_info) > 3: py_version_info = py_version_info[:3] - return cast(VersionInfo, py_version_info) + return cast('VersionInfo', py_version_info) def ensure_dir(path): From f41f747f8b70322155489d07b0f23beebf64f93f Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Sun, 4 Aug 2019 19:56:41 +0530 Subject: [PATCH 2/5] Fix handling of tokens (single part credentials) in URLs (#6818) --- news/6795.bugfix | 1 + src/pip/_internal/download.py | 26 ++++++++++++++----- tests/unit/test_download.py | 47 ++++++++++++++++++++++++++++++----- 3 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 news/6795.bugfix diff --git a/news/6795.bugfix b/news/6795.bugfix new file mode 100644 index 00000000000..f80bd9b4b2f --- /dev/null +++ b/news/6795.bugfix @@ -0,0 +1 @@ + Fix handling of tokens (single part credentials) in URLs. diff --git a/src/pip/_internal/download.py b/src/pip/_internal/download.py index 8715eb5b19d..fc1f4dddde8 100644 --- a/src/pip/_internal/download.py +++ b/src/pip/_internal/download.py @@ -295,7 +295,7 @@ def _get_new_credentials(self, original_url, allow_netrc=True, logger.debug("Found credentials in keyring for %s", netloc) return kr_auth - return None, None + return username, password def _get_url_and_credentials(self, original_url): """Return the credentials to use for the provided URL. @@ -312,15 +312,29 @@ def _get_url_and_credentials(self, original_url): # Use any stored credentials that we have for this netloc username, password = self.passwords.get(netloc, (None, None)) - # If nothing cached, acquire new credentials without prompting - # the user (e.g. from netrc, keyring, or similar). - if username is None or password is None: + if username is None and password is None: + # No stored credentials. Acquire new credentials without prompting + # the user. (e.g. from netrc, keyring, or the URL itself) username, password = self._get_new_credentials(original_url) - if username is not None and password is not None: - # Store the username and password + if username is not None or password is not None: + # Convert the username and password if they're None, so that + # this netloc will show up as "cached" in the conditional above. + # Further, HTTPBasicAuth doesn't accept None, so it makes sense to + # cache the value that is going to be used. + username = username or "" + password = password or "" + + # Store any acquired credentials. self.passwords[netloc] = (username, password) + assert ( + # Credentials were found + (username is not None and password is not None) or + # Credentials were not found + (username is None and password is None) + ), "Could not load credentials from url: {}".format(original_url) + return url, username, password def __call__(self, req): diff --git a/tests/unit/test_download.py b/tests/unit/test_download.py index 7b421a7d7d5..7be682dcdf5 100644 --- a/tests/unit/test_download.py +++ b/tests/unit/test_download.py @@ -483,18 +483,53 @@ def test_insecure_host_cache_is_not_enabled(self, tmpdir): assert not hasattr(session.adapters["https://example.com/"], "cache") -def test_get_credentials(): +@pytest.mark.parametrize(["input_url", "url", "username", "password"], [ + ( + "http://user%40email.com:password@example.com/path", + "http://example.com/path", + "user@email.com", + "password", + ), + ( + "http://username:password@example.com/path", + "http://example.com/path", + "username", + "password", + ), + ( + "http://token@example.com/path", + "http://example.com/path", + "token", + "", + ), + ( + "http://example.com/path", + "http://example.com/path", + None, + None, + ), +]) +def test_get_credentials_parses_correctly(input_url, url, username, password): auth = MultiDomainBasicAuth() get = auth._get_url_and_credentials # Check URL parsing - assert get("http://foo:bar@example.com/path") \ - == ('http://example.com/path', 'foo', 'bar') - assert auth.passwords['example.com'] == ('foo', 'bar') + assert get(input_url) == (url, username, password) + assert ( + # There are no credentials in the URL + (username is None and password is None) or + # Credentials were found and "cached" appropriately + auth.passwords['example.com'] == (username, password) + ) + +def test_get_credentials_uses_cached_credentials(): + auth = MultiDomainBasicAuth() auth.passwords['example.com'] = ('user', 'pass') - assert get("http://foo:bar@example.com/path") \ - == ('http://example.com/path', 'user', 'pass') + + got = auth._get_url_and_credentials("http://foo:bar@example.com/path") + expected = ('http://example.com/path', 'user', 'pass') + assert got == expected def test_get_index_url_credentials(): From 250f687ffe95df15d495aa2f1561c53c5c4a1c1f Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Sun, 4 Aug 2019 12:16:51 +0530 Subject: [PATCH 3/5] Merge pull request #6827 from cjerdonek/issue-6804-find-links-expansion Fix "~" expansion in --find-links paths --- news/6804.bugfix | 2 + src/pip/_internal/cli/cmdoptions.py | 8 ++-- tests/unit/test_cmdoptions.py | 57 ++++++++++++++++++++++++----- tests/unit/test_unit_outdated.py | 2 +- 4 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 news/6804.bugfix diff --git a/news/6804.bugfix b/news/6804.bugfix new file mode 100644 index 00000000000..f9599f9fda6 --- /dev/null +++ b/news/6804.bugfix @@ -0,0 +1,2 @@ +Fix a regression that caused ``~`` expansion not to occur in ``--find-links`` +paths. diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py index ecf4d20bcd6..c5c6c22dcfd 100644 --- a/src/pip/_internal/cli/cmdoptions.py +++ b/src/pip/_internal/cli/cmdoptions.py @@ -370,9 +370,11 @@ def make_search_scope(options, suppress_no_index=False): ) index_urls = [] - search_scope = SearchScope( - find_links=options.find_links, - index_urls=index_urls, + # Make sure find_links is a list before passing to create(). + find_links = options.find_links or [] + + search_scope = SearchScope.create( + find_links=find_links, index_urls=index_urls, ) return search_scope diff --git a/tests/unit/test_cmdoptions.py b/tests/unit/test_cmdoptions.py index 2c72244fdd7..d45880516a5 100644 --- a/tests/unit/test_cmdoptions.py +++ b/tests/unit/test_cmdoptions.py @@ -1,5 +1,8 @@ +import os + import pretend import pytest +from mock import patch from pip._internal.cli.cmdoptions import ( _convert_python_version, make_search_scope, @@ -7,20 +10,24 @@ @pytest.mark.parametrize( - 'no_index, suppress_no_index, expected_index_urls', [ - (False, False, ['default_url', 'url1', 'url2']), - (False, True, ['default_url', 'url1', 'url2']), - (True, False, []), + 'find_links, no_index, suppress_no_index, expected', [ + (['link1'], False, False, + (['link1'], ['default_url', 'url1', 'url2'])), + (['link1'], False, True, (['link1'], ['default_url', 'url1', 'url2'])), + (['link1'], True, False, (['link1'], [])), # Passing suppress_no_index=True suppresses no_index=True. - (True, True, ['default_url', 'url1', 'url2']), + (['link1'], True, True, (['link1'], ['default_url', 'url1', 'url2'])), + # Test options.find_links=False. + (False, False, False, ([], ['default_url', 'url1', 'url2'])), ], ) -def test_make_search_scope(no_index, suppress_no_index, expected_index_urls): +def test_make_search_scope(find_links, no_index, suppress_no_index, expected): """ - :param expected: the expected index_urls value. + :param expected: the expected (find_links, index_urls) values. """ + expected_find_links, expected_index_urls = expected options = pretend.stub( - find_links=['link1'], + find_links=find_links, index_url='default_url', extra_index_urls=['url1', 'url2'], no_index=no_index, @@ -28,10 +35,42 @@ def test_make_search_scope(no_index, suppress_no_index, expected_index_urls): search_scope = make_search_scope( options, suppress_no_index=suppress_no_index, ) - assert search_scope.find_links == ['link1'] + assert search_scope.find_links == expected_find_links assert search_scope.index_urls == expected_index_urls +@patch('pip._internal.utils.misc.expanduser') +def test_make_search_scope__find_links_expansion(mock_expanduser, tmpdir): + """ + Test "~" expansion in --find-links paths. + """ + # This is a mock version of expanduser() that expands "~" to the tmpdir. + def expand_path(path): + if path.startswith('~/'): + path = os.path.join(tmpdir, path[2:]) + return path + + mock_expanduser.side_effect = expand_path + + options = pretend.stub( + find_links=['~/temp1', '~/temp2'], + index_url='default_url', + extra_index_urls=[], + no_index=False, + ) + # Only create temp2 and not temp1 to test that "~" expansion only occurs + # when the directory exists. + temp2_dir = os.path.join(tmpdir, 'temp2') + os.mkdir(temp2_dir) + + search_scope = make_search_scope(options) + + # Only ~/temp2 gets expanded. Also, the path is normalized when expanded. + expected_temp2_dir = os.path.normcase(temp2_dir) + assert search_scope.find_links == ['~/temp1', expected_temp2_dir] + assert search_scope.index_urls == ['default_url'] + + @pytest.mark.parametrize('value, expected', [ ('', (None, None)), ('2', ((2,), None)), diff --git a/tests/unit/test_unit_outdated.py b/tests/unit/test_unit_outdated.py index 5a8eb5c1e9f..a5d37f81868 100644 --- a/tests/unit/test_unit_outdated.py +++ b/tests/unit/test_unit_outdated.py @@ -58,7 +58,7 @@ def get_metadata_lines(self, name): def _options(): ''' Some default options that we pass to outdated.pip_version_check ''' return pretend.stub( - find_links=False, index_url='default_url', extra_index_urls=[], + find_links=[], index_url='default_url', extra_index_urls=[], no_index=False, pre=False, trusted_hosts=False, cache_dir='', ) From 21b968e4d1049c7ae000beaaa109d9b51dbb4e54 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Sun, 11 Aug 2019 22:33:44 +0530 Subject: [PATCH 4/5] Bump version to 19.2.2 --- src/pip/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pip/__init__.py b/src/pip/__init__.py index a0196ad5937..04304fd573e 100644 --- a/src/pip/__init__.py +++ b/src/pip/__init__.py @@ -1 +1 @@ -__version__ = "19.2.1" +__version__ = "19.2.2" From 082bc0411e9d17b3c80efed9237c61d7446515f5 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Sun, 11 Aug 2019 22:34:16 +0530 Subject: [PATCH 5/5] Generate NEWS --- NEWS.rst | 11 +++++++++++ news/6795.bugfix | 1 - news/6804.bugfix | 2 -- 3 files changed, 11 insertions(+), 3 deletions(-) delete mode 100644 news/6795.bugfix delete mode 100644 news/6804.bugfix diff --git a/NEWS.rst b/NEWS.rst index 44e82dc40ed..7e68de9e82d 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -7,6 +7,17 @@ .. towncrier release notes start +19.2.2 (2019-08-11) +=================== + +Bug Fixes +--------- + +- Fix handling of tokens (single part credentials) in URLs. (`#6795 `_) +- Fix a regression that caused ``~`` expansion not to occur in ``--find-links`` + paths. (`#6804 `_) + + 19.2.1 (2019-07-23) =================== diff --git a/news/6795.bugfix b/news/6795.bugfix deleted file mode 100644 index f80bd9b4b2f..00000000000 --- a/news/6795.bugfix +++ /dev/null @@ -1 +0,0 @@ - Fix handling of tokens (single part credentials) in URLs. diff --git a/news/6804.bugfix b/news/6804.bugfix deleted file mode 100644 index f9599f9fda6..00000000000 --- a/news/6804.bugfix +++ /dev/null @@ -1,2 +0,0 @@ -Fix a regression that caused ``~`` expansion not to occur in ``--find-links`` -paths.