Skip to content

Commit 5ff16ec

Browse files
release: 1.20.3 (#796)
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
1 parent 4f2fc4c commit 5ff16ec

19 files changed

Lines changed: 236 additions & 62 deletions

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "1.20.2"
2+
".": "1.20.3"
33
}

.stats.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
configured_endpoints: 115
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-563a11030291b5dd44e1b1b917e3e7bb865d7c873bf49c82056bfade22166843.yml
3-
openapi_spec_hash: 20770e5f6ed8370fc14ff0e1351ccffc
4-
config_hash: 12de9459ff629b6a3072a75b236b7b70
1+
configured_endpoints: 116
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-6cf4d9a6afac92d72787088b3aefa941f5240ee522d9e98e1160eea2e29f87f4.yml
3+
openapi_spec_hash: e07fc8349cf507b083830b4e2b0caca0
4+
config_hash: 436c8d4e665915db22b5d98fe58382c1

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# Changelog
22

3+
## 1.20.3 (2026-05-08)
4+
5+
Full Changelog: [v1.20.2...v1.20.3](https://github.com/runloopai/api-client-python/compare/v1.20.2...v1.20.3)
6+
7+
### Features
8+
9+
* make agent version optional in API ([#8858](https://github.com/runloopai/api-client-python/issues/8858)) ([7e11a9d](https://github.com/runloopai/api-client-python/commit/7e11a9db85aff6c28dcc04b8d391979027f38549))
10+
* share HTTP connection pool across SDK instances; refactor polling ([#797](https://github.com/runloopai/api-client-python/issues/797)) ([4f2fc4c](https://github.com/runloopai/api-client-python/commit/4f2fc4ca6d0b7a0b13b236dafb0e4e3148c2ed58))
11+
* support setting headers via env ([54ead49](https://github.com/runloopai/api-client-python/commit/54ead49fd28a61f60e18197d727fa57216c785fd))
12+
13+
14+
### Bug Fixes
15+
16+
* use correct field name format for multipart file arrays ([c564da8](https://github.com/runloopai/api-client-python/commit/c564da85b7dfdbb77edf347f6b25ca4ca57e470e))
17+
18+
19+
### Chores
20+
21+
* add get secret to stainless ([#7833](https://github.com/runloopai/api-client-python/issues/7833)) ([ce39778](https://github.com/runloopai/api-client-python/commit/ce39778de67907365c90f11ba3b3602cbc7daa2a))
22+
* **internal:** more robust bootstrap script ([115744e](https://github.com/runloopai/api-client-python/commit/115744e3c181822a1ec172e0526684839e278899))
23+
* **internal:** reformat pyproject.toml ([89e8401](https://github.com/runloopai/api-client-python/commit/89e8401b518f0ec15cb1e394dde66cb876bf0578))
24+
325
## 1.20.2 (2026-05-01)
426

527
Full Changelog: [v1.20.1...v1.20.2](https://github.com/runloopai/api-client-python/compare/v1.20.1...v1.20.2)

api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ from runloop_api_client.types import (
381381
Methods:
382382

383383
- <code title="post /v1/secrets">client.secrets.<a href="./src/runloop_api_client/resources/secrets.py">create</a>(\*\*<a href="src/runloop_api_client/types/secret_create_params.py">params</a>) -> <a href="./src/runloop_api_client/types/secret_view.py">SecretView</a></code>
384+
- <code title="get /v1/secrets/{name}">client.secrets.<a href="./src/runloop_api_client/resources/secrets.py">retrieve</a>(name) -> <a href="./src/runloop_api_client/types/secret_view.py">SecretView</a></code>
384385
- <code title="post /v1/secrets/{name}">client.secrets.<a href="./src/runloop_api_client/resources/secrets.py">update</a>(name, \*\*<a href="src/runloop_api_client/types/secret_update_params.py">params</a>) -> <a href="./src/runloop_api_client/types/secret_view.py">SecretView</a></code>
385386
- <code title="get /v1/secrets">client.secrets.<a href="./src/runloop_api_client/resources/secrets.py">list</a>(\*\*<a href="src/runloop_api_client/types/secret_list_params.py">params</a>) -> <a href="./src/runloop_api_client/types/secret_list_view.py">SecretListView</a></code>
386387
- <code title="post /v1/secrets/{name}/delete">client.secrets.<a href="./src/runloop_api_client/resources/secrets.py">delete</a>(name) -> <a href="./src/runloop_api_client/types/secret_view.py">SecretView</a></code>

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "runloop_api_client"
3-
version = "1.20.2"
3+
version = "1.20.3"
44
description = "The official Python library for the runloop API"
55
dynamic = ["readme"]
66
license = "MIT"
@@ -157,7 +157,7 @@ show_error_codes = true
157157
#
158158
# We also exclude our `tests` as mypy doesn't always infer
159159
# types correctly and Pyright will still catch any type errors.
160-
exclude = ['src/runloop_api_client/_files.py', '_dev/.*.py', 'tests/.*']
160+
exclude = ["src/runloop_api_client/_files.py", "_dev/.*.py", "tests/.*"]
161161

162162
strict_equality = true
163163
implicit_reexport = true

scripts/bootstrap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -e
44

55
cd "$(dirname "$0")/.."
66

7-
if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then
7+
if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then
88
brew bundle check >/dev/null 2>&1 || {
99
echo -n "==> Install Homebrew dependencies? (y/N): "
1010
read -r response

src/runloop_api_client/_client.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919
RequestOptions,
2020
not_given,
2121
)
22-
from ._utils import is_given, get_async_library
22+
from ._utils import (
23+
is_given,
24+
is_mapping_t,
25+
get_async_library,
26+
)
2327
from ._compat import cached_property
2428
from ._version import __version__
2529
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
@@ -115,6 +119,15 @@ def __init__(
115119
if base_url is None:
116120
base_url = f"https://api.runloop.ai"
117121

122+
custom_headers_env = os.environ.get("RUNLOOP_CUSTOM_HEADERS")
123+
if custom_headers_env is not None:
124+
parsed: dict[str, str] = {}
125+
for line in custom_headers_env.split("\n"):
126+
colon = line.find(":")
127+
if colon >= 0:
128+
parsed[line[:colon].strip()] = line[colon + 1 :].strip()
129+
default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}
130+
118131
super().__init__(
119132
version=__version__,
120133
base_url=base_url,
@@ -388,6 +401,15 @@ def __init__(
388401
if base_url is None:
389402
base_url = f"https://api.runloop.ai"
390403

404+
custom_headers_env = os.environ.get("RUNLOOP_CUSTOM_HEADERS")
405+
if custom_headers_env is not None:
406+
parsed: dict[str, str] = {}
407+
for line in custom_headers_env.split("\n"):
408+
colon = line.find(":")
409+
if colon >= 0:
410+
parsed[line[:colon].strip()] = line[colon + 1 :].strip()
411+
default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}
412+
391413
super().__init__(
392414
version=__version__,
393415
base_url=base_url,

src/runloop_api_client/_qs.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,13 @@
22

33
from typing import Any, List, Tuple, Union, Mapping, TypeVar
44
from urllib.parse import parse_qs, urlencode
5-
from typing_extensions import Literal, get_args
5+
from typing_extensions import get_args
66

7-
from ._types import NotGiven, not_given
7+
from ._types import NotGiven, ArrayFormat, NestedFormat, not_given
88
from ._utils import flatten
99

1010
_T = TypeVar("_T")
1111

12-
13-
ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
14-
NestedFormat = Literal["dots", "brackets"]
15-
1612
PrimitiveData = Union[str, int, float, bool, None]
1713
# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"]
1814
# https://github.com/microsoft/pyright/issues/3555

src/runloop_api_client/_types.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
ModelT = TypeVar("ModelT", bound=pydantic.BaseModel)
4848
_T = TypeVar("_T")
4949

50+
ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
51+
NestedFormat = Literal["dots", "brackets"]
52+
5053

5154
# Approximates httpx internal ProxiesTypes and RequestFiles types
5255
# while adding support for `PathLike` instances

src/runloop_api_client/_utils/_utils.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
)
1818
from pathlib import Path
1919
from datetime import date, datetime
20-
from typing_extensions import TypeGuard
20+
from typing_extensions import TypeGuard, get_args
2121

2222
import sniffio
2323

24-
from .._types import Omit, NotGiven, FileTypes, HeadersLike
24+
from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike
2525

2626
_T = TypeVar("_T")
2727
_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...])
@@ -40,25 +40,45 @@ def extract_files(
4040
query: Mapping[str, object],
4141
*,
4242
paths: Sequence[Sequence[str]],
43+
array_format: ArrayFormat = "brackets",
4344
) -> list[tuple[str, FileTypes]]:
4445
"""Recursively extract files from the given dictionary based on specified paths.
4546
4647
A path may look like this ['foo', 'files', '<array>', 'data'].
4748
49+
``array_format`` controls how ``<array>`` segments contribute to the emitted
50+
field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and
51+
``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``).
52+
4853
Note: this mutates the given dictionary.
4954
"""
5055
files: list[tuple[str, FileTypes]] = []
5156
for path in paths:
52-
files.extend(_extract_items(query, path, index=0, flattened_key=None))
57+
files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format))
5358
return files
5459

5560

61+
def _array_suffix(array_format: ArrayFormat, array_index: int) -> str:
62+
if array_format == "brackets":
63+
return "[]"
64+
if array_format == "indices":
65+
return f"[{array_index}]"
66+
if array_format == "repeat" or array_format == "comma":
67+
# Both repeat the bare field name for each file part; there is no
68+
# meaningful way to comma-join binary parts.
69+
return ""
70+
raise NotImplementedError(
71+
f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
72+
)
73+
74+
5675
def _extract_items(
5776
obj: object,
5877
path: Sequence[str],
5978
*,
6079
index: int,
6180
flattened_key: str | None,
81+
array_format: ArrayFormat,
6282
) -> list[tuple[str, FileTypes]]:
6383
try:
6484
key = path[index]
@@ -75,9 +95,11 @@ def _extract_items(
7595

7696
if is_list(obj):
7797
files: list[tuple[str, FileTypes]] = []
78-
for entry in obj:
79-
assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "")
80-
files.append((flattened_key + "[]", cast(FileTypes, entry)))
98+
for array_index, entry in enumerate(obj):
99+
suffix = _array_suffix(array_format, array_index)
100+
emitted_key = (flattened_key + suffix) if flattened_key else suffix
101+
assert_is_file_content(entry, key=emitted_key)
102+
files.append((emitted_key, cast(FileTypes, entry)))
81103
return files
82104

83105
assert_is_file_content(obj, key=flattened_key)
@@ -106,6 +128,7 @@ def _extract_items(
106128
path,
107129
index=index,
108130
flattened_key=flattened_key,
131+
array_format=array_format,
109132
)
110133
elif is_list(obj):
111134
if key != "<array>":
@@ -117,9 +140,12 @@ def _extract_items(
117140
item,
118141
path,
119142
index=index,
120-
flattened_key=flattened_key + "[]" if flattened_key is not None else "[]",
143+
flattened_key=(
144+
(flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index)
145+
),
146+
array_format=array_format,
121147
)
122-
for item in obj
148+
for array_index, item in enumerate(obj)
123149
]
124150
)
125151

0 commit comments

Comments
 (0)