From 14803f9f0794abe51467a238e5b1b21c2f0c15d6 Mon Sep 17 00:00:00 2001 From: Ian Campbell Date: Thu, 6 Aug 2026 11:53:33 -0400 Subject: [PATCH 1/3] feat: add configurable request timeout to the Creators API The sync AmazonCreatorsApi called the bundled SDK without _request_timeout, so urllib3 received an explicit None and requests could hang forever. The async layer already defaulted to 30 seconds but did not let callers change it. Both classes now accept a timeout parameter, defaulting to the shared DEFAULT_TIMEOUT of 30 seconds, which accepts a single value or a (connect, read) pair. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 7 + README.md | 9 ++ amazon_creatorsapi/aio/api.py | 10 +- amazon_creatorsapi/aio/client.py | 30 ++++- amazon_creatorsapi/api.py | 10 +- amazon_creatorsapi/core/constants.py | 1 + docs/conf.py | 2 +- docs/pages/usage-guide.md | 9 ++ pyproject.toml | 2 +- tests/amazon_creatorsapi/aio/api_test.py | 141 ++++++++++++++++++++ tests/amazon_creatorsapi/aio/client_test.py | 10 ++ tests/amazon_creatorsapi/api_test.py | 121 +++++++++++++++++ 12 files changed, 342 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24c7005..3a53211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [6.4.0] - 2026-08-06 + +### Added + +- `timeout` parameter in `AmazonCreatorsApi` and `AsyncAmazonCreatorsApi` to set the request timeout +- Default request timeout of 30 seconds for `amazon_creatorsapi`, which previously made requests without any timeout + ## [6.3.0] - 2026-05-15 ### Added diff --git a/README.md b/README.md index 6887537..ec94efa 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,15 @@ amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=4) # M amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=0) # No wait time between requests ``` +### Timeout + +Timeout value represents the number of seconds to wait for a response before failing, being the default value 30 seconds. It also accepts a `(connect, read)` pair to set both timeouts separately. It is available for both the sync and the async API. + +```python +amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=10) # Fails after 10 seconds +amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=(3, 10)) # 3 seconds to connect, 10 to read +``` + ### Async Support For async/await applications, use the async version of the API with `httpx`: diff --git a/amazon_creatorsapi/aio/api.py b/amazon_creatorsapi/aio/api.py index 19e15bf..87c779b 100644 --- a/amazon_creatorsapi/aio/api.py +++ b/amazon_creatorsapi/aio/api.py @@ -12,7 +12,7 @@ from typing_extensions import Self -from amazon_creatorsapi.core.constants import DEFAULT_THROTTLING +from amazon_creatorsapi.core.constants import DEFAULT_THROTTLING, DEFAULT_TIMEOUT from amazon_creatorsapi.core.error_handling import handle_api_error from amazon_creatorsapi.core.parsers import get_asin, get_items_ids from amazon_creatorsapi.core.resources import get_all_resources @@ -104,6 +104,8 @@ class AsyncAmazonCreatorsApi: country: Country code (e.g., "ES", "US"). Used to determine marketplace. marketplace: Marketplace URL (e.g., "www.amazon.es"). Overrides country. throttling: Wait time in seconds between API calls. Defaults to 1 second. + timeout: Request timeout in seconds. Accepts a single value for the whole + request or a (connect, read) pair. Defaults to 30 seconds. Raises: InvalidArgumentError: If neither country nor marketplace is provided. @@ -121,6 +123,7 @@ def __init__( country: CountryCode | None = None, marketplace: str | None = None, throttling: float = DEFAULT_THROTTLING, + timeout: float | tuple[float, float] = DEFAULT_TIMEOUT, ) -> None: """Initialize the async Amazon Creators API client.""" # Validate version early to fail fast (before token manager initialization) @@ -133,6 +136,7 @@ def __init__( self._throttle_lock: asyncio.Lock | None = None self.tag = tag self.throttling = float(throttling) + self.timeout = timeout # Determine marketplace from country or direct value self.marketplace = validate_and_get_marketplace(country, marketplace) @@ -163,7 +167,7 @@ def _validate_version(self, version: str) -> None: async def __aenter__(self) -> Self: """Enter async context manager, creating a persistent HTTP client.""" - self._http_client = AsyncHttpClient(host=API_HOST) + self._http_client = AsyncHttpClient(host=API_HOST, timeout=self.timeout) await self._http_client.__aenter__() self._owns_client = True return self @@ -498,7 +502,7 @@ async def _make_request( if self._http_client is not None: response = await self._http_client.post(endpoint, headers, body) else: - async with AsyncHttpClient(host=API_HOST) as client: + async with AsyncHttpClient(host=API_HOST, timeout=self.timeout) as client: response = await client.post(endpoint, headers, body) # Handle errors diff --git a/amazon_creatorsapi/aio/client.py b/amazon_creatorsapi/aio/client.py index 8ec143b..dd18e91 100644 --- a/amazon_creatorsapi/aio/client.py +++ b/amazon_creatorsapi/aio/client.py @@ -12,6 +12,8 @@ from typing_extensions import Self +from amazon_creatorsapi.core.constants import DEFAULT_TIMEOUT + if TYPE_CHECKING: from types import TracebackType @@ -26,11 +28,30 @@ DEFAULT_HOST = "https://creatorsapi.amazon" -DEFAULT_TIMEOUT = 30.0 VERSION = version("python-amazon-paapi") USER_AGENT = f"python-amazon-paapi/{VERSION} (async)" +def _build_timeout(timeout: float | tuple[float, float]) -> float | httpx.Timeout: + """Return a timeout that httpx understands. + + A (connect, read) pair is mapped to an httpx timeout where the read value + also applies to the write and pool phases, so no phase is left unbounded. + + Args: + timeout: Timeout in seconds, either a single value or a + (connect, read) pair. + + Returns: + The timeout value to give to httpx. + + """ + if isinstance(timeout, tuple): + connect_timeout, read_timeout = timeout + return httpx.Timeout(read_timeout, connect=connect_timeout) + return timeout + + @dataclass class AsyncHttpResponse: """Response from an async HTTP request.""" @@ -64,18 +85,19 @@ class AsyncHttpClient: Args: host: Base URL for API requests. Defaults to Amazon Creators API. - timeout: Request timeout in seconds. Defaults to 30. + timeout: Request timeout in seconds. Accepts a single value for the whole + request or a (connect, read) pair. Defaults to 30. """ def __init__( self, host: str = DEFAULT_HOST, - timeout: float = DEFAULT_TIMEOUT, + timeout: float | tuple[float, float] = DEFAULT_TIMEOUT, ) -> None: """Initialize the async HTTP client.""" self._host = host - self._timeout = timeout + self._timeout = _build_timeout(timeout) self._client: httpx.AsyncClient | None = None self._owns_client = False diff --git a/amazon_creatorsapi/api.py b/amazon_creatorsapi/api.py index 70dc472..b78464d 100644 --- a/amazon_creatorsapi/api.py +++ b/amazon_creatorsapi/api.py @@ -8,7 +8,7 @@ import time from typing import TYPE_CHECKING, NoReturn -from amazon_creatorsapi.core.constants import DEFAULT_THROTTLING +from amazon_creatorsapi.core.constants import DEFAULT_THROTTLING, DEFAULT_TIMEOUT from amazon_creatorsapi.core.error_handling import handle_api_error from amazon_creatorsapi.core.parsers import get_asin, get_items_ids from amazon_creatorsapi.core.resources import get_all_resources @@ -58,6 +58,8 @@ class AmazonCreatorsApi: country: Country code (e.g., "ES", "US"). Used to determine marketplace. marketplace: Marketplace URL (e.g., "www.amazon.es"). Overrides country. throttling: Wait time in seconds between API calls. Defaults to 1 second. + timeout: Request timeout in seconds. Accepts a single value for the whole + request or a (connect, read) pair. Defaults to 30 seconds. Raises: InvalidArgumentError: If neither country nor marketplace is provided. @@ -83,6 +85,7 @@ def __init__( country: CountryCode | None = None, marketplace: str | None = None, throttling: float = DEFAULT_THROTTLING, + timeout: float | tuple[float, float] = DEFAULT_TIMEOUT, ) -> None: """Initialize the Amazon Creators API client.""" self._credential_id = credential_id @@ -91,6 +94,7 @@ def __init__( self._last_query_time = time.time() - throttling self.tag = tag self.throttling = float(throttling) + self.timeout = timeout # Determine marketplace from country or direct value self.marketplace = validate_and_get_marketplace(country, marketplace) @@ -148,6 +152,7 @@ def get_items( response = self._api.get_items( x_marketplace=self.marketplace, get_items_request_content=request, + _request_timeout=self.timeout, ) except ApiException as exc: self._handle_api_exception(exc) @@ -248,6 +253,7 @@ def search_items( response = self._api.search_items( x_marketplace=self.marketplace, search_items_request_content=request, + _request_timeout=self.timeout, ) except ApiException as exc: self._handle_api_exception(exc) @@ -308,6 +314,7 @@ def get_variations( response = self._api.get_variations( x_marketplace=self.marketplace, get_variations_request_content=request, + _request_timeout=self.timeout, ) except ApiException as exc: self._handle_api_exception(exc) @@ -354,6 +361,7 @@ def get_browse_nodes( response = self._api.get_browse_nodes( x_marketplace=self.marketplace, get_browse_nodes_request_content=request, + _request_timeout=self.timeout, ) except ApiException as exc: self._handle_api_exception(exc) diff --git a/amazon_creatorsapi/core/constants.py b/amazon_creatorsapi/core/constants.py index 3c44745..f502ae7 100644 --- a/amazon_creatorsapi/core/constants.py +++ b/amazon_creatorsapi/core/constants.py @@ -1,6 +1,7 @@ """Constants for the Amazon Creators API.""" DEFAULT_THROTTLING = 1 +DEFAULT_TIMEOUT = 30.0 # HTTP status codes HTTP_NOT_FOUND = 404 diff --git a/docs/conf.py b/docs/conf.py index 91bf73b..db06254 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,7 +23,7 @@ author = "Sergio Abad" # The full version, including alpha/beta/rc tags -release = "6.3.0" +release = "6.4.0" # -- General configuration --------------------------------------------------- diff --git a/docs/pages/usage-guide.md b/docs/pages/usage-guide.md index 0a2ba08..8856930 100644 --- a/docs/pages/usage-guide.md +++ b/docs/pages/usage-guide.md @@ -100,6 +100,15 @@ api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=4) # Make api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=0) # No wait time between requests ``` +## Timeout + +Timeout value represents the number of seconds to wait for a response before failing, being the default value 30 seconds. It also accepts a `(connect, read)` pair to set both timeouts separately. It is available for both the sync and the async API. + +```python +api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=10) # Fails after 10 seconds +api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=(3, 10)) # 3 to connect, 10 to read +``` + ## Async Support For async/await applications, install with async support: diff --git a/pyproject.toml b/pyproject.toml index 4b8f0e0..88d747c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-amazon-paapi" -version = "6.3.0" +version = "6.4.0" description = "Amazon Product Advertising API 5.0 wrapper for Python" readme = "README.md" requires-python = ">=3.9" diff --git a/tests/amazon_creatorsapi/aio/api_test.py b/tests/amazon_creatorsapi/aio/api_test.py index 0427d55..c1843bb 100644 --- a/tests/amazon_creatorsapi/aio/api_test.py +++ b/tests/amazon_creatorsapi/aio/api_test.py @@ -6,6 +6,8 @@ from amazon_creatorsapi.aio import ( AsyncAmazonCreatorsApi, ) +from amazon_creatorsapi.aio.api import API_HOST +from amazon_creatorsapi.core.constants import DEFAULT_TIMEOUT from amazon_creatorsapi.errors import ( AssociateValidationError, InvalidArgumentError, @@ -69,6 +71,33 @@ def test_with_custom_throttling(self, mock_token_manager: MagicMock) -> None: self.assertEqual(api.throttling, 2.5) + @patch("amazon_creatorsapi.aio.api.AsyncOAuth2TokenManager") + def test_with_default_timeout(self, mock_token_manager: MagicMock) -> None: + """Test initialization uses the default timeout value.""" + api = AsyncAmazonCreatorsApi( + credential_id="test_id", + credential_secret="test_secret", + version="2.2", + tag="test-tag", + country="US", + ) + + self.assertEqual(api.timeout, DEFAULT_TIMEOUT) + + @patch("amazon_creatorsapi.aio.api.AsyncOAuth2TokenManager") + def test_with_custom_timeout(self, mock_token_manager: MagicMock) -> None: + """Test initialization with custom timeout value.""" + api = AsyncAmazonCreatorsApi( + credential_id="test_id", + credential_secret="test_secret", + version="2.2", + tag="test-tag", + country="US", + timeout=5.0, + ) + + self.assertEqual(api.timeout, 5.0) + @patch("amazon_creatorsapi.aio.api.AsyncOAuth2TokenManager") def test_accepts_lwa_version(self, mock_token_manager: MagicMock) -> None: """Test initialization accepts an LWA-backed 3.x version.""" @@ -1340,5 +1369,117 @@ async def test_request_uses_lwa_authorization_header( self.assertEqual(headers["Authorization"], "Bearer test_token") +class TestAsyncAmazonCreatorsApiTimeout(unittest.IsolatedAsyncioTestCase): + """Tests for the timeout given to the underlying HTTP client.""" + + @patch("amazon_creatorsapi.aio.api.AsyncOAuth2TokenManager") + @patch("amazon_creatorsapi.aio.api.AsyncHttpClient") + async def test_context_manager_client_uses_default_timeout( + self, + mock_http_client_class: MagicMock, + mock_token_manager: MagicMock, + ) -> None: + """Test the persistent client is created with the default timeout.""" + mock_http_client_class.return_value = AsyncMock() + + async with AsyncAmazonCreatorsApi( + credential_id="test_id", + credential_secret="test_secret", + version="2.2", + tag="test-tag", + country="ES", + ): + pass + + mock_http_client_class.assert_called_once_with( + host=API_HOST, + timeout=DEFAULT_TIMEOUT, + ) + + @patch("amazon_creatorsapi.aio.api.AsyncOAuth2TokenManager") + @patch("amazon_creatorsapi.aio.api.AsyncHttpClient") + async def test_context_manager_client_uses_custom_timeout( + self, + mock_http_client_class: MagicMock, + mock_token_manager: MagicMock, + ) -> None: + """Test the persistent client is created with a custom timeout.""" + mock_http_client_class.return_value = AsyncMock() + + async with AsyncAmazonCreatorsApi( + credential_id="test_id", + credential_secret="test_secret", + version="2.2", + tag="test-tag", + country="ES", + timeout=5.0, + ): + pass + + mock_http_client_class.assert_called_once_with(host=API_HOST, timeout=5.0) + + @patch("amazon_creatorsapi.aio.api.AsyncOAuth2TokenManager") + @patch("amazon_creatorsapi.aio.api.AsyncHttpClient") + async def test_request_without_context_manager_uses_custom_timeout( + self, + mock_http_client_class: MagicMock, + mock_token_manager_class: MagicMock, + ) -> None: + """Test the temporary client is created with a custom timeout.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "itemsResult": {"items": [{"ASIN": "B0DLFMFBJW"}]} + } + + mock_client = AsyncMock() + mock_client.post.return_value = mock_response + mock_client.__aenter__.return_value = mock_client + mock_http_client_class.return_value = mock_client + + mock_token_manager = AsyncMock() + mock_token_manager.get_token.return_value = "test_token" + mock_token_manager_class.return_value = mock_token_manager + + api = AsyncAmazonCreatorsApi( + credential_id="test_id", + credential_secret="test_secret", + version="2.2", + tag="test-tag", + country="ES", + throttling=0, + timeout=5.0, + ) + + await api.get_items(["B0DLFMFBJW"]) + + mock_http_client_class.assert_called_once_with(host=API_HOST, timeout=5.0) + + @patch("amazon_creatorsapi.aio.api.AsyncOAuth2TokenManager") + @patch("amazon_creatorsapi.aio.api.AsyncHttpClient") + async def test_client_receives_timeout_tuple( + self, + mock_http_client_class: MagicMock, + mock_token_manager: MagicMock, + ) -> None: + """Test a (connect, read) timeout is passed to the HTTP client.""" + mock_http_client_class.return_value = AsyncMock() + + async with AsyncAmazonCreatorsApi( + credential_id="test_id", + credential_secret="test_secret", + version="2.2", + tag="test-tag", + country="ES", + timeout=(3.0, 10.0), + ): + pass + + mock_http_client_class.assert_called_once_with( + host=API_HOST, + timeout=(3.0, 10.0), + ) + + if __name__ == "__main__": unittest.main() diff --git a/tests/amazon_creatorsapi/aio/client_test.py b/tests/amazon_creatorsapi/aio/client_test.py index 3c8a605..1d4beb0 100644 --- a/tests/amazon_creatorsapi/aio/client_test.py +++ b/tests/amazon_creatorsapi/aio/client_test.py @@ -5,6 +5,8 @@ import unittest from unittest.mock import AsyncMock, MagicMock, patch +import httpx + from amazon_creatorsapi.aio.client import ( DEFAULT_HOST, DEFAULT_TIMEOUT, @@ -32,6 +34,14 @@ async def test_init_custom(self) -> None: self.assertEqual(client._host, host) self.assertEqual(client._timeout, timeout) + async def test_init_timeout_tuple(self) -> None: + """Test a (connect, read) timeout is mapped to an httpx timeout.""" + client = AsyncHttpClient(timeout=(3.0, 10.0)) + self.assertEqual( + client._timeout, + httpx.Timeout(10.0, connect=3.0), + ) + @patch("amazon_creatorsapi.aio.client.httpx.AsyncClient") async def test_context_manager(self, mock_client_cls: MagicMock) -> None: """Test context manager creates and closes client.""" diff --git a/tests/amazon_creatorsapi/api_test.py b/tests/amazon_creatorsapi/api_test.py index c471f51..a8f1b2d 100644 --- a/tests/amazon_creatorsapi/api_test.py +++ b/tests/amazon_creatorsapi/api_test.py @@ -9,6 +9,7 @@ from unittest.mock import MagicMock from amazon_creatorsapi import AmazonCreatorsApi +from amazon_creatorsapi.core.constants import DEFAULT_TIMEOUT from amazon_creatorsapi.errors import ( AssociateValidationError, InvalidArgumentError, @@ -829,3 +830,123 @@ def test_get_browse_nodes_with_explicit_resources( resources=[GetBrowseNodesResource.BROWSE_NODES_DOT_ANCESTOR], ) self.assertIsInstance(result, list) + + @mock.patch("amazon_creatorsapi.api.DefaultApi") + @mock.patch("amazon_creatorsapi.api.ApiClient") + def test_get_items_uses_default_timeout( + self, + _mock_client_class: MagicMock, + mock_api_class: MagicMock, + ) -> None: + """Test get_items forwards the default timeout to the SDK.""" + mock_api = MagicMock() + mock_api_class.return_value = mock_api + mock_response = MagicMock() + mock_response.items_result.items = [MagicMock()] + mock_api.get_items.return_value = mock_response + + api = AmazonCreatorsApi( + credential_id=self.credential_id, + credential_secret=self.credential_secret, + version=self.version, + tag=self.tag, + country=self.country, + throttling=0, + ) + api.get_items(["B0DLFMFBJW"]) + + self.assertEqual(api.timeout, DEFAULT_TIMEOUT) + self.assertEqual( + mock_api.get_items.call_args.kwargs["_request_timeout"], + DEFAULT_TIMEOUT, + ) + + @mock.patch("amazon_creatorsapi.api.DefaultApi") + @mock.patch("amazon_creatorsapi.api.ApiClient") + def test_search_items_forwards_custom_timeout( + self, + _mock_client_class: MagicMock, + mock_api_class: MagicMock, + ) -> None: + """Test search_items forwards a custom timeout to the SDK.""" + mock_api = MagicMock() + mock_api_class.return_value = mock_api + mock_response = MagicMock() + mock_response.search_result = MagicMock() + mock_api.search_items.return_value = mock_response + + api = AmazonCreatorsApi( + credential_id=self.credential_id, + credential_secret=self.credential_secret, + version=self.version, + tag=self.tag, + country=self.country, + throttling=0, + timeout=5.0, + ) + api.search_items(keywords="laptop") + + self.assertEqual( + mock_api.search_items.call_args.kwargs["_request_timeout"], + 5.0, + ) + + @mock.patch("amazon_creatorsapi.api.DefaultApi") + @mock.patch("amazon_creatorsapi.api.ApiClient") + def test_get_variations_forwards_timeout_tuple( + self, + _mock_client_class: MagicMock, + mock_api_class: MagicMock, + ) -> None: + """Test get_variations forwards a (connect, read) timeout to the SDK.""" + mock_api = MagicMock() + mock_api_class.return_value = mock_api + mock_response = MagicMock() + mock_response.variations_result = MagicMock() + mock_api.get_variations.return_value = mock_response + + api = AmazonCreatorsApi( + credential_id=self.credential_id, + credential_secret=self.credential_secret, + version=self.version, + tag=self.tag, + country=self.country, + throttling=0, + timeout=(3.0, 10.0), + ) + api.get_variations("B0DLFMFBJW") + + self.assertEqual( + mock_api.get_variations.call_args.kwargs["_request_timeout"], + (3.0, 10.0), + ) + + @mock.patch("amazon_creatorsapi.api.DefaultApi") + @mock.patch("amazon_creatorsapi.api.ApiClient") + def test_get_browse_nodes_forwards_custom_timeout( + self, + _mock_client_class: MagicMock, + mock_api_class: MagicMock, + ) -> None: + """Test get_browse_nodes forwards a custom timeout to the SDK.""" + mock_api = MagicMock() + mock_api_class.return_value = mock_api + mock_response = MagicMock() + mock_response.browse_nodes_result.browse_nodes = [MagicMock()] + mock_api.get_browse_nodes.return_value = mock_response + + api = AmazonCreatorsApi( + credential_id=self.credential_id, + credential_secret=self.credential_secret, + version=self.version, + tag=self.tag, + country=self.country, + throttling=0, + timeout=7.5, + ) + api.get_browse_nodes(["123456"]) + + self.assertEqual( + mock_api.get_browse_nodes.call_args.kwargs["_request_timeout"], + 7.5, + ) From 00e96ec4dcbff7f35d2963e150d6cb6c6ed43821 Mon Sep 17 00:00:00 2001 From: Ian Campbell Date: Thu, 6 Aug 2026 12:17:03 -0400 Subject: [PATCH 2/3] refactor: keep each layer's current timeout default Dropping the (connect, read) tuple removes the httpx phase-mapping helper it required. A single value in seconds is all the wrapper needs, and it is what both urllib3 and httpx take natively. The sync default stays None and the async default stays 30 seconds, so neither layer changes behavior for existing callers. DEFAULT_TIMEOUT stays in aio/client.py rather than moving to core/constants.py. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 4 +- README.md | 4 +- amazon_creatorsapi/aio/api.py | 10 ++--- amazon_creatorsapi/aio/client.py | 31 +++------------ amazon_creatorsapi/api.py | 8 ++-- amazon_creatorsapi/core/constants.py | 1 - docs/pages/usage-guide.md | 4 +- tests/amazon_creatorsapi/aio/api_test.py | 13 +++--- tests/amazon_creatorsapi/aio/client_test.py | 13 ++---- tests/amazon_creatorsapi/api_test.py | 44 ++++++++++++++++----- 10 files changed, 64 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a53211..656be26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `timeout` parameter in `AmazonCreatorsApi` and `AsyncAmazonCreatorsApi` to set the request timeout -- Default request timeout of 30 seconds for `amazon_creatorsapi`, which previously made requests without any timeout +- `timeout` parameter in `AmazonCreatorsApi` and `AsyncAmazonCreatorsApi` to set the request timeout in seconds +- `AmazonCreatorsApi` requests can now time out instead of waiting indefinitely, by passing `timeout` ## [6.3.0] - 2026-05-15 diff --git a/README.md b/README.md index ec94efa..3edf297 100644 --- a/README.md +++ b/README.md @@ -121,11 +121,11 @@ amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=0) # N ### Timeout -Timeout value represents the number of seconds to wait for a response before failing, being the default value 30 seconds. It also accepts a `(connect, read)` pair to set both timeouts separately. It is available for both the sync and the async API. +Timeout value represents the number of seconds to wait for a response before failing. The sync API waits indefinitely by default, while the async API waits 30 seconds. Use `None` to wait indefinitely. ```python amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=10) # Fails after 10 seconds -amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=(3, 10)) # 3 seconds to connect, 10 to read +amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=0.5) # Fails after half a second ``` ### Async Support diff --git a/amazon_creatorsapi/aio/api.py b/amazon_creatorsapi/aio/api.py index 87c779b..ca42636 100644 --- a/amazon_creatorsapi/aio/api.py +++ b/amazon_creatorsapi/aio/api.py @@ -12,7 +12,7 @@ from typing_extensions import Self -from amazon_creatorsapi.core.constants import DEFAULT_THROTTLING, DEFAULT_TIMEOUT +from amazon_creatorsapi.core.constants import DEFAULT_THROTTLING from amazon_creatorsapi.core.error_handling import handle_api_error from amazon_creatorsapi.core.parsers import get_asin, get_items_ids from amazon_creatorsapi.core.resources import get_all_resources @@ -21,7 +21,7 @@ try: from .auth import VERSION_ENDPOINTS, AsyncOAuth2TokenManager - from .client import AsyncHttpClient + from .client import DEFAULT_TIMEOUT, AsyncHttpClient except ImportError as exc: # pragma: no cover msg = ( "httpx is required for async support. " @@ -104,8 +104,8 @@ class AsyncAmazonCreatorsApi: country: Country code (e.g., "ES", "US"). Used to determine marketplace. marketplace: Marketplace URL (e.g., "www.amazon.es"). Overrides country. throttling: Wait time in seconds between API calls. Defaults to 1 second. - timeout: Request timeout in seconds. Accepts a single value for the whole - request or a (connect, read) pair. Defaults to 30 seconds. + timeout: Request timeout in seconds, or None to wait indefinitely. + Defaults to 30 seconds. Raises: InvalidArgumentError: If neither country nor marketplace is provided. @@ -123,7 +123,7 @@ def __init__( country: CountryCode | None = None, marketplace: str | None = None, throttling: float = DEFAULT_THROTTLING, - timeout: float | tuple[float, float] = DEFAULT_TIMEOUT, + timeout: float | None = DEFAULT_TIMEOUT, ) -> None: """Initialize the async Amazon Creators API client.""" # Validate version early to fail fast (before token manager initialization) diff --git a/amazon_creatorsapi/aio/client.py b/amazon_creatorsapi/aio/client.py index dd18e91..4eab5e8 100644 --- a/amazon_creatorsapi/aio/client.py +++ b/amazon_creatorsapi/aio/client.py @@ -12,8 +12,6 @@ from typing_extensions import Self -from amazon_creatorsapi.core.constants import DEFAULT_TIMEOUT - if TYPE_CHECKING: from types import TracebackType @@ -28,30 +26,11 @@ DEFAULT_HOST = "https://creatorsapi.amazon" +DEFAULT_TIMEOUT = 30.0 VERSION = version("python-amazon-paapi") USER_AGENT = f"python-amazon-paapi/{VERSION} (async)" -def _build_timeout(timeout: float | tuple[float, float]) -> float | httpx.Timeout: - """Return a timeout that httpx understands. - - A (connect, read) pair is mapped to an httpx timeout where the read value - also applies to the write and pool phases, so no phase is left unbounded. - - Args: - timeout: Timeout in seconds, either a single value or a - (connect, read) pair. - - Returns: - The timeout value to give to httpx. - - """ - if isinstance(timeout, tuple): - connect_timeout, read_timeout = timeout - return httpx.Timeout(read_timeout, connect=connect_timeout) - return timeout - - @dataclass class AsyncHttpResponse: """Response from an async HTTP request.""" @@ -85,19 +64,19 @@ class AsyncHttpClient: Args: host: Base URL for API requests. Defaults to Amazon Creators API. - timeout: Request timeout in seconds. Accepts a single value for the whole - request or a (connect, read) pair. Defaults to 30. + timeout: Request timeout in seconds, or None to wait indefinitely. + Defaults to 30. """ def __init__( self, host: str = DEFAULT_HOST, - timeout: float | tuple[float, float] = DEFAULT_TIMEOUT, + timeout: float | None = DEFAULT_TIMEOUT, ) -> None: """Initialize the async HTTP client.""" self._host = host - self._timeout = _build_timeout(timeout) + self._timeout = timeout self._client: httpx.AsyncClient | None = None self._owns_client = False diff --git a/amazon_creatorsapi/api.py b/amazon_creatorsapi/api.py index b78464d..885555d 100644 --- a/amazon_creatorsapi/api.py +++ b/amazon_creatorsapi/api.py @@ -8,7 +8,7 @@ import time from typing import TYPE_CHECKING, NoReturn -from amazon_creatorsapi.core.constants import DEFAULT_THROTTLING, DEFAULT_TIMEOUT +from amazon_creatorsapi.core.constants import DEFAULT_THROTTLING from amazon_creatorsapi.core.error_handling import handle_api_error from amazon_creatorsapi.core.parsers import get_asin, get_items_ids from amazon_creatorsapi.core.resources import get_all_resources @@ -58,8 +58,8 @@ class AmazonCreatorsApi: country: Country code (e.g., "ES", "US"). Used to determine marketplace. marketplace: Marketplace URL (e.g., "www.amazon.es"). Overrides country. throttling: Wait time in seconds between API calls. Defaults to 1 second. - timeout: Request timeout in seconds. Accepts a single value for the whole - request or a (connect, read) pair. Defaults to 30 seconds. + timeout: Request timeout in seconds. Defaults to None, which waits + indefinitely for a response. Raises: InvalidArgumentError: If neither country nor marketplace is provided. @@ -85,7 +85,7 @@ def __init__( country: CountryCode | None = None, marketplace: str | None = None, throttling: float = DEFAULT_THROTTLING, - timeout: float | tuple[float, float] = DEFAULT_TIMEOUT, + timeout: float | None = None, ) -> None: """Initialize the Amazon Creators API client.""" self._credential_id = credential_id diff --git a/amazon_creatorsapi/core/constants.py b/amazon_creatorsapi/core/constants.py index f502ae7..3c44745 100644 --- a/amazon_creatorsapi/core/constants.py +++ b/amazon_creatorsapi/core/constants.py @@ -1,7 +1,6 @@ """Constants for the Amazon Creators API.""" DEFAULT_THROTTLING = 1 -DEFAULT_TIMEOUT = 30.0 # HTTP status codes HTTP_NOT_FOUND = 404 diff --git a/docs/pages/usage-guide.md b/docs/pages/usage-guide.md index 8856930..229ceed 100644 --- a/docs/pages/usage-guide.md +++ b/docs/pages/usage-guide.md @@ -102,11 +102,11 @@ api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=0) # No w ## Timeout -Timeout value represents the number of seconds to wait for a response before failing, being the default value 30 seconds. It also accepts a `(connect, read)` pair to set both timeouts separately. It is available for both the sync and the async API. +Timeout value represents the number of seconds to wait for a response before failing. The sync API waits indefinitely by default, while the async API waits 30 seconds. Use `None` to wait indefinitely. ```python api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=10) # Fails after 10 seconds -api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=(3, 10)) # 3 to connect, 10 to read +api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=0.5) # Fails after half a second ``` ## Async Support diff --git a/tests/amazon_creatorsapi/aio/api_test.py b/tests/amazon_creatorsapi/aio/api_test.py index c1843bb..6b088ed 100644 --- a/tests/amazon_creatorsapi/aio/api_test.py +++ b/tests/amazon_creatorsapi/aio/api_test.py @@ -7,7 +7,7 @@ AsyncAmazonCreatorsApi, ) from amazon_creatorsapi.aio.api import API_HOST -from amazon_creatorsapi.core.constants import DEFAULT_TIMEOUT +from amazon_creatorsapi.aio.client import DEFAULT_TIMEOUT from amazon_creatorsapi.errors import ( AssociateValidationError, InvalidArgumentError, @@ -1457,12 +1457,12 @@ async def test_request_without_context_manager_uses_custom_timeout( @patch("amazon_creatorsapi.aio.api.AsyncOAuth2TokenManager") @patch("amazon_creatorsapi.aio.api.AsyncHttpClient") - async def test_client_receives_timeout_tuple( + async def test_client_receives_disabled_timeout( self, mock_http_client_class: MagicMock, mock_token_manager: MagicMock, ) -> None: - """Test a (connect, read) timeout is passed to the HTTP client.""" + """Test a None timeout is passed to the HTTP client to disable it.""" mock_http_client_class.return_value = AsyncMock() async with AsyncAmazonCreatorsApi( @@ -1471,14 +1471,11 @@ async def test_client_receives_timeout_tuple( version="2.2", tag="test-tag", country="ES", - timeout=(3.0, 10.0), + timeout=None, ): pass - mock_http_client_class.assert_called_once_with( - host=API_HOST, - timeout=(3.0, 10.0), - ) + mock_http_client_class.assert_called_once_with(host=API_HOST, timeout=None) if __name__ == "__main__": diff --git a/tests/amazon_creatorsapi/aio/client_test.py b/tests/amazon_creatorsapi/aio/client_test.py index 1d4beb0..e71ebd3 100644 --- a/tests/amazon_creatorsapi/aio/client_test.py +++ b/tests/amazon_creatorsapi/aio/client_test.py @@ -5,8 +5,6 @@ import unittest from unittest.mock import AsyncMock, MagicMock, patch -import httpx - from amazon_creatorsapi.aio.client import ( DEFAULT_HOST, DEFAULT_TIMEOUT, @@ -34,13 +32,10 @@ async def test_init_custom(self) -> None: self.assertEqual(client._host, host) self.assertEqual(client._timeout, timeout) - async def test_init_timeout_tuple(self) -> None: - """Test a (connect, read) timeout is mapped to an httpx timeout.""" - client = AsyncHttpClient(timeout=(3.0, 10.0)) - self.assertEqual( - client._timeout, - httpx.Timeout(10.0, connect=3.0), - ) + async def test_init_timeout_disabled(self) -> None: + """Test the timeout can be disabled with None.""" + client = AsyncHttpClient(timeout=None) + self.assertIsNone(client._timeout) @patch("amazon_creatorsapi.aio.client.httpx.AsyncClient") async def test_context_manager(self, mock_client_cls: MagicMock) -> None: diff --git a/tests/amazon_creatorsapi/api_test.py b/tests/amazon_creatorsapi/api_test.py index a8f1b2d..2e3670f 100644 --- a/tests/amazon_creatorsapi/api_test.py +++ b/tests/amazon_creatorsapi/api_test.py @@ -9,7 +9,6 @@ from unittest.mock import MagicMock from amazon_creatorsapi import AmazonCreatorsApi -from amazon_creatorsapi.core.constants import DEFAULT_TIMEOUT from amazon_creatorsapi.errors import ( AssociateValidationError, InvalidArgumentError, @@ -833,12 +832,12 @@ def test_get_browse_nodes_with_explicit_resources( @mock.patch("amazon_creatorsapi.api.DefaultApi") @mock.patch("amazon_creatorsapi.api.ApiClient") - def test_get_items_uses_default_timeout( + def test_get_items_without_timeout_by_default( self, _mock_client_class: MagicMock, mock_api_class: MagicMock, ) -> None: - """Test get_items forwards the default timeout to the SDK.""" + """Test get_items sends no timeout to the SDK unless one is given.""" mock_api = MagicMock() mock_api_class.return_value = mock_api mock_response = MagicMock() @@ -855,10 +854,37 @@ def test_get_items_uses_default_timeout( ) api.get_items(["B0DLFMFBJW"]) - self.assertEqual(api.timeout, DEFAULT_TIMEOUT) + self.assertIsNone(api.timeout) + self.assertIsNone(mock_api.get_items.call_args.kwargs["_request_timeout"]) + + @mock.patch("amazon_creatorsapi.api.DefaultApi") + @mock.patch("amazon_creatorsapi.api.ApiClient") + def test_get_items_forwards_custom_timeout( + self, + _mock_client_class: MagicMock, + mock_api_class: MagicMock, + ) -> None: + """Test get_items forwards a custom timeout to the SDK.""" + mock_api = MagicMock() + mock_api_class.return_value = mock_api + mock_response = MagicMock() + mock_response.items_result.items = [MagicMock()] + mock_api.get_items.return_value = mock_response + + api = AmazonCreatorsApi( + credential_id=self.credential_id, + credential_secret=self.credential_secret, + version=self.version, + tag=self.tag, + country=self.country, + throttling=0, + timeout=30.0, + ) + api.get_items(["B0DLFMFBJW"]) + self.assertEqual( mock_api.get_items.call_args.kwargs["_request_timeout"], - DEFAULT_TIMEOUT, + 30.0, ) @mock.patch("amazon_creatorsapi.api.DefaultApi") @@ -893,12 +919,12 @@ def test_search_items_forwards_custom_timeout( @mock.patch("amazon_creatorsapi.api.DefaultApi") @mock.patch("amazon_creatorsapi.api.ApiClient") - def test_get_variations_forwards_timeout_tuple( + def test_get_variations_forwards_custom_timeout( self, _mock_client_class: MagicMock, mock_api_class: MagicMock, ) -> None: - """Test get_variations forwards a (connect, read) timeout to the SDK.""" + """Test get_variations forwards a custom timeout to the SDK.""" mock_api = MagicMock() mock_api_class.return_value = mock_api mock_response = MagicMock() @@ -912,13 +938,13 @@ def test_get_variations_forwards_timeout_tuple( tag=self.tag, country=self.country, throttling=0, - timeout=(3.0, 10.0), + timeout=3.5, ) api.get_variations("B0DLFMFBJW") self.assertEqual( mock_api.get_variations.call_args.kwargs["_request_timeout"], - (3.0, 10.0), + 3.5, ) @mock.patch("amazon_creatorsapi.api.DefaultApi") From cec4c14da75174c3fe8afde77e15aed3f1d8ea79 Mon Sep 17 00:00:00 2001 From: Ian Campbell Date: Thu, 6 Aug 2026 13:27:09 -0400 Subject: [PATCH 3/3] feat: default the sync timeout to the existing 30 seconds The async layer already used DEFAULT_TIMEOUT of 30 seconds, so the sync layer now reuses that same value rather than defaulting to no timeout. Requests that previously hung forever now fail after 30 seconds, and timeout=None restores the old behavior. DEFAULT_TIMEOUT moves to core.constants so both layers can share it. The sync layer cannot import it from aio.client, which requires the optional httpx dependency. It is re-exported from aio.client for compatibility. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 7 ++-- README.md | 2 +- amazon_creatorsapi/aio/api.py | 4 +-- amazon_creatorsapi/aio/client.py | 3 +- amazon_creatorsapi/api.py | 8 ++--- amazon_creatorsapi/core/constants.py | 1 + docs/pages/usage-guide.md | 2 +- tests/amazon_creatorsapi/aio/api_test.py | 2 +- tests/amazon_creatorsapi/api_test.py | 43 ++++++++++++++++++++---- 9 files changed, 54 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 656be26..769354f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `timeout` parameter in `AmazonCreatorsApi` and `AsyncAmazonCreatorsApi` to set the request timeout in seconds -- `AmazonCreatorsApi` requests can now time out instead of waiting indefinitely, by passing `timeout` +- `timeout` parameter in `AmazonCreatorsApi` and `AsyncAmazonCreatorsApi` to set the request timeout in seconds, or `None` to wait indefinitely + +### Changed + +- `AmazonCreatorsApi` requests now time out after 30 seconds instead of waiting indefinitely, matching the timeout already used by `AsyncAmazonCreatorsApi`. Pass `timeout=None` to restore the previous behavior ## [6.3.0] - 2026-05-15 diff --git a/README.md b/README.md index 3edf297..7b10c81 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=0) # N ### Timeout -Timeout value represents the number of seconds to wait for a response before failing. The sync API waits indefinitely by default, while the async API waits 30 seconds. Use `None` to wait indefinitely. +Timeout value represents the number of seconds to wait for a response before failing, being the default value 30 seconds. Use `None` to wait indefinitely. ```python amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=10) # Fails after 10 seconds diff --git a/amazon_creatorsapi/aio/api.py b/amazon_creatorsapi/aio/api.py index ca42636..ae29fc1 100644 --- a/amazon_creatorsapi/aio/api.py +++ b/amazon_creatorsapi/aio/api.py @@ -12,7 +12,7 @@ from typing_extensions import Self -from amazon_creatorsapi.core.constants import DEFAULT_THROTTLING +from amazon_creatorsapi.core.constants import DEFAULT_THROTTLING, DEFAULT_TIMEOUT from amazon_creatorsapi.core.error_handling import handle_api_error from amazon_creatorsapi.core.parsers import get_asin, get_items_ids from amazon_creatorsapi.core.resources import get_all_resources @@ -21,7 +21,7 @@ try: from .auth import VERSION_ENDPOINTS, AsyncOAuth2TokenManager - from .client import DEFAULT_TIMEOUT, AsyncHttpClient + from .client import AsyncHttpClient except ImportError as exc: # pragma: no cover msg = ( "httpx is required for async support. " diff --git a/amazon_creatorsapi/aio/client.py b/amazon_creatorsapi/aio/client.py index 4eab5e8..20d486c 100644 --- a/amazon_creatorsapi/aio/client.py +++ b/amazon_creatorsapi/aio/client.py @@ -12,6 +12,8 @@ from typing_extensions import Self +from amazon_creatorsapi.core.constants import DEFAULT_TIMEOUT + if TYPE_CHECKING: from types import TracebackType @@ -26,7 +28,6 @@ DEFAULT_HOST = "https://creatorsapi.amazon" -DEFAULT_TIMEOUT = 30.0 VERSION = version("python-amazon-paapi") USER_AGENT = f"python-amazon-paapi/{VERSION} (async)" diff --git a/amazon_creatorsapi/api.py b/amazon_creatorsapi/api.py index 885555d..5f0101f 100644 --- a/amazon_creatorsapi/api.py +++ b/amazon_creatorsapi/api.py @@ -8,7 +8,7 @@ import time from typing import TYPE_CHECKING, NoReturn -from amazon_creatorsapi.core.constants import DEFAULT_THROTTLING +from amazon_creatorsapi.core.constants import DEFAULT_THROTTLING, DEFAULT_TIMEOUT from amazon_creatorsapi.core.error_handling import handle_api_error from amazon_creatorsapi.core.parsers import get_asin, get_items_ids from amazon_creatorsapi.core.resources import get_all_resources @@ -58,8 +58,8 @@ class AmazonCreatorsApi: country: Country code (e.g., "ES", "US"). Used to determine marketplace. marketplace: Marketplace URL (e.g., "www.amazon.es"). Overrides country. throttling: Wait time in seconds between API calls. Defaults to 1 second. - timeout: Request timeout in seconds. Defaults to None, which waits - indefinitely for a response. + timeout: Request timeout in seconds, or None to wait indefinitely. + Defaults to 30 seconds. Raises: InvalidArgumentError: If neither country nor marketplace is provided. @@ -85,7 +85,7 @@ def __init__( country: CountryCode | None = None, marketplace: str | None = None, throttling: float = DEFAULT_THROTTLING, - timeout: float | None = None, + timeout: float | None = DEFAULT_TIMEOUT, ) -> None: """Initialize the Amazon Creators API client.""" self._credential_id = credential_id diff --git a/amazon_creatorsapi/core/constants.py b/amazon_creatorsapi/core/constants.py index 3c44745..f502ae7 100644 --- a/amazon_creatorsapi/core/constants.py +++ b/amazon_creatorsapi/core/constants.py @@ -1,6 +1,7 @@ """Constants for the Amazon Creators API.""" DEFAULT_THROTTLING = 1 +DEFAULT_TIMEOUT = 30.0 # HTTP status codes HTTP_NOT_FOUND = 404 diff --git a/docs/pages/usage-guide.md b/docs/pages/usage-guide.md index 229ceed..a949eed 100644 --- a/docs/pages/usage-guide.md +++ b/docs/pages/usage-guide.md @@ -102,7 +102,7 @@ api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=0) # No w ## Timeout -Timeout value represents the number of seconds to wait for a response before failing. The sync API waits indefinitely by default, while the async API waits 30 seconds. Use `None` to wait indefinitely. +Timeout value represents the number of seconds to wait for a response before failing, being the default value 30 seconds. Use `None` to wait indefinitely. ```python api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=10) # Fails after 10 seconds diff --git a/tests/amazon_creatorsapi/aio/api_test.py b/tests/amazon_creatorsapi/aio/api_test.py index 6b088ed..3332c9b 100644 --- a/tests/amazon_creatorsapi/aio/api_test.py +++ b/tests/amazon_creatorsapi/aio/api_test.py @@ -7,7 +7,7 @@ AsyncAmazonCreatorsApi, ) from amazon_creatorsapi.aio.api import API_HOST -from amazon_creatorsapi.aio.client import DEFAULT_TIMEOUT +from amazon_creatorsapi.core.constants import DEFAULT_TIMEOUT from amazon_creatorsapi.errors import ( AssociateValidationError, InvalidArgumentError, diff --git a/tests/amazon_creatorsapi/api_test.py b/tests/amazon_creatorsapi/api_test.py index 2e3670f..bd5b55e 100644 --- a/tests/amazon_creatorsapi/api_test.py +++ b/tests/amazon_creatorsapi/api_test.py @@ -9,6 +9,7 @@ from unittest.mock import MagicMock from amazon_creatorsapi import AmazonCreatorsApi +from amazon_creatorsapi.core.constants import DEFAULT_TIMEOUT from amazon_creatorsapi.errors import ( AssociateValidationError, InvalidArgumentError, @@ -832,12 +833,12 @@ def test_get_browse_nodes_with_explicit_resources( @mock.patch("amazon_creatorsapi.api.DefaultApi") @mock.patch("amazon_creatorsapi.api.ApiClient") - def test_get_items_without_timeout_by_default( + def test_get_items_uses_default_timeout( self, _mock_client_class: MagicMock, mock_api_class: MagicMock, ) -> None: - """Test get_items sends no timeout to the SDK unless one is given.""" + """Test get_items forwards the default timeout to the SDK.""" mock_api = MagicMock() mock_api_class.return_value = mock_api mock_response = MagicMock() @@ -854,8 +855,11 @@ def test_get_items_without_timeout_by_default( ) api.get_items(["B0DLFMFBJW"]) - self.assertIsNone(api.timeout) - self.assertIsNone(mock_api.get_items.call_args.kwargs["_request_timeout"]) + self.assertEqual(api.timeout, DEFAULT_TIMEOUT) + self.assertEqual( + mock_api.get_items.call_args.kwargs["_request_timeout"], + DEFAULT_TIMEOUT, + ) @mock.patch("amazon_creatorsapi.api.DefaultApi") @mock.patch("amazon_creatorsapi.api.ApiClient") @@ -878,14 +882,41 @@ def test_get_items_forwards_custom_timeout( tag=self.tag, country=self.country, throttling=0, - timeout=30.0, + timeout=15.0, ) api.get_items(["B0DLFMFBJW"]) self.assertEqual( mock_api.get_items.call_args.kwargs["_request_timeout"], - 30.0, + 15.0, + ) + + @mock.patch("amazon_creatorsapi.api.DefaultApi") + @mock.patch("amazon_creatorsapi.api.ApiClient") + def test_get_items_with_timeout_disabled( + self, + _mock_client_class: MagicMock, + mock_api_class: MagicMock, + ) -> None: + """Test get_items sends no timeout to the SDK when it is disabled.""" + mock_api = MagicMock() + mock_api_class.return_value = mock_api + mock_response = MagicMock() + mock_response.items_result.items = [MagicMock()] + mock_api.get_items.return_value = mock_response + + api = AmazonCreatorsApi( + credential_id=self.credential_id, + credential_secret=self.credential_secret, + version=self.version, + tag=self.tag, + country=self.country, + throttling=0, + timeout=None, ) + api.get_items(["B0DLFMFBJW"]) + + self.assertIsNone(mock_api.get_items.call_args.kwargs["_request_timeout"]) @mock.patch("amazon_creatorsapi.api.DefaultApi") @mock.patch("amazon_creatorsapi.api.ApiClient")