From 0a46f0a17f78a2a21aae637af66fb6b5671c12c5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 07:47:16 +0000 Subject: [PATCH 01/14] feat(client): add support for binary request streaming --- src/blooio/_base_client.py | 145 +++++++++++++++++++++++++--- src/blooio/_models.py | 17 +++- src/blooio/_types.py | 9 ++ tests/test_client.py | 187 ++++++++++++++++++++++++++++++++++++- 4 files changed, 344 insertions(+), 14 deletions(-) diff --git a/src/blooio/_base_client.py b/src/blooio/_base_client.py index 3293983..7875c31 100644 --- a/src/blooio/_base_client.py +++ b/src/blooio/_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, @@ -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 @@ -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 @@ -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, @@ -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], @@ -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, @@ -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)) @@ -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) @@ -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) @@ -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( @@ -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, @@ -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], @@ -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, @@ -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) @@ -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) @@ -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) @@ -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( diff --git a/src/blooio/_models.py b/src/blooio/_models.py index ca9500b..29070e0 100644 --- a/src/blooio/_models.py +++ b/src/blooio/_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/blooio/_types.py b/src/blooio/_types.py index b974bad..059d3f5 100644 --- a/src/blooio/_types.py +++ b/src/blooio/_types.py @@ -13,9 +13,11 @@ Mapping, TypeVar, Callable, + Iterable, Iterator, Optional, Sequence, + AsyncIterable, ) from typing_extensions import ( Set, @@ -56,6 +58,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/tests/test_client.py b/tests/test_client.py index f9ee51f..d70b3bf 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: Blooio | AsyncBlooio) -> int: transport = client._client._transport assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) @@ -500,6 +553,70 @@ def test_multipart_repeating_array(self, client: Blooio) -> None: b"", ] + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload(self, respx_mock: MockRouter, client: Blooio) -> 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 Blooio( + 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: Blooio) -> 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: Blooio) -> None: class Model1(BaseModel): @@ -1319,6 +1436,72 @@ def test_multipart_repeating_array(self, async_client: AsyncBlooio) -> None: b"", ] + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncBlooio) -> 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 AsyncBlooio( + 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: AsyncBlooio + ) -> 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: AsyncBlooio) -> None: class Model1(BaseModel): From 3f2ef546ebf7281cfca3d17255de0cdc288e1013 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 06:04:38 +0000 Subject: [PATCH 02/14] chore(internal): update `actions/checkout` version --- .github/workflows/ci.yml | 6 +++--- .github/workflows/publish-pypi.yml | 2 +- .github/workflows/release-doctor.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8f9721..4710f3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/blooio-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/blooio-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/blooio-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 d75aae2..0e4dc76 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 fb0881c..8585f44 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'Blooio/blooio-python-sdk' && (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: | From 5e006c28777fae0e393b573d148432644a5d181b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:31:33 +0000 Subject: [PATCH 03/14] chore(ci): upgrade `actions/github-script` --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4710f3a..ef2f6af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: - name: Get GitHub OIDC Token if: github.repository == 'stainless-sdks/blooio-python' id: github-oidc - uses: actions/github-script@v6 + uses: actions/github-script@v8 with: script: core.setOutput('github_token', await core.getIDToken()); From c02699e0bb5a7411efe9ba37898f74bce58fc4a8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 05:19:59 +0000 Subject: [PATCH 04/14] feat(client): add custom JSON encoder for extended type support --- src/blooio/_base_client.py | 7 +- src/blooio/_compat.py | 6 +- src/blooio/_utils/_json.py | 35 ++++++++++ tests/test_utils/test_json.py | 126 ++++++++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 src/blooio/_utils/_json.py create mode 100644 tests/test_utils/test_json.py diff --git a/src/blooio/_base_client.py b/src/blooio/_base_client.py index 7875c31..36c55e9 100644 --- a/src/blooio/_base_client.py +++ b/src/blooio/_base_client.py @@ -86,6 +86,7 @@ APIConnectionError, APIResponseValidationError, ) +from ._utils._json import openapi_dumps log: logging.Logger = logging.getLogger(__name__) @@ -554,8 +555,10 @@ def _build_request( 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 + elif not files: + # Don't set content when JSON is sent as multipart/form-data, + # since httpx's content param overrides other body arguments + kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None kwargs["files"] = files else: headers.pop("Content-Type", None) diff --git a/src/blooio/_compat.py b/src/blooio/_compat.py index bdef67f..786ff42 100644 --- a/src/blooio/_compat.py +++ b/src/blooio/_compat.py @@ -139,6 +139,7 @@ def model_dump( exclude_defaults: bool = False, warnings: bool = True, mode: Literal["json", "python"] = "python", + by_alias: bool | None = None, ) -> dict[str, Any]: if (not PYDANTIC_V1) or hasattr(model, "model_dump"): return model.model_dump( @@ -148,13 +149,12 @@ def model_dump( exclude_defaults=exclude_defaults, # warnings are not supported in Pydantic v1 warnings=True if PYDANTIC_V1 else warnings, + by_alias=by_alias, ) return cast( "dict[str, Any]", model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - exclude=exclude, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, + exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias) ), ) diff --git a/src/blooio/_utils/_json.py b/src/blooio/_utils/_json.py new file mode 100644 index 0000000..6058421 --- /dev/null +++ b/src/blooio/_utils/_json.py @@ -0,0 +1,35 @@ +import json +from typing import Any +from datetime import datetime +from typing_extensions import override + +import pydantic + +from .._compat import model_dump + + +def openapi_dumps(obj: Any) -> bytes: + """ + Serialize an object to UTF-8 encoded JSON bytes. + + Extends the standard json.dumps with support for additional types + commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc. + """ + return json.dumps( + obj, + cls=_CustomEncoder, + # Uses the same defaults as httpx's JSON serialization + ensure_ascii=False, + separators=(",", ":"), + allow_nan=False, + ).encode() + + +class _CustomEncoder(json.JSONEncoder): + @override + def default(self, o: Any) -> Any: + if isinstance(o, datetime): + return o.isoformat() + if isinstance(o, pydantic.BaseModel): + return model_dump(o, exclude_unset=True, mode="json", by_alias=True) + return super().default(o) diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py new file mode 100644 index 0000000..9f63846 --- /dev/null +++ b/tests/test_utils/test_json.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import datetime +from typing import Union + +import pydantic + +from blooio import _compat +from blooio._utils._json import openapi_dumps + + +class TestOpenapiDumps: + def test_basic(self) -> None: + data = {"key": "value", "number": 42} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"key":"value","number":42}' + + def test_datetime_serialization(self) -> None: + dt = datetime.datetime(2023, 1, 1, 12, 0, 0) + data = {"datetime": dt} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}' + + def test_pydantic_model_serialization(self) -> None: + class User(pydantic.BaseModel): + first_name: str + last_name: str + age: int + + model_instance = User(first_name="John", last_name="Kramer", age=83) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}' + + def test_pydantic_model_with_default_values(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + score: int = 0 + + model_instance = User(name="Alice") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Alice"}}' + + def test_pydantic_model_with_default_values_overridden(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + + model_instance = User(name="Bob", role="admin", active=False) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}' + + def test_pydantic_model_with_alias(self) -> None: + class User(pydantic.BaseModel): + first_name: str = pydantic.Field(alias="firstName") + last_name: str = pydantic.Field(alias="lastName") + + model_instance = User(firstName="John", lastName="Doe") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}' + + def test_pydantic_model_with_alias_and_default(self) -> None: + class User(pydantic.BaseModel): + user_name: str = pydantic.Field(alias="userName") + user_role: str = pydantic.Field(default="member", alias="userRole") + is_active: bool = pydantic.Field(default=True, alias="isActive") + + model_instance = User(userName="charlie") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"charlie"}}' + + model_with_overrides = User(userName="diana", userRole="admin", isActive=False) + data = {"model": model_with_overrides} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}' + + def test_pydantic_model_with_nested_models_and_defaults(self) -> None: + class Address(pydantic.BaseModel): + street: str + city: str = "Unknown" + + class User(pydantic.BaseModel): + name: str + address: Address + verified: bool = False + + if _compat.PYDANTIC_V1: + # to handle forward references in Pydantic v1 + User.update_forward_refs(**locals()) # type: ignore[reportDeprecated] + + address = Address(street="123 Main St") + user = User(name="Diana", address=address) + data = {"user": user} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}' + + address_with_city = Address(street="456 Oak Ave", city="Boston") + user_verified = User(name="Eve", address=address_with_city, verified=True) + data = {"user": user_verified} + json_bytes = openapi_dumps(data) + assert ( + json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}' + ) + + def test_pydantic_model_with_optional_fields(self) -> None: + class User(pydantic.BaseModel): + name: str + email: Union[str, None] + phone: Union[str, None] + + model_with_none = User(name="Eve", email=None, phone=None) + data = {"model": model_with_none} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}' + + model_with_values = User(name="Frank", email="frank@example.com", phone=None) + data = {"model": model_with_values} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}' From e6b7ec8b91ed416baf78c830519f226a4a361a98 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 05:30:24 +0000 Subject: [PATCH 05/14] chore(internal): bump dependencies --- requirements-dev.lock | 20 ++++++++++---------- requirements.lock | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index 8a92795..b11e210 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -12,14 +12,14 @@ -e file:. aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.13.2 +aiohttp==3.13.3 # via blooio # via httpx-aiohttp aiosignal==1.4.0 # via aiohttp annotated-types==0.7.0 # via pydantic -anyio==4.12.0 +anyio==4.12.1 # via blooio # via httpx argcomplete==3.6.3 @@ -31,7 +31,7 @@ attrs==25.4.0 # via nox backports-asyncio-runner==1.2.0 # via pytest-asyncio -certifi==2025.11.12 +certifi==2026.1.4 # via httpcore # via httpx colorlog==6.10.1 @@ -61,7 +61,7 @@ httpx==0.28.1 # via blooio # via httpx-aiohttp # via respx -httpx-aiohttp==0.1.9 +httpx-aiohttp==0.1.12 # via blooio humanize==4.13.0 # via nox @@ -69,7 +69,7 @@ idna==3.11 # via anyio # via httpx # via yarl -importlib-metadata==8.7.0 +importlib-metadata==8.7.1 iniconfig==2.1.0 # via pytest markdown-it-py==3.0.0 @@ -82,14 +82,14 @@ multidict==6.7.0 mypy==1.17.0 mypy-extensions==1.1.0 # via mypy -nodeenv==1.9.1 +nodeenv==1.10.0 # via pyright nox==2025.11.12 packaging==25.0 # via dependency-groups # via nox # via pytest -pathspec==0.12.1 +pathspec==1.0.3 # via mypy platformdirs==4.4.0 # via virtualenv @@ -115,13 +115,13 @@ python-dateutil==2.9.0.post0 # via time-machine respx==0.22.0 rich==14.2.0 -ruff==0.14.7 +ruff==0.14.13 six==1.17.0 # via python-dateutil sniffio==1.3.1 # via blooio time-machine==2.19.0 -tomli==2.3.0 +tomli==2.4.0 # via dependency-groups # via mypy # via nox @@ -141,7 +141,7 @@ typing-extensions==4.15.0 # via virtualenv typing-inspection==0.4.2 # via pydantic -virtualenv==20.35.4 +virtualenv==20.36.1 # via nox yarl==1.22.0 # via aiohttp diff --git a/requirements.lock b/requirements.lock index 4360b3c..8bb50a2 100644 --- a/requirements.lock +++ b/requirements.lock @@ -12,21 +12,21 @@ -e file:. aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.13.2 +aiohttp==3.13.3 # via blooio # via httpx-aiohttp aiosignal==1.4.0 # via aiohttp annotated-types==0.7.0 # via pydantic -anyio==4.12.0 +anyio==4.12.1 # via blooio # via httpx async-timeout==5.0.1 # via aiohttp attrs==25.4.0 # via aiohttp -certifi==2025.11.12 +certifi==2026.1.4 # via httpcore # via httpx distro==1.9.0 @@ -43,7 +43,7 @@ httpcore==1.0.9 httpx==0.28.1 # via blooio # via httpx-aiohttp -httpx-aiohttp==0.1.9 +httpx-aiohttp==0.1.12 # via blooio idna==3.11 # via anyio From 34988f7b47fd8e01bbb8a0ba61abf52d66a9598a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 07:14:27 +0000 Subject: [PATCH 06/14] chore(internal): fix lint error on Python 3.14 --- src/blooio/_utils/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blooio/_utils/_compat.py b/src/blooio/_utils/_compat.py index dd70323..2c70b29 100644 --- a/src/blooio/_utils/_compat.py +++ b/src/blooio/_utils/_compat.py @@ -26,7 +26,7 @@ def is_union(tp: Optional[Type[Any]]) -> bool: else: import types - return tp is Union or tp is types.UnionType + return tp is Union or tp is types.UnionType # type: ignore[comparison-overlap] def is_typeddict(tp: Type[Any]) -> bool: From 3d045e8d5b9cc2afca1d6c85ded4b0e531c28228 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 04:52:21 +0000 Subject: [PATCH 07/14] chore: format all `api.md` files --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c399c78..0f85d2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ format = { chain = [ # run formatting again to fix any inconsistencies when imports are stripped "format:ruff", ]} -"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md" +"format:docs" = "bash -c 'python scripts/utils/ruffen-docs.py README.md $(find . -type f -name api.md)'" "format:ruff" = "ruff format" "lint" = { chain = [ From 6d1b141349ac7e1eefe21ef50dc3b5d8de6707d6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 06:38:58 +0000 Subject: [PATCH 08/14] chore(internal): remove mock server code --- scripts/mock | 41 ----------------------------------------- scripts/test | 46 ---------------------------------------------- 2 files changed, 87 deletions(-) delete mode 100755 scripts/mock diff --git a/scripts/mock b/scripts/mock deleted file mode 100755 index 0b28f6e..0000000 --- a/scripts/mock +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -if [[ -n "$1" && "$1" != '--'* ]]; then - URL="$1" - shift -else - URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" -fi - -# Check if the URL is empty -if [ -z "$URL" ]; then - echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" - exit 1 -fi - -echo "==> Starting mock server with URL ${URL}" - -# Run prism mock on the given spec -if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & - - # Wait for server to come online - echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do - echo -n "." - sleep 0.1 - done - - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - - echo -else - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" -fi diff --git a/scripts/test b/scripts/test index dbeda2d..39729d0 100755 --- a/scripts/test +++ b/scripts/test @@ -4,53 +4,7 @@ set -e cd "$(dirname "$0")/.." -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 -} - -kill_server_on_port() { - pids=$(lsof -t -i tcp:"$1" || echo "") - if [ "$pids" != "" ]; then - kill "$pids" - echo "Stopped $pids." - fi -} - -function is_overriding_api_base_url() { - [ -n "$TEST_API_BASE_URL" ] -} - -if ! is_overriding_api_base_url && ! prism_is_running ; then - # When we exit this script, make sure to kill the background mock server process - trap 'kill_server_on_port 4010' EXIT - - # Start the dev server - ./scripts/mock --daemon -fi - -if is_overriding_api_base_url ; then - echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" - echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" - echo -e "running against your OpenAPI spec." - echo - echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" - echo - echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" - echo - - exit 1 -else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" - echo -fi export DEFER_PYDANTIC_BUILD=false From b1c88e2d8f1aac114dac3b288d540cae6f35d70c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 06:39:39 +0000 Subject: [PATCH 09/14] chore: update mock server docs --- CONTRIBUTING.md | 7 --- tests/api_resources/config/test_webhook.py | 24 ++++---- tests/api_resources/test_batches.py | 60 ++++++++++---------- tests/api_resources/test_contacts.py | 16 +++--- tests/api_resources/test_me.py | 12 ++-- tests/api_resources/test_messages.py | 64 +++++++++++----------- 6 files changed, 88 insertions(+), 95 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 59291dc..c06e935 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,13 +85,6 @@ $ pip install ./path-to-wheel-file.whl ## Running tests -Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. - -```sh -# you will need npm installed -$ npx prism mock path/to/your/openapi.yml -``` - ```sh $ ./scripts/test ``` diff --git a/tests/api_resources/config/test_webhook.py b/tests/api_resources/config/test_webhook.py index 64632f9..f1f7520 100644 --- a/tests/api_resources/config/test_webhook.py +++ b/tests/api_resources/config/test_webhook.py @@ -17,13 +17,13 @@ class TestWebhook: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve(self, client: Blooio) -> None: webhook = client.config.webhook.retrieve() assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Blooio) -> None: response = client.config.webhook.with_raw_response.retrieve() @@ -33,7 +33,7 @@ def test_raw_response_retrieve(self, client: Blooio) -> None: webhook = response.parse() assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Blooio) -> None: with client.config.webhook.with_streaming_response.retrieve() as response: @@ -45,7 +45,7 @@ def test_streaming_response_retrieve(self, client: Blooio) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_update(self, client: Blooio) -> None: webhook = client.config.webhook.update( @@ -53,7 +53,7 @@ def test_method_update(self, client: Blooio) -> None: ) assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_update(self, client: Blooio) -> None: response = client.config.webhook.with_raw_response.update( @@ -65,7 +65,7 @@ def test_raw_response_update(self, client: Blooio) -> None: webhook = response.parse() assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_update(self, client: Blooio) -> None: with client.config.webhook.with_streaming_response.update( @@ -85,13 +85,13 @@ class TestAsyncWebhook: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncBlooio) -> None: webhook = await async_client.config.webhook.retrieve() assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBlooio) -> None: response = await async_client.config.webhook.with_raw_response.retrieve() @@ -101,7 +101,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBlooio) -> None: webhook = await response.parse() assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBlooio) -> None: async with async_client.config.webhook.with_streaming_response.retrieve() as response: @@ -113,7 +113,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBlooio) -> N assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_update(self, async_client: AsyncBlooio) -> None: webhook = await async_client.config.webhook.update( @@ -121,7 +121,7 @@ async def test_method_update(self, async_client: AsyncBlooio) -> None: ) assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_update(self, async_client: AsyncBlooio) -> None: response = await async_client.config.webhook.with_raw_response.update( @@ -133,7 +133,7 @@ async def test_raw_response_update(self, async_client: AsyncBlooio) -> None: webhook = await response.parse() assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_update(self, async_client: AsyncBlooio) -> None: async with async_client.config.webhook.with_streaming_response.update( diff --git a/tests/api_resources/test_batches.py b/tests/api_resources/test_batches.py index 78c8c35..3a03921 100644 --- a/tests/api_resources/test_batches.py +++ b/tests/api_resources/test_batches.py @@ -15,13 +15,13 @@ class TestBatches: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_create(self, client: Blooio) -> None: batch = client.batches.create() assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_create(self, client: Blooio) -> None: response = client.batches.with_raw_response.create() @@ -31,7 +31,7 @@ def test_raw_response_create(self, client: Blooio) -> None: batch = response.parse() assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_create(self, client: Blooio) -> None: with client.batches.with_streaming_response.create() as response: @@ -43,7 +43,7 @@ def test_streaming_response_create(self, client: Blooio) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve(self, client: Blooio) -> None: batch = client.batches.retrieve( @@ -51,7 +51,7 @@ def test_method_retrieve(self, client: Blooio) -> None: ) assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Blooio) -> None: response = client.batches.with_raw_response.retrieve( @@ -63,7 +63,7 @@ def test_raw_response_retrieve(self, client: Blooio) -> None: batch = response.parse() assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Blooio) -> None: with client.batches.with_streaming_response.retrieve( @@ -77,7 +77,7 @@ def test_streaming_response_retrieve(self, client: Blooio) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_retrieve(self, client: Blooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): @@ -85,7 +85,7 @@ def test_path_params_retrieve(self, client: Blooio) -> None: "", ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_messages(self, client: Blooio) -> None: batch = client.batches.list_messages( @@ -93,7 +93,7 @@ def test_method_list_messages(self, client: Blooio) -> None: ) assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list_messages(self, client: Blooio) -> None: response = client.batches.with_raw_response.list_messages( @@ -105,7 +105,7 @@ def test_raw_response_list_messages(self, client: Blooio) -> None: batch = response.parse() assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list_messages(self, client: Blooio) -> None: with client.batches.with_streaming_response.list_messages( @@ -119,7 +119,7 @@ def test_streaming_response_list_messages(self, client: Blooio) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_list_messages(self, client: Blooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): @@ -127,7 +127,7 @@ def test_path_params_list_messages(self, client: Blooio) -> None: "", ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve_status(self, client: Blooio) -> None: batch = client.batches.retrieve_status( @@ -135,7 +135,7 @@ def test_method_retrieve_status(self, client: Blooio) -> None: ) assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve_status(self, client: Blooio) -> None: response = client.batches.with_raw_response.retrieve_status( @@ -147,7 +147,7 @@ def test_raw_response_retrieve_status(self, client: Blooio) -> None: batch = response.parse() assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve_status(self, client: Blooio) -> None: with client.batches.with_streaming_response.retrieve_status( @@ -161,7 +161,7 @@ def test_streaming_response_retrieve_status(self, client: Blooio) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_retrieve_status(self, client: Blooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): @@ -175,13 +175,13 @@ class TestAsyncBatches: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_create(self, async_client: AsyncBlooio) -> None: batch = await async_client.batches.create() assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncBlooio) -> None: response = await async_client.batches.with_raw_response.create() @@ -191,7 +191,7 @@ async def test_raw_response_create(self, async_client: AsyncBlooio) -> None: batch = await response.parse() assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_create(self, async_client: AsyncBlooio) -> None: async with async_client.batches.with_streaming_response.create() as response: @@ -203,7 +203,7 @@ async def test_streaming_response_create(self, async_client: AsyncBlooio) -> Non assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncBlooio) -> None: batch = await async_client.batches.retrieve( @@ -211,7 +211,7 @@ async def test_method_retrieve(self, async_client: AsyncBlooio) -> None: ) assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBlooio) -> None: response = await async_client.batches.with_raw_response.retrieve( @@ -223,7 +223,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBlooio) -> None: batch = await response.parse() assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBlooio) -> None: async with async_client.batches.with_streaming_response.retrieve( @@ -237,7 +237,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBlooio) -> N assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncBlooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): @@ -245,7 +245,7 @@ async def test_path_params_retrieve(self, async_client: AsyncBlooio) -> None: "", ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_messages(self, async_client: AsyncBlooio) -> None: batch = await async_client.batches.list_messages( @@ -253,7 +253,7 @@ async def test_method_list_messages(self, async_client: AsyncBlooio) -> None: ) assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list_messages(self, async_client: AsyncBlooio) -> None: response = await async_client.batches.with_raw_response.list_messages( @@ -265,7 +265,7 @@ async def test_raw_response_list_messages(self, async_client: AsyncBlooio) -> No batch = await response.parse() assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_list_messages(self, async_client: AsyncBlooio) -> None: async with async_client.batches.with_streaming_response.list_messages( @@ -279,7 +279,7 @@ async def test_streaming_response_list_messages(self, async_client: AsyncBlooio) assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_list_messages(self, async_client: AsyncBlooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): @@ -287,7 +287,7 @@ async def test_path_params_list_messages(self, async_client: AsyncBlooio) -> Non "", ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve_status(self, async_client: AsyncBlooio) -> None: batch = await async_client.batches.retrieve_status( @@ -295,7 +295,7 @@ async def test_method_retrieve_status(self, async_client: AsyncBlooio) -> None: ) assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_retrieve_status(self, async_client: AsyncBlooio) -> None: response = await async_client.batches.with_raw_response.retrieve_status( @@ -307,7 +307,7 @@ async def test_raw_response_retrieve_status(self, async_client: AsyncBlooio) -> batch = await response.parse() assert batch is None - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_retrieve_status(self, async_client: AsyncBlooio) -> None: async with async_client.batches.with_streaming_response.retrieve_status( @@ -321,7 +321,7 @@ async def test_streaming_response_retrieve_status(self, async_client: AsyncBlooi assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_retrieve_status(self, async_client: AsyncBlooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): diff --git a/tests/api_resources/test_contacts.py b/tests/api_resources/test_contacts.py index 1a70b94..66dfd0d 100644 --- a/tests/api_resources/test_contacts.py +++ b/tests/api_resources/test_contacts.py @@ -17,7 +17,7 @@ class TestContacts: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_check_capabilities(self, client: Blooio) -> None: contact = client.contacts.check_capabilities( @@ -25,7 +25,7 @@ def test_method_check_capabilities(self, client: Blooio) -> None: ) assert_matches_type(ContactCheckCapabilitiesResponse, contact, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_check_capabilities(self, client: Blooio) -> None: response = client.contacts.with_raw_response.check_capabilities( @@ -37,7 +37,7 @@ def test_raw_response_check_capabilities(self, client: Blooio) -> None: contact = response.parse() assert_matches_type(ContactCheckCapabilitiesResponse, contact, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_check_capabilities(self, client: Blooio) -> None: with client.contacts.with_streaming_response.check_capabilities( @@ -51,7 +51,7 @@ def test_streaming_response_check_capabilities(self, client: Blooio) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_check_capabilities(self, client: Blooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `contact` but received ''"): @@ -65,7 +65,7 @@ class TestAsyncContacts: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_check_capabilities(self, async_client: AsyncBlooio) -> None: contact = await async_client.contacts.check_capabilities( @@ -73,7 +73,7 @@ async def test_method_check_capabilities(self, async_client: AsyncBlooio) -> Non ) assert_matches_type(ContactCheckCapabilitiesResponse, contact, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_check_capabilities(self, async_client: AsyncBlooio) -> None: response = await async_client.contacts.with_raw_response.check_capabilities( @@ -85,7 +85,7 @@ async def test_raw_response_check_capabilities(self, async_client: AsyncBlooio) contact = await response.parse() assert_matches_type(ContactCheckCapabilitiesResponse, contact, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_check_capabilities(self, async_client: AsyncBlooio) -> None: async with async_client.contacts.with_streaming_response.check_capabilities( @@ -99,7 +99,7 @@ async def test_streaming_response_check_capabilities(self, async_client: AsyncBl assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_check_capabilities(self, async_client: AsyncBlooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `contact` but received ''"): diff --git a/tests/api_resources/test_me.py b/tests/api_resources/test_me.py index 046d4d3..54edc2d 100644 --- a/tests/api_resources/test_me.py +++ b/tests/api_resources/test_me.py @@ -17,13 +17,13 @@ class TestMe: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve(self, client: Blooio) -> None: me = client.me.retrieve() assert_matches_type(MeRetrieveResponse, me, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Blooio) -> None: response = client.me.with_raw_response.retrieve() @@ -33,7 +33,7 @@ def test_raw_response_retrieve(self, client: Blooio) -> None: me = response.parse() assert_matches_type(MeRetrieveResponse, me, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Blooio) -> None: with client.me.with_streaming_response.retrieve() as response: @@ -51,13 +51,13 @@ class TestAsyncMe: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncBlooio) -> None: me = await async_client.me.retrieve() assert_matches_type(MeRetrieveResponse, me, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBlooio) -> None: response = await async_client.me.with_raw_response.retrieve() @@ -67,7 +67,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBlooio) -> None: me = await response.parse() assert_matches_type(MeRetrieveResponse, me, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBlooio) -> None: async with async_client.me.with_streaming_response.retrieve() as response: diff --git a/tests/api_resources/test_messages.py b/tests/api_resources/test_messages.py index bea0b8b..46e06ad 100644 --- a/tests/api_resources/test_messages.py +++ b/tests/api_resources/test_messages.py @@ -22,7 +22,7 @@ class TestMessages: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve(self, client: Blooio) -> None: message = client.messages.retrieve( @@ -30,7 +30,7 @@ def test_method_retrieve(self, client: Blooio) -> None: ) assert_matches_type(MessageRetrieveResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_retrieve(self, client: Blooio) -> None: response = client.messages.with_raw_response.retrieve( @@ -42,7 +42,7 @@ def test_raw_response_retrieve(self, client: Blooio) -> None: message = response.parse() assert_matches_type(MessageRetrieveResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: Blooio) -> None: with client.messages.with_streaming_response.retrieve( @@ -56,7 +56,7 @@ def test_streaming_response_retrieve(self, client: Blooio) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_retrieve(self, client: Blooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): @@ -64,7 +64,7 @@ def test_path_params_retrieve(self, client: Blooio) -> None: "", ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_cancel(self, client: Blooio) -> None: message = client.messages.cancel( @@ -72,7 +72,7 @@ def test_method_cancel(self, client: Blooio) -> None: ) assert_matches_type(MessageCancelResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_cancel(self, client: Blooio) -> None: response = client.messages.with_raw_response.cancel( @@ -84,7 +84,7 @@ def test_raw_response_cancel(self, client: Blooio) -> None: message = response.parse() assert_matches_type(MessageCancelResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_cancel(self, client: Blooio) -> None: with client.messages.with_streaming_response.cancel( @@ -98,7 +98,7 @@ def test_streaming_response_cancel(self, client: Blooio) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_cancel(self, client: Blooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): @@ -106,7 +106,7 @@ def test_path_params_cancel(self, client: Blooio) -> None: "", ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_get_status(self, client: Blooio) -> None: message = client.messages.get_status( @@ -114,7 +114,7 @@ def test_method_get_status(self, client: Blooio) -> None: ) assert_matches_type(MessageGetStatusResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_get_status(self, client: Blooio) -> None: response = client.messages.with_raw_response.get_status( @@ -126,7 +126,7 @@ def test_raw_response_get_status(self, client: Blooio) -> None: message = response.parse() assert_matches_type(MessageGetStatusResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_get_status(self, client: Blooio) -> None: with client.messages.with_streaming_response.get_status( @@ -140,7 +140,7 @@ def test_streaming_response_get_status(self, client: Blooio) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_get_status(self, client: Blooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): @@ -148,7 +148,7 @@ def test_path_params_get_status(self, client: Blooio) -> None: "", ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_send(self, client: Blooio) -> None: message = client.messages.send( @@ -156,7 +156,7 @@ def test_method_send(self, client: Blooio) -> None: ) assert_matches_type(MessageSendResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_send_with_all_params(self, client: Blooio) -> None: message = client.messages.send( @@ -168,7 +168,7 @@ def test_method_send_with_all_params(self, client: Blooio) -> None: ) assert_matches_type(MessageSendResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_send(self, client: Blooio) -> None: response = client.messages.with_raw_response.send( @@ -180,7 +180,7 @@ def test_raw_response_send(self, client: Blooio) -> None: message = response.parse() assert_matches_type(MessageSendResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_send(self, client: Blooio) -> None: with client.messages.with_streaming_response.send( @@ -200,7 +200,7 @@ class TestAsyncMessages: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncBlooio) -> None: message = await async_client.messages.retrieve( @@ -208,7 +208,7 @@ async def test_method_retrieve(self, async_client: AsyncBlooio) -> None: ) assert_matches_type(MessageRetrieveResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBlooio) -> None: response = await async_client.messages.with_raw_response.retrieve( @@ -220,7 +220,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBlooio) -> None: message = await response.parse() assert_matches_type(MessageRetrieveResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBlooio) -> None: async with async_client.messages.with_streaming_response.retrieve( @@ -234,7 +234,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBlooio) -> N assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncBlooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): @@ -242,7 +242,7 @@ async def test_path_params_retrieve(self, async_client: AsyncBlooio) -> None: "", ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_cancel(self, async_client: AsyncBlooio) -> None: message = await async_client.messages.cancel( @@ -250,7 +250,7 @@ async def test_method_cancel(self, async_client: AsyncBlooio) -> None: ) assert_matches_type(MessageCancelResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_cancel(self, async_client: AsyncBlooio) -> None: response = await async_client.messages.with_raw_response.cancel( @@ -262,7 +262,7 @@ async def test_raw_response_cancel(self, async_client: AsyncBlooio) -> None: message = await response.parse() assert_matches_type(MessageCancelResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_cancel(self, async_client: AsyncBlooio) -> None: async with async_client.messages.with_streaming_response.cancel( @@ -276,7 +276,7 @@ async def test_streaming_response_cancel(self, async_client: AsyncBlooio) -> Non assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_cancel(self, async_client: AsyncBlooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): @@ -284,7 +284,7 @@ async def test_path_params_cancel(self, async_client: AsyncBlooio) -> None: "", ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_get_status(self, async_client: AsyncBlooio) -> None: message = await async_client.messages.get_status( @@ -292,7 +292,7 @@ async def test_method_get_status(self, async_client: AsyncBlooio) -> None: ) assert_matches_type(MessageGetStatusResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_get_status(self, async_client: AsyncBlooio) -> None: response = await async_client.messages.with_raw_response.get_status( @@ -304,7 +304,7 @@ async def test_raw_response_get_status(self, async_client: AsyncBlooio) -> None: message = await response.parse() assert_matches_type(MessageGetStatusResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_get_status(self, async_client: AsyncBlooio) -> None: async with async_client.messages.with_streaming_response.get_status( @@ -318,7 +318,7 @@ async def test_streaming_response_get_status(self, async_client: AsyncBlooio) -> assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_get_status(self, async_client: AsyncBlooio) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): @@ -326,7 +326,7 @@ async def test_path_params_get_status(self, async_client: AsyncBlooio) -> None: "", ) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_send(self, async_client: AsyncBlooio) -> None: message = await async_client.messages.send( @@ -334,7 +334,7 @@ async def test_method_send(self, async_client: AsyncBlooio) -> None: ) assert_matches_type(MessageSendResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_send_with_all_params(self, async_client: AsyncBlooio) -> None: message = await async_client.messages.send( @@ -346,7 +346,7 @@ async def test_method_send_with_all_params(self, async_client: AsyncBlooio) -> N ) assert_matches_type(MessageSendResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_send(self, async_client: AsyncBlooio) -> None: response = await async_client.messages.with_raw_response.send( @@ -358,7 +358,7 @@ async def test_raw_response_send(self, async_client: AsyncBlooio) -> None: message = await response.parse() assert_matches_type(MessageSendResponse, message, path=["response"]) - @pytest.mark.skip(reason="Prism tests are disabled") + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_send(self, async_client: AsyncBlooio) -> None: async with async_client.messages.with_streaming_response.send( From 3d004d8b3892f4f8a212ef00745919dc59a2e099 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 06:50:55 +0000 Subject: [PATCH 10/14] chore(internal): add request options to SSE classes --- src/blooio/_response.py | 3 +++ src/blooio/_streaming.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/blooio/_response.py b/src/blooio/_response.py index 9be93fd..19ec5d4 100644 --- a/src/blooio/_response.py +++ b/src/blooio/_response.py @@ -152,6 +152,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -162,6 +163,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -175,6 +177,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) diff --git a/src/blooio/_streaming.py b/src/blooio/_streaming.py index 3536bda..2bb5675 100644 --- a/src/blooio/_streaming.py +++ b/src/blooio/_streaming.py @@ -4,7 +4,7 @@ import json import inspect from types import TracebackType -from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable import httpx @@ -13,6 +13,7 @@ if TYPE_CHECKING: from ._client import Blooio, AsyncBlooio + from ._models import FinalRequestOptions _T = TypeVar("_T") @@ -22,7 +23,7 @@ class Stream(Generic[_T]): """Provides the core interface to iterate over a synchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEBytesDecoder def __init__( @@ -31,10 +32,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: Blooio, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -85,7 +88,7 @@ class AsyncStream(Generic[_T]): """Provides the core interface to iterate over an asynchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEDecoder | SSEBytesDecoder def __init__( @@ -94,10 +97,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: AsyncBlooio, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() From fa78df3c42ab33e1f47de28ff366be72c2732134 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 06:57:06 +0000 Subject: [PATCH 11/14] chore(internal): make `test_proxy_environment_variables` more resilient --- tests/test_client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index d70b3bf..4b7d111 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -945,6 +945,8 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has this set + monkeypatch.delenv("HTTP_PROXY", raising=False) client = DefaultHttpxClient() @@ -1847,6 +1849,8 @@ async def test_get_platform(self) -> None: async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has this set + monkeypatch.delenv("HTTP_PROXY", raising=False) client = DefaultAsyncHttpxClient() From a70f1e597d63bd7c03f5262d29b32a16ca7eff4f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 06:41:11 +0000 Subject: [PATCH 12/14] chore(internal): make `test_proxy_environment_variables` more resilient to env --- tests/test_client.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 4b7d111..e9ebdb0 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -945,8 +945,14 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") - # Delete in case our environment has this set + # Delete in case our environment has any proxy env vars set monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultHttpxClient() @@ -1849,8 +1855,14 @@ async def test_get_platform(self) -> None: async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") - # Delete in case our environment has this set + # Delete in case our environment has any proxy env vars set monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultAsyncHttpxClient() From bdc07aa8716f9de74bebb431a448dbf1f19a0837 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 11:41:08 +0000 Subject: [PATCH 13/14] chore(internal): codegen related update --- src/blooio/_client.py | 24 ++++++++++++++++++++++++ src/blooio/resources/batches.py | 4 ++++ src/blooio/resources/config/config.py | 6 ++++++ src/blooio/resources/config/webhook.py | 4 ++++ src/blooio/resources/contacts.py | 4 ++++ src/blooio/resources/me.py | 4 ++++ src/blooio/resources/messages.py | 4 ++++ 7 files changed, 50 insertions(+) diff --git a/src/blooio/_client.py b/src/blooio/_client.py index 540214b..05c6db8 100644 --- a/src/blooio/_client.py +++ b/src/blooio/_client.py @@ -98,18 +98,21 @@ def __init__( @cached_property def me(self) -> MeResource: + """Account and API key information""" from .resources.me import MeResource return MeResource(self) @cached_property def contacts(self) -> ContactsResource: + """Contact-related operations""" from .resources.contacts import ContactsResource return ContactsResource(self) @cached_property def messages(self) -> MessagesResource: + """Send and manage individual messages""" from .resources.messages import MessagesResource return MessagesResource(self) @@ -122,6 +125,7 @@ def config(self) -> ConfigResource: @cached_property def batches(self) -> BatchesResource: + """Bulk/batch operations (stubbed)""" from .resources.batches import BatchesResource return BatchesResource(self) @@ -296,18 +300,21 @@ def __init__( @cached_property def me(self) -> AsyncMeResource: + """Account and API key information""" from .resources.me import AsyncMeResource return AsyncMeResource(self) @cached_property def contacts(self) -> AsyncContactsResource: + """Contact-related operations""" from .resources.contacts import AsyncContactsResource return AsyncContactsResource(self) @cached_property def messages(self) -> AsyncMessagesResource: + """Send and manage individual messages""" from .resources.messages import AsyncMessagesResource return AsyncMessagesResource(self) @@ -320,6 +327,7 @@ def config(self) -> AsyncConfigResource: @cached_property def batches(self) -> AsyncBatchesResource: + """Bulk/batch operations (stubbed)""" from .resources.batches import AsyncBatchesResource return AsyncBatchesResource(self) @@ -445,18 +453,21 @@ def __init__(self, client: Blooio) -> None: @cached_property def me(self) -> me.MeResourceWithRawResponse: + """Account and API key information""" from .resources.me import MeResourceWithRawResponse return MeResourceWithRawResponse(self._client.me) @cached_property def contacts(self) -> contacts.ContactsResourceWithRawResponse: + """Contact-related operations""" from .resources.contacts import ContactsResourceWithRawResponse return ContactsResourceWithRawResponse(self._client.contacts) @cached_property def messages(self) -> messages.MessagesResourceWithRawResponse: + """Send and manage individual messages""" from .resources.messages import MessagesResourceWithRawResponse return MessagesResourceWithRawResponse(self._client.messages) @@ -469,6 +480,7 @@ def config(self) -> config.ConfigResourceWithRawResponse: @cached_property def batches(self) -> batches.BatchesResourceWithRawResponse: + """Bulk/batch operations (stubbed)""" from .resources.batches import BatchesResourceWithRawResponse return BatchesResourceWithRawResponse(self._client.batches) @@ -482,18 +494,21 @@ def __init__(self, client: AsyncBlooio) -> None: @cached_property def me(self) -> me.AsyncMeResourceWithRawResponse: + """Account and API key information""" from .resources.me import AsyncMeResourceWithRawResponse return AsyncMeResourceWithRawResponse(self._client.me) @cached_property def contacts(self) -> contacts.AsyncContactsResourceWithRawResponse: + """Contact-related operations""" from .resources.contacts import AsyncContactsResourceWithRawResponse return AsyncContactsResourceWithRawResponse(self._client.contacts) @cached_property def messages(self) -> messages.AsyncMessagesResourceWithRawResponse: + """Send and manage individual messages""" from .resources.messages import AsyncMessagesResourceWithRawResponse return AsyncMessagesResourceWithRawResponse(self._client.messages) @@ -506,6 +521,7 @@ def config(self) -> config.AsyncConfigResourceWithRawResponse: @cached_property def batches(self) -> batches.AsyncBatchesResourceWithRawResponse: + """Bulk/batch operations (stubbed)""" from .resources.batches import AsyncBatchesResourceWithRawResponse return AsyncBatchesResourceWithRawResponse(self._client.batches) @@ -519,18 +535,21 @@ def __init__(self, client: Blooio) -> None: @cached_property def me(self) -> me.MeResourceWithStreamingResponse: + """Account and API key information""" from .resources.me import MeResourceWithStreamingResponse return MeResourceWithStreamingResponse(self._client.me) @cached_property def contacts(self) -> contacts.ContactsResourceWithStreamingResponse: + """Contact-related operations""" from .resources.contacts import ContactsResourceWithStreamingResponse return ContactsResourceWithStreamingResponse(self._client.contacts) @cached_property def messages(self) -> messages.MessagesResourceWithStreamingResponse: + """Send and manage individual messages""" from .resources.messages import MessagesResourceWithStreamingResponse return MessagesResourceWithStreamingResponse(self._client.messages) @@ -543,6 +562,7 @@ def config(self) -> config.ConfigResourceWithStreamingResponse: @cached_property def batches(self) -> batches.BatchesResourceWithStreamingResponse: + """Bulk/batch operations (stubbed)""" from .resources.batches import BatchesResourceWithStreamingResponse return BatchesResourceWithStreamingResponse(self._client.batches) @@ -556,18 +576,21 @@ def __init__(self, client: AsyncBlooio) -> None: @cached_property def me(self) -> me.AsyncMeResourceWithStreamingResponse: + """Account and API key information""" from .resources.me import AsyncMeResourceWithStreamingResponse return AsyncMeResourceWithStreamingResponse(self._client.me) @cached_property def contacts(self) -> contacts.AsyncContactsResourceWithStreamingResponse: + """Contact-related operations""" from .resources.contacts import AsyncContactsResourceWithStreamingResponse return AsyncContactsResourceWithStreamingResponse(self._client.contacts) @cached_property def messages(self) -> messages.AsyncMessagesResourceWithStreamingResponse: + """Send and manage individual messages""" from .resources.messages import AsyncMessagesResourceWithStreamingResponse return AsyncMessagesResourceWithStreamingResponse(self._client.messages) @@ -580,6 +603,7 @@ def config(self) -> config.AsyncConfigResourceWithStreamingResponse: @cached_property def batches(self) -> batches.AsyncBatchesResourceWithStreamingResponse: + """Bulk/batch operations (stubbed)""" from .resources.batches import AsyncBatchesResourceWithStreamingResponse return AsyncBatchesResourceWithStreamingResponse(self._client.batches) diff --git a/src/blooio/resources/batches.py b/src/blooio/resources/batches.py index 1cb733c..29163a5 100644 --- a/src/blooio/resources/batches.py +++ b/src/blooio/resources/batches.py @@ -19,6 +19,8 @@ class BatchesResource(SyncAPIResource): + """Bulk/batch operations (stubbed)""" + @cached_property def with_raw_response(self) -> BatchesResourceWithRawResponse: """ @@ -162,6 +164,8 @@ def retrieve_status( class AsyncBatchesResource(AsyncAPIResource): + """Bulk/batch operations (stubbed)""" + @cached_property def with_raw_response(self) -> AsyncBatchesResourceWithRawResponse: """ diff --git a/src/blooio/resources/config/config.py b/src/blooio/resources/config/config.py index c748805..ac6dec6 100644 --- a/src/blooio/resources/config/config.py +++ b/src/blooio/resources/config/config.py @@ -19,6 +19,7 @@ class ConfigResource(SyncAPIResource): @cached_property def webhook(self) -> WebhookResource: + """Account-level configuration""" return WebhookResource(self._client) @cached_property @@ -44,6 +45,7 @@ def with_streaming_response(self) -> ConfigResourceWithStreamingResponse: class AsyncConfigResource(AsyncAPIResource): @cached_property def webhook(self) -> AsyncWebhookResource: + """Account-level configuration""" return AsyncWebhookResource(self._client) @cached_property @@ -72,6 +74,7 @@ def __init__(self, config: ConfigResource) -> None: @cached_property def webhook(self) -> WebhookResourceWithRawResponse: + """Account-level configuration""" return WebhookResourceWithRawResponse(self._config.webhook) @@ -81,6 +84,7 @@ def __init__(self, config: AsyncConfigResource) -> None: @cached_property def webhook(self) -> AsyncWebhookResourceWithRawResponse: + """Account-level configuration""" return AsyncWebhookResourceWithRawResponse(self._config.webhook) @@ -90,6 +94,7 @@ def __init__(self, config: ConfigResource) -> None: @cached_property def webhook(self) -> WebhookResourceWithStreamingResponse: + """Account-level configuration""" return WebhookResourceWithStreamingResponse(self._config.webhook) @@ -99,4 +104,5 @@ def __init__(self, config: AsyncConfigResource) -> None: @cached_property def webhook(self) -> AsyncWebhookResourceWithStreamingResponse: + """Account-level configuration""" return AsyncWebhookResourceWithStreamingResponse(self._config.webhook) diff --git a/src/blooio/resources/config/webhook.py b/src/blooio/resources/config/webhook.py index a0c953f..f5b83e0 100644 --- a/src/blooio/resources/config/webhook.py +++ b/src/blooio/resources/config/webhook.py @@ -23,6 +23,8 @@ class WebhookResource(SyncAPIResource): + """Account-level configuration""" + @cached_property def with_raw_response(self) -> WebhookResourceWithRawResponse: """ @@ -106,6 +108,8 @@ def update( class AsyncWebhookResource(AsyncAPIResource): + """Account-level configuration""" + @cached_property def with_raw_response(self) -> AsyncWebhookResourceWithRawResponse: """ diff --git a/src/blooio/resources/contacts.py b/src/blooio/resources/contacts.py index 741d1fe..245a43d 100644 --- a/src/blooio/resources/contacts.py +++ b/src/blooio/resources/contacts.py @@ -20,6 +20,8 @@ class ContactsResource(SyncAPIResource): + """Contact-related operations""" + @cached_property def with_raw_response(self) -> ContactsResourceWithRawResponse: """ @@ -75,6 +77,8 @@ def check_capabilities( class AsyncContactsResource(AsyncAPIResource): + """Contact-related operations""" + @cached_property def with_raw_response(self) -> AsyncContactsResourceWithRawResponse: """ diff --git a/src/blooio/resources/me.py b/src/blooio/resources/me.py index 6059951..a7fa06b 100644 --- a/src/blooio/resources/me.py +++ b/src/blooio/resources/me.py @@ -20,6 +20,8 @@ class MeResource(SyncAPIResource): + """Account and API key information""" + @cached_property def with_raw_response(self) -> MeResourceWithRawResponse: """ @@ -63,6 +65,8 @@ def retrieve( class AsyncMeResource(AsyncAPIResource): + """Account and API key information""" + @cached_property def with_raw_response(self) -> AsyncMeResourceWithRawResponse: """ diff --git a/src/blooio/resources/messages.py b/src/blooio/resources/messages.py index 9025a7c..4ea1efd 100644 --- a/src/blooio/resources/messages.py +++ b/src/blooio/resources/messages.py @@ -25,6 +25,8 @@ class MessagesResource(SyncAPIResource): + """Send and manage individual messages""" + @cached_property def with_raw_response(self) -> MessagesResourceWithRawResponse: """ @@ -210,6 +212,8 @@ def send( class AsyncMessagesResource(AsyncAPIResource): + """Send and manage individual messages""" + @cached_property def with_raw_response(self) -> AsyncMessagesResourceWithRawResponse: """ From 4063b5e323e85c3a5c27ab1c563c242f5b7ca546 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 11:41:26 +0000 Subject: [PATCH 14/14] release: 1.1.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 24 ++++++++++++++++++++++++ pyproject.toml | 2 +- src/blooio/_version.py | 2 +- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1214610..2601677 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.0.5" + ".": "1.1.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 68fe48c..864832f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## 1.1.0 (2026-03-03) + +Full Changelog: [v1.0.5...v1.1.0](https://github.com/Blooio/blooio-python-sdk/compare/v1.0.5...v1.1.0) + +### Features + +* **client:** add custom JSON encoder for extended type support ([c02699e](https://github.com/Blooio/blooio-python-sdk/commit/c02699e0bb5a7411efe9ba37898f74bce58fc4a8)) +* **client:** add support for binary request streaming ([0a46f0a](https://github.com/Blooio/blooio-python-sdk/commit/0a46f0a17f78a2a21aae637af66fb6b5671c12c5)) + + +### Chores + +* **ci:** upgrade `actions/github-script` ([5e006c2](https://github.com/Blooio/blooio-python-sdk/commit/5e006c28777fae0e393b573d148432644a5d181b)) +* format all `api.md` files ([3d045e8](https://github.com/Blooio/blooio-python-sdk/commit/3d045e8d5b9cc2afca1d6c85ded4b0e531c28228)) +* **internal:** add request options to SSE classes ([3d004d8](https://github.com/Blooio/blooio-python-sdk/commit/3d004d8b3892f4f8a212ef00745919dc59a2e099)) +* **internal:** bump dependencies ([e6b7ec8](https://github.com/Blooio/blooio-python-sdk/commit/e6b7ec8b91ed416baf78c830519f226a4a361a98)) +* **internal:** codegen related update ([bdc07aa](https://github.com/Blooio/blooio-python-sdk/commit/bdc07aa8716f9de74bebb431a448dbf1f19a0837)) +* **internal:** fix lint error on Python 3.14 ([34988f7](https://github.com/Blooio/blooio-python-sdk/commit/34988f7b47fd8e01bbb8a0ba61abf52d66a9598a)) +* **internal:** make `test_proxy_environment_variables` more resilient ([fa78df3](https://github.com/Blooio/blooio-python-sdk/commit/fa78df3c42ab33e1f47de28ff366be72c2732134)) +* **internal:** make `test_proxy_environment_variables` more resilient to env ([a70f1e5](https://github.com/Blooio/blooio-python-sdk/commit/a70f1e597d63bd7c03f5262d29b32a16ca7eff4f)) +* **internal:** remove mock server code ([6d1b141](https://github.com/Blooio/blooio-python-sdk/commit/6d1b141349ac7e1eefe21ef50dc3b5d8de6707d6)) +* **internal:** update `actions/checkout` version ([3f2ef54](https://github.com/Blooio/blooio-python-sdk/commit/3f2ef546ebf7281cfca3d17255de0cdc288e1013)) +* update mock server docs ([b1c88e2](https://github.com/Blooio/blooio-python-sdk/commit/b1c88e2d8f1aac114dac3b288d540cae6f35d70c)) + ## 1.0.5 (2025-12-19) Full Changelog: [v1.0.4...v1.0.5](https://github.com/Blooio/blooio-python-sdk/compare/v1.0.4...v1.0.5) diff --git a/pyproject.toml b/pyproject.toml index 0f85d2e..a069484 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "blooio" -version = "1.0.5" +version = "1.1.0" description = "The official Python library for the blooio API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/blooio/_version.py b/src/blooio/_version.py index dcf7039..b7d98e7 100644 --- a/src/blooio/_version.py +++ b/src/blooio/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "blooio" -__version__ = "1.0.5" # x-release-please-version +__version__ = "1.1.0" # x-release-please-version