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
35 changes: 32 additions & 3 deletions src/openai/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import time
import uuid
import email
import os
import asyncio
import inspect
import logging
import platform
import warnings
import email.utils
from contextlib import contextmanager
from types import TracebackType
from random import random
from typing import (
Expand Down Expand Up @@ -79,6 +81,30 @@
OVERRIDE_CAST_TO_HEADER,
DEFAULT_CONNECTION_LIMITS,
)


def _normalize_no_proxy_value(value: str) -> str:
entries = [entry.strip() for entry in value.replace("\r", ",").replace("\n", ",").split(",")]
return ",".join(entry for entry in entries if entry)


@contextmanager
def _sanitized_proxy_env() -> Generator[None, None, None]:
original: dict[str, str] = {}

try:
for key in ("NO_PROXY", "no_proxy"):
value = os.environ.get(key)
if value is None or ("\n" not in value and "\r" not in value):
continue

original[key] = value
os.environ[key] = _normalize_no_proxy_value(value)

yield
finally:
for key, value in original.items():
os.environ[key] = value
from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder
from ._exceptions import (
APIStatusError,
Expand Down Expand Up @@ -814,7 +840,8 @@ def __init__(self, **kwargs: Any) -> None:
kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
kwargs.setdefault("follow_redirects", True)
super().__init__(**kwargs)
with _sanitized_proxy_env():
super().__init__(**kwargs)


if TYPE_CHECKING:
Expand Down Expand Up @@ -1388,7 +1415,8 @@ def __init__(self, **kwargs: Any) -> None:
kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
kwargs.setdefault("follow_redirects", True)
super().__init__(**kwargs)
with _sanitized_proxy_env():
super().__init__(**kwargs)
Comment on lines +1418 to +1419

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 Apply the proxy sanitizer to DefaultAioHttpClient too

When users opt into the aiohttp transport via the exported DefaultAioHttpClient, its initializer just below still calls httpx_aiohttp.HttpxAiohttpClient/httpx.AsyncClient.__init__ without this context. With NO_PROXY or no_proxy containing newlines, DefaultAioHttpClient() will therefore hit the same httpx environment-proxy parsing error that this patch fixes for DefaultAsyncHttpxClient; wrap that initializer as well so the optional async client behaves consistently.

Useful? React with 👍 / 👎.



try:
Expand All @@ -1406,7 +1434,8 @@ def __init__(self, **kwargs: Any) -> None:
kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
kwargs.setdefault("follow_redirects", True)

super().__init__(**kwargs)
with _sanitized_proxy_env():
super().__init__(**kwargs)


if TYPE_CHECKING:
Expand Down
12 changes: 12 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,12 @@ def test_validate_headers(self) -> None:
client2 = OpenAI(base_url=base_url, api_key=None, _strict_response_validation=True)
_ = client2

def test_no_proxy_with_newlines_is_sanitized_for_default_http_client(self) -> None:
with update_env(NO_PROXY="localhost\n127.0.0.1"):
client = OpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True)
assert os.environ["NO_PROXY"] == "localhost\n127.0.0.1"
client.close()

def test_default_query_option(self) -> None:
client = OpenAI(
base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"}
Expand Down Expand Up @@ -1438,6 +1444,12 @@ async def test_validate_headers(self) -> None:
client2 = AsyncOpenAI(base_url=base_url, api_key=None, _strict_response_validation=True)
_ = client2

async def test_no_proxy_with_newlines_is_sanitized_for_default_http_client(self) -> None:
with update_env(NO_PROXY="localhost\n127.0.0.1"):
client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True)
assert os.environ["NO_PROXY"] == "localhost\n127.0.0.1"
await client.close()

async def test_default_query_option(self) -> None:
client = AsyncOpenAI(
base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"}
Expand Down