Skip to content

Commit c62e990

Browse files
feat(client): add support for aiohttp
1 parent 0bef1d0 commit c62e990

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+265
-44
lines changed

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,45 @@ asyncio.run(main())
145145

146146
Functionality between the synchronous and asynchronous clients is otherwise identical.
147147

148+
### With aiohttp
149+
150+
By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.
151+
152+
You can enable this by installing `aiohttp`:
153+
154+
```sh
155+
# install from PyPI
156+
pip install openai[aiohttp]
157+
```
158+
159+
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
160+
161+
```python
162+
import os
163+
import asyncio
164+
from openai import DefaultAioHttpClient
165+
from openai import AsyncOpenAI
166+
167+
168+
async def main() -> None:
169+
async with AsyncOpenAI(
170+
api_key=os.environ.get("OPENAI_API_KEY"), # This is the default and can be omitted
171+
http_client=DefaultAioHttpClient(),
172+
) as client:
173+
chat_completion = await client.chat.completions.create(
174+
messages=[
175+
{
176+
"role": "user",
177+
"content": "Say this is a test",
178+
}
179+
],
180+
model="gpt-4o",
181+
)
182+
183+
184+
asyncio.run(main())
185+
```
186+
148187
## Streaming responses
149188

150189
We provide support for streaming responses using Server Side Events (SSE).

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Repository = "https://github.com/openai/openai-python"
4343
openai = "openai.cli:main"
4444

4545
[project.optional-dependencies]
46+
aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"]
4647
realtime = ["websockets >= 13, < 16"]
4748
datalib = ["numpy >= 1", "pandas >= 1.2.3", "pandas-stubs >= 1.1.0.11"]
4849
voice_helpers = ["sounddevice>=0.5.1", "numpy>=2.0.2"]

requirements-dev.lock

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
# universal: false
1111

1212
-e file:.
13+
aiohappyeyeballs==2.6.1
14+
# via aiohttp
15+
aiohttp==3.12.13
16+
# via httpx-aiohttp
17+
# via openai
18+
aiosignal==1.3.2
19+
# via aiohttp
1320
annotated-types==0.6.0
1421
# via pydantic
1522
anyio==4.1.0
@@ -19,7 +26,10 @@ argcomplete==3.1.2
1926
# via nox
2027
asttokens==2.4.1
2128
# via inline-snapshot
29+
async-timeout==5.0.1
30+
# via aiohttp
2231
attrs==24.2.0
32+
# via aiohttp
2333
# via outcome
2434
# via trio
2535
azure-core==1.31.0
@@ -60,18 +70,25 @@ executing==2.1.0
6070
# via inline-snapshot
6171
filelock==3.12.4
6272
# via virtualenv
73+
frozenlist==1.7.0
74+
# via aiohttp
75+
# via aiosignal
6376
h11==0.14.0
6477
# via httpcore
6578
httpcore==1.0.2
6679
# via httpx
6780
httpx==0.28.1
81+
# via httpx-aiohttp
6882
# via openai
6983
# via respx
84+
httpx-aiohttp==0.1.6
85+
# via openai
7086
idna==3.4
7187
# via anyio
7288
# via httpx
7389
# via requests
7490
# via trio
91+
# via yarl
7592
importlib-metadata==7.0.0
7693
iniconfig==2.0.0
7794
# via pytest
@@ -87,6 +104,9 @@ msal==1.31.0
87104
# via msal-extensions
88105
msal-extensions==1.2.0
89106
# via azure-identity
107+
multidict==6.5.0
108+
# via aiohttp
109+
# via yarl
90110
mypy==1.14.1
91111
mypy-extensions==1.0.0
92112
# via black
@@ -118,6 +138,9 @@ pluggy==1.5.0
118138
# via pytest
119139
portalocker==2.10.1
120140
# via msal-extensions
141+
propcache==0.3.2
142+
# via aiohttp
143+
# via yarl
121144
pycparser==2.22
122145
# via cffi
123146
pydantic==2.10.3
@@ -181,6 +204,7 @@ typing-extensions==4.12.2
181204
# via azure-core
182205
# via azure-identity
183206
# via black
207+
# via multidict
184208
# via mypy
185209
# via openai
186210
# via pydantic
@@ -194,5 +218,7 @@ virtualenv==20.24.5
194218
# via nox
195219
websockets==15.0.1
196220
# via openai
221+
yarl==1.20.1
222+
# via aiohttp
197223
zipp==3.17.0
198224
# via importlib-metadata

requirements.lock

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,22 @@
1010
# universal: false
1111

1212
-e file:.
13+
aiohappyeyeballs==2.6.1
14+
# via aiohttp
15+
aiohttp==3.12.13
16+
# via httpx-aiohttp
17+
# via openai
18+
aiosignal==1.3.2
19+
# via aiohttp
1320
annotated-types==0.6.0
1421
# via pydantic
1522
anyio==4.1.0
1623
# via httpx
1724
# via openai
25+
async-timeout==5.0.1
26+
# via aiohttp
27+
attrs==25.3.0
28+
# via aiohttp
1829
certifi==2023.7.22
1930
# via httpcore
2031
# via httpx
@@ -24,17 +35,27 @@ distro==1.8.0
2435
# via openai
2536
exceptiongroup==1.2.2
2637
# via anyio
38+
frozenlist==1.7.0
39+
# via aiohttp
40+
# via aiosignal
2741
h11==0.14.0
2842
# via httpcore
2943
httpcore==1.0.2
3044
# via httpx
3145
httpx==0.28.1
46+
# via httpx-aiohttp
47+
# via openai
48+
httpx-aiohttp==0.1.6
3249
# via openai
3350
idna==3.4
3451
# via anyio
3552
# via httpx
53+
# via yarl
3654
jiter==0.6.1
3755
# via openai
56+
multidict==6.5.0
57+
# via aiohttp
58+
# via yarl
3859
numpy==2.0.2
3960
# via openai
4061
# via pandas
@@ -43,6 +64,9 @@ pandas==2.2.3
4364
# via openai
4465
pandas-stubs==2.2.2.240807
4566
# via openai
67+
propcache==0.3.2
68+
# via aiohttp
69+
# via yarl
4670
pycparser==2.22
4771
# via cffi
4872
pydantic==2.10.3
@@ -65,10 +89,13 @@ tqdm==4.66.5
6589
types-pytz==2024.2.0.20241003
6690
# via pandas-stubs
6791
typing-extensions==4.12.2
92+
# via multidict
6893
# via openai
6994
# via pydantic
7095
# via pydantic-core
7196
tzdata==2024.1
7297
# via pandas
7398
websockets==15.0.1
7499
# via openai
100+
yarl==1.20.1
101+
# via aiohttp

src/openai/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
APIResponseValidationError,
3333
ContentFilterFinishReasonError,
3434
)
35-
from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient
35+
from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
3636
from ._utils._logs import setup_logging as _setup_logging
3737
from ._legacy_response import HttpxBinaryResponseContent as HttpxBinaryResponseContent
3838

@@ -77,6 +77,7 @@
7777
"DEFAULT_CONNECTION_LIMITS",
7878
"DefaultHttpxClient",
7979
"DefaultAsyncHttpxClient",
80+
"DefaultAioHttpClient",
8081
]
8182

8283
if not _t.TYPE_CHECKING:

src/openai/_base_client.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,24 @@ def __init__(self, **kwargs: Any) -> None:
13061306
super().__init__(**kwargs)
13071307

13081308

1309+
try:
1310+
import httpx_aiohttp
1311+
except ImportError:
1312+
1313+
class _DefaultAioHttpClient(httpx.AsyncClient):
1314+
def __init__(self, **_kwargs: Any) -> None:
1315+
raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra")
1316+
else:
1317+
1318+
class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore
1319+
def __init__(self, **kwargs: Any) -> None:
1320+
kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
1321+
kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
1322+
kwargs.setdefault("follow_redirects", True)
1323+
1324+
super().__init__(**kwargs)
1325+
1326+
13091327
if TYPE_CHECKING:
13101328
DefaultAsyncHttpxClient = httpx.AsyncClient
13111329
"""An alias to `httpx.AsyncClient` that provides the same defaults that this SDK
@@ -1314,8 +1332,12 @@ def __init__(self, **kwargs: Any) -> None:
13141332
This is useful because overriding the `http_client` with your own instance of
13151333
`httpx.AsyncClient` will result in httpx's defaults being used, not ours.
13161334
"""
1335+
1336+
DefaultAioHttpClient = httpx.AsyncClient
1337+
"""An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`."""
13171338
else:
13181339
DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient
1340+
DefaultAioHttpClient = _DefaultAioHttpClient
13191341

13201342

