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
14 changes: 9 additions & 5 deletions src/openai/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,12 +451,16 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0
lower_custom_headers = [header.lower() for header in custom_headers]
if "x-stainless-retry-count" not in lower_custom_headers:
headers["x-stainless-retry-count"] = str(retries_taken)
timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout
if "x-stainless-read-timeout" not in lower_custom_headers:
timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout
if isinstance(timeout, Timeout):
timeout = timeout.read
if timeout is not None:
headers["x-stainless-read-timeout"] = str(timeout)
read_timeout = timeout
if isinstance(read_timeout, Timeout):
read_timeout = read_timeout.read
if read_timeout is not None:
headers["x-stainless-read-timeout"] = str(read_timeout)
if "request-timeout-ms" not in lower_custom_headers:
if not isinstance(timeout, Timeout) and timeout is not None:
headers["request-timeout-ms"] = str(int(timeout * 1000))
Comment on lines +461 to +463
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve default request-timeout-ms overrides

When a client is configured with default_headers={"request-timeout-ms": "..."} and a numeric timeout, this block still overwrites that caller-supplied default because lower_custom_headers only contains per-request extra_headers, while headers already contains merged default headers. The repo's header behavior allows default_headers to override built-in headers (for example X-Stainless-Lang in tests/test_client.py), so this new automatic header cannot be globally overridden via the same public option unless the caller repeats extra_headers on every request.

Useful? React with 👍 / 👎.


return headers

Expand Down
24 changes: 24 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,30 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:

assert response.http_request.headers.get("x-stainless-retry-count") == "42"

@pytest.mark.respx(base_url=base_url)
@pytest.mark.parametrize(
("timeout", "extra_headers", "expected"),
[
(12.5, {}, "12500"),
(httpx.Timeout(12.5), {}, None),
(12.5, {"request-timeout-ms": Omit()}, None),
(12.5, {"request-timeout-ms": "42"}, "42"),
],
)
def test_request_timeout_ms_header(
self, client: OpenAI, respx_mock: MockRouter, timeout: float | httpx.Timeout, extra_headers: dict[str, Any], expected: str | None
) -> None:
respx_mock.post("/chat/completions").mock(return_value=httpx.Response(200))

response = client.chat.completions.with_raw_response.create(
messages=[{"content": "string", "role": "developer"}],
model="gpt-4o",
timeout=timeout,
extra_headers=extra_headers,
)

assert response.http_request.headers.get("request-timeout-ms") == expected

@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
Expand Down