Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/zerobouncesdk/zerobouncesdk.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import warnings
from datetime import date
from datetime import date, timedelta
import os
from typing import List, Optional, Union

Expand Down Expand Up @@ -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
Expand All @@ -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
------
Expand All @@ -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()
Expand All @@ -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:
Expand Down
19 changes: 18 additions & 1 deletion tests/zero_bounce_test_case.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import date, datetime
from datetime import date, datetime, timedelta
from pathlib import Path


Expand All @@ -13,6 +13,7 @@
ZBValidateBatchError,
ZeroBounce,
)
from zerobouncesdk._zb_response import ZBResponse

class ZeroBounceTestCase(BaseTestCase):

Expand Down Expand Up @@ -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)