Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release: 0.3.14 #152

Merged
merged 19 commits into from
Oct 11, 2023
Merged
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1f98257
docs: add some missing inline documentation (#151)
stainless-bot Sep 13, 2023
9cac11b
ci: add workflow to handle release PR edit (#153)
stainless-bot Sep 14, 2023
da6ccb1
fix(client): properly configure model set fields (#154)
stainless-bot Sep 14, 2023
46386f8
feat(client): retry on 408 Request Timeout (#155)
stainless-bot Sep 14, 2023
00f5a19
chore(internal): add helpers (#156)
stainless-bot Sep 14, 2023
59fe4e3
ci: configure release PR title (#158)
stainless-bot Sep 15, 2023
e22f0e7
ci: fix handle-release-pr-title-edit workflow (#159)
stainless-bot Sep 15, 2023
43544a6
feat(types): improve params type names (#160)
stainless-bot Sep 19, 2023
76cfcf9
fix(client): don't error by default for unexpected content types (#161)
stainless-bot Sep 22, 2023
329b307
chore(internal): move error classes from _base_exceptions to _excepti…
stainless-bot Sep 22, 2023
e7aa3e7
feat(package): export a root error type (#163)
stainless-bot Sep 25, 2023
8042473
chore(tests): improve raw response test (#166)
stainless-bot Sep 27, 2023
bb7d129
ci: only run workflow handle-release-pr-title-edit for release PRs (#…
stainless-bot Oct 2, 2023
afeabf1
feat(client): handle retry-after header with a date format (#168)
stainless-bot Oct 2, 2023
b472605
test: rename `API_BASE_URL` to `TEST_API_BASE_URL` (#169)
stainless-bot Oct 2, 2023
4c5289e
feat(client): add forwards-compatible pydantic methods (#171)
stainless-bot Oct 9, 2023
351095b
docs: update readme (#172)
stainless-bot Oct 11, 2023
25046c4
feat(client): add support for passing in a httpx client (#173)
stainless-bot Oct 11, 2023
432a3ac
release: 0.3.14
stainless-bot Oct 11, 2023
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
Prev Previous commit
Next Next commit
feat(client): handle retry-after header with a date format (#168)
stainless-bot authored Oct 2, 2023
commit afeabf13aa5795a7fadd141e53ec81eadbce099a
18 changes: 16 additions & 2 deletions src/anthropic/_base_client.py
Original file line number Diff line number Diff line change
@@ -3,8 +3,10 @@
import json
import time
import uuid
import email
import inspect
import platform
import email.utils
from types import TracebackType
from random import random
from typing import (
@@ -616,10 +618,22 @@ def _calculate_retry_timeout(
try:
# About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
#
# TODO: we may want to handle the case where the header is using the http-date syntax: "Retry-After:
# <http-date>". See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax for
# details.
retry_after = -1 if response_headers is None else int(response_headers.get("retry-after"))
if response_headers is not None:
retry_header = response_headers.get("retry-after")
try:
retry_after = int(retry_header)
except Exception:
retry_date_tuple = email.utils.parsedate_tz(retry_header)
if retry_date_tuple is None:
retry_after = -1
else:
retry_date = email.utils.mktime_tz(retry_date_tuple)
retry_after = int(retry_date - time.time())
else:
retry_after = -1

except Exception:
retry_after = -1

56 changes: 56 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
import asyncio
import inspect
from typing import Any, Dict, Union, cast
from unittest import mock

import httpx
import pytest
@@ -423,6 +424,33 @@ class Model(BaseModel):
response = client.get("/foo", cast_to=Model)
assert isinstance(response, str) # type: ignore[unreachable]

@pytest.mark.parametrize(
"remaining_retries,retry_after,timeout",
[
[3, "20", 20],
[3, "0", 2],
[3, "-10", 2],
[3, "60", 60],
[3, "61", 2],
[3, "Fri, 29 Sep 2023 16:26:57 GMT", 20],
[3, "Fri, 29 Sep 2023 16:26:37 GMT", 2],
[3, "Fri, 29 Sep 2023 16:26:27 GMT", 2],
[3, "Fri, 29 Sep 2023 16:27:37 GMT", 60],
[3, "Fri, 29 Sep 2023 16:27:38 GMT", 2],
[3, "99999999999999999999999999999999999", 2],
[3, "Zun, 29 Sep 2023 16:26:27 GMT", 2],
[3, "", 2],
],
)
@mock.patch("time.time", mock.MagicMock(return_value=1696004797))
def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
client = Anthropic(base_url=base_url, api_key=api_key, _strict_response_validation=True)

headers = httpx.Headers({"retry-after": retry_after})
options = FinalRequestOptions(method="get", url="/foo", max_retries=2)
calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
assert calculated == pytest.approx(timeout, 0.6) # pyright: ignore[reportUnknownMemberType]


class TestAsyncAnthropic:
client = AsyncAnthropic(base_url=base_url, api_key=api_key, _strict_response_validation=True)
@@ -821,3 +849,31 @@ class Model(BaseModel):

response = await client.get("/foo", cast_to=Model)
assert isinstance(response, str) # type: ignore[unreachable]

@pytest.mark.parametrize(
"remaining_retries,retry_after,timeout",
[
[3, "20", 20],
[3, "0", 2],
[3, "-10", 2],
[3, "60", 60],
[3, "61", 2],
[3, "Fri, 29 Sep 2023 16:26:57 GMT", 20],
[3, "Fri, 29 Sep 2023 16:26:37 GMT", 2],
[3, "Fri, 29 Sep 2023 16:26:27 GMT", 2],
[3, "Fri, 29 Sep 2023 16:27:37 GMT", 60],
[3, "Fri, 29 Sep 2023 16:27:38 GMT", 2],
[3, "99999999999999999999999999999999999", 2],
[3, "Zun, 29 Sep 2023 16:26:27 GMT", 2],
[3, "", 2],
],
)
@mock.patch("time.time", mock.MagicMock(return_value=1696004797))
@pytest.mark.asyncio
async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
client = AsyncAnthropic(base_url=base_url, api_key=api_key, _strict_response_validation=True)

headers = httpx.Headers({"retry-after": retry_after})
options = FinalRequestOptions(method="get", url="/foo", max_retries=2)
calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
assert calculated == pytest.approx(timeout, 0.6) # pyright: ignore[reportUnknownMemberType]