From e79a290ec9331e618297c7104f619e7acf28312c Mon Sep 17 00:00:00 2001 From: Victor D'AGOSTINO Date: Wed, 7 Dec 2022 10:13:10 -0500 Subject: [PATCH] Add support for requests timeout --- jira/client.py | 22 +++++++++++++++------- jira/resilientsession.py | 8 +++++--- tests/test_resilientsession.py | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/jira/client.py b/jira/client.py index fec48d9cf..c2664bab0 100644 --- a/jira/client.py +++ b/jira/client.py @@ -387,7 +387,9 @@ def __init__( logging: bool = True, max_retries: int = 3, proxies: Any = None, - timeout: Optional[Union[Union[float, int], Tuple[float, float]]] = None, + timeout: Optional[ + Union[None, float, Tuple[float, float], Tuple[float, None]] + ] = None, auth: Tuple[str, str] = None, default_batch_sizes: Optional[Dict[Type[Resource], Optional[int]]] = None, ): @@ -458,7 +460,7 @@ def __init__( are available. async_ (bool): To enable async requests for those actions where we implemented it, like issue update() or delete(). async_workers (int): Set the number of worker threads for async operations. - timeout (Optional[Union[Union[float, int], Tuple[float, float]]]): Set a read/connect timeout for the underlying + timeout (Optional[Union[Union[float, int], Tuple[float, float]]]): Set a connect/read timeout for the underlying calls to Jira (default: None). Obviously this means that you cannot rely on the return code when this is enabled. max_retries (int): Sets the amount Retries for the HTTP sessions initiated by the client. (Default: 3) @@ -615,7 +617,9 @@ def _is_cloud(self) -> bool: def _create_cookie_auth( self, auth: Tuple[str, str], - timeout: Optional[Union[Union[float, int], Tuple[float, float]]], + timeout: Optional[ + Union[None, float, Tuple[float, float], Tuple[float, None]] + ] = None, ): warnings.warn( "Use OAuth or Token based authentication " @@ -3594,14 +3598,16 @@ def _create_http_basic_session( self, username: str, password: str, - timeout: Optional[Union[Union[float, int], Tuple[float, float]]] = None, + timeout: Optional[ + Union[None, float, Tuple[float, float], Tuple[float, None]] + ] = None, ): """Creates a basic http session. Args: username (str): Username for the session password (str): Password for the username - timeout (Optional[int]): If set determines the timeout period for the Session. + timeout (Optional[int]): If set determines the connection/read timeout delay for the Session. Returns: ResilientSession @@ -3628,7 +3634,7 @@ def _create_oauth_session( def _create_kerberos_session( self, - timeout: Optional[Union[Union[float, int], Tuple[float, float]]], + timeout: Optional[Union[None, float, Tuple[float, float], Tuple[float, None]]], kerberos_options=None, ): if kerberos_options is None: @@ -3703,7 +3709,9 @@ def _create_jwt_session( def _create_token_session( self, token_auth: str, - timeout: Optional[Union[Union[float, int], Tuple[float, float]]], + timeout: Optional[ + Union[None, float, Tuple[float, float], Tuple[float, None]] + ] = None, ): """ Creates token-based session. diff --git a/jira/resilientsession.py b/jira/resilientsession.py index 476340d3d..97d579d77 100644 --- a/jira/resilientsession.py +++ b/jira/resilientsession.py @@ -150,11 +150,11 @@ def __init__(self, timeout=None, max_retries: int = 3, max_retry_delay: int = 60 """A Session subclass catered for the Jira API with exponential delaying retry. Args: - timeout (Optional[int]): Timeout. Defaults to None. + timeout (Optional[Union[Union[float, int], Tuple[float, float]]]): Connection/read timeout delay. Defaults to None. max_retries (int): Max number of times to retry a request. Defaults to 3. max_retry_delay (int): Max delay allowed between retries. Defaults to 60. """ - self.timeout = timeout # TODO: Unused? + self.timeout = timeout self.max_retries = max_retries self.max_retry_delay = max_retry_delay super().__init__() @@ -216,7 +216,9 @@ def is_allowed_to_retry() -> bool: exception = None try: - response = super().request(method, url, **processed_kwargs) + response = super().request( + method, url, timeout=self.timeout, **processed_kwargs + ) if response.ok: self.__handle_known_ok_response_errors(response) return response diff --git a/tests/test_resilientsession.py b/tests/test_resilientsession.py index 114b24985..94e1ea7cf 100644 --- a/tests/test_resilientsession.py +++ b/tests/test_resilientsession.py @@ -152,3 +152,21 @@ def test_nonempty_body_is_forwarded(mocked_request_method: Mock): session.get(url="mocked_url", data={"some": "fake-data"}) kwargs = mocked_request_method.call_args.kwargs assert kwargs["data"] == '{"some": "fake-data"}' + + +@patch("requests.Session.request") +def test_with_requests_simple_timeout(mocked_request_method: Mock): + # Disable retries for this test. + session = jira.resilientsession.ResilientSession(max_retries=0, timeout=1) + session.get(url="mocked_url", data={"some": "fake-data"}) + kwargs = mocked_request_method.call_args.kwargs + assert kwargs["data"] == '{"some": "fake-data"}' + + +@patch("requests.Session.request") +def test_with_requests_tuple_timeout(mocked_request_method: Mock): + # Disable retries for this test. + session = jira.resilientsession.ResilientSession(max_retries=0, timeout=(1, 3.5)) + session.get(url="mocked_url", data={"some": "fake-data"}) + kwargs = mocked_request_method.call_args.kwargs + assert kwargs["data"] == '{"some": "fake-data"}'