Skip to content

Commit 9eeac4b

Browse files
feat(client): add support for aiohttp
1 parent 28e4f0d commit 9eeac4b

38 files changed

+242
-38
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,38 @@ asyncio.run(main())
6464

6565
Functionality between the synchronous and asynchronous clients is otherwise identical.
6666

67+
### With aiohttp
68+
69+
By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.
70+
71+
You can enable this by installing `aiohttp`:
72+
73+
```sh
74+
# install from PyPI
75+
pip install gitpod-sdk[aiohttp]
76+
```
77+
78+
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
79+
80+
```python
81+
import os
82+
import asyncio
83+
from gitpod import DefaultAioHttpClient
84+
from gitpod import AsyncGitpod
85+
86+
87+
async def main() -> None:
88+
async with AsyncGitpod(
89+
bearer_token=os.environ.get("GITPOD_API_KEY"), # This is the default and can be omitted
90+
http_client=DefaultAioHttpClient(),
91+
) as client:
92+
response = await client.identity.get_authenticated_identity()
93+
print(response.organization_id)
94+
95+
96+
asyncio.run(main())
97+
```
98+
6799
## Using types
68100

69101
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/gitpod-io/gitpod-sdk-python"
3838
Repository = "https://github.com/gitpod-io/gitpod-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,6 +10,13 @@
1010
# universal: false
1111

1212
-e file:.
13+
aiohappyeyeballs==2.6.1
14+
# via aiohttp
15+
aiohttp==3.12.8
16+
# via gitpod-sdk
17+
# via httpx-aiohttp
18+
aiosignal==1.3.2
19+
# via aiohttp
1320
annotated-types==0.6.0
1421
# via pydantic
1522
anthropic==0.45.2
@@ -19,6 +26,10 @@ anyio==4.4.0
1926
# via httpx
2027
argcomplete==3.1.2
2128
# via nox
29+
async-timeout==5.0.1
30+
# via aiohttp
31+
attrs==25.3.0
32+
# via aiohttp
2233
bcrypt==4.2.1
2334
# via paramiko
2435
certifi==2023.7.22
@@ -45,17 +56,24 @@ execnet==2.1.1
4556
# via pytest-xdist
4657
filelock==3.12.4
4758
# via virtualenv
59+
frozenlist==1.6.2
60+
# via aiohttp
61+
# via aiosignal
4862
h11==0.14.0
4963
# via httpcore
5064
httpcore==1.0.2
5165
# via httpx
5266
httpx==0.28.1
5367
# via anthropic
5468
# via gitpod-sdk
69+
# via httpx-aiohttp
5570
# via respx
71+
httpx-aiohttp==0.1.6
72+
# via gitpod-sdk
5673
idna==3.4
5774
# via anyio
5875
# via httpx
76+
# via yarl
5977
importlib-metadata==7.0.0
6078
iniconfig==2.0.0
6179
# via pytest
@@ -65,6 +83,9 @@ markdown-it-py==3.0.0
6583
# via rich
6684
mdurl==0.1.2
6785
# via markdown-it-py
86+
multidict==6.4.4
87+
# via aiohttp
88+
# via yarl
6889
mypy==1.14.1
6990
mypy-extensions==1.0.0
7091
# via mypy
@@ -80,6 +101,9 @@ platformdirs==3.11.0
80101
# via virtualenv
81102
pluggy==1.5.0
82103
# via pytest
104+
propcache==0.3.1
105+
# via aiohttp
106+
# via yarl
83107
pycparser==2.22
84108
# via cffi
85109
pydantic==2.10.3
@@ -121,11 +145,14 @@ typing-extensions==4.12.2
121145
# via anthropic
122146
# via anyio
123147
# via gitpod-sdk
148+
# via multidict
124149
# via mypy
125150
# via pydantic
126151
# via pydantic-core
127152
# via pyright
128153
virtualenv==20.24.5
129154
# via nox
155+
yarl==1.20.0
156+
# via aiohttp
130157
zipp==3.17.0
131158
# 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 gitpod-sdk
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 gitpod-sdk
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 gitpod-sdk
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 gitpod-sdk
45+
# via httpx-aiohttp
46+
httpx-aiohttp==0.1.6
47+
# via gitpod-sdk
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 gitpod-sdk
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 gitpod-sdk
68+
# via multidict
4469
# via pydantic
4570
# via pydantic-core
71+
yarl==1.20.0
72+
# via aiohttp

src/gitpod/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
UnprocessableEntityError,
2727
APIResponseValidationError,
2828
)
29-
from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient
29+
from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
3030
from ._utils._logs import setup_logging as _setup_logging
3131

3232
__all__ = [
@@ -68,6 +68,7 @@
6868
"DEFAULT_CONNECTION_LIMITS",
6969
"DefaultHttpxClient",
7070
"DefaultAsyncHttpxClient",
71+
"DefaultAioHttpClient",
7172
]
7273

7374
if not _t.TYPE_CHECKING:

src/gitpod/_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/environments/automations/tasks/test_executions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,9 @@ def test_streaming_response_stop(self, client: Gitpod) -> None:
142142

143143

144144
class TestAsyncExecutions:
145-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
145+
parametrize = pytest.mark.parametrize(
146+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
147+
)
146148

147149
@pytest.mark.skip()
148150
@parametrize

tests/api_resources/environments/automations/test_services.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,9 @@ def test_streaming_response_stop(self, client: Gitpod) -> None:
354354

355355

356356
class TestAsyncServices:
357-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
357+
parametrize = pytest.mark.parametrize(
358+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
359+
)
358360

359361
@pytest.mark.skip()
360362
@parametrize

tests/api_resources/environments/automations/test_tasks.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,9 @@ def test_streaming_response_start(self, client: Gitpod) -> None:
302302

303303

304304
class TestAsyncTasks:
305-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
305+
parametrize = pytest.mark.parametrize(
306+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
307+
)
306308

307309
@pytest.mark.skip()
308310
@parametrize

tests/api_resources/environments/test_automations.py

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

9191

9292
class TestAsyncAutomations:
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
@pytest.mark.skip()
9698
@parametrize

0 commit comments

Comments
 (0)