Skip to content

Commit 3516092

Browse files
feat(client): add support for aiohttp
1 parent 4bbda56 commit 3516092

15 files changed

+175
-15
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,40 @@ rye run example playwright_basic # replace with the example you want to run
8181
> [!NOTE]
8282
> Make sure you have a `.env` file that matches the [.env.example](.env.example) file in the root of this repository.
8383
84+
### With aiohttp
85+
86+
By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.
87+
88+
You can enable this by installing `aiohttp`:
89+
90+
```sh
91+
# install from PyPI
92+
pip install browserbase[aiohttp]
93+
```
94+
95+
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
96+
97+
```python
98+
import os
99+
import asyncio
100+
from browserbase import DefaultAioHttpClient
101+
from browserbase import AsyncBrowserbase
102+
103+
104+
async def main() -> None:
105+
async with AsyncBrowserbase(
106+
api_key=os.environ.get("BROWSERBASE_API_KEY"), # This is the default and can be omitted
107+
http_client=DefaultAioHttpClient(),
108+
) as client:
109+
session = await client.sessions.create(
110+
project_id="your_project_id",
111+
)
112+
print(session.id)
113+
114+
115+
asyncio.run(main())
116+
```
117+
84118
## Using types
85119

86120
Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like:

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ classifiers = [
3737
Homepage = "https://github.com/browserbase/sdk-python"
3838
Repository = "https://github.com/browserbase/sdk-python"
3939

40+
[project.optional-dependencies]
41+
aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"]
4042

4143
[tool.rye]
4244
managed = true

requirements-dev.lock

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

1212
-e file:.
13+
aiohappyeyeballs==2.6.1
14+
# via aiohttp
15+
aiohttp==3.12.15
16+
# via browserbase
17+
# via httpx-aiohttp
18+
aiosignal==1.4.0
19+
# via aiohttp
1320
annotated-types==0.7.0
1421
# via pydantic
1522
anyio==4.6.2.post1
1623
# via browserbase
1724
# via httpx
1825
argcomplete==3.5.1
1926
# via nox
27+
async-timeout==5.0.1
28+
# via aiohttp
2029
attrs==24.2.0
30+
# via aiohttp
2131
# via outcome
2232
# via trio
2333
certifi==2024.8.30
@@ -43,6 +53,9 @@ execnet==2.1.1
4353
# via pytest-xdist
4454
filelock==3.16.1
4555
# via virtualenv
56+
frozenlist==1.7.0
57+
# via aiohttp
58+
# via aiosignal
4659
greenlet==3.1.1
4760
# via playwright
4861
h11==0.14.0
@@ -52,19 +65,26 @@ httpcore==1.0.6
5265
# via httpx
5366
httpx==0.28.1
5467
# via browserbase
68+
# via httpx-aiohttp
5569
# via respx
70+
httpx-aiohttp==0.1.8
71+
# via browserbase
5672
idna==3.10
5773
# via anyio
5874
# via httpx
5975
# via requests
6076
# via trio
77+
# via yarl
6178
importlib-metadata==8.5.0
6279
iniconfig==2.0.0
6380
# via pytest
6481
markdown-it-py==3.0.0
6582
# via rich
6683
mdurl==0.1.2
6784
# via markdown-it-py
85+
multidict==6.6.4
86+
# via aiohttp
87+
# via yarl
6888
mypy==1.14.1
6989
mypy-extensions==1.0.0
7090
# via mypy
@@ -83,6 +103,9 @@ playwright==1.48.0
83103
# via pytest-playwright
84104
pluggy==1.5.0
85105
# via pytest
106+
propcache==0.3.2
107+
# via aiohttp
108+
# via yarl
86109
pydantic==2.10.3
87110
# via browserbase
88111
pydantic-core==2.27.1
@@ -136,8 +159,10 @@ trio==0.27.0
136159
trio-websocket==0.11.1
137160
# via selenium
138161
typing-extensions==4.12.2
162+
# via aiosignal
139163
# via anyio
140164
# via browserbase
165+
# via multidict
141166
# via mypy
142167
# via pydantic
143168
# via pydantic-core
@@ -154,5 +179,7 @@ websocket-client==1.8.0
154179
# via selenium
155180
wsproto==1.2.0
156181
# via trio-websocket
182+
yarl==1.20.1
183+
# via aiohttp
157184
zipp==3.20.2
158185
# via importlib-metadata

requirements.lock

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

1212
-e file:.
13+
aiohappyeyeballs==2.6.1
14+
# via aiohttp
15+
aiohttp==3.12.8
16+
# via browserbase
17+
# via httpx-aiohttp
18+
aiosignal==1.3.2
19+
# via aiohttp
1320
annotated-types==0.6.0
1421
# via pydantic
1522
anyio==4.4.0
1623
# via browserbase
1724
# via httpx
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
2132
distro==1.8.0
2233
# via browserbase
2334
exceptiongroup==1.2.2
2435
# via anyio
36+
frozenlist==1.6.2
37+
# via aiohttp
38+
# via aiosignal
2539
h11==0.14.0
2640
# via httpcore
2741
httpcore==1.0.2
2842
# via httpx
2943
httpx==0.28.1
3044
# via browserbase
45+
# via httpx-aiohttp
46+
httpx-aiohttp==0.1.6
47+
# via browserbase
3148
idna==3.4
3249
# via anyio
3350
# via httpx
51+
# via yarl
52+
multidict==6.4.4
53+
# via aiohttp
54+
# via yarl
55+
propcache==0.3.1
56+
# via aiohttp
57+
# via yarl
3458
pydantic==2.10.3
3559
# via browserbase
3660
pydantic-core==2.27.1
@@ -41,5 +65,8 @@ sniffio==1.3.0
4165
typing-extensions==4.12.2
4266
# via anyio
4367
# via browserbase
68+
# via multidict
4469
# via pydantic
4570
# via pydantic-core
71+
yarl==1.20.0
72+
# via aiohttp

src/browserbase/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
UnprocessableEntityError,
3737
APIResponseValidationError,
3838
)
39-
from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient
39+
from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
4040
from ._utils._logs import setup_logging as _setup_logging
4141

