Skip to content

Commit e64e998

Browse files
release: 7.7.0 (#122)
* chore(internal): regenerate SDK with no functional changes * feat(client): add support for binary request streaming * chore(internal): regenerate SDK with no functional changes * release: 7.7.0 --------- Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
1 parent 74d08a0 commit e64e998

29 files changed

Lines changed: 554 additions & 246 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-
".": "7.6.1"
2+
".": "7.7.0"
33
}

.stats.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 78
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier%2Fcourier-b79e0eb1ab06f4076c48fa519e2b2ad792a0c483a5d017e43c938ca4c4be6988.yml
3-
openapi_spec_hash: cb3cc2c1145503e5737a880326857aa4
4-
config_hash: ff903e824043dc81aca51a0ce21896d6
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier%2Fcourier-e3e54d99e2a73fd87519270f2685131050d342e86a4e96130247b854deae5c20.yml
3+
openapi_spec_hash: 897a3fbee24f24d021d6af0df480220c
4+
config_hash: 66a5c28bb74d78454456d9ce7d1c0a0c

CHANGELOG.md

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

3+
## 7.7.0 (2026-01-14)
4+
5+
Full Changelog: [v7.6.1...v7.7.0](https://github.com/trycourier/courier-python/compare/v7.6.1...v7.7.0)
6+
7+
### Features
8+
9+
* **client:** add support for binary request streaming ([f0cc366](https://github.com/trycourier/courier-python/commit/f0cc366e61a95d66d2cf01477822c21625e3a2a3))
10+
11+
12+
### Chores
13+
14+
* **internal:** regenerate SDK with no functional changes ([a9d8cf1](https://github.com/trycourier/courier-python/commit/a9d8cf18d10f801475fb21b74546781769244cb4))
15+
* **internal:** regenerate SDK with no functional changes ([4b373e6](https://github.com/trycourier/courier-python/commit/4b373e6fabfd0005eb5f4d954fc9f2826120cc09))
16+
317
## 7.6.1 (2026-01-12)
418

519
Full Changelog: [v7.6.0...v7.6.1](https://github.com/trycourier/courier-python/compare/v7.6.0...v7.6.1)

api.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ from courier.types import (
66
AirshipProfileAudience,
77
Alignment,
88
AudienceFilter,
9+
AudienceFilterConfig,
910
AudienceRecipient,
1011
ChannelClassification,
1112
ChannelPreference,
@@ -24,6 +25,7 @@ from courier.types import (
2425
ElementalQuoteNodeWithType,
2526
ElementalTextNodeWithType,
2627
Expo,
28+
FilterConfig,
2729
Intercom,
2830
IntercomRecipient,
2931
ListFilter,
@@ -91,9 +93,6 @@ Types:
9193
```python
9294
from courier.types import (
9395
Audience,
94-
Filter,
95-
NestedFilterConfig,
96-
SingleFilterConfig,
9796
AudienceUpdateResponse,
9897
AudienceListResponse,
9998
AudienceListMembersResponse,

pyproject.toml

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

src/courier/_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,
@@ -477,8 +480,19 @@ def _build_request(
477480
retries_taken: int = 0,
478481
) -> httpx.Request:
479482
if log.isEnabledFor(logging.DEBUG):
480-
log.debug("Request options: %s", model_dump(options, exclude_unset=True))
481-
483+
log.debug(
484+
"Request options: %s",
485+
model_dump(
486+
options,
487+
exclude_unset=True,
488+
# Pydantic v1 can't dump every type we support in content, so we exclude it for now.
489+
exclude={
490+
"content",
491+
}
492+
if PYDANTIC_V1
493+
else {},
494+
),
495+
)
482496
kwargs: dict[str, Any] = {}
483497

484498
json_data = options.json_data
@@ -532,7 +546,13 @@ def _build_request(
532546
is_body_allowed = options.method.lower() != "get"
533547

534548
if is_body_allowed:
535-
if isinstance(json_data, bytes):
549+
if options.content is not None and json_data is not None:
550+
raise TypeError("Passing both `content` and `json_data` is not supported")
551+
if options.content is not None and files is not None:
552+
raise TypeError("Passing both `content` and `files` is not supported")
553+
if options.content is not None:
554+
kwargs["content"] = options.content
555+
elif isinstance(json_data, bytes):
536556
kwargs["content"] = json_data
537557
else:
538558
kwargs["json"] = json_data if is_given(json_data) else None
@@ -1194,6 +1214,7 @@ def post(
11941214
*,
11951215
cast_to: Type[ResponseT],
11961216
body: Body | None = None,
1217+
content: BinaryTypes | None = None,
11971218
options: RequestOptions = {},
11981219
files: RequestFiles | None = None,
11991220
stream: Literal[False] = False,
@@ -1206,6 +1227,7 @@ def post(
12061227
*,
12071228
cast_to: Type[ResponseT],
12081229
body: Body | None = None,
1230+
content: BinaryTypes | None = None,
12091231
options: RequestOptions = {},
12101232
files: RequestFiles | None = None,
12111233
stream: Literal[True],
@@ -1219,6 +1241,7 @@ def post(
12191241
*,
12201242
cast_to: Type[ResponseT],
12211243
body: Body | None = None,
1244+
content: BinaryTypes | None = None,
12221245
options: RequestOptions = {},
12231246
files: RequestFiles | None = None,
12241247
stream: bool,
@@ -1231,13 +1254,25 @@ def post(
12311254
*,
12321255
cast_to: Type[ResponseT],
12331256
body: Body | None = None,
1257+
content: BinaryTypes | None = None,
12341258
options: RequestOptions = {},
12351259
files: RequestFiles | None = None,
12361260
stream: bool = False,
12371261
stream_cls: type[_StreamT] | None = None,
12381262
) -> ResponseT | _StreamT:
1263+
if body is not None and content is not None:
1264+
raise TypeError("Passing both `body` and `content` is not supported")
1265+
if files is not None and content is not None:
1266+
raise TypeError("Passing both `files` and `content` is not supported")
1267+
if isinstance(body, bytes):
1268+
warnings.warn(
1269+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1270+
"Please pass raw bytes via the `content` parameter instead.",
1271+
DeprecationWarning,
1272+
stacklevel=2,
1273+
)
12391274
opts = FinalRequestOptions.construct(
1240-
method="post", url=path, json_data=body, files=to_httpx_files(files), **options
1275+
method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
12411276
)
12421277
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
12431278

@@ -1247,11 +1282,23 @@ def patch(
12471282
*,
12481283
cast_to: Type[ResponseT],
12491284
body: Body | None = None,
1285+
content: BinaryTypes | None = None,
12501286
files: RequestFiles | None = None,
12511287
options: RequestOptions = {},
12521288
) -> ResponseT:
1289+
if body is not None and content is not None:
1290+
raise TypeError("Passing both `body` and `content` is not supported")
1291+
if files is not None and content is not None:
1292+
raise TypeError("Passing both `files` and `content` is not supported")
1293+
if isinstance(body, bytes):
1294+
warnings.warn(
1295+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1296+
"Please pass raw bytes via the `content` parameter instead.",
1297+
DeprecationWarning,
1298+
stacklevel=2,
1299+
)
12531300
opts = FinalRequestOptions.construct(
1254-
method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
1301+
method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
12551302
)
12561303
return self.request(cast_to, opts)
12571304

@@ -1261,11 +1308,23 @@ def put(
12611308
*,
12621309
cast_to: Type[ResponseT],
12631310
body: Body | None = None,
1311+
content: BinaryTypes | None = None,
12641312
files: RequestFiles | None = None,
12651313
options: RequestOptions = {},
12661314
) -> ResponseT:
1315+
if body is not None and content is not None:
1316+
raise TypeError("Passing both `body` and `content` is not supported")
1317+
if files is not None and content is not None:
1318+
raise TypeError("Passing both `files` and `content` is not supported")
1319+
if isinstance(body, bytes):
1320+
warnings.warn(
1321+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1322+
"Please pass raw bytes via the `content` parameter instead.",
1323+
DeprecationWarning,
1324+
stacklevel=2,
1325+
)
12671326
opts = FinalRequestOptions.construct(
1268-
method="put", url=path, json_data=body, files=to_httpx_files(files), **options
1327+
method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
12691328
)
12701329
return self.request(cast_to, opts)
12711330

@@ -1275,9 +1334,19 @@ def delete(
12751334
*,
12761335
cast_to: Type[ResponseT],
12771336
body: Body | None = None,
1337+
content: BinaryTypes | None = None,
12781338
options: RequestOptions = {},
12791339
) -> ResponseT:
1280-
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
1340+
if body is not None and content is not None:
1341+
raise TypeError("Passing both `body` and `content` is not supported")
1342+
if isinstance(body, bytes):
1343+
warnings.warn(
1344+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1345+
"Please pass raw bytes via the `content` parameter instead.",
1346+
DeprecationWarning,
1347+
stacklevel=2,
1348+
)
1349+
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
12811350
return self.request(cast_to, opts)
12821351

12831352
def get_api_list(
@@ -1717,6 +1786,7 @@ async def post(
17171786
*,
17181787
cast_to: Type[ResponseT],
17191788
body: Body | None = None,
1789+
content: AsyncBinaryTypes | None = None,
17201790
files: RequestFiles | None = None,
17211791
options: RequestOptions = {},
17221792
stream: Literal[False] = False,
@@ -1729,6 +1799,7 @@ async def post(
17291799
*,
17301800
cast_to: Type[ResponseT],
17311801
body: Body | None = None,
1802+
content: AsyncBinaryTypes | None = None,
17321803
files: RequestFiles | None = None,
17331804
options: RequestOptions = {},
17341805
stream: Literal[True],
@@ -1742,6 +1813,7 @@ async def post(
17421813
*,
17431814
cast_to: Type[ResponseT],
17441815
body: Body | None = None,
1816+
content: AsyncBinaryTypes | None = None,
17451817
files: RequestFiles | None = None,
17461818
options: RequestOptions = {},
17471819
stream: bool,
@@ -1754,13 +1826,25 @@ async def post(
17541826
*,
17551827
cast_to: Type[ResponseT],
17561828
body: Body | None = None,
1829+
content: AsyncBinaryTypes | None = None,
17571830
files: RequestFiles | None = None,
17581831
options: RequestOptions = {},
17591832
stream: bool = False,
17601833
stream_cls: type[_AsyncStreamT] | None = None,
17611834
) -> ResponseT | _AsyncStreamT:
1835+
if body is not None and content is not None:
1836+
raise TypeError("Passing both `body` and `content` is not supported")
1837+
if files is not None and content is not None:
1838+
raise TypeError("Passing both `files` and `content` is not supported")
1839+
if isinstance(body, bytes):
1840+
warnings.warn(
1841+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1842+
"Please pass raw bytes via the `content` parameter instead.",
1843+
DeprecationWarning,
1844+
stacklevel=2,
1845+
)
17621846
opts = FinalRequestOptions.construct(
1763-
method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options
1847+
method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
17641848
)
17651849
return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
17661850

@@ -1770,11 +1854,28 @@ async def patch(
17701854
*,
17711855
cast_to: Type[ResponseT],
17721856
body: Body | None = None,
1857+
content: AsyncBinaryTypes | None = None,
17731858
files: RequestFiles | None = None,
17741859
options: RequestOptions = {},
17751860
) -> ResponseT:
1861+
if body is not None and content is not None:
1862+
raise TypeError("Passing both `body` and `content` is not supported")
1863+
if files is not None and content is not None:
1864+
raise TypeError("Passing both `files` and `content` is not supported")
1865+
if isinstance(body, bytes):
1866+
warnings.warn(
1867+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1868+
"Please pass raw bytes via the `content` parameter instead.",
1869+
DeprecationWarning,
1870+
stacklevel=2,
1871+
)
17761872
opts = FinalRequestOptions.construct(
1777-
method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options
1873+
method="patch",
1874+
url=path,
1875+
json_data=body,
1876+
content=content,
1877+
files=await async_to_httpx_files(files),
1878+
**options,
17781879
)
17791880
return await self.request(cast_to, opts)
17801881

@@ -1784,11 +1885,23 @@ async def put(
17841885
*,
17851886
cast_to: Type[ResponseT],
17861887
body: Body | None = None,
1888+
content: AsyncBinaryTypes | None = None,
17871889
files: RequestFiles | None = None,
17881890
options: RequestOptions = {},
17891891
) -> ResponseT:
1892+
if body is not None and content is not None:
1893+
raise TypeError("Passing both `body` and `content` is not supported")
1894+
if files is not None and content is not None:
1895+
raise TypeError("Passing both `files` and `content` is not supported")
1896+
if isinstance(body, bytes):
1897+
warnings.warn(
1898+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1899+
"Please pass raw bytes via the `content` parameter instead.",
1900+
DeprecationWarning,
1901+
stacklevel=2,
1902+
)
17901903
opts = FinalRequestOptions.construct(
1791-
method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options
1904+
method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
17921905
)
17931906
return await self.request(cast_to, opts)
17941907

@@ -1798,9 +1911,19 @@ async def delete(
17981911
*,
17991912
cast_to: Type[ResponseT],
18001913
body: Body | None = None,
1914+
content: AsyncBinaryTypes | None = None,
18011915
options: RequestOptions = {},
18021916
) -> ResponseT:
1803-
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
1917+
if body is not None and content is not None:
1918+
raise TypeError("Passing both `body` and `content` is not supported")
1919+
if isinstance(body, bytes):
1920+
warnings.warn(
1921+
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
1922+
"Please pass raw bytes via the `content` parameter instead.",
1923+
DeprecationWarning,
1924+
stacklevel=2,
1925+
)
1926+
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
18041927
return await self.request(cast_to, opts)
18051928

18061929
def get_api_list(

0 commit comments

Comments
 (0)