Skip to content

Commit 65701fb

Browse files
feat(client): allow overriding retry count header (#1745)
1 parent c118d98 commit 65701fb

File tree

2 files changed

+134
-1
lines changed

2 files changed

+134
-1
lines changed

src/openai/_base_client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,10 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0
413413
if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
414414
headers[idempotency_header] = options.idempotency_key or self._idempotency_key()
415415

416-
headers.setdefault("x-stainless-retry-count", str(retries_taken))
416+
# Don't set the retry count header if it was already set or removed by the caller. We check
417+
# `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
418+
if "x-stainless-retry-count" not in (header.lower() for header in custom_headers):
419+
headers["x-stainless-retry-count"] = str(retries_taken)
417420

418421
return headers
419422

tests/test_client.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,70 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
790790
assert response.retries_taken == failures_before_success
791791
assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
792792

793+
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
794+
@mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
795+
@pytest.mark.respx(base_url=base_url)
796+
def test_omit_retry_count_header(
797+
self, client: OpenAI, failures_before_success: int, respx_mock: MockRouter
798+
) -> None:
799+
client = client.with_options(max_retries=4)
800+
801+
nb_retries = 0
802+
803+
def retry_handler(_request: httpx.Request) -> httpx.Response:
804+
nonlocal nb_retries
805+
if nb_retries < failures_before_success:
806+
nb_retries += 1
807+
return httpx.Response(500)
808+
return httpx.Response(200)
809+
810+
respx_mock.post("/chat/completions").mock(side_effect=retry_handler)
811+
812+
response = client.chat.completions.with_raw_response.create(
813+
messages=[
814+
{
815+
"content": "string",
816+
"role": "system",
817+
}
818+
],
819+
model="gpt-4o",
820+
extra_headers={"x-stainless-retry-count": Omit()},
821+
)
822+
823+
assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
824+
825+
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
826+
@mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
827+
@pytest.mark.respx(base_url=base_url)
828+
def test_overwrite_retry_count_header(
829+
self, client: OpenAI, failures_before_success: int, respx_mock: MockRouter
830+
) -> None:
831+
client = client.with_options(max_retries=4)
832+
833+
nb_retries = 0
834+
835+
def retry_handler(_request: httpx.Request) -> httpx.Response:
836+
nonlocal nb_retries
837+
if nb_retries < failures_before_success:
838+
nb_retries += 1
839+
return httpx.Response(500)
840+
return httpx.Response(200)
841+
842+
respx_mock.post("/chat/completions").mock(side_effect=retry_handler)
843+
844+
response = client.chat.completions.with_raw_response.create(
845+
messages=[
846+
{
847+
"content": "string",
848+
"role": "system",
849+
}
850+
],
851+
model="gpt-4o",
852+
extra_headers={"x-stainless-retry-count": "42"},
853+
)
854+
855+
assert response.http_request.headers.get("x-stainless-retry-count") == "42"
856+
793857
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
794858
@mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
795859
@pytest.mark.respx(base_url=base_url)
@@ -1586,6 +1650,72 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
15861650
assert response.retries_taken == failures_before_success
15871651
assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
15881652

1653+
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
1654+
@mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
1655+
@pytest.mark.respx(base_url=base_url)
1656+
@pytest.mark.asyncio
1657+
async def test_omit_retry_count_header(
1658+
self, async_client: AsyncOpenAI, failures_before_success: int, respx_mock: MockRouter
1659+
) -> None:
1660+
client = async_client.with_options(max_retries=4)
1661+
1662+
nb_retries = 0
1663+
1664+
def retry_handler(_request: httpx.Request) -> httpx.Response:
1665+
nonlocal nb_retries
1666+
if nb_retries < failures_before_success:
1667+
nb_retries += 1
1668+
return httpx.Response(500)
1669+
return httpx.Response(200)
1670+
1671+
respx_mock.post("/chat/completions").mock(side_effect=retry_handler)
1672+
1673+
response = await client.chat.completions.with_raw_response.create(
1674+
messages=[
1675+
{
1676+
"content": "string",
1677+
"role": "system",
1678+
}
1679+
],
1680+
model="gpt-4o",
1681+
extra_headers={"x-stainless-retry-count": Omit()},
1682+
)
1683+
1684+
assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
1685+
1686+
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
1687+
@mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
1688+
@pytest.mark.respx(base_url=base_url)
1689+
@pytest.mark.asyncio
1690+
async def test_overwrite_retry_count_header(
1691+
self, async_client: AsyncOpenAI, failures_before_success: int, respx_mock: MockRouter
1692+
) -> None:
1693+
client = async_client.with_options(max_retries=4)
1694+
1695+
nb_retries = 0
1696+
1697+
def retry_handler(_request: httpx.Request) -> httpx.Response:
1698+
nonlocal nb_retries
1699+
if nb_retries < failures_before_success:
1700+
nb_retries += 1
1701+
return httpx.Response(500)
1702+
return httpx.Response(200)
1703+
1704+
respx_mock.post("/chat/completions").mock(side_effect=retry_handler)
1705+
1706+
response = await client.chat.completions.with_raw_response.create(
1707+
messages=[
1708+
{
1709+
"content": "string",
1710+
"role": "system",
1711+
}
1712+
],
1713+
model="gpt-4o",
1714+
extra_headers={"x-stainless-retry-count": "42"},
1715+
)
1716+
1717+
assert response.http_request.headers.get("x-stainless-retry-count") == "42"
1718+
15891719
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
15901720
@mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
15911721
@pytest.mark.respx(base_url=base_url)

0 commit comments

Comments
 (0)