diff --git a/src/pip/_internal/index/collector.py b/src/pip/_internal/index/collector.py index 068bad5ce50..af72e041eb4 100644 --- a/src/pip/_internal/index/collector.py +++ b/src/pip/_internal/index/collector.py @@ -166,7 +166,9 @@ def _get_html_response(url, session): # trip for the conditional GET now instead of only # once per 10 minutes. # For more information, please see pypa/pip#5670. - "Cache-Control": "max-age=0", + # However if we want to override Cache-Control, e.g. via CLI, + # we can still do so. + "Cache-Control": session.headers.get('Cache-Control', 'max-age=0'), }, ) raise_for_status(resp) diff --git a/tests/unit/test_collector.py b/tests/unit/test_collector.py index fa1057b640e..261a7d94ccb 100644 --- a/tests/unit/test_collector.py +++ b/tests/unit/test_collector.py @@ -64,6 +64,7 @@ def test_get_html_response_archive_to_http_scheme(mock_raise_for_status, url, if the scheme supports it, and raise `_NotHTML` if the response isn't HTML. """ session = mock.Mock(PipSession) + session.headers = {} session.head.return_value = mock.Mock(**{ "request.method": "HEAD", "headers": {"Content-Type": content_type}, @@ -120,6 +121,7 @@ def test_get_html_response_archive_to_http_scheme_is_html( request is responded with text/html. """ session = mock.Mock(PipSession) + session.headers = {} session.head.return_value = mock.Mock(**{ "request.method": "HEAD", "headers": {"Content-Type": "text/html"}, @@ -156,6 +158,7 @@ def test_get_html_response_no_head(mock_raise_for_status, url): look like an archive, only the GET request that retrieves data. """ session = mock.Mock(PipSession) + session.headers = {} # Mock the headers dict to ensure it is accessed. session.get.return_value = mock.Mock(headers=mock.Mock(**{ @@ -183,6 +186,7 @@ def test_get_html_response_dont_log_clear_text_password(mock_raise_for_status, in its DEBUG log message. """ session = mock.Mock(PipSession) + session.headers = {} # Mock the headers dict to ensure it is accessed. session.get.return_value = mock.Mock(headers=mock.Mock(**{ @@ -206,6 +210,57 @@ def test_get_html_response_dont_log_clear_text_password(mock_raise_for_status, ] +@pytest.mark.parametrize( + ("url", "headers", "cache_control"), + [ + ( + "http://python.org/python-3.7.1.zip", + {}, + "max-age=0", + ), + ( + "https://pypi.org/pip-18.0.tar.gz", + {}, + "max-age=0", + ), + ( + "http://python.org/python-3.7.1.zip", + {"Cache-Control": "max-age=1"}, + "max-age=1", + ), + ( + "https://pypi.org/pip-18.0.tar.gz", + {"Cache-Control": "max-age=1"}, + "max-age=1", + ), + ], +) +def test_get_html_response_override_cache_control(url, headers, cache_control): + """ + `_get_html_response()` should use the session's default value for the + Cache-Control header if provided. + """ + session = mock.Mock(PipSession) + session.headers = headers + session.head.return_value = mock.Mock(**{ + "request.method": "HEAD", + "headers": {"Content-Type": "text/html"}, + }) + session.get.return_value = mock.Mock(headers={"Content-Type": "text/html"}) + + resp = _get_html_response(url, session=session) + + assert resp is not None + assert session.mock_calls == [ + mock.call.head(url, allow_redirects=True), + mock.call.head().raise_for_status(), + mock.call.get(url, headers={ + "Accept": "text/html", "Cache-Control": cache_control, + }), + mock.call.get().raise_for_status(), + ] + + @pytest.mark.parametrize( ("html", "url", "expected"), [ @@ -456,6 +511,7 @@ def test_request_http_error(mock_raise_for_status, caplog): caplog.set_level(logging.DEBUG) link = Link('http://localhost') session = Mock(PipSession) + session.headers = {} session.get.return_value = Mock() mock_raise_for_status.side_effect = NetworkConnectionError('Http error') assert _get_html_page(link, session=session) is None @@ -469,6 +525,7 @@ def test_request_retries(caplog): caplog.set_level(logging.DEBUG) link = Link('http://localhost') session = Mock(PipSession) + session.headers = {} session.get.side_effect = requests.exceptions.RetryError('Retry error') assert _get_html_page(link, session=session) is None assert ( @@ -574,6 +631,7 @@ def test_get_html_page_directory_append_index(tmpdir): expected_url = "{}/index.html".format(dir_url.rstrip("/")) session = mock.Mock(PipSession) + session.headers = {} fake_response = make_fake_html_response(expected_url) mock_func = mock.patch("pip._internal.index.collector._get_html_response") with mock_func as mock_func: