From 69178fdd12ccdf7e4c26319a06ac5bc42933108b Mon Sep 17 00:00:00 2001 From: goodspark Date: Sat, 14 Feb 2026 10:40:39 -0800 Subject: [PATCH] Add timeout arg to client Resolves #7 I used timedelta for the type because it's a nicer (IMO) way to specify durations, since it has self-documenting args like 'seconds' whereas as simple int or float would need additional context to clarify the unit. --- src/zerobouncesdk/zerobouncesdk.py | 11 +++++++---- tests/zero_bounce_test_case.py | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/zerobouncesdk/zerobouncesdk.py b/src/zerobouncesdk/zerobouncesdk.py index e74ccdb..162e19f 100644 --- a/src/zerobouncesdk/zerobouncesdk.py +++ b/src/zerobouncesdk/zerobouncesdk.py @@ -1,5 +1,5 @@ import warnings -from datetime import date +from datetime import date, timedelta import os from typing import List, Optional, Union @@ -31,7 +31,7 @@ class ZeroBounce: BULK_BASE_URL = "https://bulkapi.zerobounce.net/v2" SCORING_BASE_URL = "https://bulkapi.zerobounce.net/v2/scoring" - def __init__(self, api_key: str, base_url: Optional[Union[ZBApiUrl, str]] = None): + def __init__(self, api_key: str, base_url: Optional[Union[ZBApiUrl, str]] = None, timeout: timedelta | None = None): """Initialize the ZeroBounce client. Parameters @@ -41,6 +41,7 @@ def __init__(self, api_key: str, base_url: Optional[Union[ZBApiUrl, str]] = None base_url: Optional[Union[ZBApiUrl, str]], default ZBApiUrl.API_DEFAULT_URL The base URL for the API. Can be a ZBApiUrl enum value, a custom URL string, or None to use the default URL. + timeout: Optional[timedelta], default timeout to use for API requests. Raises ------ @@ -64,11 +65,13 @@ def __init__(self, api_key: str, base_url: Optional[Union[ZBApiUrl, str]] = None # Remove trailing slash if present to maintain consistent URL construction self._base_url = self._base_url.rstrip('/') + self._timeout_s: float | None = None if timeout is None else timeout.total_seconds() + def _get(self, url, response_class, params=None): if not params: params = {} params["api_key"] = self._api_key - response = requests.get(url, params=params) + response = requests.get(url, params=params, timeout=self._timeout_s) try: json_response = response.json() @@ -81,7 +84,7 @@ def _get(self, url, response_class, params=None): return response_class(json_response) def _post(self, url, response_class, data=None, json=None, files=None): - response = requests.post(url, data=data, json=json, files=files) + response = requests.post(url, data=data, json=json, files=files, timeout=self._timeout_s) try: json_response = response.json() except ValueError as e: diff --git a/tests/zero_bounce_test_case.py b/tests/zero_bounce_test_case.py index 0fb44a0..fdc0acb 100644 --- a/tests/zero_bounce_test_case.py +++ b/tests/zero_bounce_test_case.py @@ -1,4 +1,4 @@ -from datetime import date, datetime +from datetime import date, datetime, timedelta from pathlib import Path @@ -13,6 +13,7 @@ ZBValidateBatchError, ZeroBounce, ) +from zerobouncesdk._zb_response import ZBResponse class ZeroBounceTestCase(BaseTestCase): @@ -495,3 +496,19 @@ def test_find_domain_confidence_enum_conversion(self): response = self.zero_bounce_client.find_domain(domain="example.com") self.assertIsInstance(response.confidence, ZBConfidence) self.assertEqual(response.confidence, ZBConfidence.high) + + def test_gets_pass_timeout(self): + self.requests_mock.get.return_value = MockResponse({'a': 'b'}) + + client = ZeroBounce("dummy_key", timeout=timedelta(milliseconds=123)) + response = client._get("https://example.com", ZBResponse) + self.assertEqual(response.a, 'b') + self.requests_mock.get.assert_called_with('https://example.com', params={'api_key': 'dummy_key'}, timeout=0.123) + + def test_posts_pass_timeout(self): + self.requests_mock.post.return_value = MockResponse({'a': 'b'}) + + client = ZeroBounce("dummy_key", timeout=timedelta(milliseconds=123)) + response = client._post("https://example.com", ZBResponse) + self.assertEqual(response.a, 'b') + self.requests_mock.post.assert_called_with('https://example.com', data=None, json=None, files=None, timeout=0.123)