4242
__all__ = [
@@ -78,6 +78,7 @@
7878
"DEFAULT_CONNECTION_LIMITS",
7979
"DefaultHttpxClient",
8080
"DefaultAsyncHttpxClient",
81+
"DefaultAioHttpClient",
8182
]
8283

8384
if not _t.TYPE_CHECKING:

src/browserbase/_base_client.py

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

12911291

1292+
try:
1293+
import httpx_aiohttp
1294+
except ImportError:
1295+
1296+
class _DefaultAioHttpClient(httpx.AsyncClient):
1297+
def __init__(self, **_kwargs: Any) -> None:
1298+
raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra")
1299+
else:
1300+
1301+
class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore
1302+
def __init__(self, **kwargs: Any) -> None:
1303+
kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
1304+
kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
1305+
kwargs.setdefault("follow_redirects", True)
1306+
1307+
super().__init__(**kwargs)
1308+
1309+
12921310
if TYPE_CHECKING:
12931311
DefaultAsyncHttpxClient = httpx.AsyncClient
12941312
"""An alias to `httpx.AsyncClient` that provides the same defaults that this SDK
@@ -1297,8 +1315,12 @@ def __init__(self, **kwargs: Any) -> None:
12971315
This is useful because overriding the `http_client` with your own instance of
12981316
`httpx.AsyncClient` will result in httpx's defaults being used, not ours.
12991317
"""
1318+
1319+
DefaultAioHttpClient = httpx.AsyncClient
1320+
"""An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`."""
13001321
else:
13011322
DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient
1323+
DefaultAioHttpClient = _DefaultAioHttpClient
13021324

13031325

13041326
class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient):

tests/api_resources/sessions/test_downloads.py

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

7676

7777
class TestAsyncDownloads:
78-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
78+
parametrize = pytest.mark.parametrize(
79+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
80+
)
7981

8082
@parametrize
8183
@pytest.mark.respx(base_url=base_url)

tests/api_resources/sessions/test_logs.py

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

5858

5959
class TestAsyncLogs:
60-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
60+
parametrize = pytest.mark.parametrize(
61+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
62+
)
6163

6264
@parametrize
6365
async def test_method_list(self, async_client: AsyncBrowserbase) -> None:

tests/api_resources/sessions/test_recording.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ def test_path_params_retrieve(self, client: Browserbase) -> None:
5757

5858

5959
class TestAsyncRecording:
60-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
60+
parametrize = pytest.mark.parametrize(
61+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
62+
)
6163

6264
@parametrize
6365
async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None:

tests/api_resources/sessions/test_uploads.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ def test_path_params_create(self, client: Browserbase) -> None:
6161

6262

6363
class TestAsyncUploads:
64-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
64+
parametrize = pytest.mark.parametrize(
65+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
66+
)
6567

6668
@parametrize
6769
async def test_method_create(self, async_client: AsyncBrowserbase) -> None:

0 commit comments

Comments
 (0)