diff --git a/src/anthropic/_base_client.py b/src/anthropic/_base_client.py index 963cd1b9..bc34419a 100644 --- a/src/anthropic/_base_client.py +++ b/src/anthropic/_base_client.py @@ -368,7 +368,7 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): _client: _HttpxClientT _version: str _base_url: URL - max_retries: int + max_retries: int | float timeout: Union[float, Timeout, None] _strict_response_validation: bool _idempotency_header: str | None @@ -380,7 +380,7 @@ def __init__( version: str, base_url: str | URL, _strict_response_validation: bool, - max_retries: int = DEFAULT_MAX_RETRIES, + max_retries: int | float = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None = DEFAULT_TIMEOUT, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, @@ -913,7 +913,7 @@ def __init__( *, version: str, base_url: str | URL, - max_retries: int = DEFAULT_MAX_RETRIES, + max_retries: int | float = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, @@ -1047,7 +1047,15 @@ def request( max_retries = input_options.get_max_retries(self.max_retries) retries_taken = 0 - for retries_taken in range(max_retries + 1): + # Cap max_retries for range() — math.inf is a float and cannot be passed + # to range(). The error message in __init__ advertises math.inf as valid, + # so we must handle it here. Use sys.maxsize as the effective upper bound. + # Also wrap with int() to handle finite floats like 2.0 (range(3.0) raises TypeError). + if max_retries == math.inf or max_retries >= sys.maxsize: + range_limit = sys.maxsize + else: + range_limit = int(max_retries) + 1 + for retries_taken in range(range_limit): options = model_copy(input_options) options = self._prepare_options(options) @@ -1552,7 +1560,7 @@ def __init__( version: str, base_url: str | URL, _strict_response_validation: bool, - max_retries: int = DEFAULT_MAX_RETRIES, + max_retries: int | float = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, @@ -1687,7 +1695,13 @@ async def request( max_retries = input_options.get_max_retries(self.max_retries) retries_taken = 0 - for retries_taken in range(max_retries + 1): + # Cap max_retries for range() — math.inf is a float and cannot be passed + # to range(). Also wrap with int() to handle finite floats like 2.0 (range(3.0) raises TypeError). + if max_retries == math.inf or max_retries >= sys.maxsize: + range_limit = sys.maxsize + else: + range_limit = int(max_retries) + 1 + for retries_taken in range(range_limit): options = model_copy(input_options) options = await self._prepare_options(options) diff --git a/src/anthropic/_models.py b/src/anthropic/_models.py index 2d979355..be7cd3a3 100644 --- a/src/anthropic/_models.py +++ b/src/anthropic/_models.py @@ -878,7 +878,7 @@ class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] else: model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) - def get_max_retries(self, max_retries: int) -> int: + def get_max_retries(self, max_retries: int | float) -> int | float: if isinstance(self.max_retries, NotGiven): return max_retries return self.max_retries