Skip to content

Commit 372ac6e

Browse files
release: 1.44.0 (#689)
* feat(api): api update * fix(client): loosen auth header validation * feat(client): add support for binary request streaming * release: 1.44.0 --------- Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
1 parent 8eec43d commit 372ac6e

20 files changed

+401
-101
lines changed

.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.43.0"
2+
".": "1.44.0"
33
}

.stats.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 46
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-ded87cb73affcaff9cf779d9cfd119a4026cfc1757b39be95d933edea48a0328.yml
3-
openapi_spec_hash: 0e6394b222fc68d7607114e70b72d23e
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-46f433f34d440aa1dfcc48cc8d822c598571b68be2f723ec99e1b4fba6c13b1e.yml
3+
openapi_spec_hash: 5b5cd728776723ac773900f7e8a32c05
44
config_hash: 0892e2e0eeb0343a022afa62e9080dd1

CHANGELOG.md

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

3+
## 1.44.0 (2026-01-13)
4+
5+
Full Changelog: [v1.43.0...v1.44.0](https://github.com/Finch-API/finch-api-python/compare/v1.43.0...v1.44.0)
6+
7+
### Features
8+
9+
* **api:** api update ([004c50f](https://github.com/Finch-API/finch-api-python/commit/004c50fd1518ea7bbab70bf275aa86cc9556b2ab))
10+
* **client:** add support for binary request streaming ([c5fde16](https://github.com/Finch-API/finch-api-python/commit/c5fde16f5560a89e53f94ac444f86537c3946f73))
11+
12+
13+
### Bug Fixes
14+
15+
* **client:** loosen auth header validation ([61fb929](https://github.com/Finch-API/finch-api-python/commit/61fb929e1cfc7f7c055f13a1c12c9590cb428594))
16+
317
## 1.43.0 (2026-01-05)
418

519
Full Changelog: [v1.42.3...v1.43.0](https://github.com/Finch-API/finch-api-python/compare/v1.42.3...v1.43.0)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "finch-api"
3-
version = "1.43.0"
3+
version = "1.44.0"
44
description = "The official Python library for the Finch API"
55
dynamic = ["readme"]
66
license = "Apache-2.0"

src/finch/_base_client.py

Lines changed: 134 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import inspect
1010
import logging
1111
import platform
12+
import warnings
1213
import email.utils
1314
from types import TracebackType
1415
from random import random
@@ -51,9 +52,11 @@
5152
ResponseT,
5253
AnyMapping,
5354
PostParser,
55+
BinaryTypes,
5456
RequestFiles,
5557
HttpxSendArgs,
5658
RequestOptions,
59+
AsyncBinaryTypes,
5760
HttpxRequestFiles,
5861
ModelBuilderProtocol,
5962
not_given,
@@ -478,8 +481,19 @@ def _build_request(
478481
retries_taken: int = 0,
479482
) -> httpx.Request:
480483
if log.isEnabledFor(logging.DEBUG):
481-
log.debug("Request options: %s", model_dump(options, exclude_unset=True))
482-
484+
log.debug(
485+
"Request options: %s",
486+
model_dump(
487+
options,
488+
exclude_unset=True,
489+
# Pydantic v1 can't dump every type we support in content, so we exclude it for now.
490+
exclude={
491+
"content",
492+
}
493+
if PYDANTIC_V1
494+
else {},
495+
),
496+
)
483497
kwargs: dict[str, Any] = {}
484498

485499
json_data = options.json_data
@@ -533,7 +547,13 @@ def _build_request(
533547
is_body_allowed = options.method.lower() != "get"
534548

535549
if is_body_allowed:
536-
if isinstance(json_data, bytes):
550+
if options.content is not None and json_data is not None:
551+
raise TypeError("Passing both `content` and `json_data` is not supported")
552+
if options.content is not None and files is not None:
553+
raise TypeError("Passing both `content` and `files` is not supported")
554+
if options.content is not None:
555+
kwargs["content"] = options.content
556+
elif isinstance(json_data, bytes):
537557
kwargs["content"] = json_data
538558
else:
539559
kwargs["json"] = json_data if is_given(json_data) else None
@@ -1209,6 +1229,7 @@ def post(
12091229
*,
12101230
cast_to: Type[ResponseT],
12111231
body: Body | None = None,
1232+
content: BinaryTypes | None = None,
12121233
options: RequestOptions = {},
12131234
files: RequestFiles | None = None,
12141235
stream: Literal[False] = False,
@@ -1221,6 +1242,7 @@ def post(
12211242
*,
12221243
cast_to: Type[ResponseT],
12231244
body: Body | None = None,
1245+
content: BinaryTypes | None = None,
12241246
options: RequestOptions = {},
12251247
files: RequestFiles | None = None,
12261248
stream: Literal[True],
@@ -1234,6 +1256,7 @@ def post(
12341256
*,
12351257
cast_to: Type[ResponseT],
12361258
body: Body | None = None,
1259+
content: BinaryTypes | None = None,
12371260
options: RequestOptions = {},
12381261
files: RequestFiles | None = None,
12391262
stream: bool,
@@ -1246,13 +1269,25 @@ def post(
12461269
*,
12471270
cast_to: Type[ResponseT],
12481271
body: Body | None = None,
1272+
content: BinaryTypes | None = None,
12491273
options: RequestOptions = {},
12501274
files: RequestFiles | None = None,
12511275
stream: bool = False,
12521276
stream_cls: type[_StreamT] | None = None,
12531277
) -> ResponseT | _StreamT:
1278+
if body is not None and content is not None:
1279+
raise TypeError("Passing both `body` and `content` is not supported")
1280+
if files is not None and content is not None:
1281+
raise TypeError("Passing both `files` and `content` is not supported")
1282+
if isinstance(body, bytes):
1283+
warnings.warn(
1284+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1285+
"Please pass raw bytes via the `content` parameter instead.",
1286+
DeprecationWarning,
1287+
stacklevel=2,
1288+
)
12541289
opts = FinalRequestOptions.construct(
1255-
method="post", url=path, json_data=body, files=to_httpx_files(files), **options
1290+
method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
12561291
)
12571292
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
12581293

@@ -1262,11 +1297,23 @@ def patch(
12621297
*,
12631298
cast_to: Type[ResponseT],
12641299
body: Body | None = None,
1300+
content: BinaryTypes | None = None,
12651301
files: RequestFiles | None = None,
12661302
options: RequestOptions = {},
12671303
) -> ResponseT:
1304+
if body is not None and content is not None:
1305+
raise TypeError("Passing both `body` and `content` is not supported")
1306+
if files is not None and content is not None:
1307+
raise TypeError("Passing both `files` and `content` is not supported")
1308+
if isinstance(body, bytes):
1309+
warnings.warn(
1310+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1311+
"Please pass raw bytes via the `content` parameter instead.",
1312+
DeprecationWarning,
1313+
stacklevel=2,
1314+
)
12681315
opts = FinalRequestOptions.construct(
1269-
method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
1316+
method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
12701317
)
12711318
return self.request(cast_to, opts)
12721319

@@ -1276,11 +1323,23 @@ def put(
12761323
*,
12771324
cast_to: Type[ResponseT],
12781325
body: Body | None = None,
1326+
content: BinaryTypes | None = None,
12791327
files: RequestFiles | None = None,
12801328
options: RequestOptions = {},
12811329
) -> ResponseT:
1330+
if body is not None and content is not None:
1331+
raise TypeError("Passing both `body` and `content` is not supported")
1332+
if files is not None and content is not None:
1333+
raise TypeError("Passing both `files` and `content` is not supported")
1334+
if isinstance(body, bytes):
1335+
warnings.warn(
1336+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1337+
"Please pass raw bytes via the `content` parameter instead.",
1338+
DeprecationWarning,
1339+
stacklevel=2,
1340+
)
12821341
opts = FinalRequestOptions.construct(
1283-
method="put", url=path, json_data=body, files=to_httpx_files(files), **options
1342+
method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
12841343
)
12851344
return self.request(cast_to, opts)
12861345

@@ -1290,9 +1349,19 @@ def delete(
12901349
*,
12911350
cast_to: Type[ResponseT],
12921351
body: Body | None = None,
1352+
content: BinaryTypes | None = None,
12931353
options: RequestOptions = {},
12941354
) -> ResponseT:
1295-
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
1355+
if body is not None and content is not None:
1356+
raise TypeError("Passing both `body` and `content` is not supported")
1357+
if isinstance(body, bytes):
1358+
warnings.warn(
1359+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1360+
"Please pass raw bytes via the `content` parameter instead.",
1361+
DeprecationWarning,
1362+
stacklevel=2,
1363+
)
1364+
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
12961365
return self.request(cast_to, opts)
12971366

12981367
def get_api_list(
@@ -1746,6 +1815,7 @@ async def post(
17461815
*,
17471816
cast_to: Type[ResponseT],
17481817
body: Body | None = None,
1818+
content: AsyncBinaryTypes | None = None,
17491819
files: RequestFiles | None = None,
17501820
options: RequestOptions = {},
17511821
stream: Literal[False] = False,
@@ -1758,6 +1828,7 @@ async def post(
17581828
*,
17591829
cast_to: Type[ResponseT],
17601830
body: Body | None = None,
1831+
content: AsyncBinaryTypes | None = None,
17611832
files: RequestFiles | None = None,
17621833
options: RequestOptions = {},
17631834
stream: Literal[True],
@@ -1771,6 +1842,7 @@ async def post(
17711842
*,
17721843
cast_to: Type[ResponseT],
17731844
body: Body | None = None,
1845+
content: AsyncBinaryTypes | None = None,
17741846
files: RequestFiles | None = None,
17751847
options: RequestOptions = {},
17761848
stream: bool,
@@ -1783,13 +1855,25 @@ async def post(
17831855
*,
17841856
cast_to: Type[ResponseT],
17851857
body: Body | None = None,
1858+
content: AsyncBinaryTypes | None = None,
17861859
files: RequestFiles | None = None,
17871860
options: RequestOptions = {},
17881861
stream: bool = False,
17891862
stream_cls: type[_AsyncStreamT] | None = None,
17901863
) -> ResponseT | _AsyncStreamT:
1864+
if body is not None and content is not None:
1865+
raise TypeError("Passing both `body` and `content` is not supported")
1866+
if files is not None and content is not None:
1867+
raise TypeError("Passing both `files` and `content` is not supported")
1868+
if isinstance(body, bytes):
1869+
warnings.warn(
1870+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1871+
"Please pass raw bytes via the `content` parameter instead.",
1872+
DeprecationWarning,
1873+
stacklevel=2,
1874+
)
17911875
opts = FinalRequestOptions.construct(
1792-
method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options
1876+
method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
17931877
)
17941878
return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
17951879

@@ -1799,11 +1883,28 @@ async def patch(
17991883
*,
18001884
cast_to: Type[ResponseT],
18011885
body: Body | None = None,
1886+
content: AsyncBinaryTypes | None = None,
18021887
files: RequestFiles | None = None,
18031888
options: RequestOptions = {},
18041889
) -> ResponseT:
1890+
if body is not None and content is not None:
1891+
raise TypeError("Passing both `body` and `content` is not supported")
1892+
if files is not None and content is not None:
1893+
raise TypeError("Passing both `files` and `content` is not supported")
1894+
if isinstance(body, bytes):
1895+
warnings.warn(
1896+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1897+
"Please pass raw bytes via the `content` parameter instead.",
1898+
DeprecationWarning,
1899+
stacklevel=2,
1900+
)
18051901
opts = FinalRequestOptions.construct(
1806-
method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options
1902+
method="patch",
1903+
url=path,
1904+
json_data=body,
1905+
content=content,
1906+
files=await async_to_httpx_files(files),
1907+
**options,
18071908
)
18081909
return await self.request(cast_to, opts)
18091910

@@ -1813,11 +1914,23 @@ async def put(
18131914
*,
18141915
cast_to: Type[ResponseT],
18151916
body: Body | None = None,
1917+
content: AsyncBinaryTypes | None = None,
18161918
files: RequestFiles | None = None,
18171919
options: RequestOptions = {},
18181920
) -> ResponseT:
1921+
if body is not None and content is not None:
1922+
raise TypeError("Passing both `body` and `content` is not supported")
1923+
if files is not None and content is not None:
1924+
raise TypeError("Passing both `files` and `content` is not supported")
1925+
if isinstance(body, bytes):
1926+
warnings.warn(
1927+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1928+
"Please pass raw bytes via the `content` parameter instead.",
1929+
DeprecationWarning,
1930+
stacklevel=2,
1931+
)
18191932
opts = FinalRequestOptions.construct(
1820-
method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options
1933+
method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
18211934
)
18221935
return await self.request(cast_to, opts)
18231936

@@ -1827,9 +1940,19 @@ async def delete(
18271940
*,
18281941
cast_to: Type[ResponseT],
18291942
body: Body | None = None,
1943+
content: AsyncBinaryTypes | None = None,
18301944
options: RequestOptions = {},
18311945
) -> ResponseT:
1832-
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
1946+
if body is not None and content is not None:
1947+
raise TypeError("Passing both `body` and `content` is not supported")
1948+
if isinstance(body, bytes):
1949+
warnings.warn(
1950+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1951+
"Please pass raw bytes via the `content` parameter instead.",
1952+
DeprecationWarning,
1953+
stacklevel=2,
1954+
)
1955+
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
18331956
return await self.request(cast_to, opts)
18341957

18351958
def get_api_list(

src/finch/_client.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,7 @@ def default_headers(self) -> dict[str, str | Omit]:
239239

240240
@override
241241
def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
242-
if self.access_token and headers.get("Authorization"):
243-
return
244-
if isinstance(custom_headers.get("Authorization"), Omit):
245-
return
246-
247-
if self.client_id and self.client_secret and headers.get("Authorization"):
248-
return
249-
if isinstance(custom_headers.get("Authorization"), Omit):
242+
if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit):
250243
return
251244

252245
raise TypeError(
@@ -589,14 +582,7 @@ def default_headers(self) -> dict[str, str | Omit]:
589582

590583
@override
591584
def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
592-
if self.access_token and headers.get("Authorization"):
593-
return
594-
if isinstance(custom_headers.get("Authorization"), Omit):
595-
return
596-
597-
if self.client_id and self.client_secret and headers.get("Authorization"):
598-
return
599-
if isinstance(custom_headers.get("Authorization"), Omit):
585+
if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit):
600586
return
601587

602588
raise TypeError(

0 commit comments

Comments
 (0)