Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.88.0"
".": "1.89.0"
}
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## 1.89.0 (2025-06-20)

Full Changelog: [v1.88.0...v1.89.0](https://github.com/openai/openai-python/compare/v1.88.0...v1.89.0)

### Features

* **client:** add support for aiohttp ([9218b07](https://github.com/openai/openai-python/commit/9218b07727bf6f6eb00953df66de6ab061fecddb))


### Bug Fixes

* **tests:** fix: tests which call HTTP endpoints directly with the example parameters ([35bcc4b](https://github.com/openai/openai-python/commit/35bcc4b80bdbaa31108650f2a515902e83794e5a))


### Chores

* **readme:** update badges ([68044ee](https://github.com/openai/openai-python/commit/68044ee85d1bf324b17d3f60c914df4725d47fc8))

## 1.88.0 (2025-06-17)

Full Changelog: [v1.87.0...v1.88.0](https://github.com/openai/openai-python/compare/v1.87.0...v1.88.0)
Expand Down
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# OpenAI Python API library

[![PyPI version](https://img.shields.io/pypi/v/openai.svg)](https://pypi.org/project/openai/)
[![PyPI version](<https://img.shields.io/pypi/v/openai.svg?label=pypi%20(stable)>)](https://pypi.org/project/openai/)

The OpenAI Python library provides convenient access to the OpenAI REST API from any Python 3.8+
application. The library includes type definitions for all request params and response fields,
Expand Down Expand Up @@ -145,6 +145,45 @@ asyncio.run(main())

Functionality between the synchronous and asynchronous clients is otherwise identical.

### With aiohttp

By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.

You can enable this by installing `aiohttp`:

```sh
# install from PyPI
pip install openai[aiohttp]
```

Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:

```python
import os
import asyncio
from openai import DefaultAioHttpClient
from openai import AsyncOpenAI


async def main() -> None:
async with AsyncOpenAI(
api_key=os.environ.get("OPENAI_API_KEY"), # This is the default and can be omitted
http_client=DefaultAioHttpClient(),
) as client:
chat_completion = await client.chat.completions.create(
messages=[
{
"role": "user",
"content": "Say this is a test",
}
],
model="gpt-4o",
)


asyncio.run(main())
```

## Streaming responses

We provide support for streaming responses using Server Side Events (SSE).
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "openai"
version = "1.88.0"
version = "1.89.0"
description = "The official Python library for the openai API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down Expand Up @@ -43,6 +43,7 @@ Repository = "https://github.com/openai/openai-python"
openai = "openai.cli:main"

[project.optional-dependencies]
aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"]
realtime = ["websockets >= 13, < 16"]
datalib = ["numpy >= 1", "pandas >= 1.2.3", "pandas-stubs >= 1.1.0.11"]
voice_helpers = ["sounddevice>=0.5.1", "numpy>=2.0.2"]
Expand Down
26 changes: 26 additions & 0 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
# universal: false

-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
aiohttp==3.12.13
# via httpx-aiohttp
# via openai
aiosignal==1.3.2
# via aiohttp
annotated-types==0.6.0
# via pydantic
anyio==4.1.0
Expand All @@ -19,7 +26,10 @@ argcomplete==3.1.2
# via nox
asttokens==2.4.1
# via inline-snapshot
async-timeout==5.0.1
# via aiohttp
attrs==24.2.0
# via aiohttp
# via outcome
# via trio
azure-core==1.31.0
Expand Down Expand Up @@ -60,18 +70,25 @@ executing==2.1.0
# via inline-snapshot
filelock==3.12.4
# via virtualenv
frozenlist==1.7.0
# via aiohttp
# via aiosignal
h11==0.14.0
# via httpcore
httpcore==1.0.2
# via httpx
httpx==0.28.1
# via httpx-aiohttp
# via openai
# via respx
httpx-aiohttp==0.1.6
# via openai
idna==3.4
# via anyio
# via httpx
# via requests
# via trio
# via yarl
importlib-metadata==7.0.0
iniconfig==2.0.0
# via pytest
Expand All @@ -87,6 +104,9 @@ msal==1.31.0
# via msal-extensions
msal-extensions==1.2.0
# via azure-identity
multidict==6.5.0
# via aiohttp
# via yarl
mypy==1.14.1
mypy-extensions==1.0.0
# via black
Expand Down Expand Up @@ -118,6 +138,9 @@ pluggy==1.5.0
# via pytest
portalocker==2.10.1
# via msal-extensions
propcache==0.3.2
# via aiohttp
# via yarl
pycparser==2.22
# via cffi
pydantic==2.10.3
Expand Down Expand Up @@ -181,6 +204,7 @@ typing-extensions==4.12.2
# via azure-core
# via azure-identity
# via black
# via multidict
# via mypy
# via openai
# via pydantic
Expand All @@ -194,5 +218,7 @@ virtualenv==20.24.5
# via nox
websockets==15.0.1
# via openai
yarl==1.20.1
# via aiohttp
zipp==3.17.0
# via importlib-metadata
27 changes: 27 additions & 0 deletions requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,22 @@
# universal: false

-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
aiohttp==3.12.13
# via httpx-aiohttp
# via openai
aiosignal==1.3.2
# via aiohttp
annotated-types==0.6.0
# via pydantic
anyio==4.1.0
# via httpx
# via openai
async-timeout==5.0.1
# via aiohttp
attrs==25.3.0
# via aiohttp
certifi==2023.7.22
# via httpcore
# via httpx
Expand All @@ -24,17 +35,27 @@ distro==1.8.0
# via openai
exceptiongroup==1.2.2
# via anyio
frozenlist==1.7.0
# via aiohttp
# via aiosignal
h11==0.14.0
# via httpcore
httpcore==1.0.2
# via httpx
httpx==0.28.1
# via httpx-aiohttp
# via openai
httpx-aiohttp==0.1.6
# via openai
idna==3.4
# via anyio
# via httpx
# via yarl
jiter==0.6.1
# via openai
multidict==6.5.0
# via aiohttp
# via yarl
numpy==2.0.2
# via openai
# via pandas
Expand All @@ -43,6 +64,9 @@ pandas==2.2.3
# via openai
pandas-stubs==2.2.2.240807
# via openai
propcache==0.3.2
# via aiohttp
# via yarl
pycparser==2.22
# via cffi
pydantic==2.10.3
Expand All @@ -65,10 +89,13 @@ tqdm==4.66.5
types-pytz==2024.2.0.20241003
# via pandas-stubs
typing-extensions==4.12.2
# via multidict
# via openai
# via pydantic
# via pydantic-core
tzdata==2024.1
# via pandas
websockets==15.0.1
# via openai
yarl==1.20.1
# via aiohttp
3 changes: 2 additions & 1 deletion src/openai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
APIResponseValidationError,
ContentFilterFinishReasonError,
)
from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient
from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
from ._utils._logs import setup_logging as _setup_logging
from ._legacy_response import HttpxBinaryResponseContent as HttpxBinaryResponseContent

Expand Down Expand Up @@ -77,6 +77,7 @@
"DEFAULT_CONNECTION_LIMITS",
"DefaultHttpxClient",
"DefaultAsyncHttpxClient",
"DefaultAioHttpClient",
]

if not _t.TYPE_CHECKING:
Expand Down
22 changes: 22 additions & 0 deletions src/openai/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,24 @@ def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)


try:
import httpx_aiohttp
except ImportError:

class _DefaultAioHttpClient(httpx.AsyncClient):
def __init__(self, **_kwargs: Any) -> None:
raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra")
else:

class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore
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)


if TYPE_CHECKING:
DefaultAsyncHttpxClient = httpx.AsyncClient
"""An alias to `httpx.AsyncClient` that provides the same defaults that this SDK
Expand All @@ -1314,8 +1332,12 @@ def __init__(self, **kwargs: Any) -> None:
This is useful because overriding the `http_client` with your own instance of
`httpx.AsyncClient` will result in httpx's defaults being used, not ours.
"""

DefaultAioHttpClient = httpx.AsyncClient
"""An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`."""
else:
DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient
DefaultAioHttpClient = _DefaultAioHttpClient


class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient):
Expand Down
2 changes: 1 addition & 1 deletion src/openai/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "openai"
__version__ = "1.88.0" # x-release-please-version
__version__ = "1.89.0" # x-release-please-version
4 changes: 3 additions & 1 deletion tests/api_resources/audio/test_speech.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ def test_streaming_response_create(self, client: OpenAI, respx_mock: MockRouter)


class TestAsyncSpeech:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)

@parametrize
@pytest.mark.respx(base_url=base_url)
Expand Down
4 changes: 3 additions & 1 deletion tests/api_resources/audio/test_transcriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ def test_streaming_response_create_overload_2(self, client: OpenAI) -> None:


class TestAsyncTranscriptions:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)

@parametrize
async def test_method_create_overload_1(self, async_client: AsyncOpenAI) -> None:
Expand Down
4 changes: 3 additions & 1 deletion tests/api_resources/audio/test_translations.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ def test_streaming_response_create(self, client: OpenAI) -> None:


class TestAsyncTranslations:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)

@parametrize
async def test_method_create(self, async_client: AsyncOpenAI) -> None:
Expand Down
4 changes: 3 additions & 1 deletion tests/api_resources/beta/realtime/test_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ def test_streaming_response_create(self, client: OpenAI) -> None:


class TestAsyncSessions:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)

@parametrize
async def test_method_create(self, async_client: AsyncOpenAI) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ def test_streaming_response_create(self, client: OpenAI) -> None:


class TestAsyncTranscriptionSessions:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)

@parametrize
async def test_method_create(self, async_client: AsyncOpenAI) -> None:
Expand Down
4 changes: 3 additions & 1 deletion tests/api_resources/beta/test_assistants.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,9 @@ def test_path_params_delete(self, client: OpenAI) -> None:


class TestAsyncAssistants:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)

@parametrize
async def test_method_create(self, async_client: AsyncOpenAI) -> None:
Expand Down
4 changes: 3 additions & 1 deletion tests/api_resources/beta/test_realtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ class TestRealtime:


class TestAsyncRealtime:
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
Loading