Skip to content

Commit ffde2dc

Browse files
authored
Merge branch 'next' into main--merge-conflict
2 parents f4feadf + 49cdc9c commit ffde2dc

22 files changed

+342
-154
lines changed

.github/workflows/ci.yml

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ on:
77
- 'integrated/**'
88
- 'stl-preview-head/**'
99
- 'stl-preview-base/**'
10+
pull_request:
11+
branches-ignore:
12+
- 'stl-preview-head/**'
13+
- 'stl-preview-base/**'
1014

1115
jobs:
1216
lint:
1317
timeout-minutes: 10
1418
name: lint
1519
runs-on: ${{ github.repository == 'stainless-sdks/openlayer-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
20+
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
1621
steps:
1722
- uses: actions/checkout@v4
1823

@@ -30,17 +35,31 @@ jobs:
3035
- name: Run lints
3136
run: ./scripts/lint
3237

33-
upload:
34-
if: github.repository == 'stainless-sdks/openlayer-python'
38+
build:
39+
if: github.repository == 'stainless-sdks/openlayer-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork)
3540
timeout-minutes: 10
36-
name: upload
41+
name: build
3742
permissions:
3843
contents: read
3944
id-token: write
4045
runs-on: depot-ubuntu-24.04
4146
steps:
4247
- uses: actions/checkout@v4
4348

49+
- name: Install Rye
50+
run: |
51+
curl -sSf https://rye.astral.sh/get | bash
52+
echo "$HOME/.rye/shims" >> $GITHUB_PATH
53+
env:
54+
RYE_VERSION: '0.44.0'
55+
RYE_INSTALL_OPTION: '--yes'
56+
57+
- name: Install dependencies
58+
run: rye sync --all-features
59+
60+
- name: Run build
61+
run: rye build
62+
4463
- name: Get GitHub OIDC Token
4564
id: github-oidc
4665
uses: actions/github-script@v6

README.md

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Openlayer Python API library
22

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

55
The Openlayer Python library provides convenient access to the Openlayer REST API from any Python 3.8+
66
application. The library includes type definitions for all request params and response fields,
@@ -100,6 +100,56 @@ asyncio.run(main())
100100

101101
Functionality between the synchronous and asynchronous clients is otherwise identical.
102102

103+
### With aiohttp
104+
105+
By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend.
106+
107+
You can enable this by installing `aiohttp`:
108+
109+
```sh
110+
# install from PyPI
111+
pip install --pre openlayer[aiohttp]
112+
```
113+
114+
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
115+
116+
```python
117+
import os
118+
import asyncio
119+
from openlayer import DefaultAioHttpClient
120+
from openlayer import AsyncOpenlayer
121+
122+
123+
async def main() -> None:
124+
async with AsyncOpenlayer(
125+
api_key=os.environ.get("OPENLAYER_API_KEY"), # This is the default and can be omitted
126+
http_client=DefaultAioHttpClient(),
127+
) as client:
128+
response = await client.inference_pipelines.data.stream(
129+
inference_pipeline_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
130+
config={
131+
"input_variable_names": ["user_query"],
132+
"output_column_name": "output",
133+
"num_of_token_column_name": "tokens",
134+
"cost_column_name": "cost",
135+
"timestamp_column_name": "timestamp",
136+
},
137+
rows=[
138+
{
139+
"user_query": "what is the meaning of life?",
140+
"output": "42",
141+
"tokens": 7,
142+
"cost": 0.02,
143+
"timestamp": 1610000000,
144+
}
145+
],
146+
)
147+
print(response.success)
148+
149+
150+
asyncio.run(main())
151+
```
152+
103153
## Using types
104154

105155
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:
@@ -227,7 +277,7 @@ client.with_options(max_retries=5).inference_pipelines.data.stream(
227277
### Timeouts
228278

229279
By default requests time out after 1 minute. You can configure this with a `timeout` option,
230-
which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
280+
which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object:
231281

232282
```python
233283
from openlayer import Openlayer

bin/check-release-environment

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
errors=()
44

55
if [ -z "${PYPI_TOKEN}" ]; then
6-
errors+=("The OPENLAYER_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
6+
errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
77
fi
88

99
lenErrors=${#errors[@]}

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ classifiers = [
4343
Homepage = "https://github.com/openlayer-ai/openlayer-python"
4444
Repository = "https://github.com/openlayer-ai/openlayer-python"
4545

46+
[project.optional-dependencies]
47+
aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"]
4648

4749
[tool.rye]
4850
managed = true

requirements-dev.lock

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,24 @@
88
# with-sources: false
99

1010
-e file:.
11+
aiohappyeyeballs==2.6.1
12+
# via aiohttp
13+
aiohttp==3.12.13
14+
# via httpx-aiohttp
15+
# via openlayer
16+
aiosignal==1.3.2
17+
# via aiohttp
1118
annotated-types==0.6.0
1219
# via pydantic
1320
anyio==4.4.0
1421
# via httpx
1522
# via openlayer
1623
argcomplete==3.1.2
1724
# via nox
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
@@ -35,24 +46,34 @@ execnet==2.1.1
3546
# via pytest-xdist
3647
filelock==3.12.4
3748
# via virtualenv
49+
frozenlist==1.7.0
50+
# via aiohttp
51+
# via aiosignal
3852
h11==0.14.0
3953
# via httpcore
4054
httpcore==1.0.2
4155
# via httpx
4256
httpx==0.28.1
57+
# via httpx-aiohttp
4358
# via openlayer
4459
# via respx
60+
httpx-aiohttp==0.1.6
61+
# via openlayer
4562
idna==3.4
4663
# via anyio
4764
# via httpx
4865
# via requests
66+
# via yarl
4967
importlib-metadata==7.0.0
5068
iniconfig==2.0.0
5169
# via pytest
5270
markdown-it-py==3.0.0
5371
# via rich
5472
mdurl==0.1.2
5573
# via markdown-it-py
74+
multidict==6.5.0
75+
# via aiohttp
76+
# via yarl
5677
mypy==1.14.1
5778
mypy-extensions==1.0.0
5879
# via mypy
@@ -73,6 +94,9 @@ platformdirs==3.11.0
7394
# via virtualenv
7495
pluggy==1.5.0
7596
# via pytest
97+
propcache==0.3.2
98+
# via aiohttp
99+
# via yarl
76100
pyarrow==15.0.2
77101
# via openlayer
78102
pydantic==2.10.3
@@ -117,6 +141,7 @@ tqdm==4.67.1
117141
# via openlayer
118142
typing-extensions==4.12.2
119143
# via anyio
144+
# via multidict
120145
# via mypy
121146
# via openlayer
122147
# via pydantic
@@ -128,5 +153,7 @@ urllib3==2.2.3
128153
# via requests
129154
virtualenv==20.24.5
130155
# via nox
156+
yarl==1.20.1
157+
# via aiohttp
131158
zipp==3.17.0
132159
# via importlib-metadata

requirements.lock

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,22 @@
88
# with-sources: false
99

1010
-e file:.
11+
aiohappyeyeballs==2.6.1
12+
# via aiohttp
13+
aiohttp==3.12.13
14+
# via httpx-aiohttp
15+
# via openlayer
16+
aiosignal==1.3.2
17+
# via aiohttp
1118
annotated-types==0.6.0
1219
# via pydantic
1320
anyio==4.4.0
1421
# via httpx
1522
# via openlayer
23+
async-timeout==5.0.1
24+
# via aiohttp
25+
attrs==25.3.0
26+
# via aiohttp
1627
certifi==2023.7.22
1728
# via httpcore
1829
# via httpx
@@ -23,22 +34,35 @@ distro==1.8.0
2334
# via openlayer
2435
exceptiongroup==1.2.2
2536
# via anyio
37+
frozenlist==1.7.0
38+
# via aiohttp
39+
# via aiosignal
2640
h11==0.14.0
2741
# via httpcore
2842
httpcore==1.0.2
2943
# via httpx
3044
httpx==0.28.1
45+
# via httpx-aiohttp
46+
# via openlayer
47+
httpx-aiohttp==0.1.6
3148
# via openlayer
3249
idna==3.4
3350
# via anyio
3451
# via httpx
3552
# via requests
53+
# via yarl
54+
multidict==6.5.0
55+
# via aiohttp
56+
# via yarl
3657
numpy==1.26.4
3758
# via openlayer
3859
# via pandas
3960
# via pyarrow
4061
pandas==2.2.2
4162
# via openlayer
63+
propcache==0.3.2
64+
# via aiohttp
65+
# via yarl
4266
pyarrow==15.0.2
4367
# via openlayer
4468
pydantic==2.10.3
@@ -64,10 +88,13 @@ tqdm==4.67.1
6488
# via openlayer
6589
typing-extensions==4.12.2
6690
# via anyio
91+
# via multidict
6792
# via openlayer
6893
# via pydantic
6994
# via pydantic-core
7095
tzdata==2024.1
7196
# via pandas
7297
urllib3==2.2.3
7398
# via requests
99+
yarl==1.20.1
100+
# via aiohttp

scripts/utils/upload-artifact.sh

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#!/usr/bin/env bash
22
set -exuo pipefail
33

4-
RESPONSE=$(curl -X POST "$URL" \
4+
FILENAME=$(basename dist/*.whl)
5+
6+
RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \
57
-H "Authorization: Bearer $AUTH" \
68
-H "Content-Type: application/json")
79

@@ -12,13 +14,13 @@ if [[ "$SIGNED_URL" == "null" ]]; then
1214
exit 1
1315
fi
1416

15-
UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \
16-
-H "Content-Type: application/gzip" \
17-
--data-binary @- "$SIGNED_URL" 2>&1)
17+
UPLOAD_RESPONSE=$(curl -v -X PUT \
18+
-H "Content-Type: binary/octet-stream" \
19+
--data-binary "@dist/$FILENAME" "$SIGNED_URL" 2>&1)
1820

1921
if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then
2022
echo -e "\033[32mUploaded build to Stainless storage.\033[0m"
21-
echo -e "\033[32mInstallation: pip install --pre 'https://pkg.stainless.com/s/openlayer-python/$SHA'\033[0m"
23+
echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/openlayer-python/$SHA/$FILENAME'\033[0m"
2224
else
2325
echo -e "\033[31mFailed to upload artifact.\033[0m"
2426
exit 1

src/openlayer/__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/openlayer/_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/commits/test_test_results.py

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

7070

7171
class TestAsyncTestResults:
72-
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
72+
parametrize = pytest.mark.parametrize(
73+
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
74+
)
7375

7476
@parametrize
7577
async def test_method_list(self, async_client: AsyncOpenlayer) -> None:

0 commit comments

Comments
 (0)