From 9f74fa57c3d4bfebf2d5b4765d100f0e663b4ed6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 18:13:03 +0000 Subject: [PATCH 1/3] chore(internal): codegen related update --- .github/workflows/ci.yml | 6 +- .github/workflows/publish-pypi.yml | 2 +- .github/workflows/release-doctor.yml | 2 +- src/orb/_base_client.py | 145 ++++++++++++-- src/orb/_models.py | 17 +- src/orb/_types.py | 9 + src/orb/resources/invoices.py | 4 +- src/orb/types/invoice_list_summary_params.py | 4 +- tests/api_resources/test_invoices.py | 4 +- tests/test_client.py | 187 ++++++++++++++++++- 10 files changed, 355 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5cdc6f7d..835aa18b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/orb-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | @@ -44,7 +44,7 @@ jobs: id-token: write runs-on: ${{ github.repository == 'stainless-sdks/orb-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | @@ -81,7 +81,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/orb-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index b34bb186..a978bc83 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 34250833..c791ea6b 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'orbcorp/orb-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Check release environment run: | diff --git a/src/orb/_base_client.py b/src/orb/_base_client.py index 8d746475..8425aeba 100644 --- a/src/orb/_base_client.py +++ b/src/orb/_base_client.py @@ -9,6 +9,7 @@ import inspect import logging import platform +import warnings import email.utils from types import TracebackType from random import random @@ -51,9 +52,11 @@ ResponseT, AnyMapping, PostParser, + BinaryTypes, RequestFiles, HttpxSendArgs, RequestOptions, + AsyncBinaryTypes, HttpxRequestFiles, ModelBuilderProtocol, not_given, @@ -478,8 +481,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 @@ -533,7 +547,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 @@ -1209,6 +1229,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, @@ -1221,6 +1242,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[True], @@ -1234,6 +1256,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool, @@ -1246,13 +1269,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)) @@ -1262,11 +1297,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) @@ -1276,11 +1323,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) @@ -1290,9 +1349,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( @@ -1746,6 +1815,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, @@ -1758,6 +1828,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], @@ -1771,6 +1842,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool, @@ -1783,13 +1855,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) @@ -1799,11 +1883,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) @@ -1813,11 +1914,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) @@ -1827,9 +1940,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( diff --git a/src/orb/_models.py b/src/orb/_models.py index ca9500b2..29070e05 100644 --- a/src/orb/_models.py +++ b/src/orb/_models.py @@ -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, @@ -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 @@ -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 diff --git a/src/orb/_types.py b/src/orb/_types.py index 3f7795d0..76df2189 100644 --- a/src/orb/_types.py +++ b/src/orb/_types.py @@ -13,9 +13,11 @@ Mapping, TypeVar, Callable, + Iterable, Iterator, Optional, Sequence, + AsyncIterable, ) from typing_extensions import ( Set, @@ -57,6 +59,13 @@ else: Base64FileInput = Union[IO[bytes], PathLike] FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. + + +# Used for sending raw binary data / streaming data in request bodies +# e.g. for file uploads without multipart encoding +BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]] +AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]] + FileTypes = Union[ # file (or bytes) FileContent, diff --git a/src/orb/resources/invoices.py b/src/orb/resources/invoices.py index 63fa9ab4..c10867e8 100644 --- a/src/orb/resources/invoices.py +++ b/src/orb/resources/invoices.py @@ -519,7 +519,7 @@ def list_summary( invoice_date_lte: Union[str, datetime, None] | Omit = omit, is_recurring: Optional[bool] | Omit = omit, limit: int | Omit = omit, - status: Optional[Literal["draft", "issued", "paid", "synced", "void"]] | Omit = omit, + status: Optional[List[Literal["draft", "issued", "paid", "synced", "void"]]] | Omit = omit, subscription_id: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -1239,7 +1239,7 @@ def list_summary( invoice_date_lte: Union[str, datetime, None] | Omit = omit, is_recurring: Optional[bool] | Omit = omit, limit: int | Omit = omit, - status: Optional[Literal["draft", "issued", "paid", "synced", "void"]] | Omit = omit, + status: Optional[List[Literal["draft", "issued", "paid", "synced", "void"]]] | Omit = omit, subscription_id: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. diff --git a/src/orb/types/invoice_list_summary_params.py b/src/orb/types/invoice_list_summary_params.py index 41b86e65..7f8849a1 100644 --- a/src/orb/types/invoice_list_summary_params.py +++ b/src/orb/types/invoice_list_summary_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Union, Optional +from typing import List, Union, Optional from datetime import date, datetime from typing_extensions import Literal, Annotated, TypedDict @@ -58,6 +58,6 @@ class InvoiceListSummaryParams(TypedDict, total=False): limit: int """The number of items to fetch. Defaults to 20.""" - status: Optional[Literal["draft", "issued", "paid", "synced", "void"]] + status: Optional[List[Literal["draft", "issued", "paid", "synced", "void"]]] subscription_id: Optional[str] diff --git a/tests/api_resources/test_invoices.py b/tests/api_resources/test_invoices.py index 38ef17ea..423bfc3b 100644 --- a/tests/api_resources/test_invoices.py +++ b/tests/api_resources/test_invoices.py @@ -419,7 +419,7 @@ def test_method_list_summary_with_all_params(self, client: Orb) -> None: invoice_date_lte=parse_datetime("2019-12-27T18:11:19.117Z"), is_recurring=True, limit=1, - status="draft", + status=["draft"], subscription_id="subscription_id", ) assert_matches_type(SyncPage[InvoiceListSummaryResponse], invoice, path=["response"]) @@ -974,7 +974,7 @@ async def test_method_list_summary_with_all_params(self, async_client: AsyncOrb) invoice_date_lte=parse_datetime("2019-12-27T18:11:19.117Z"), is_recurring=True, limit=1, - status="draft", + status=["draft"], subscription_id="subscription_id", ) assert_matches_type(AsyncPage[InvoiceListSummaryResponse], invoice, path=["response"]) diff --git a/tests/test_client.py b/tests/test_client.py index 1ae3ebcb..32ed4d23 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -8,10 +8,11 @@ import json import asyncio import inspect +import dataclasses import tracemalloc -from typing import Any, Union, cast +from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast from unittest import mock -from typing_extensions import Literal +from typing_extensions import Literal, AsyncIterator, override import httpx import pytest @@ -36,6 +37,7 @@ from .utils import update_env +T = TypeVar("T") base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") api_key = "My API Key" @@ -50,6 +52,57 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float: return 0.1 +def mirror_request_content(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, content=request.content) + + +# note: we can't use the httpx.MockTransport class as it consumes the request +# body itself, which means we can't test that the body is read lazily +class MockTransport(httpx.BaseTransport, httpx.AsyncBaseTransport): + def __init__( + self, + handler: Callable[[httpx.Request], httpx.Response] + | Callable[[httpx.Request], Coroutine[Any, Any, httpx.Response]], + ) -> None: + self.handler = handler + + @override + def handle_request( + self, + request: httpx.Request, + ) -> httpx.Response: + assert not inspect.iscoroutinefunction(self.handler), "handler must not be a coroutine function" + assert inspect.isfunction(self.handler), "handler must be a function" + return self.handler(request) + + @override + async def handle_async_request( + self, + request: httpx.Request, + ) -> httpx.Response: + assert inspect.iscoroutinefunction(self.handler), "handler must be a coroutine function" + return await self.handler(request) + + +@dataclasses.dataclass +class Counter: + value: int = 0 + + +def _make_sync_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> Iterator[T]: + for item in iterable: + if counter: + counter.value += 1 + yield item + + +async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> AsyncIterator[T]: + for item in iterable: + if counter: + counter.value += 1 + yield item + + def _get_open_connections(client: Orb | AsyncOrb) -> int: transport = client._client._transport assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) @@ -492,6 +545,70 @@ def test_multipart_repeating_array(self, client: Orb) -> None: b"", ] + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload(self, respx_mock: MockRouter, client: Orb) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + response = client.post( + "/upload", + content=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + def test_binary_content_upload_with_iterator(self) -> None: + file_content = b"Hello, this is a test file." + counter = Counter() + iterator = _make_sync_iterator([file_content], counter=counter) + + def mock_handler(request: httpx.Request) -> httpx.Response: + assert counter.value == 0, "the request body should not have been read" + return httpx.Response(200, content=request.read()) + + with Orb( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=httpx.Client(transport=MockTransport(handler=mock_handler)), + ) as client: + response = client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: Orb) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + with pytest.deprecated_call( + match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." + ): + response = client.post( + "/upload", + body=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + @pytest.mark.respx(base_url=base_url) def test_basic_union_response(self, respx_mock: MockRouter, client: Orb) -> None: class Model1(BaseModel): @@ -1367,6 +1484,72 @@ def test_multipart_repeating_array(self, async_client: AsyncOrb) -> None: b"", ] + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncOrb) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + response = await async_client.post( + "/upload", + content=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + async def test_binary_content_upload_with_asynciterator(self) -> None: + file_content = b"Hello, this is a test file." + counter = Counter() + iterator = _make_async_iterator([file_content], counter=counter) + + async def mock_handler(request: httpx.Request) -> httpx.Response: + assert counter.value == 0, "the request body should not have been read" + return httpx.Response(200, content=await request.aread()) + + async with AsyncOrb( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)), + ) as client: + response = await client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload_with_body_is_deprecated( + self, respx_mock: MockRouter, async_client: AsyncOrb + ) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + with pytest.deprecated_call( + match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." + ): + response = await async_client.post( + "/upload", + body=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + @pytest.mark.respx(base_url=base_url) async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncOrb) -> None: class Model1(BaseModel): From 5b5c348e954f79937614e47481e3a91dc8692751 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 02:23:43 +0000 Subject: [PATCH 2/3] feat(api): api update --- .stats.yml | 4 +-- src/orb/resources/customers/credits/ledger.py | 32 +++++++------------ src/orb/resources/prices/prices.py | 30 +++++++++-------- ...scription_minimum_composite_price_param.py | 12 +++---- src/orb/types/price_create_params.py | 12 +++---- .../new_floating_minimum_composite_price.py | 12 +++---- .../new_plan_minimum_composite_price.py | 12 +++---- src/orb/types/shared/price.py | 12 +++---- .../new_floating_minimum_composite_price.py | 12 +++---- .../new_plan_minimum_composite_price.py | 12 +++---- tests/api_resources/test_prices.py | 32 +++++++++---------- 11 files changed, 88 insertions(+), 94 deletions(-) diff --git a/.stats.yml b/.stats.yml index df8db457..1f41e32f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 126 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-a5a28a58483355d3cc3da7ac5c452d548ee17183324318198052968121ca7dba.yml -openapi_spec_hash: a317931a99e6d4a122919135a0363e40 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-6017828d1287c194d4d7759dc5b5410225ab58af66ff8605315f70f5e623b82d.yml +openapi_spec_hash: 4390eaf377258fcd7db1dbc073a2c23f config_hash: bcf82bddb691f6be773ac6cae8c03b9a diff --git a/src/orb/resources/customers/credits/ledger.py b/src/orb/resources/customers/credits/ledger.py index 2434b9da..7336b325 100644 --- a/src/orb/resources/customers/credits/ledger.py +++ b/src/orb/resources/customers/credits/ledger.py @@ -110,11 +110,9 @@ def list( As usage for a customer is reported into Orb, credits may be deducted according to the customer's plan configuration. An automated deduction of this type will - result in a ledger entry, also with a starting and ending balance. In order to - provide better tracing capabilities for automatic deductions, Orb always - associates each automatic deduction with the `event_id` at the time of - ingestion, used to pinpoint _why_ credit deduction took place and to ensure that - credits are never deducted without an associated usage event. + result in a ledger entry, also with a starting and ending balance. Each day's + usage for a particular price, invoice, and block will be grouped into a single + entry. By default, Orb uses an algorithm that automatically deducts from the _soonest expiring credit block_ first in order to ensure that all credits are utilized @@ -2076,11 +2074,9 @@ def list_by_external_id( As usage for a customer is reported into Orb, credits may be deducted according to the customer's plan configuration. An automated deduction of this type will - result in a ledger entry, also with a starting and ending balance. In order to - provide better tracing capabilities for automatic deductions, Orb always - associates each automatic deduction with the `event_id` at the time of - ingestion, used to pinpoint _why_ credit deduction took place and to ensure that - credits are never deducted without an associated usage event. + result in a ledger entry, also with a starting and ending balance. Each day's + usage for a particular price, invoice, and block will be grouped into a single + entry. By default, Orb uses an algorithm that automatically deducts from the _soonest expiring credit block_ first in order to ensure that all credits are utilized @@ -2261,11 +2257,9 @@ def list( As usage for a customer is reported into Orb, credits may be deducted according to the customer's plan configuration. An automated deduction of this type will - result in a ledger entry, also with a starting and ending balance. In order to - provide better tracing capabilities for automatic deductions, Orb always - associates each automatic deduction with the `event_id` at the time of - ingestion, used to pinpoint _why_ credit deduction took place and to ensure that - credits are never deducted without an associated usage event. + result in a ledger entry, also with a starting and ending balance. Each day's + usage for a particular price, invoice, and block will be grouped into a single + entry. By default, Orb uses an algorithm that automatically deducts from the _soonest expiring credit block_ first in order to ensure that all credits are utilized @@ -4227,11 +4221,9 @@ def list_by_external_id( As usage for a customer is reported into Orb, credits may be deducted according to the customer's plan configuration. An automated deduction of this type will - result in a ledger entry, also with a starting and ending balance. In order to - provide better tracing capabilities for automatic deductions, Orb always - associates each automatic deduction with the `event_id` at the time of - ingestion, used to pinpoint _why_ credit deduction took place and to ensure that - credits are never deducted without an associated usage event. + result in a ledger entry, also with a starting and ending balance. Each day's + usage for a particular price, invoice, and block will be grouped into a single + entry. By default, Orb uses an algorithm that automatically deducts from the _soonest expiring credit block_ first in order to ensure that all credits are utilized diff --git a/src/orb/resources/prices/prices.py b/src/orb/resources/prices/prices.py index 970d9e16..2b1f85fc 100644 --- a/src/orb/resources/prices/prices.py +++ b/src/orb/resources/prices/prices.py @@ -2776,8 +2776,8 @@ def create( cadence: Literal["annual", "semi_annual", "monthly", "quarterly", "one_time", "custom"], currency: str, item_id: str, - minimum_config: price_create_params.NewFloatingMinimumCompositePriceMinimumConfig, - model_type: Literal["minimum"], + minimum_composite_config: price_create_params.NewFloatingMinimumCompositePriceMinimumCompositeConfig, + model_type: Literal["minimum_composite"], name: str, billable_metric_id: Optional[str] | Omit = omit, billed_in_advance: Optional[bool] | Omit = omit, @@ -2818,7 +2818,7 @@ def create( item_id: The id of the item the price will be associated with. - minimum_config: Configuration for minimum pricing + minimum_composite_config: Configuration for minimum_composite pricing model_type: The pricing model type @@ -3086,7 +3086,7 @@ def create( ["cadence", "currency", "item_id", "model_type", "name", "scalable_matrix_with_tiered_pricing_config"], ["cadence", "cumulative_grouped_bulk_config", "currency", "item_id", "model_type", "name"], ["cadence", "cumulative_grouped_allocation_config", "currency", "item_id", "model_type", "name"], - ["cadence", "currency", "item_id", "minimum_config", "model_type", "name"], + ["cadence", "currency", "item_id", "minimum_composite_config", "model_type", "name"], ["cadence", "currency", "item_id", "model_type", "name", "percent_config"], ["cadence", "currency", "event_output_config", "item_id", "model_type", "name"], ) @@ -3124,7 +3124,7 @@ def create( | Literal["scalable_matrix_with_tiered_pricing"] | Literal["cumulative_grouped_bulk"] | Literal["cumulative_grouped_allocation"] - | Literal["minimum"] + | Literal["minimum_composite"] | Literal["percent"] | Literal["event_output"], name: str, @@ -3218,7 +3218,8 @@ def create( | Omit = omit, cumulative_grouped_allocation_config: price_create_params.NewFloatingCumulativeGroupedAllocationPriceCumulativeGroupedAllocationConfig | Omit = omit, - minimum_config: price_create_params.NewFloatingMinimumCompositePriceMinimumConfig | Omit = omit, + minimum_composite_config: price_create_params.NewFloatingMinimumCompositePriceMinimumCompositeConfig + | Omit = omit, percent_config: price_create_params.NewFloatingPercentCompositePricePercentConfig | Omit = omit, event_output_config: price_create_params.NewFloatingEventOutputPriceEventOutputConfig | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -3279,7 +3280,7 @@ def create( "scalable_matrix_with_tiered_pricing_config": scalable_matrix_with_tiered_pricing_config, "cumulative_grouped_bulk_config": cumulative_grouped_bulk_config, "cumulative_grouped_allocation_config": cumulative_grouped_allocation_config, - "minimum_config": minimum_config, + "minimum_composite_config": minimum_composite_config, "percent_config": percent_config, "event_output_config": event_output_config, }, @@ -6432,8 +6433,8 @@ async def create( cadence: Literal["annual", "semi_annual", "monthly", "quarterly", "one_time", "custom"], currency: str, item_id: str, - minimum_config: price_create_params.NewFloatingMinimumCompositePriceMinimumConfig, - model_type: Literal["minimum"], + minimum_composite_config: price_create_params.NewFloatingMinimumCompositePriceMinimumCompositeConfig, + model_type: Literal["minimum_composite"], name: str, billable_metric_id: Optional[str] | Omit = omit, billed_in_advance: Optional[bool] | Omit = omit, @@ -6474,7 +6475,7 @@ async def create( item_id: The id of the item the price will be associated with. - minimum_config: Configuration for minimum pricing + minimum_composite_config: Configuration for minimum_composite pricing model_type: The pricing model type @@ -6742,7 +6743,7 @@ async def create( ["cadence", "currency", "item_id", "model_type", "name", "scalable_matrix_with_tiered_pricing_config"], ["cadence", "cumulative_grouped_bulk_config", "currency", "item_id", "model_type", "name"], ["cadence", "cumulative_grouped_allocation_config", "currency", "item_id", "model_type", "name"], - ["cadence", "currency", "item_id", "minimum_config", "model_type", "name"], + ["cadence", "currency", "item_id", "minimum_composite_config", "model_type", "name"], ["cadence", "currency", "item_id", "model_type", "name", "percent_config"], ["cadence", "currency", "event_output_config", "item_id", "model_type", "name"], ) @@ -6780,7 +6781,7 @@ async def create( | Literal["scalable_matrix_with_tiered_pricing"] | Literal["cumulative_grouped_bulk"] | Literal["cumulative_grouped_allocation"] - | Literal["minimum"] + | Literal["minimum_composite"] | Literal["percent"] | Literal["event_output"], name: str, @@ -6874,7 +6875,8 @@ async def create( | Omit = omit, cumulative_grouped_allocation_config: price_create_params.NewFloatingCumulativeGroupedAllocationPriceCumulativeGroupedAllocationConfig | Omit = omit, - minimum_config: price_create_params.NewFloatingMinimumCompositePriceMinimumConfig | Omit = omit, + minimum_composite_config: price_create_params.NewFloatingMinimumCompositePriceMinimumCompositeConfig + | Omit = omit, percent_config: price_create_params.NewFloatingPercentCompositePricePercentConfig | Omit = omit, event_output_config: price_create_params.NewFloatingEventOutputPriceEventOutputConfig | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -6935,7 +6937,7 @@ async def create( "scalable_matrix_with_tiered_pricing_config": scalable_matrix_with_tiered_pricing_config, "cumulative_grouped_bulk_config": cumulative_grouped_bulk_config, "cumulative_grouped_allocation_config": cumulative_grouped_allocation_config, - "minimum_config": minimum_config, + "minimum_composite_config": minimum_composite_config, "percent_config": percent_config, "event_output_config": event_output_config, }, diff --git a/src/orb/types/new_subscription_minimum_composite_price_param.py b/src/orb/types/new_subscription_minimum_composite_price_param.py index 0d5dadf5..671d9e16 100644 --- a/src/orb/types/new_subscription_minimum_composite_price_param.py +++ b/src/orb/types/new_subscription_minimum_composite_price_param.py @@ -10,11 +10,11 @@ from .shared_params.new_billing_cycle_configuration import NewBillingCycleConfiguration from .shared_params.new_dimensional_price_configuration import NewDimensionalPriceConfiguration -__all__ = ["NewSubscriptionMinimumCompositePriceParam", "MinimumConfig", "ConversionRateConfig"] +__all__ = ["NewSubscriptionMinimumCompositePriceParam", "MinimumCompositeConfig", "ConversionRateConfig"] -class MinimumConfig(TypedDict, total=False): - """Configuration for minimum pricing""" +class MinimumCompositeConfig(TypedDict, total=False): + """Configuration for minimum_composite pricing""" minimum_amount: Required[str] """The minimum amount to apply""" @@ -33,10 +33,10 @@ class NewSubscriptionMinimumCompositePriceParam(TypedDict, total=False): item_id: Required[str] """The id of the item the price will be associated with.""" - minimum_config: Required[MinimumConfig] - """Configuration for minimum pricing""" + minimum_composite_config: Required[MinimumCompositeConfig] + """Configuration for minimum_composite pricing""" - model_type: Required[Literal["minimum"]] + model_type: Required[Literal["minimum_composite"]] """The pricing model type""" name: Required[str] diff --git a/src/orb/types/price_create_params.py b/src/orb/types/price_create_params.py index 10adb48a..a9dfeb20 100644 --- a/src/orb/types/price_create_params.py +++ b/src/orb/types/price_create_params.py @@ -115,7 +115,7 @@ "NewFloatingCumulativeGroupedAllocationPriceCumulativeGroupedAllocationConfig", "NewFloatingCumulativeGroupedAllocationPriceConversionRateConfig", "NewFloatingMinimumCompositePrice", - "NewFloatingMinimumCompositePriceMinimumConfig", + "NewFloatingMinimumCompositePriceMinimumCompositeConfig", "NewFloatingMinimumCompositePriceConversionRateConfig", "NewFloatingPercentCompositePrice", "NewFloatingPercentCompositePricePercentConfig", @@ -2775,10 +2775,10 @@ class NewFloatingMinimumCompositePrice(TypedDict, total=False): item_id: Required[str] """The id of the item the price will be associated with.""" - minimum_config: Required[NewFloatingMinimumCompositePriceMinimumConfig] - """Configuration for minimum pricing""" + minimum_composite_config: Required[NewFloatingMinimumCompositePriceMinimumCompositeConfig] + """Configuration for minimum_composite pricing""" - model_type: Required[Literal["minimum"]] + model_type: Required[Literal["minimum_composite"]] """The pricing model type""" name: Required[str] @@ -2837,8 +2837,8 @@ class NewFloatingMinimumCompositePrice(TypedDict, total=False): """ -class NewFloatingMinimumCompositePriceMinimumConfig(TypedDict, total=False): - """Configuration for minimum pricing""" +class NewFloatingMinimumCompositePriceMinimumCompositeConfig(TypedDict, total=False): + """Configuration for minimum_composite pricing""" minimum_amount: Required[str] """The minimum amount to apply""" diff --git a/src/orb/types/shared/new_floating_minimum_composite_price.py b/src/orb/types/shared/new_floating_minimum_composite_price.py index 6f11f920..e372400d 100644 --- a/src/orb/types/shared/new_floating_minimum_composite_price.py +++ b/src/orb/types/shared/new_floating_minimum_composite_price.py @@ -12,11 +12,11 @@ from .new_billing_cycle_configuration import NewBillingCycleConfiguration from .new_dimensional_price_configuration import NewDimensionalPriceConfiguration -__all__ = ["NewFloatingMinimumCompositePrice", "MinimumConfig", "ConversionRateConfig"] +__all__ = ["NewFloatingMinimumCompositePrice", "MinimumCompositeConfig", "ConversionRateConfig"] -class MinimumConfig(BaseModel): - """Configuration for minimum pricing""" +class MinimumCompositeConfig(BaseModel): + """Configuration for minimum_composite pricing""" minimum_amount: str """The minimum amount to apply""" @@ -40,10 +40,10 @@ class NewFloatingMinimumCompositePrice(BaseModel): item_id: str """The id of the item the price will be associated with.""" - minimum_config: MinimumConfig - """Configuration for minimum pricing""" + minimum_composite_config: MinimumCompositeConfig + """Configuration for minimum_composite pricing""" - price_model_type: Literal["minimum"] = FieldInfo(alias="model_type") + price_model_type: Literal["minimum_composite"] = FieldInfo(alias="model_type") """The pricing model type""" name: str diff --git a/src/orb/types/shared/new_plan_minimum_composite_price.py b/src/orb/types/shared/new_plan_minimum_composite_price.py index 3723a761..1db75ffa 100644 --- a/src/orb/types/shared/new_plan_minimum_composite_price.py +++ b/src/orb/types/shared/new_plan_minimum_composite_price.py @@ -12,11 +12,11 @@ from .new_billing_cycle_configuration import NewBillingCycleConfiguration from .new_dimensional_price_configuration import NewDimensionalPriceConfiguration -__all__ = ["NewPlanMinimumCompositePrice", "MinimumConfig", "ConversionRateConfig"] +__all__ = ["NewPlanMinimumCompositePrice", "MinimumCompositeConfig", "ConversionRateConfig"] -class MinimumConfig(BaseModel): - """Configuration for minimum pricing""" +class MinimumCompositeConfig(BaseModel): + """Configuration for minimum_composite pricing""" minimum_amount: str """The minimum amount to apply""" @@ -37,10 +37,10 @@ class NewPlanMinimumCompositePrice(BaseModel): item_id: str """The id of the item the price will be associated with.""" - minimum_config: MinimumConfig - """Configuration for minimum pricing""" + minimum_composite_config: MinimumCompositeConfig + """Configuration for minimum_composite pricing""" - price_model_type: Literal["minimum"] = FieldInfo(alias="model_type") + price_model_type: Literal["minimum_composite"] = FieldInfo(alias="model_type") """The pricing model type""" name: str diff --git a/src/orb/types/shared/price.py b/src/orb/types/shared/price.py index d6d6d819..b5163dc6 100644 --- a/src/orb/types/shared/price.py +++ b/src/orb/types/shared/price.py @@ -154,7 +154,7 @@ "MinimumCompositePrice", "MinimumCompositePriceCompositePriceFilter", "MinimumCompositePriceConversionRateConfig", - "MinimumCompositePriceMinimumConfig", + "MinimumCompositePriceMinimumCompositeConfig", "PercentCompositePrice", "PercentCompositePriceCompositePriceFilter", "PercentCompositePriceConversionRateConfig", @@ -3175,8 +3175,8 @@ class MinimumCompositePriceCompositePriceFilter(BaseModel): ] -class MinimumCompositePriceMinimumConfig(BaseModel): - """Configuration for minimum pricing""" +class MinimumCompositePriceMinimumCompositeConfig(BaseModel): + """Configuration for minimum_composite pricing""" minimum_amount: str """The minimum amount to apply""" @@ -3238,10 +3238,10 @@ class MinimumCompositePrice(BaseModel): minimum_amount: Optional[str] = None - minimum_config: MinimumCompositePriceMinimumConfig - """Configuration for minimum pricing""" + minimum_composite_config: MinimumCompositePriceMinimumCompositeConfig + """Configuration for minimum_composite pricing""" - price_model_type: Literal["minimum"] = FieldInfo(alias="model_type") + price_model_type: Literal["minimum_composite"] = FieldInfo(alias="model_type") """The pricing model type""" name: str diff --git a/src/orb/types/shared_params/new_floating_minimum_composite_price.py b/src/orb/types/shared_params/new_floating_minimum_composite_price.py index 6f7b54dc..e139e1e0 100644 --- a/src/orb/types/shared_params/new_floating_minimum_composite_price.py +++ b/src/orb/types/shared_params/new_floating_minimum_composite_price.py @@ -10,11 +10,11 @@ from .new_billing_cycle_configuration import NewBillingCycleConfiguration from .new_dimensional_price_configuration import NewDimensionalPriceConfiguration -__all__ = ["NewFloatingMinimumCompositePrice", "MinimumConfig", "ConversionRateConfig"] +__all__ = ["NewFloatingMinimumCompositePrice", "MinimumCompositeConfig", "ConversionRateConfig"] -class MinimumConfig(TypedDict, total=False): - """Configuration for minimum pricing""" +class MinimumCompositeConfig(TypedDict, total=False): + """Configuration for minimum_composite pricing""" minimum_amount: Required[str] """The minimum amount to apply""" @@ -36,10 +36,10 @@ class NewFloatingMinimumCompositePrice(TypedDict, total=False): item_id: Required[str] """The id of the item the price will be associated with.""" - minimum_config: Required[MinimumConfig] - """Configuration for minimum pricing""" + minimum_composite_config: Required[MinimumCompositeConfig] + """Configuration for minimum_composite pricing""" - model_type: Required[Literal["minimum"]] + model_type: Required[Literal["minimum_composite"]] """The pricing model type""" name: Required[str] diff --git a/src/orb/types/shared_params/new_plan_minimum_composite_price.py b/src/orb/types/shared_params/new_plan_minimum_composite_price.py index 66f2fec6..d23cdfc1 100644 --- a/src/orb/types/shared_params/new_plan_minimum_composite_price.py +++ b/src/orb/types/shared_params/new_plan_minimum_composite_price.py @@ -10,11 +10,11 @@ from .new_billing_cycle_configuration import NewBillingCycleConfiguration from .new_dimensional_price_configuration import NewDimensionalPriceConfiguration -__all__ = ["NewPlanMinimumCompositePrice", "MinimumConfig", "ConversionRateConfig"] +__all__ = ["NewPlanMinimumCompositePrice", "MinimumCompositeConfig", "ConversionRateConfig"] -class MinimumConfig(TypedDict, total=False): - """Configuration for minimum pricing""" +class MinimumCompositeConfig(TypedDict, total=False): + """Configuration for minimum_composite pricing""" minimum_amount: Required[str] """The minimum amount to apply""" @@ -33,10 +33,10 @@ class NewPlanMinimumCompositePrice(TypedDict, total=False): item_id: Required[str] """The id of the item the price will be associated with.""" - minimum_config: Required[MinimumConfig] - """Configuration for minimum pricing""" + minimum_composite_config: Required[MinimumCompositeConfig] + """Configuration for minimum_composite pricing""" - model_type: Required[Literal["minimum"]] + model_type: Required[Literal["minimum_composite"]] """The pricing model type""" name: Required[str] diff --git a/tests/api_resources/test_prices.py b/tests/api_resources/test_prices.py index c0c6c6e5..09a010d8 100644 --- a/tests/api_resources/test_prices.py +++ b/tests/api_resources/test_prices.py @@ -3242,8 +3242,8 @@ def test_method_create_overload_29(self, client: Orb) -> None: cadence="annual", currency="currency", item_id="item_id", - minimum_config={"minimum_amount": "minimum_amount"}, - model_type="minimum", + minimum_composite_config={"minimum_amount": "minimum_amount"}, + model_type="minimum_composite", name="Annual fee", ) assert_matches_type(Price, price, path=["response"]) @@ -3254,11 +3254,11 @@ def test_method_create_with_all_params_overload_29(self, client: Orb) -> None: cadence="annual", currency="currency", item_id="item_id", - minimum_config={ + minimum_composite_config={ "minimum_amount": "minimum_amount", "prorated": True, }, - model_type="minimum", + model_type="minimum_composite", name="Annual fee", billable_metric_id="billable_metric_id", billed_in_advance=True, @@ -3293,8 +3293,8 @@ def test_raw_response_create_overload_29(self, client: Orb) -> None: cadence="annual", currency="currency", item_id="item_id", - minimum_config={"minimum_amount": "minimum_amount"}, - model_type="minimum", + minimum_composite_config={"minimum_amount": "minimum_amount"}, + model_type="minimum_composite", name="Annual fee", ) @@ -3309,8 +3309,8 @@ def test_streaming_response_create_overload_29(self, client: Orb) -> None: cadence="annual", currency="currency", item_id="item_id", - minimum_config={"minimum_amount": "minimum_amount"}, - model_type="minimum", + minimum_composite_config={"minimum_amount": "minimum_amount"}, + model_type="minimum_composite", name="Annual fee", ) as response: assert not response.is_closed @@ -7072,8 +7072,8 @@ async def test_method_create_overload_29(self, async_client: AsyncOrb) -> None: cadence="annual", currency="currency", item_id="item_id", - minimum_config={"minimum_amount": "minimum_amount"}, - model_type="minimum", + minimum_composite_config={"minimum_amount": "minimum_amount"}, + model_type="minimum_composite", name="Annual fee", ) assert_matches_type(Price, price, path=["response"]) @@ -7084,11 +7084,11 @@ async def test_method_create_with_all_params_overload_29(self, async_client: Asy cadence="annual", currency="currency", item_id="item_id", - minimum_config={ + minimum_composite_config={ "minimum_amount": "minimum_amount", "prorated": True, }, - model_type="minimum", + model_type="minimum_composite", name="Annual fee", billable_metric_id="billable_metric_id", billed_in_advance=True, @@ -7123,8 +7123,8 @@ async def test_raw_response_create_overload_29(self, async_client: AsyncOrb) -> cadence="annual", currency="currency", item_id="item_id", - minimum_config={"minimum_amount": "minimum_amount"}, - model_type="minimum", + minimum_composite_config={"minimum_amount": "minimum_amount"}, + model_type="minimum_composite", name="Annual fee", ) @@ -7139,8 +7139,8 @@ async def test_streaming_response_create_overload_29(self, async_client: AsyncOr cadence="annual", currency="currency", item_id="item_id", - minimum_config={"minimum_amount": "minimum_amount"}, - model_type="minimum", + minimum_composite_config={"minimum_amount": "minimum_amount"}, + model_type="minimum_composite", name="Annual fee", ) as response: assert not response.is_closed From c31a2b58cb154fdf62ff57146c19ef6183a634d9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 02:24:28 +0000 Subject: [PATCH 3/3] release: 4.49.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ pyproject.toml | 2 +- src/orb/_version.py | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7553ced8..d351f029 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.48.0" + ".": "4.49.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index af9f7bf7..0f0dce00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 4.49.0 (2026-01-21) + +Full Changelog: [v4.48.0...v4.49.0](https://github.com/orbcorp/orb-python/compare/v4.48.0...v4.49.0) + +### Features + +* **api:** api update ([5b5c348](https://github.com/orbcorp/orb-python/commit/5b5c348e954f79937614e47481e3a91dc8692751)) + + +### Chores + +* **internal:** codegen related update ([9f74fa5](https://github.com/orbcorp/orb-python/commit/9f74fa57c3d4bfebf2d5b4765d100f0e663b4ed6)) + ## 4.48.0 (2026-01-08) Full Changelog: [v4.47.0...v4.48.0](https://github.com/orbcorp/orb-python/compare/v4.47.0...v4.48.0) diff --git a/pyproject.toml b/pyproject.toml index e783c6e6..40b3a98a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "orb-billing" -version = "4.48.0" +version = "4.49.0" description = "The official Python library for the orb API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/orb/_version.py b/src/orb/_version.py index 1a499960..800d6acc 100644 --- a/src/orb/_version.py +++ b/src/orb/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "orb" -__version__ = "4.48.0" # x-release-please-version +__version__ = "4.49.0" # x-release-please-version