13211343
class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient):

tests/api_resources/audio/test_speech.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ def test_streaming_response_create(self, client: OpenAI, respx_mock: MockRouter)
8383

8484

8585
class TestAsyncSpeech:
86-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
86+
parametrize = pytest.mark.parametrize(
87+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
88+
)
8789

8890
@parametrize
8991
@pytest.mark.respx(base_url=base_url)

tests/api_resources/audio/test_transcriptions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ def test_streaming_response_create_overload_2(self, client: OpenAI) -> None:
121121

122122

123123
class TestAsyncTranscriptions:
124-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
124+
parametrize = pytest.mark.parametrize(
125+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
126+
)
125127

126128
@parametrize
127129
async def test_method_create_overload_1(self, async_client: AsyncOpenAI) -> None:

tests/api_resources/audio/test_translations.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ def test_streaming_response_create(self, client: OpenAI) -> None:
6464

6565

6666
class TestAsyncTranslations:
67-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
67+
parametrize = pytest.mark.parametrize(
68+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
69+
)
6870

6971
@parametrize
7072
async def test_method_create(self, async_client: AsyncOpenAI) -> None:

tests/api_resources/beta/realtime/test_sessions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ def test_streaming_response_create(self, client: OpenAI) -> None:
9090

9191

9292
class TestAsyncSessions:
93-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
93+
parametrize = pytest.mark.parametrize(
94+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
95+
)
9496

9597
@parametrize
9698
async def test_method_create(self, async_client: AsyncOpenAI) -> None:

tests/api_resources/beta/realtime/test_transcription_sessions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ def test_streaming_response_create(self, client: OpenAI) -> None:
7474

7575

7676
class TestAsyncTranscriptionSessions:
77-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
77+
parametrize = pytest.mark.parametrize(
78+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
79+
)
7880

7981
@parametrize
8082
async def test_method_create(self, async_client: AsyncOpenAI) -> None:

tests/api_resources/beta/test_assistants.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,9 @@ def test_path_params_delete(self, client: OpenAI) -> None:
253253

254254

255255
class TestAsyncAssistants:
256-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
256+
parametrize = pytest.mark.parametrize(
257+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
258+
)
257259

258260
@parametrize
259261
async def test_method_create(self, async_client: AsyncOpenAI) -> None:

tests/api_resources/beta/test_realtime.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ class TestRealtime:
1414

1515

1616
class TestAsyncRealtime:
17-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
17+
parametrize = pytest.mark.parametrize(
18+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
19+
)

tests/api_resources/beta/test_threads.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,9 @@ def test_streaming_response_create_and_run_overload_2(self, client: OpenAI) -> N
420420

421421

422422
class TestAsyncThreads:
423-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
423+
parametrize = pytest.mark.parametrize(
424+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
425+
)
424426

425427
@parametrize
426428
async def test_method_create(self, async_client: AsyncOpenAI) -> None:

tests/api_resources/beta/threads/runs/test_steps.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,9 @@ def test_path_params_list(self, client: OpenAI) -> None:
167167

168168

169169
class TestAsyncSteps:
170-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
170+
parametrize = pytest.mark.parametrize(
171+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
172+
)
171173

172174
@parametrize
173175
async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None:

tests/api_resources/beta/threads/test_messages.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,9 @@ def test_path_params_delete(self, client: OpenAI) -> None:
321321

322322

323323
class TestAsyncMessages:
324-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
324+
parametrize = pytest.mark.parametrize(
325+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
326+
)
325327

326328
@parametrize
327329
async def test_method_create(self, async_client: AsyncOpenAI) -> None:

tests/api_resources/beta/threads/test_runs.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,9 @@ def test_path_params_submit_tool_outputs_overload_2(self, client: OpenAI) -> Non
568568

569569

570570
class TestAsyncRuns:
571-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
571+
parametrize = pytest.mark.parametrize(
572+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
573+
)
572574

573575
@parametrize
574576
async def test_method_create_overload_1(self, async_client: AsyncOpenAI) -> None:

tests/api_resources/chat/completions/test_messages.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ def test_path_params_list(self, client: OpenAI) -> None:
6868

6969

7070
class TestAsyncMessages:
71-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
71+
parametrize = pytest.mark.parametrize(
72+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
73+
)
7274

7375
@parametrize
7476
async def test_method_list(self, async_client: AsyncOpenAI) -> None:

0 commit comments

Comments
 (0)