From e1e6af8018fd62d1f3d18b088eb7a410cad00ddc Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Wed, 14 Oct 2020 19:42:51 -0400 Subject: [PATCH] Add timeout parameter to new methods --- src/pypi_simple/client.py | 31 ++++++++++++++++++++++++------- test/test_client.py | 20 +++++++++++++++----- tox.ini | 1 + 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/pypi_simple/client.py b/src/pypi_simple/client.py index e2aaf16..1f32225 100644 --- a/src/pypi_simple/client.py +++ b/src/pypi_simple/client.py @@ -1,5 +1,5 @@ import platform -from typing import Any, Iterator, List, Optional +from typing import Any, Iterator, List, Optional, Tuple, Union from warnings import warn from packaging.utils import canonicalize_name as normalize import requests @@ -64,7 +64,10 @@ def __init__(self, endpoint: str = PYPI_SIMPLE_ENDPOINT, auth: Any = None, if auth is not None: self.s.auth = auth - def get_index_page(self) -> IndexPage: + def get_index_page( + self, + timeout: Union[float, Tuple[float,float], None] = None, + ) -> IndexPage: """ .. versionadded:: 0.7.0 @@ -76,17 +79,23 @@ def get_index_page(self) -> IndexPage: PyPI's project index file is very large and takes several seconds to parse. Use this method sparingly. + :param timeout: optional timeout to pass to the ``requests`` call + :type timeout: Union[float, Tuple[float,float], None] :rtype: IndexPage :raises requests.HTTPError: if the repository responds with an HTTP error code :raises UnsupportedRepoVersionError: if the repository version has a greater major component than the supported repository version """ - r = self.s.get(self.endpoint) + r = self.s.get(self.endpoint, timeout=timeout) r.raise_for_status() return parse_repo_index_response(r) - def stream_project_names(self, chunk_size: int = 65535) -> Iterator[str]: + def stream_project_names( + self, + chunk_size: int = 65535, + timeout: Union[float, Tuple[float,float], None] = None, + ) -> Iterator[str]: """ .. versionadded:: 0.7.0 @@ -106,18 +115,24 @@ def stream_project_names(self, chunk_size: int = 65535) -> Iterator[str]: :param int chunk_size: how many bytes to read from the response at a time + :param timeout: optional timeout to pass to the ``requests`` call + :type timeout: Union[float, Tuple[float,float], None] :rtype: Iterator[str] :raises requests.HTTPError: if the repository responds with an HTTP error code :raises UnsupportedRepoVersionError: if the repository version has a greater major component than the supported repository version """ - with self.s.get(self.endpoint, stream=True) as r: + with self.s.get(self.endpoint, stream=True, timeout=timeout) as r: r.raise_for_status() for link in parse_links_stream_response(r, chunk_size): yield link.text - def get_project_page(self, project: str) -> Optional[ProjectPage]: + def get_project_page( + self, + project: str, + timeout: Union[float, Tuple[float,float], None] = None, + ) -> Optional[ProjectPage]: """ .. versionadded:: 0.7.0 @@ -128,6 +143,8 @@ def get_project_page(self, project: str) -> Optional[ProjectPage]: :param str project: The name of the project to fetch information on. The name does not need to be normalized. + :param timeout: optional timeout to pass to the ``requests`` call + :type timeout: Union[float, Tuple[float,float], None] :rtype: Optional[ProjectPage] :raises requests.HTTPError: if the repository responds with an HTTP error code other than 404 @@ -135,7 +152,7 @@ def get_project_page(self, project: str) -> Optional[ProjectPage]: greater major component than the supported repository version """ url = self.get_project_url(project) - r = self.s.get(url) + r = self.s.get(url, timeout=timeout) if r.status_code == 404: return None r.raise_for_status() diff --git a/test/test_client.py b/test/test_client.py index cf32e97..fb1f5fc 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -12,7 +12,7 @@ 'text/html; charset=utf-8', ]) @responses.activate -def test_session(content_type): +def test_session(mocker, content_type): session_dir = DATA_DIR / 'session01' with (session_dir / 'simple.html').open() as fp: responses.add( @@ -43,14 +43,18 @@ def test_session(content_type): status=404, ) simple = PyPISimple('https://test.nil/simple/') - assert simple.get_index_page() == IndexPage( + spy = mocker.spy(simple.s, 'get') + assert simple.get_index_page(timeout=3.14) == IndexPage( projects=['in_place', 'foo', 'BAR'], last_serial='12345', repository_version='1.0', ) + call, = spy.call_args_list + assert call.kwargs["timeout"] == 3.14 + spy.reset_mock() assert simple.get_project_url('IN.PLACE') \ == 'https://test.nil/simple/in-place/' - assert simple.get_project_page('IN.PLACE') == ProjectPage( + assert simple.get_project_page('IN.PLACE', timeout=2.718) == ProjectPage( project='IN.PLACE', packages=[ DistributionPackage( @@ -117,6 +121,8 @@ def test_session(content_type): last_serial='54321', repository_version='1.0', ) + call, = spy.call_args_list + assert call.kwargs["timeout"] == 2.718 assert simple.get_project_page('nonexistent') is None @responses.activate @@ -303,7 +309,7 @@ def test_auth_override_custom_session(): assert simple.s.auth == ('user', 'password') @responses.activate -def test_stream_project_names(): +def test_stream_project_names(mocker): session_dir = DATA_DIR / 'session01' with (session_dir / 'simple.html').open() as fp: responses.add( @@ -321,4 +327,8 @@ def test_stream_project_names(): status=500, ) simple = PyPISimple('https://test.nil/simple/') - assert list(simple.stream_project_names()) == ['in_place', 'foo', 'BAR'] + spy = mocker.spy(simple.s, 'get') + assert list(simple.stream_project_names(timeout=1.618)) \ + == ['in_place', 'foo', 'BAR'] + call, = spy.call_args_list + assert call.kwargs["timeout"] == 1.618 diff --git a/tox.ini b/tox.ini index 916ca99..e04c89c 100644 --- a/tox.ini +++ b/tox.ini @@ -12,6 +12,7 @@ deps = flake8-import-order-jwodder pytest~=6.0 pytest-cov~=2.0 + pytest-mock~=3.0 responses~=0.12.0 commands = flake8 src test