diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ca58f43..c5b6474 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.18.1" + ".": "7.18.2" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 6620784..fa82f66 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 135 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-0b80b9b90e51b4d19f5cebadddf91c345090af9e6ad46cfb9364f0421e35e0bb.yml -openapi_spec_hash: 5283fb7d76bbdd7dcf48784f7128513b -config_hash: 66d7703eac15d2affc326ac25b55bcd6 +configured_endpoints: 136 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-d2b3bdb6747f4acc997702dbd9f24340a3d85f22c00e1f17af2bd6621358bc77.yml +openapi_spec_hash: b6791652567d04fd87c6117090de7635 +config_hash: 627f8592c19dfb77f91ffbec2a057c3a diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e31c65..4fc41b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 7.18.2 (2026-07-09) + +Full Changelog: [v7.18.1...v7.18.2](https://github.com/trycourier/courier-python/compare/v7.18.1...v7.18.2) + +### Chores + +* update api-spec with new message resend endpoint ([692be20](https://github.com/trycourier/courier-python/commit/692be204486c24fa3c92740f806a54233b738ef4)) + ## 7.18.1 (2026-07-07) Full Changelog: [v7.18.0...v7.18.1](https://github.com/trycourier/courier-python/compare/v7.18.0...v7.18.1) diff --git a/api.md b/api.md index b685418..7aee335 100644 --- a/api.md +++ b/api.md @@ -388,6 +388,7 @@ from courier.types import ( MessageListResponse, MessageContentResponse, MessageHistoryResponse, + MessageResendResponse, ) ``` @@ -398,6 +399,7 @@ Methods: - client.messages.cancel(message_id) -> MessageDetails - client.messages.content(message_id) -> MessageContentResponse - client.messages.history(message_id, \*\*params) -> MessageHistoryResponse +- client.messages.resend(message_id) -> MessageResendResponse # Requests diff --git a/pyproject.toml b/pyproject.toml index 5984e9c..d9f4536 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "trycourier" -version = "7.18.1" +version = "7.18.2" description = "The official Python library for the Courier API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/courier/_version.py b/src/courier/_version.py index 2ca08ce..1dabad9 100644 --- a/src/courier/_version.py +++ b/src/courier/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "courier" -__version__ = "7.18.1" # x-release-please-version +__version__ = "7.18.2" # x-release-please-version diff --git a/src/courier/resources/messages.py b/src/courier/resources/messages.py index a607c86..c8c87ce 100644 --- a/src/courier/resources/messages.py +++ b/src/courier/resources/messages.py @@ -20,6 +20,7 @@ from .._base_client import make_request_options from ..types.message_details import MessageDetails from ..types.message_list_response import MessageListResponse +from ..types.message_resend_response import MessageResendResponse from ..types.message_content_response import MessageContentResponse from ..types.message_history_response import MessageHistoryResponse from ..types.message_retrieve_response import MessageRetrieveResponse @@ -294,6 +295,44 @@ def history( cast_to=MessageHistoryResponse, ) + def resend( + self, + message_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> MessageResendResponse: + """Resend a previously sent message. + + The original send request is loaded from + storage and a brand-new send is enqueued for the same recipient and content, + producing a **new** `messageId` — the original message is not modified. + Throttled by a per-message rate limit; a repeat inside the limit window returns + `429 Too Many Requests`. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not message_id: + raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}") + return self._post( + path_template("/messages/{message_id}/resend", message_id=message_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MessageResendResponse, + ) + class AsyncMessagesResource(AsyncAPIResource): @cached_property @@ -562,6 +601,44 @@ async def history( cast_to=MessageHistoryResponse, ) + async def resend( + self, + message_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> MessageResendResponse: + """Resend a previously sent message. + + The original send request is loaded from + storage and a brand-new send is enqueued for the same recipient and content, + producing a **new** `messageId` — the original message is not modified. + Throttled by a per-message rate limit; a repeat inside the limit window returns + `429 Too Many Requests`. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not message_id: + raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}") + return await self._post( + path_template("/messages/{message_id}/resend", message_id=message_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MessageResendResponse, + ) + class MessagesResourceWithRawResponse: def __init__(self, messages: MessagesResource) -> None: @@ -582,6 +659,9 @@ def __init__(self, messages: MessagesResource) -> None: self.history = to_raw_response_wrapper( messages.history, ) + self.resend = to_raw_response_wrapper( + messages.resend, + ) class AsyncMessagesResourceWithRawResponse: @@ -603,6 +683,9 @@ def __init__(self, messages: AsyncMessagesResource) -> None: self.history = async_to_raw_response_wrapper( messages.history, ) + self.resend = async_to_raw_response_wrapper( + messages.resend, + ) class MessagesResourceWithStreamingResponse: @@ -624,6 +707,9 @@ def __init__(self, messages: MessagesResource) -> None: self.history = to_streamed_response_wrapper( messages.history, ) + self.resend = to_streamed_response_wrapper( + messages.resend, + ) class AsyncMessagesResourceWithStreamingResponse: @@ -645,3 +731,6 @@ def __init__(self, messages: AsyncMessagesResource) -> None: self.history = async_to_streamed_response_wrapper( messages.history, ) + self.resend = async_to_streamed_response_wrapper( + messages.resend, + ) diff --git a/src/courier/types/__init__.py b/src/courier/types/__init__.py index a6a96a0..8861c80 100644 --- a/src/courier/types/__init__.py +++ b/src/courier/types/__init__.py @@ -192,6 +192,7 @@ from .journey_exit_node_param import JourneyExitNodeParam as JourneyExitNodeParam from .journey_retrieve_params import JourneyRetrieveParams as JourneyRetrieveParams from .journey_send_node_param import JourneySendNodeParam as JourneySendNodeParam +from .message_resend_response import MessageResendResponse as MessageResendResponse from .profile_create_response import ProfileCreateResponse as ProfileCreateResponse from .providers_catalog_entry import ProvidersCatalogEntry as ProvidersCatalogEntry from .widget_background_param import WidgetBackgroundParam as WidgetBackgroundParam diff --git a/src/courier/types/message_resend_response.py b/src/courier/types/message_resend_response.py new file mode 100644 index 0000000..e469183 --- /dev/null +++ b/src/courier/types/message_resend_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["MessageResendResponse"] + + +class MessageResendResponse(BaseModel): + message_id: str = FieldInfo(alias="messageId") + """The new message id for the resent message. + + It is distinct from the id of the original message that was resent. + """ diff --git a/tests/api_resources/test_messages.py b/tests/api_resources/test_messages.py index 3f39e7c..f65faba 100644 --- a/tests/api_resources/test_messages.py +++ b/tests/api_resources/test_messages.py @@ -12,6 +12,7 @@ from courier.types import ( MessageDetails, MessageListResponse, + MessageResendResponse, MessageContentResponse, MessageHistoryResponse, MessageRetrieveResponse, @@ -249,6 +250,48 @@ def test_path_params_history(self, client: Courier) -> None: message_id="", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_resend(self, client: Courier) -> None: + message = client.messages.resend( + "message_id", + ) + assert_matches_type(MessageResendResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_resend(self, client: Courier) -> None: + response = client.messages.with_raw_response.resend( + "message_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + message = response.parse() + assert_matches_type(MessageResendResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_resend(self, client: Courier) -> None: + with client.messages.with_streaming_response.resend( + "message_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = response.parse() + assert_matches_type(MessageResendResponse, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_resend(self, client: Courier) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): + client.messages.with_raw_response.resend( + "", + ) + class TestAsyncMessages: parametrize = pytest.mark.parametrize( @@ -480,3 +523,45 @@ async def test_path_params_history(self, async_client: AsyncCourier) -> None: await async_client.messages.with_raw_response.history( message_id="", ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_resend(self, async_client: AsyncCourier) -> None: + message = await async_client.messages.resend( + "message_id", + ) + assert_matches_type(MessageResendResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_resend(self, async_client: AsyncCourier) -> None: + response = await async_client.messages.with_raw_response.resend( + "message_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + message = await response.parse() + assert_matches_type(MessageResendResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_resend(self, async_client: AsyncCourier) -> None: + async with async_client.messages.with_streaming_response.resend( + "message_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = await response.parse() + assert_matches_type(MessageResendResponse, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_resend(self, async_client: AsyncCourier) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): + await async_client.messages.with_raw_response.resend( + "", + )