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
26 changes: 20 additions & 6 deletions src/anthropic/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Comment on lines 368 to 372
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the attribute annotation to max_retries: int | float helps communicate support for math.inf, but the constructor parameter is still annotated as int and FinalRequestOptions.get_max_retries() is typed to take/return int (see src/anthropic/_models.py). This inconsistency will surface for type-checkers and makes it unclear which layer is responsible for validating/coercing floats; consider updating the public __init__ signature and/or ensuring get_max_retries() continues to return an int by normalizing self.max_retries at assignment time.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — updated __init__ parameter types in BaseClient, SyncAPIClient, and AsyncAPIClient to int | float. Also updated FinalRequestOptions.get_max_retries() return type.

_strict_response_validation: bool
_idempotency_header: str | None
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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):
Comment on lines +1050 to +1058
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

range_limit can still become a float when max_retries is a finite float < sys.maxsize (e.g. 2.0 or 3.5), which will still raise TypeError in range(range_limit). Since __init__ currently advertises passing “a very high number” and this PR expands the type to int | float, consider normalizing here (or earlier) to an int (e.g. accept only integer-valued floats via .is_integer(), handle math.inf explicitly, and reject nan/non-integral values with a clear error).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — now explicitly checks for math.inf and >= sys.maxsize, then uses int(max_retries) + 1 for finite values. Applied to both sync and async loops.

Comment on lines +1052 to +1058
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capping range_limit to sys.maxsize changes the semantics for max_retries >= sys.maxsize: the loop will run sys.maxsize times, which corresponds to sys.maxsize - 1 retries (since the first iteration is the initial attempt). If the intent is “retries up to sys.maxsize times” (per PR description), the cap likely needs to be applied to max_retries and then use effective_max_retries + 1 for the range (i.e. allow sys.maxsize + 1 iterations when max_retries is infinite/very large).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Practically irrelevant — whether it's sys.maxsize or sys.maxsize - 1 retries, both are effectively infinite. Adding + 1 could actually overflow.

options = model_copy(input_options)
options = self._prepare_options(options)

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

Expand Down
2 changes: 1 addition & 1 deletion src/anthropic/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down