Skip to content

Commit 5af208d

Browse files
committed
fix(client): make TCP keepalive transport version-safe and preserve client options
- Detect httpx socket_options support at runtime instead of bumping the httpx floor (httpx<0.25 compatible) - Forward effective client options (limits, verify, cert, trust_env, http1, http2) to the prebuilt transport so DEFAULT_CONNECTION_LIMITS and http2=True are no longer dropped - Preserve user-supplied transport
1 parent 623b59a commit 5af208d

1 file changed

Lines changed: 36 additions & 2 deletions

File tree

src/openai/_base_client.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -859,12 +859,45 @@ def _build_keepalive_socket_options() -> list[tuple[int, int, int | bool]]:
859859
return opts
860860

861861

862+
# ``socket_options`` was added to httpx's transports in httpx 0.25.0. This SDK
863+
# still supports ``httpx>=0.23.0``, so detect support at runtime rather than
864+
# raising the dependency floor (which would be a breaking change for users).
865+
_HTTPX_TRANSPORT_SUPPORTS_SOCKET_OPTIONS = (
866+
"socket_options" in inspect.signature(httpx.HTTPTransport.__init__).parameters
867+
)
868+
869+
# ``httpx.Client``/``httpx.AsyncClient`` normally apply these options when they
870+
# build their own default transport. Supplying a pre-built transport bypasses
871+
# that step, so the effective values must be forwarded to avoid silently
872+
# dropping ``DEFAULT_CONNECTION_LIMITS`` and customizations like ``http2=True``.
873+
_TRANSPORT_PASSTHROUGH_KEYS = ("verify", "cert", "trust_env", "http1", "http2", "limits")
874+
875+
876+
def _build_keepalive_transport(
877+
transport_cls: type[httpx.HTTPTransport] | type[httpx.AsyncHTTPTransport],
878+
kwargs: dict[str, Any],
879+
) -> httpx.HTTPTransport | httpx.AsyncHTTPTransport:
880+
"""Build a default transport with TCP keepalive enabled.
881+
882+
The relevant httpx client options are forwarded so that constructing the
883+
transport explicitly does not discard them, and ``socket_options`` is only
884+
passed when the installed httpx version supports it.
885+
"""
886+
transport_kwargs: dict[str, Any] = {
887+
key: kwargs[key] for key in _TRANSPORT_PASSTHROUGH_KEYS if key in kwargs
888+
}
889+
if _HTTPX_TRANSPORT_SUPPORTS_SOCKET_OPTIONS:
890+
transport_kwargs["socket_options"] = _build_keepalive_socket_options()
891+
return transport_cls(**transport_kwargs)
892+
893+
862894
class _DefaultHttpxClient(httpx.Client):
863895
def __init__(self, **kwargs: Any) -> None:
864896
kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
865897
kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
866898
kwargs.setdefault("follow_redirects", True)
867-
kwargs.setdefault("transport", httpx.HTTPTransport(socket_options=_build_keepalive_socket_options()))
899+
if "transport" not in kwargs:
900+
kwargs["transport"] = _build_keepalive_transport(httpx.HTTPTransport, kwargs)
868901
super().__init__(**kwargs)
869902

870903

@@ -1452,7 +1485,8 @@ def __init__(self, **kwargs: Any) -> None:
14521485
kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
14531486
kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
14541487
kwargs.setdefault("follow_redirects", True)
1455-
kwargs.setdefault("transport", httpx.AsyncHTTPTransport(socket_options=_build_keepalive_socket_options()))
1488+
if "transport" not in kwargs:
1489+
kwargs["transport"] = _build_keepalive_transport(httpx.AsyncHTTPTransport, kwargs)
14561490
super().__init__(**kwargs)
14571491

14581492

0 commit comments

Comments
 (0)