diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 821a1fc..c6500a3 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.23.0" + ".": "7.24.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 29ea7c7..2b72539 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 147 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-ca32540df5bca42c8113de4004e7751b74bb440e3d73267e7cbad84dd3c3e82a.yml -openapi_spec_hash: 5576c8cf8e5be1f250b5dd1eb6d30571 -config_hash: c6cefb51165221f01d442a42ac4a13b9 +configured_endpoints: 145 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-9074e771bd8042233d1c6d73d11ea3c41ee63f3639119deae5dc3b7a5655e568.yml +openapi_spec_hash: f09e821d35afa0ad0117a1d8eb9d4d16 +config_hash: 3eb0070d128aef7a6da16173f7050177 diff --git a/CHANGELOG.md b/CHANGELOG.md index 112e207..b434e63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 7.24.0 (2026-08-03) + +Full Changelog: [v7.23.0...v7.24.0](https://github.com/trycourier/courier-python/compare/v7.23.0...v7.24.0) + +### Features + +* Merge pull request [#183](https://github.com/trycourier/courier-python/issues/183) from trycourier/dantran/c-19684-api-spec-document-messagecontexttenant_id-on-journey-send ([b7a7bef](https://github.com/trycourier/courier-python/commit/b7a7bef41f9aa21c72b105e29f7cb0cf855e4043)) +* spec: remove inbox message delete/restore endpoints for now (C-19268) ([#182](https://github.com/trycourier/courier-python/issues/182)) ([dfdc63d](https://github.com/trycourier/courier-python/commit/dfdc63d3d184091ddc3f81a851aa156d43f6b61e)) +* spec: reorder API reference sections to match the Studio nav ([#180](https://github.com/trycourier/courier-python/issues/180)) ([044758a](https://github.com/trycourier/courier-python/commit/044758a95673f15a0a7b01b3021ff0359ef6c5bd)) + ## 7.23.0 (2026-07-29) Full Changelog: [v7.22.0...v7.23.0](https://github.com/trycourier/courier-python/compare/v7.22.0...v7.23.0) diff --git a/api.md b/api.md index 4603c9d..856a963 100644 --- a/api.md +++ b/api.md @@ -385,15 +385,6 @@ Methods: - client.lists.subscriptions.subscribe_user(user_id, \*, list_id, \*\*params) -> None - client.lists.subscriptions.unsubscribe_user(user_id, \*, list_id) -> None -# Inbox - -## Messages - -Methods: - -- client.inbox.messages.delete(message_id) -> None -- client.inbox.messages.restore(message_id) -> None - # Messages Types: diff --git a/pyproject.toml b/pyproject.toml index 1b672bf..fad6e6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "trycourier" -version = "7.23.0" +version = "7.24.0" description = "The official Python library for the Courier API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/courier/_client.py b/src/courier/_client.py index b6797bf..f548a44 100644 --- a/src/courier/_client.py +++ b/src/courier/_client.py @@ -38,7 +38,6 @@ from .resources import ( auth, send, - inbox, lists, users, brands, @@ -67,7 +66,6 @@ from .resources.requests import RequestsResource, AsyncRequestsResource from .resources.audiences import AudiencesResource, AsyncAudiencesResource from .resources.broadcasts import BroadcastsResource, AsyncBroadcastsResource - from .resources.inbox.inbox import InboxResource, AsyncInboxResource from .resources.lists.lists import ListsResource, AsyncListsResource from .resources.users.users import UsersResource, AsyncUsersResource from .resources.audit_events import AuditEventsResource, AsyncAuditEventsResource @@ -215,6 +213,9 @@ def journeys(self) -> JourneysResource: @cached_property def broadcasts(self) -> BroadcastsResource: + """ + Create a one-off send to a list or audience, author its content, then send it immediately or schedule it for later. + """ from .resources.broadcasts import BroadcastsResource return BroadcastsResource(self) @@ -252,12 +253,6 @@ def lists(self) -> ListsResource: return ListsResource(self) - @cached_property - def inbox(self) -> InboxResource: - from .resources.inbox import InboxResource - - return InboxResource(self) - @cached_property def messages(self) -> MessagesResource: """ @@ -576,6 +571,9 @@ def journeys(self) -> AsyncJourneysResource: @cached_property def broadcasts(self) -> AsyncBroadcastsResource: + """ + Create a one-off send to a list or audience, author its content, then send it immediately or schedule it for later. + """ from .resources.broadcasts import AsyncBroadcastsResource return AsyncBroadcastsResource(self) @@ -613,12 +611,6 @@ def lists(self) -> AsyncListsResource: return AsyncListsResource(self) - @cached_property - def inbox(self) -> AsyncInboxResource: - from .resources.inbox import AsyncInboxResource - - return AsyncInboxResource(self) - @cached_property def messages(self) -> AsyncMessagesResource: """ @@ -879,6 +871,9 @@ def journeys(self) -> journeys.JourneysResourceWithRawResponse: @cached_property def broadcasts(self) -> broadcasts.BroadcastsResourceWithRawResponse: + """ + Create a one-off send to a list or audience, author its content, then send it immediately or schedule it for later. + """ from .resources.broadcasts import BroadcastsResourceWithRawResponse return BroadcastsResourceWithRawResponse(self._client.broadcasts) @@ -916,12 +911,6 @@ def lists(self) -> lists.ListsResourceWithRawResponse: return ListsResourceWithRawResponse(self._client.lists) - @cached_property - def inbox(self) -> inbox.InboxResourceWithRawResponse: - from .resources.inbox import InboxResourceWithRawResponse - - return InboxResourceWithRawResponse(self._client.inbox) - @cached_property def messages(self) -> messages.MessagesResourceWithRawResponse: """ @@ -1070,6 +1059,9 @@ def journeys(self) -> journeys.AsyncJourneysResourceWithRawResponse: @cached_property def broadcasts(self) -> broadcasts.AsyncBroadcastsResourceWithRawResponse: + """ + Create a one-off send to a list or audience, author its content, then send it immediately or schedule it for later. + """ from .resources.broadcasts import AsyncBroadcastsResourceWithRawResponse return AsyncBroadcastsResourceWithRawResponse(self._client.broadcasts) @@ -1107,12 +1099,6 @@ def lists(self) -> lists.AsyncListsResourceWithRawResponse: return AsyncListsResourceWithRawResponse(self._client.lists) - @cached_property - def inbox(self) -> inbox.AsyncInboxResourceWithRawResponse: - from .resources.inbox import AsyncInboxResourceWithRawResponse - - return AsyncInboxResourceWithRawResponse(self._client.inbox) - @cached_property def messages(self) -> messages.AsyncMessagesResourceWithRawResponse: """ @@ -1261,6 +1247,9 @@ def journeys(self) -> journeys.JourneysResourceWithStreamingResponse: @cached_property def broadcasts(self) -> broadcasts.BroadcastsResourceWithStreamingResponse: + """ + Create a one-off send to a list or audience, author its content, then send it immediately or schedule it for later. + """ from .resources.broadcasts import BroadcastsResourceWithStreamingResponse return BroadcastsResourceWithStreamingResponse(self._client.broadcasts) @@ -1298,12 +1287,6 @@ def lists(self) -> lists.ListsResourceWithStreamingResponse: return ListsResourceWithStreamingResponse(self._client.lists) - @cached_property - def inbox(self) -> inbox.InboxResourceWithStreamingResponse: - from .resources.inbox import InboxResourceWithStreamingResponse - - return InboxResourceWithStreamingResponse(self._client.inbox) - @cached_property def messages(self) -> messages.MessagesResourceWithStreamingResponse: """ @@ -1452,6 +1435,9 @@ def journeys(self) -> journeys.AsyncJourneysResourceWithStreamingResponse: @cached_property def broadcasts(self) -> broadcasts.AsyncBroadcastsResourceWithStreamingResponse: + """ + Create a one-off send to a list or audience, author its content, then send it immediately or schedule it for later. + """ from .resources.broadcasts import AsyncBroadcastsResourceWithStreamingResponse return AsyncBroadcastsResourceWithStreamingResponse(self._client.broadcasts) @@ -1489,12 +1475,6 @@ def lists(self) -> lists.AsyncListsResourceWithStreamingResponse: return AsyncListsResourceWithStreamingResponse(self._client.lists) - @cached_property - def inbox(self) -> inbox.AsyncInboxResourceWithStreamingResponse: - from .resources.inbox import AsyncInboxResourceWithStreamingResponse - - return AsyncInboxResourceWithStreamingResponse(self._client.inbox) - @cached_property def messages(self) -> messages.AsyncMessagesResourceWithStreamingResponse: """ diff --git a/src/courier/_version.py b/src/courier/_version.py index 3bba92e..dd757ad 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.23.0" # x-release-please-version +__version__ = "7.24.0" # x-release-please-version diff --git a/src/courier/resources/__init__.py b/src/courier/resources/__init__.py index 1528692..036149d 100644 --- a/src/courier/resources/__init__.py +++ b/src/courier/resources/__init__.py @@ -16,14 +16,6 @@ SendResourceWithStreamingResponse, AsyncSendResourceWithStreamingResponse, ) -from .inbox import ( - InboxResource, - AsyncInboxResource, - InboxResourceWithRawResponse, - AsyncInboxResourceWithRawResponse, - InboxResourceWithStreamingResponse, - AsyncInboxResourceWithStreamingResponse, -) from .lists import ( ListsResource, AsyncListsResource, @@ -250,12 +242,6 @@ "AsyncListsResourceWithRawResponse", "ListsResourceWithStreamingResponse", "AsyncListsResourceWithStreamingResponse", - "InboxResource", - "AsyncInboxResource", - "InboxResourceWithRawResponse", - "AsyncInboxResourceWithRawResponse", - "InboxResourceWithStreamingResponse", - "AsyncInboxResourceWithStreamingResponse", "MessagesResource", "AsyncMessagesResource", "MessagesResourceWithRawResponse", diff --git a/src/courier/resources/broadcasts.py b/src/courier/resources/broadcasts.py index 0465e10..aa55c88 100644 --- a/src/courier/resources/broadcasts.py +++ b/src/courier/resources/broadcasts.py @@ -38,6 +38,10 @@ class BroadcastsResource(SyncAPIResource): + """ + Create a one-off send to a list or audience, author its content, then send it immediately or schedule it for later. + """ + @cached_property def with_raw_response(self) -> BroadcastsResourceWithRawResponse: """ @@ -529,6 +533,10 @@ def send( class AsyncBroadcastsResource(AsyncAPIResource): + """ + Create a one-off send to a list or audience, author its content, then send it immediately or schedule it for later. + """ + @cached_property def with_raw_response(self) -> AsyncBroadcastsResourceWithRawResponse: """ diff --git a/src/courier/resources/inbox/__init__.py b/src/courier/resources/inbox/__init__.py deleted file mode 100644 index 13574c1..0000000 --- a/src/courier/resources/inbox/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .inbox import ( - InboxResource, - AsyncInboxResource, - InboxResourceWithRawResponse, - AsyncInboxResourceWithRawResponse, - InboxResourceWithStreamingResponse, - AsyncInboxResourceWithStreamingResponse, -) -from .messages import ( - MessagesResource, - AsyncMessagesResource, - MessagesResourceWithRawResponse, - AsyncMessagesResourceWithRawResponse, - MessagesResourceWithStreamingResponse, - AsyncMessagesResourceWithStreamingResponse, -) - -__all__ = [ - "MessagesResource", - "AsyncMessagesResource", - "MessagesResourceWithRawResponse", - "AsyncMessagesResourceWithRawResponse", - "MessagesResourceWithStreamingResponse", - "AsyncMessagesResourceWithStreamingResponse", - "InboxResource", - "AsyncInboxResource", - "InboxResourceWithRawResponse", - "AsyncInboxResourceWithRawResponse", - "InboxResourceWithStreamingResponse", - "AsyncInboxResourceWithStreamingResponse", -] diff --git a/src/courier/resources/inbox/inbox.py b/src/courier/resources/inbox/inbox.py deleted file mode 100644 index 4ab7084..0000000 --- a/src/courier/resources/inbox/inbox.py +++ /dev/null @@ -1,108 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from .messages import ( - MessagesResource, - AsyncMessagesResource, - MessagesResourceWithRawResponse, - AsyncMessagesResourceWithRawResponse, - MessagesResourceWithStreamingResponse, - AsyncMessagesResourceWithStreamingResponse, -) -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource - -__all__ = ["InboxResource", "AsyncInboxResource"] - - -class InboxResource(SyncAPIResource): - @cached_property - def messages(self) -> MessagesResource: - """Manage the messages in a user's in-app inbox.""" - return MessagesResource(self._client) - - @cached_property - def with_raw_response(self) -> InboxResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/trycourier/courier-python#accessing-raw-response-data-eg-headers - """ - return InboxResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> InboxResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/trycourier/courier-python#with_streaming_response - """ - return InboxResourceWithStreamingResponse(self) - - -class AsyncInboxResource(AsyncAPIResource): - @cached_property - def messages(self) -> AsyncMessagesResource: - """Manage the messages in a user's in-app inbox.""" - return AsyncMessagesResource(self._client) - - @cached_property - def with_raw_response(self) -> AsyncInboxResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/trycourier/courier-python#accessing-raw-response-data-eg-headers - """ - return AsyncInboxResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncInboxResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/trycourier/courier-python#with_streaming_response - """ - return AsyncInboxResourceWithStreamingResponse(self) - - -class InboxResourceWithRawResponse: - def __init__(self, inbox: InboxResource) -> None: - self._inbox = inbox - - @cached_property - def messages(self) -> MessagesResourceWithRawResponse: - """Manage the messages in a user's in-app inbox.""" - return MessagesResourceWithRawResponse(self._inbox.messages) - - -class AsyncInboxResourceWithRawResponse: - def __init__(self, inbox: AsyncInboxResource) -> None: - self._inbox = inbox - - @cached_property - def messages(self) -> AsyncMessagesResourceWithRawResponse: - """Manage the messages in a user's in-app inbox.""" - return AsyncMessagesResourceWithRawResponse(self._inbox.messages) - - -class InboxResourceWithStreamingResponse: - def __init__(self, inbox: InboxResource) -> None: - self._inbox = inbox - - @cached_property - def messages(self) -> MessagesResourceWithStreamingResponse: - """Manage the messages in a user's in-app inbox.""" - return MessagesResourceWithStreamingResponse(self._inbox.messages) - - -class AsyncInboxResourceWithStreamingResponse: - def __init__(self, inbox: AsyncInboxResource) -> None: - self._inbox = inbox - - @cached_property - def messages(self) -> AsyncMessagesResourceWithStreamingResponse: - """Manage the messages in a user's in-app inbox.""" - return AsyncMessagesResourceWithStreamingResponse(self._inbox.messages) diff --git a/src/courier/resources/inbox/messages.py b/src/courier/resources/inbox/messages.py deleted file mode 100644 index 75bf3da..0000000 --- a/src/courier/resources/inbox/messages.py +++ /dev/null @@ -1,253 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import httpx - -from ..._types import Body, Query, Headers, NoneType, NotGiven, not_given -from ..._utils import path_template -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from ..._base_client import make_request_options - -__all__ = ["MessagesResource", "AsyncMessagesResource"] - - -class MessagesResource(SyncAPIResource): - """Manage the messages in a user's in-app inbox.""" - - @cached_property - def with_raw_response(self) -> MessagesResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/trycourier/courier-python#accessing-raw-response-data-eg-headers - """ - return MessagesResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> MessagesResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/trycourier/courier-python#with_streaming_response - """ - return MessagesResourceWithStreamingResponse(self) - - def delete( - 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, - ) -> None: - """Delete a user's inbox message. - - The message is removed from every inbox read (it - stops appearing in the recipient's Inbox); it can be restored. - - 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}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return self._delete( - path_template("/inbox/messages/{message_id}", message_id=message_id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - - def restore( - 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, - ) -> None: - """ - Restore a previously deleted inbox message. - - 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}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return self._put( - path_template("/inbox/messages/{message_id}/restore", message_id=message_id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - - -class AsyncMessagesResource(AsyncAPIResource): - """Manage the messages in a user's in-app inbox.""" - - @cached_property - def with_raw_response(self) -> AsyncMessagesResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/trycourier/courier-python#accessing-raw-response-data-eg-headers - """ - return AsyncMessagesResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncMessagesResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/trycourier/courier-python#with_streaming_response - """ - return AsyncMessagesResourceWithStreamingResponse(self) - - async def delete( - 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, - ) -> None: - """Delete a user's inbox message. - - The message is removed from every inbox read (it - stops appearing in the recipient's Inbox); it can be restored. - - 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}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return await self._delete( - path_template("/inbox/messages/{message_id}", message_id=message_id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - - async def restore( - 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, - ) -> None: - """ - Restore a previously deleted inbox message. - - 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}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return await self._put( - path_template("/inbox/messages/{message_id}/restore", message_id=message_id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - - -class MessagesResourceWithRawResponse: - def __init__(self, messages: MessagesResource) -> None: - self._messages = messages - - self.delete = to_raw_response_wrapper( - messages.delete, - ) - self.restore = to_raw_response_wrapper( - messages.restore, - ) - - -class AsyncMessagesResourceWithRawResponse: - def __init__(self, messages: AsyncMessagesResource) -> None: - self._messages = messages - - self.delete = async_to_raw_response_wrapper( - messages.delete, - ) - self.restore = async_to_raw_response_wrapper( - messages.restore, - ) - - -class MessagesResourceWithStreamingResponse: - def __init__(self, messages: MessagesResource) -> None: - self._messages = messages - - self.delete = to_streamed_response_wrapper( - messages.delete, - ) - self.restore = to_streamed_response_wrapper( - messages.restore, - ) - - -class AsyncMessagesResourceWithStreamingResponse: - def __init__(self, messages: AsyncMessagesResource) -> None: - self._messages = messages - - self.delete = async_to_streamed_response_wrapper( - messages.delete, - ) - self.restore = async_to_streamed_response_wrapper( - messages.restore, - ) diff --git a/src/courier/types/inbox/__init__.py b/src/courier/types/inbox/__init__.py deleted file mode 100644 index f8ee8b1..0000000 --- a/src/courier/types/inbox/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations diff --git a/src/courier/types/journey_send_node.py b/src/courier/types/journey_send_node.py index d346cac..c60900a 100644 --- a/src/courier/types/journey_send_node.py +++ b/src/courier/types/journey_send_node.py @@ -7,7 +7,29 @@ from .journey_experiment import JourneyExperiment from .journey_conditions_field import JourneyConditionsField -__all__ = ["JourneySendNode", "Message", "MessageDelay", "MessageTo"] +__all__ = ["JourneySendNode", "Message", "MessageContext", "MessageDelay", "MessageTo"] + + +class MessageContext(BaseModel): + """Tenant context for this send. + + Set it to deliver on behalf of one of your customers, so the message uses that tenant's brand and settings. + """ + + tenant_id: str + """The tenant to send as. + + Accepts either a literal tenant id (`acme-tenant`) or a whole-string mustache + reference to a value the run already holds — `{{data.tenant_id}}` from the + invocation payload, or `{{f1.body.tenant_id}}` from the response of an earlier + fetch node with id `f1`. A reference is resolved separately on every run, so a + single journey can deliver as many tenants. Two forms are rejected with `400`: + mid-string interpolation such as `tenant-{{data.region}}`, and any value + beginning with `refs.`, which is reserved for internal use. A reference that + resolves to nothing at run time does not stop the run — the message is still + sent, with no tenant context — so make sure the referenced value is always + present. `GET` returns the value in the same form it was supplied. + """ class MessageDelay(BaseModel): @@ -25,6 +47,13 @@ class MessageTo(BaseModel): class Message(BaseModel): + context: Optional[MessageContext] = None + """Tenant context for this send. + + Set it to deliver on behalf of one of your customers, so the message uses that + tenant's brand and settings. + """ + data: Optional[Dict[str, object]] = None delay: Optional[MessageDelay] = None @@ -37,7 +66,7 @@ class Message(BaseModel): class JourneySendNode(BaseModel): """Send to the recipient. - A send node sources its content from EXACTLY ONE of `message.template` (a single notification template) or `experiment` (an A/B split across weighted template variants) — supplying both, or neither, is rejected. Optionally override the recipient address, delay the send, or attach `data`. + A send node sources its content from EXACTLY ONE of `message.template` (a single notification template) or `experiment` (an A/B split across weighted template variants) — supplying both, or neither, is rejected. Optionally override the recipient address, send as a tenant, delay the send, or attach `data`. """ message: Message diff --git a/src/courier/types/journey_send_node_param.py b/src/courier/types/journey_send_node_param.py index 5e051e7..4a5069c 100644 --- a/src/courier/types/journey_send_node_param.py +++ b/src/courier/types/journey_send_node_param.py @@ -8,7 +8,29 @@ from .journey_experiment_param import JourneyExperimentParam from .journey_conditions_field_param import JourneyConditionsFieldParam -__all__ = ["JourneySendNodeParam", "Message", "MessageDelay", "MessageTo"] +__all__ = ["JourneySendNodeParam", "Message", "MessageContext", "MessageDelay", "MessageTo"] + + +class MessageContext(TypedDict, total=False): + """Tenant context for this send. + + Set it to deliver on behalf of one of your customers, so the message uses that tenant's brand and settings. + """ + + tenant_id: Required[str] + """The tenant to send as. + + Accepts either a literal tenant id (`acme-tenant`) or a whole-string mustache + reference to a value the run already holds — `{{data.tenant_id}}` from the + invocation payload, or `{{f1.body.tenant_id}}` from the response of an earlier + fetch node with id `f1`. A reference is resolved separately on every run, so a + single journey can deliver as many tenants. Two forms are rejected with `400`: + mid-string interpolation such as `tenant-{{data.region}}`, and any value + beginning with `refs.`, which is reserved for internal use. A reference that + resolves to nothing at run time does not stop the run — the message is still + sent, with no tenant context — so make sure the referenced value is always + present. `GET` returns the value in the same form it was supplied. + """ class MessageDelay(TypedDict, total=False): @@ -26,6 +48,13 @@ class MessageTo(TypedDict, total=False): class Message(TypedDict, total=False): + context: MessageContext + """Tenant context for this send. + + Set it to deliver on behalf of one of your customers, so the message uses that + tenant's brand and settings. + """ + data: Dict[str, object] delay: MessageDelay @@ -38,7 +67,7 @@ class Message(TypedDict, total=False): class JourneySendNodeParam(TypedDict, total=False): """Send to the recipient. - A send node sources its content from EXACTLY ONE of `message.template` (a single notification template) or `experiment` (an A/B split across weighted template variants) — supplying both, or neither, is rejected. Optionally override the recipient address, delay the send, or attach `data`. + A send node sources its content from EXACTLY ONE of `message.template` (a single notification template) or `experiment` (an A/B split across weighted template variants) — supplying both, or neither, is rejected. Optionally override the recipient address, send as a tenant, delay the send, or attach `data`. """ message: Required[Message] diff --git a/tests/api_resources/inbox/__init__.py b/tests/api_resources/inbox/__init__.py deleted file mode 100644 index fd8019a..0000000 --- a/tests/api_resources/inbox/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/inbox/test_messages.py b/tests/api_resources/inbox/test_messages.py deleted file mode 100644 index 129fbfe..0000000 --- a/tests/api_resources/inbox/test_messages.py +++ /dev/null @@ -1,190 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from courier import Courier, AsyncCourier - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestMessages: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_delete(self, client: Courier) -> None: - message = client.inbox.messages.delete( - "message_id", - ) - assert message is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_delete(self, client: Courier) -> None: - response = client.inbox.messages.with_raw_response.delete( - "message_id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = response.parse() - assert message is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_delete(self, client: Courier) -> None: - with client.inbox.messages.with_streaming_response.delete( - "message_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - message = response.parse() - assert message is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_delete(self, client: Courier) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): - client.inbox.messages.with_raw_response.delete( - "", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_restore(self, client: Courier) -> None: - message = client.inbox.messages.restore( - "message_id", - ) - assert message is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_restore(self, client: Courier) -> None: - response = client.inbox.messages.with_raw_response.restore( - "message_id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = response.parse() - assert message is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_restore(self, client: Courier) -> None: - with client.inbox.messages.with_streaming_response.restore( - "message_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - message = response.parse() - assert message is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_restore(self, client: Courier) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): - client.inbox.messages.with_raw_response.restore( - "", - ) - - -class TestAsyncMessages: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_delete(self, async_client: AsyncCourier) -> None: - message = await async_client.inbox.messages.delete( - "message_id", - ) - assert message is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_delete(self, async_client: AsyncCourier) -> None: - response = await async_client.inbox.messages.with_raw_response.delete( - "message_id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = await response.parse() - assert message is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_delete(self, async_client: AsyncCourier) -> None: - async with async_client.inbox.messages.with_streaming_response.delete( - "message_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - message = await response.parse() - assert message is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_delete(self, async_client: AsyncCourier) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): - await async_client.inbox.messages.with_raw_response.delete( - "", - ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_restore(self, async_client: AsyncCourier) -> None: - message = await async_client.inbox.messages.restore( - "message_id", - ) - assert message is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_restore(self, async_client: AsyncCourier) -> None: - response = await async_client.inbox.messages.with_raw_response.restore( - "message_id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - message = await response.parse() - assert message is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_restore(self, async_client: AsyncCourier) -> None: - async with async_client.inbox.messages.with_streaming_response.restore( - "message_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - message = await response.parse() - assert message is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_restore(self, async_client: AsyncCourier) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"): - await async_client.inbox.messages.with_raw_response.restore( - "", - ) diff --git a/tests/api_resources/test_journeys.py b/tests/api_resources/test_journeys.py index 3b1c49d..c47fab5 100644 --- a/tests/api_resources/test_journeys.py +++ b/tests/api_resources/test_journeys.py @@ -34,9 +34,10 @@ def test_method_create(self, client: Courier) -> None: "type": "trigger", }, { - "trigger_type": "api-invoke", - "type": "trigger", + "message": {}, + "type": "send", }, + {"type": "exit"}, ], ) assert_matches_type(JourneyResponse, journey, path=["response"]) @@ -55,11 +56,46 @@ def test_method_create_with_all_params(self, client: Courier) -> None: "schema": {"foo": "bar"}, }, { - "trigger_type": "api-invoke", - "type": "trigger", + "message": { + "context": {"tenant_id": "x"}, + "data": {"foo": "bar"}, + "delay": { + "until": "x", + "timezone": "x", + }, + "template": "nt_01kx4h2jdafq8bk9aftxak4b40", + "to": { + "email_override": "x", + "phone_number_override": "x", + "user_id_override": "x", + }, + }, + "type": "send", "id": "send-1", "conditions": ["string", "string"], - "schema": {"foo": "bar"}, + "experiment": { + "bucketing_key": "x", + "variants": [ + { + "id": "x", + "template_id": "x", + "weight": 0, + "name": "name", + }, + { + "id": "x", + "template_id": "x", + "weight": 0, + "name": "name", + }, + ], + "id": "x", + "name": "name", + }, + }, + { + "type": "exit", + "id": "exit-1", }, ], enabled=True, @@ -80,9 +116,10 @@ def test_raw_response_create(self, client: Courier) -> None: "type": "trigger", }, { - "trigger_type": "api-invoke", - "type": "trigger", + "message": {}, + "type": "send", }, + {"type": "exit"}, ], ) @@ -102,9 +139,10 @@ def test_streaming_response_create(self, client: Courier) -> None: "type": "trigger", }, { - "trigger_type": "api-invoke", - "type": "trigger", + "message": {}, + "type": "send", }, + {"type": "exit"}, ], ) as response: assert not response.is_closed @@ -593,9 +631,10 @@ async def test_method_create(self, async_client: AsyncCourier) -> None: "type": "trigger", }, { - "trigger_type": "api-invoke", - "type": "trigger", + "message": {}, + "type": "send", }, + {"type": "exit"}, ], ) assert_matches_type(JourneyResponse, journey, path=["response"]) @@ -614,11 +653,46 @@ async def test_method_create_with_all_params(self, async_client: AsyncCourier) - "schema": {"foo": "bar"}, }, { - "trigger_type": "api-invoke", - "type": "trigger", + "message": { + "context": {"tenant_id": "x"}, + "data": {"foo": "bar"}, + "delay": { + "until": "x", + "timezone": "x", + }, + "template": "nt_01kx4h2jdafq8bk9aftxak4b40", + "to": { + "email_override": "x", + "phone_number_override": "x", + "user_id_override": "x", + }, + }, + "type": "send", "id": "send-1", "conditions": ["string", "string"], - "schema": {"foo": "bar"}, + "experiment": { + "bucketing_key": "x", + "variants": [ + { + "id": "x", + "template_id": "x", + "weight": 0, + "name": "name", + }, + { + "id": "x", + "template_id": "x", + "weight": 0, + "name": "name", + }, + ], + "id": "x", + "name": "name", + }, + }, + { + "type": "exit", + "id": "exit-1", }, ], enabled=True, @@ -639,9 +713,10 @@ async def test_raw_response_create(self, async_client: AsyncCourier) -> None: "type": "trigger", }, { - "trigger_type": "api-invoke", - "type": "trigger", + "message": {}, + "type": "send", }, + {"type": "exit"}, ], ) @@ -661,9 +736,10 @@ async def test_streaming_response_create(self, async_client: AsyncCourier) -> No "type": "trigger", }, { - "trigger_type": "api-invoke", - "type": "trigger", + "message": {}, + "type": "send", }, + {"type": "exit"}, ], ) as response: assert not response.is_closed