Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.0.5"
".": "1.1.0"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 11
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/alisson%2Fbrapi-11630ce70d2f2c187912989dd9004b10828eaab889f617ba39d5a7a8e4b03b62.yml
openapi_spec_hash: 4198e5f7a76f3002723c113663465c00
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/alisson%2Fbrapi-76a60a630b8eaac37bdec27ffec5cbdf6640fb884186adb08211f1b81832c075.yml
openapi_spec_hash: 51fab4b9fd59ce7421f3fdf03644c987
config_hash: 6f10a67950f65bf850612b59838ad03b
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 1.1.0 (2026-01-14)

Full Changelog: [v1.0.5...v1.1.0](https://github.com/brapi-dev/brapi-python/compare/v1.0.5...v1.1.0)

### Features

* **client:** add support for binary request streaming ([f7eb87e](https://github.com/brapi-dev/brapi-python/commit/f7eb87e9fc953d88d4089f62da193dac2348f940))


### Chores

* **internal:** codegen related update ([320cbf8](https://github.com/brapi-dev/brapi-python/commit/320cbf88b64040419879dd30e0afd4e349ddbd16))

## 1.0.5 (2025-12-19)

Full Changelog: [v1.0.4...v1.0.5](https://github.com/brapi-dev/brapi-python/compare/v1.0.4...v1.0.5)
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2025 Brapi
Copyright 2026 Brapi

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "brapi"
version = "1.0.5"
version = "1.1.0"
description = "The official Python library for the brapi API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
145 changes: 134 additions & 11 deletions src/brapi/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import inspect
import logging
import platform
import warnings
import email.utils
from types import TracebackType
from random import random
Expand Down Expand Up @@ -51,9 +52,11 @@
ResponseT,
AnyMapping,
PostParser,
BinaryTypes,
RequestFiles,
HttpxSendArgs,
RequestOptions,
AsyncBinaryTypes,
HttpxRequestFiles,
ModelBuilderProtocol,
not_given,
Expand Down Expand Up @@ -477,8 +480,19 @@ def _build_request(
retries_taken: int = 0,
) -> httpx.Request:
if log.isEnabledFor(logging.DEBUG):
log.debug("Request options: %s", model_dump(options, exclude_unset=True))

log.debug(
"Request options: %s",
model_dump(
options,
exclude_unset=True,
# Pydantic v1 can't dump every type we support in content, so we exclude it for now.
exclude={
"content",
}
if PYDANTIC_V1
else {},
),
)
kwargs: dict[str, Any] = {}

json_data = options.json_data
Expand Down Expand Up @@ -532,7 +546,13 @@ def _build_request(
is_body_allowed = options.method.lower() != "get"

if is_body_allowed:
if isinstance(json_data, bytes):
if options.content is not None and json_data is not None:
raise TypeError("Passing both `content` and `json_data` is not supported")
if options.content is not None and files is not None:
raise TypeError("Passing both `content` and `files` is not supported")
if options.content is not None:
kwargs["content"] = options.content
elif isinstance(json_data, bytes):
kwargs["content"] = json_data
else:
kwargs["json"] = json_data if is_given(json_data) else None
Expand Down Expand Up @@ -1194,6 +1214,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: Literal[False] = False,
Expand All @@ -1206,6 +1227,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: Literal[True],
Expand All @@ -1219,6 +1241,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: bool,
Expand All @@ -1231,13 +1254,25 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: bool = False,
stream_cls: type[_StreamT] | None = None,
) -> ResponseT | _StreamT:
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if files is not None and content is not None:
raise TypeError("Passing both `files` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(
method="post", url=path, json_data=body, files=to_httpx_files(files), **options
method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))

Expand All @@ -1247,11 +1282,23 @@ def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if files is not None and content is not None:
raise TypeError("Passing both `files` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(
method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return self.request(cast_to, opts)

Expand All @@ -1261,11 +1308,23 @@ def put(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if files is not None and content is not None:
raise TypeError("Passing both `files` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(
method="put", url=path, json_data=body, files=to_httpx_files(files), **options
method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return self.request(cast_to, opts)

Expand All @@ -1275,9 +1334,19 @@ def delete(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
options: RequestOptions = {},
) -> ResponseT:
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
return self.request(cast_to, opts)

def get_api_list(
Expand Down Expand Up @@ -1717,6 +1786,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: Literal[False] = False,
Expand All @@ -1729,6 +1799,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: Literal[True],
Expand All @@ -1742,6 +1813,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: bool,
Expand All @@ -1754,13 +1826,25 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: bool = False,
stream_cls: type[_AsyncStreamT] | None = None,
) -> ResponseT | _AsyncStreamT:
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if files is not None and content is not None:
raise TypeError("Passing both `files` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(
method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options
method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)

Expand All @@ -1770,11 +1854,28 @@ async def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if files is not None and content is not None:
raise TypeError("Passing both `files` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(
method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options
method="patch",
url=path,
json_data=body,
content=content,
files=await async_to_httpx_files(files),
**options,
)
return await self.request(cast_to, opts)

Expand All @@ -1784,11 +1885,23 @@ async def put(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if files is not None and content is not None:
raise TypeError("Passing both `files` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(
method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options
method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts)

Expand All @@ -1798,9 +1911,19 @@ async def delete(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
options: RequestOptions = {},
) -> ResponseT:
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
return await self.request(cast_to, opts)

def get_api_list(
Expand Down
17 changes: 16 additions & 1 deletion src/brapi/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,20 @@
import os
import inspect
import weakref
from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
from typing import (
IO,
TYPE_CHECKING,
Any,
Type,
Union,
Generic,
TypeVar,
Callable,
Iterable,
Optional,
AsyncIterable,
cast,
)
from datetime import date, datetime
from typing_extensions import (
List,
Expand Down Expand Up @@ -787,6 +800,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
timeout: float | Timeout | None
files: HttpxRequestFiles | None
idempotency_key: str
content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None]
json_data: Body
extra_json: AnyMapping
follow_redirects: bool
Expand All @@ -805,6 +819,7 @@ class FinalRequestOptions(pydantic.BaseModel):
post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
follow_redirects: Union[bool, None] = None

content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None
# It should be noted that we cannot use `json` here as that would override
# a BaseModel method in an incompatible fashion.
json_data: Union[Body, None] = None
Expand Down
Loading