diff --git a/.stats.yml b/.stats.yml index 9f97df3..7980543 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 149 +configured_endpoints: 154 diff --git a/api.md b/api.md index 4434998..67a02f1 100644 --- a/api.md +++ b/api.md @@ -177,6 +177,10 @@ Types: ```python from courier.types import ( AutomationInvokeResponse, + AutomationRunListItem, + AutomationRunListResponse, + AutomationRunStep, + AutomationRunStepsResponse, AutomationTemplate, AutomationTemplateListResponse, ) @@ -193,6 +197,13 @@ Methods: - client.automations.invoke.invoke_ad_hoc(\*\*params) -> AutomationInvokeResponse - client.automations.invoke.invoke_by_template(template_id, \*\*params) -> AutomationInvokeResponse +## Runs + +Methods: + +- client.automations.runs.list(\*\*params) -> AutomationRunListResponse +- client.automations.runs.list_steps(id) -> AutomationRunStepsResponse + # Journeys Types: @@ -205,6 +216,7 @@ from courier.types import ( Journey, JourneyAINode, JourneyAPIInvokeTriggerNode, + JourneyAudienceTriggerNode, JourneyConditionAtom, JourneyConditionGroup, JourneyConditionNestedGroup, @@ -220,6 +232,12 @@ from courier.types import ( JourneyNode, JourneyPublishRequest, JourneyResponse, + JourneyRun, + JourneyRunListItem, + JourneyRunListResponse, + JourneyRunResponse, + JourneyRunStep, + JourneyRunStepsResponse, JourneySegmentTriggerNode, JourneySendNode, JourneyState, @@ -233,6 +251,7 @@ from courier.types import ( JourneyThrottleStaticNode, JourneyVersionItem, JourneyVersionsListResponse, + JourneyWebhookTriggerNode, JourneysInvokeRequest, JourneysInvokeResponse, JourneysListResponse, @@ -266,6 +285,14 @@ Methods: - client.journeys.templates.replace(notification_id, \*, template_id, \*\*params) -> JourneyTemplateGetResponse - client.journeys.templates.retrieve_content(notification_id, \*, template_id, \*\*params) -> NotificationContentGetResponse +## Runs + +Methods: + +- client.journeys.runs.retrieve(run_id) -> JourneyRunResponse +- client.journeys.runs.list(\*\*params) -> JourneyRunListResponse +- client.journeys.runs.list_steps(run_id) -> JourneyRunStepsResponse + # Broadcasts Types: diff --git a/src/courier/resources/automations/__init__.py b/src/courier/resources/automations/__init__.py index 6596023..5b8fb8b 100644 --- a/src/courier/resources/automations/__init__.py +++ b/src/courier/resources/automations/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .runs import ( + RunsResource, + AsyncRunsResource, + RunsResourceWithRawResponse, + AsyncRunsResourceWithRawResponse, + RunsResourceWithStreamingResponse, + AsyncRunsResourceWithStreamingResponse, +) from .invoke import ( InvokeResource, AsyncInvokeResource, @@ -24,6 +32,12 @@ "AsyncInvokeResourceWithRawResponse", "InvokeResourceWithStreamingResponse", "AsyncInvokeResourceWithStreamingResponse", + "RunsResource", + "AsyncRunsResource", + "RunsResourceWithRawResponse", + "AsyncRunsResourceWithRawResponse", + "RunsResourceWithStreamingResponse", + "AsyncRunsResourceWithStreamingResponse", "AutomationsResource", "AsyncAutomationsResource", "AutomationsResourceWithRawResponse", diff --git a/src/courier/resources/automations/automations.py b/src/courier/resources/automations/automations.py index 1ae9add..ebd3aad 100644 --- a/src/courier/resources/automations/automations.py +++ b/src/courier/resources/automations/automations.py @@ -6,6 +6,14 @@ import httpx +from .runs import ( + RunsResource, + AsyncRunsResource, + RunsResourceWithRawResponse, + AsyncRunsResourceWithRawResponse, + RunsResourceWithStreamingResponse, + AsyncRunsResourceWithStreamingResponse, +) from .invoke import ( InvokeResource, AsyncInvokeResource, @@ -43,6 +51,13 @@ def invoke(self) -> InvokeResource: """ return InvokeResource(self._client) + @cached_property + def runs(self) -> RunsResource: + """ + Invoke a stored automation template or an ad hoc automation defined in the request. + """ + return RunsResource(self._client) + @cached_property def with_raw_response(self) -> AutomationsResourceWithRawResponse: """ @@ -124,6 +139,13 @@ def invoke(self) -> AsyncInvokeResource: """ return AsyncInvokeResource(self._client) + @cached_property + def runs(self) -> AsyncRunsResource: + """ + Invoke a stored automation template or an ad hoc automation defined in the request. + """ + return AsyncRunsResource(self._client) + @cached_property def with_raw_response(self) -> AsyncAutomationsResourceWithRawResponse: """ @@ -208,6 +230,13 @@ def invoke(self) -> InvokeResourceWithRawResponse: """ return InvokeResourceWithRawResponse(self._automations.invoke) + @cached_property + def runs(self) -> RunsResourceWithRawResponse: + """ + Invoke a stored automation template or an ad hoc automation defined in the request. + """ + return RunsResourceWithRawResponse(self._automations.runs) + class AsyncAutomationsResourceWithRawResponse: def __init__(self, automations: AsyncAutomationsResource) -> None: @@ -224,6 +253,13 @@ def invoke(self) -> AsyncInvokeResourceWithRawResponse: """ return AsyncInvokeResourceWithRawResponse(self._automations.invoke) + @cached_property + def runs(self) -> AsyncRunsResourceWithRawResponse: + """ + Invoke a stored automation template or an ad hoc automation defined in the request. + """ + return AsyncRunsResourceWithRawResponse(self._automations.runs) + class AutomationsResourceWithStreamingResponse: def __init__(self, automations: AutomationsResource) -> None: @@ -240,6 +276,13 @@ def invoke(self) -> InvokeResourceWithStreamingResponse: """ return InvokeResourceWithStreamingResponse(self._automations.invoke) + @cached_property + def runs(self) -> RunsResourceWithStreamingResponse: + """ + Invoke a stored automation template or an ad hoc automation defined in the request. + """ + return RunsResourceWithStreamingResponse(self._automations.runs) + class AsyncAutomationsResourceWithStreamingResponse: def __init__(self, automations: AsyncAutomationsResource) -> None: @@ -255,3 +298,10 @@ def invoke(self) -> AsyncInvokeResourceWithStreamingResponse: Invoke a stored automation template or an ad hoc automation defined in the request. """ return AsyncInvokeResourceWithStreamingResponse(self._automations.invoke) + + @cached_property + def runs(self) -> AsyncRunsResourceWithStreamingResponse: + """ + Invoke a stored automation template or an ad hoc automation defined in the request. + """ + return AsyncRunsResourceWithStreamingResponse(self._automations.runs) diff --git a/src/courier/resources/automations/runs.py b/src/courier/resources/automations/runs.py new file mode 100644 index 0000000..d93a880 --- /dev/null +++ b/src/courier/resources/automations/runs.py @@ -0,0 +1,330 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +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 +from ...types.automations import run_list_params +from ...types.automation_run_list_response import AutomationRunListResponse +from ...types.automation_run_steps_response import AutomationRunStepsResponse + +__all__ = ["RunsResource", "AsyncRunsResource"] + + +class RunsResource(SyncAPIResource): + """ + Invoke a stored automation template or an ad hoc automation defined in the request. + """ + + @cached_property + def with_raw_response(self) -> RunsResourceWithRawResponse: + """ + 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 RunsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RunsResourceWithStreamingResponse: + """ + 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 RunsResourceWithStreamingResponse(self) + + def list( + self, + *, + cursor: str | Omit = omit, + end_date: str | Omit = omit, + limit: str | Omit = omit, + start_date: str | Omit = omit, + status: str | Omit = omit, + template_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AutomationRunListResponse: + """ + List runs of the workspace's v2 Automations, newest first, filtered by status, + Template, or date range and paged by cursor. Journey (v3) runs are listed by + `GET /journeys/runs` instead — the two surfaces never return each other's runs. + Runs are retained for 95 days. + + Args: + cursor: A cursor token for pagination. Use the `next_cursor` from the previous response + to fetch the next page of results. Treat it as opaque. + + end_date: An inclusive upper bound on `created_at`, in the same format as `start_date`. + + limit: The number of runs to return per page, between `1` and `50`. Defaults to `20`. + Values outside the range are clamped, and a non-numeric value falls back to + `20`. + + start_date: An inclusive lower bound on `created_at`, as an ISO 8601 date or timestamp (e.g. + `2026-08-18` or `2026-08-18T20:06:36.259Z`). Any other format returns `400`. + + status: A comma-separated list of run statuses to filter on, e.g. `PROCESSED,ERROR`. + + template_id: A comma-separated list of Automation Template ids to filter on. + + 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 + """ + return self._get( + "/automations/runs", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "cursor": cursor, + "end_date": end_date, + "limit": limit, + "start_date": start_date, + "status": status, + "template_id": template_id, + }, + run_list_params.RunListParams, + ), + ), + cast_to=AutomationRunListResponse, + ) + + def list_steps( + self, + 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, + ) -> AutomationRunStepsResponse: + """ + List the per-step state of one Automation run, in full — this endpoint is not + paginated. `message_id` is present on send steps that produced a message; follow + it to `GET /messages/{message_id}` for delivery status. A send to a List or an + Audience yields one `message_id` for the request, not one per recipient. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/automations/runs/{id}/steps", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AutomationRunStepsResponse, + ) + + +class AsyncRunsResource(AsyncAPIResource): + """ + Invoke a stored automation template or an ad hoc automation defined in the request. + """ + + @cached_property + def with_raw_response(self) -> AsyncRunsResourceWithRawResponse: + """ + 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 AsyncRunsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRunsResourceWithStreamingResponse: + """ + 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 AsyncRunsResourceWithStreamingResponse(self) + + async def list( + self, + *, + cursor: str | Omit = omit, + end_date: str | Omit = omit, + limit: str | Omit = omit, + start_date: str | Omit = omit, + status: str | Omit = omit, + template_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AutomationRunListResponse: + """ + List runs of the workspace's v2 Automations, newest first, filtered by status, + Template, or date range and paged by cursor. Journey (v3) runs are listed by + `GET /journeys/runs` instead — the two surfaces never return each other's runs. + Runs are retained for 95 days. + + Args: + cursor: A cursor token for pagination. Use the `next_cursor` from the previous response + to fetch the next page of results. Treat it as opaque. + + end_date: An inclusive upper bound on `created_at`, in the same format as `start_date`. + + limit: The number of runs to return per page, between `1` and `50`. Defaults to `20`. + Values outside the range are clamped, and a non-numeric value falls back to + `20`. + + start_date: An inclusive lower bound on `created_at`, as an ISO 8601 date or timestamp (e.g. + `2026-08-18` or `2026-08-18T20:06:36.259Z`). Any other format returns `400`. + + status: A comma-separated list of run statuses to filter on, e.g. `PROCESSED,ERROR`. + + template_id: A comma-separated list of Automation Template ids to filter on. + + 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 + """ + return await self._get( + "/automations/runs", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "cursor": cursor, + "end_date": end_date, + "limit": limit, + "start_date": start_date, + "status": status, + "template_id": template_id, + }, + run_list_params.RunListParams, + ), + ), + cast_to=AutomationRunListResponse, + ) + + async def list_steps( + self, + 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, + ) -> AutomationRunStepsResponse: + """ + List the per-step state of one Automation run, in full — this endpoint is not + paginated. `message_id` is present on send steps that produced a message; follow + it to `GET /messages/{message_id}` for delivery status. A send to a List or an + Audience yields one `message_id` for the request, not one per recipient. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/automations/runs/{id}/steps", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AutomationRunStepsResponse, + ) + + +class RunsResourceWithRawResponse: + def __init__(self, runs: RunsResource) -> None: + self._runs = runs + + self.list = to_raw_response_wrapper( + runs.list, + ) + self.list_steps = to_raw_response_wrapper( + runs.list_steps, + ) + + +class AsyncRunsResourceWithRawResponse: + def __init__(self, runs: AsyncRunsResource) -> None: + self._runs = runs + + self.list = async_to_raw_response_wrapper( + runs.list, + ) + self.list_steps = async_to_raw_response_wrapper( + runs.list_steps, + ) + + +class RunsResourceWithStreamingResponse: + def __init__(self, runs: RunsResource) -> None: + self._runs = runs + + self.list = to_streamed_response_wrapper( + runs.list, + ) + self.list_steps = to_streamed_response_wrapper( + runs.list_steps, + ) + + +class AsyncRunsResourceWithStreamingResponse: + def __init__(self, runs: AsyncRunsResource) -> None: + self._runs = runs + + self.list = async_to_streamed_response_wrapper( + runs.list, + ) + self.list_steps = async_to_streamed_response_wrapper( + runs.list_steps, + ) diff --git a/src/courier/resources/journeys/__init__.py b/src/courier/resources/journeys/__init__.py index f705015..908833c 100644 --- a/src/courier/resources/journeys/__init__.py +++ b/src/courier/resources/journeys/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .runs import ( + RunsResource, + AsyncRunsResource, + RunsResourceWithRawResponse, + AsyncRunsResourceWithRawResponse, + RunsResourceWithStreamingResponse, + AsyncRunsResourceWithStreamingResponse, +) from .journeys import ( JourneysResource, AsyncJourneysResource, @@ -24,6 +32,12 @@ "AsyncTemplatesResourceWithRawResponse", "TemplatesResourceWithStreamingResponse", "AsyncTemplatesResourceWithStreamingResponse", + "RunsResource", + "AsyncRunsResource", + "RunsResourceWithRawResponse", + "AsyncRunsResourceWithRawResponse", + "RunsResourceWithStreamingResponse", + "AsyncRunsResourceWithStreamingResponse", "JourneysResource", "AsyncJourneysResource", "JourneysResourceWithRawResponse", diff --git a/src/courier/resources/journeys/journeys.py b/src/courier/resources/journeys/journeys.py index 7bb1439..5f43631 100644 --- a/src/courier/resources/journeys/journeys.py +++ b/src/courier/resources/journeys/journeys.py @@ -7,6 +7,14 @@ import httpx +from .runs import ( + RunsResource, + AsyncRunsResource, + RunsResourceWithRawResponse, + AsyncRunsResourceWithRawResponse, + RunsResourceWithStreamingResponse, + AsyncRunsResourceWithStreamingResponse, +) from ...types import ( JourneyState, journey_list_params, @@ -59,6 +67,13 @@ def templates(self) -> TemplatesResource: """ return TemplatesResource(self._client) + @cached_property + def runs(self) -> RunsResource: + """ + Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them. + """ + return RunsResource(self._client) + @cached_property def with_raw_response(self) -> JourneysResourceWithRawResponse: """ @@ -579,6 +594,13 @@ def templates(self) -> AsyncTemplatesResource: """ return AsyncTemplatesResource(self._client) + @cached_property + def runs(self) -> AsyncRunsResource: + """ + Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them. + """ + return AsyncRunsResource(self._client) + @cached_property def with_raw_response(self) -> AsyncJourneysResourceWithRawResponse: """ @@ -1126,6 +1148,13 @@ def templates(self) -> TemplatesResourceWithRawResponse: """ return TemplatesResourceWithRawResponse(self._journeys.templates) + @cached_property + def runs(self) -> RunsResourceWithRawResponse: + """ + Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them. + """ + return RunsResourceWithRawResponse(self._journeys.runs) + class AsyncJourneysResourceWithRawResponse: def __init__(self, journeys: AsyncJourneysResource) -> None: @@ -1166,6 +1195,13 @@ def templates(self) -> AsyncTemplatesResourceWithRawResponse: """ return AsyncTemplatesResourceWithRawResponse(self._journeys.templates) + @cached_property + def runs(self) -> AsyncRunsResourceWithRawResponse: + """ + Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them. + """ + return AsyncRunsResourceWithRawResponse(self._journeys.runs) + class JourneysResourceWithStreamingResponse: def __init__(self, journeys: JourneysResource) -> None: @@ -1206,6 +1242,13 @@ def templates(self) -> TemplatesResourceWithStreamingResponse: """ return TemplatesResourceWithStreamingResponse(self._journeys.templates) + @cached_property + def runs(self) -> RunsResourceWithStreamingResponse: + """ + Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them. + """ + return RunsResourceWithStreamingResponse(self._journeys.runs) + class AsyncJourneysResourceWithStreamingResponse: def __init__(self, journeys: AsyncJourneysResource) -> None: @@ -1245,3 +1288,10 @@ def templates(self) -> AsyncTemplatesResourceWithStreamingResponse: Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them. """ return AsyncTemplatesResourceWithStreamingResponse(self._journeys.templates) + + @cached_property + def runs(self) -> AsyncRunsResourceWithStreamingResponse: + """ + Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them. + """ + return AsyncRunsResourceWithStreamingResponse(self._journeys.runs) diff --git a/src/courier/resources/journeys/runs.py b/src/courier/resources/journeys/runs.py new file mode 100644 index 0000000..690f070 --- /dev/null +++ b/src/courier/resources/journeys/runs.py @@ -0,0 +1,419 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +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 +from ...types.journeys import run_list_params +from ...types.journey_run_response import JourneyRunResponse +from ...types.journey_run_list_response import JourneyRunListResponse +from ...types.journey_run_steps_response import JourneyRunStepsResponse + +__all__ = ["RunsResource", "AsyncRunsResource"] + + +class RunsResource(SyncAPIResource): + """ + Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them. + """ + + @cached_property + def with_raw_response(self) -> RunsResourceWithRawResponse: + """ + 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 RunsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RunsResourceWithStreamingResponse: + """ + 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 RunsResourceWithStreamingResponse(self) + + def retrieve( + self, + run_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, + ) -> JourneyRunResponse: + """Fetch one Journey run by id. + + Returns `404` for an unknown run, a run belonging + to another workspace, a run past the 95-day retention window, or an Automation + run id — the same body in every case, so the response never reveals whether a + run exists elsewhere. + + 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 run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return self._get( + path_template("/journeys/runs/{run_id}", run_id=run_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=JourneyRunResponse, + ) + + def list( + self, + *, + cursor: str | Omit = omit, + end_date: str | Omit = omit, + limit: str | Omit = omit, + start_date: str | Omit = omit, + status: str | Omit = omit, + template_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> JourneyRunListResponse: + """ + List runs of the workspace's Journeys, newest first, filtered by status, + Journey, or date range and paged by cursor. Runs of v2 Automations are listed by + `GET /automations/runs` instead — the two surfaces never return each other's + runs. Runs are retained for 95 days. + + Args: + cursor: A cursor token for pagination. Use the `next_cursor` from the previous response + to fetch the next page of results. Treat it as opaque. + + end_date: An inclusive upper bound on `created_at`, in the same format as `start_date`. + + limit: The number of runs to return per page, between `1` and `50`. Defaults to `20`. + Values outside the range are clamped, and a non-numeric value falls back to + `20`. + + start_date: An inclusive lower bound on `created_at`, as an ISO 8601 date or timestamp (e.g. + `2026-08-18` or `2026-08-18T20:06:36.259Z`). Any other format returns `400`. + + status: A comma-separated list of run statuses to filter on, e.g. `PROCESSED,ERROR`. + + template_id: A comma-separated list of Journey ids to filter on. + + 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 + """ + return self._get( + "/journeys/runs", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "cursor": cursor, + "end_date": end_date, + "limit": limit, + "start_date": start_date, + "status": status, + "template_id": template_id, + }, + run_list_params.RunListParams, + ), + ), + cast_to=JourneyRunListResponse, + ) + + def list_steps( + self, + run_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, + ) -> JourneyRunStepsResponse: + """ + List the per-node state of one Journey run, in full — this endpoint is not + paginated. Each step's `node_id` is the id of the node in the published Journey, + so a step maps directly onto the Journey graph. `message_id` is present on send + steps that produced a message; follow it to `GET /messages/{message_id}` for + delivery status. + + 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 run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return self._get( + path_template("/journeys/runs/{run_id}/steps", run_id=run_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=JourneyRunStepsResponse, + ) + + +class AsyncRunsResource(AsyncAPIResource): + """ + Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them. + """ + + @cached_property + def with_raw_response(self) -> AsyncRunsResourceWithRawResponse: + """ + 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 AsyncRunsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRunsResourceWithStreamingResponse: + """ + 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 AsyncRunsResourceWithStreamingResponse(self) + + async def retrieve( + self, + run_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, + ) -> JourneyRunResponse: + """Fetch one Journey run by id. + + Returns `404` for an unknown run, a run belonging + to another workspace, a run past the 95-day retention window, or an Automation + run id — the same body in every case, so the response never reveals whether a + run exists elsewhere. + + 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 run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return await self._get( + path_template("/journeys/runs/{run_id}", run_id=run_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=JourneyRunResponse, + ) + + async def list( + self, + *, + cursor: str | Omit = omit, + end_date: str | Omit = omit, + limit: str | Omit = omit, + start_date: str | Omit = omit, + status: str | Omit = omit, + template_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> JourneyRunListResponse: + """ + List runs of the workspace's Journeys, newest first, filtered by status, + Journey, or date range and paged by cursor. Runs of v2 Automations are listed by + `GET /automations/runs` instead — the two surfaces never return each other's + runs. Runs are retained for 95 days. + + Args: + cursor: A cursor token for pagination. Use the `next_cursor` from the previous response + to fetch the next page of results. Treat it as opaque. + + end_date: An inclusive upper bound on `created_at`, in the same format as `start_date`. + + limit: The number of runs to return per page, between `1` and `50`. Defaults to `20`. + Values outside the range are clamped, and a non-numeric value falls back to + `20`. + + start_date: An inclusive lower bound on `created_at`, as an ISO 8601 date or timestamp (e.g. + `2026-08-18` or `2026-08-18T20:06:36.259Z`). Any other format returns `400`. + + status: A comma-separated list of run statuses to filter on, e.g. `PROCESSED,ERROR`. + + template_id: A comma-separated list of Journey ids to filter on. + + 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 + """ + return await self._get( + "/journeys/runs", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "cursor": cursor, + "end_date": end_date, + "limit": limit, + "start_date": start_date, + "status": status, + "template_id": template_id, + }, + run_list_params.RunListParams, + ), + ), + cast_to=JourneyRunListResponse, + ) + + async def list_steps( + self, + run_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, + ) -> JourneyRunStepsResponse: + """ + List the per-node state of one Journey run, in full — this endpoint is not + paginated. Each step's `node_id` is the id of the node in the published Journey, + so a step maps directly onto the Journey graph. `message_id` is present on send + steps that produced a message; follow it to `GET /messages/{message_id}` for + delivery status. + + 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 run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return await self._get( + path_template("/journeys/runs/{run_id}/steps", run_id=run_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=JourneyRunStepsResponse, + ) + + +class RunsResourceWithRawResponse: + def __init__(self, runs: RunsResource) -> None: + self._runs = runs + + self.retrieve = to_raw_response_wrapper( + runs.retrieve, + ) + self.list = to_raw_response_wrapper( + runs.list, + ) + self.list_steps = to_raw_response_wrapper( + runs.list_steps, + ) + + +class AsyncRunsResourceWithRawResponse: + def __init__(self, runs: AsyncRunsResource) -> None: + self._runs = runs + + self.retrieve = async_to_raw_response_wrapper( + runs.retrieve, + ) + self.list = async_to_raw_response_wrapper( + runs.list, + ) + self.list_steps = async_to_raw_response_wrapper( + runs.list_steps, + ) + + +class RunsResourceWithStreamingResponse: + def __init__(self, runs: RunsResource) -> None: + self._runs = runs + + self.retrieve = to_streamed_response_wrapper( + runs.retrieve, + ) + self.list = to_streamed_response_wrapper( + runs.list, + ) + self.list_steps = to_streamed_response_wrapper( + runs.list_steps, + ) + + +class AsyncRunsResourceWithStreamingResponse: + def __init__(self, runs: AsyncRunsResource) -> None: + self._runs = runs + + self.retrieve = async_to_streamed_response_wrapper( + runs.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + runs.list, + ) + self.list_steps = async_to_streamed_response_wrapper( + runs.list_steps, + ) diff --git a/src/courier/types/__init__.py b/src/courier/types/__init__.py index 2b929ce..16d98eb 100644 --- a/src/courier/types/__init__.py +++ b/src/courier/types/__init__.py @@ -110,6 +110,7 @@ from .logo_param import LogoParam as LogoParam from .audit_event import AuditEvent as AuditEvent from .icons_param import IconsParam as IconsParam +from .journey_run import JourneyRun as JourneyRun from .brand_colors import BrandColors as BrandColors from .email_footer import EmailFooter as EmailFooter from .email_header import EmailHeader as EmailHeader @@ -127,6 +128,7 @@ from .base_check_param import BaseCheckParam as BaseCheckParam from .email_head_param import EmailHeadParam as EmailHeadParam from .journey_response import JourneyResponse as JourneyResponse +from .journey_run_step import JourneyRunStep as JourneyRunStep from .list_list_params import ListListParams as ListListParams from .brand_list_params import BrandListParams as BrandListParams from .journey_exit_node import JourneyExitNode as JourneyExitNode @@ -143,6 +145,7 @@ from .list_update_params import ListUpdateParams as ListUpdateParams from .tenant_association import TenantAssociation as TenantAssociation from .tenant_list_params import TenantListParams as TenantListParams +from .automation_run_step import AutomationRunStep as AutomationRunStep from .automation_template import AutomationTemplate as AutomationTemplate from .brand_create_params import BrandCreateParams as BrandCreateParams from .brand_list_response import BrandListResponse as BrandListResponse @@ -158,6 +161,7 @@ from .brand_snippets_param import BrandSnippetsParam as BrandSnippetsParam from .brand_template_param import BrandTemplateParam as BrandTemplateParam from .inbound_bulk_message import InboundBulkMessage as InboundBulkMessage +from .journey_run_response import JourneyRunResponse as JourneyRunResponse from .journey_version_item import JourneyVersionItem as JourneyVersionItem from .provider_list_params import ProviderListParams as ProviderListParams from .tenant_list_response import TenantListResponse as TenantListResponse @@ -170,6 +174,7 @@ from .journey_cancel_params import JourneyCancelParams as JourneyCancelParams from .journey_create_params import JourneyCreateParams as JourneyCreateParams from .journey_invoke_params import JourneyInvokeParams as JourneyInvokeParams +from .journey_run_list_item import JourneyRunListItem as JourneyRunListItem from .message_list_response import MessageListResponse as MessageListResponse from .profile_create_params import ProfileCreateParams as ProfileCreateParams from .profile_update_params import ProfileUpdateParams as ProfileUpdateParams @@ -206,6 +211,7 @@ from .providers_catalog_entry import ProvidersCatalogEntry as ProvidersCatalogEntry from .widget_background_param import WidgetBackgroundParam as WidgetBackgroundParam from .audience_update_response import AudienceUpdateResponse as AudienceUpdateResponse +from .automation_run_list_item import AutomationRunListItem as AutomationRunListItem from .bulk_create_job_response import BulkCreateJobResponse as BulkCreateJobResponse from .bulk_list_users_response import BulkListUsersResponse as BulkListUsersResponse from .journey_conditions_field import JourneyConditionsField as JourneyConditionsField @@ -226,6 +232,7 @@ from .broadcast_schedule_params import BroadcastScheduleParams as BroadcastScheduleParams from .default_preferences_param import DefaultPreferencesParam as DefaultPreferencesParam from .inbound_bulk_message_user import InboundBulkMessageUser as InboundBulkMessageUser +from .journey_run_list_response import JourneyRunListResponse as JourneyRunListResponse from .message_retrieve_response import MessageRetrieveResponse as MessageRetrieveResponse from .profile_retrieve_response import ProfileRetrieveResponse as ProfileRetrieveResponse from .translation_update_params import TranslationUpdateParams as TranslationUpdateParams @@ -235,6 +242,7 @@ from .inbound_bulk_message_param import InboundBulkMessageParam as InboundBulkMessageParam from .inbound_track_event_params import InboundTrackEventParams as InboundTrackEventParams from .journey_experiment_variant import JourneyExperimentVariant as JourneyExperimentVariant +from .journey_run_steps_response import JourneyRunStepsResponse as JourneyRunStepsResponse from .notification_create_params import NotificationCreateParams as NotificationCreateParams from .notification_list_response import NotificationListResponse as NotificationListResponse from .tenant_list_users_response import TenantListUsersResponse as TenantListUsersResponse @@ -247,18 +255,22 @@ from .notification_template_state import NotificationTemplateState as NotificationTemplateState from .tenant_template_input_param import TenantTemplateInputParam as TenantTemplateInputParam from .audience_list_members_params import AudienceListMembersParams as AudienceListMembersParams +from .automation_run_list_response import AutomationRunListResponse as AutomationRunListResponse from .broadcast_put_content_params import BroadcastPutContentParams as BroadcastPutContentParams from .cancel_journey_request_param import CancelJourneyRequestParam as CancelJourneyRequestParam from .inbound_track_event_response import InboundTrackEventResponse as InboundTrackEventResponse from .journey_condition_atom_param import JourneyConditionAtomParam as JourneyConditionAtomParam from .journey_segment_trigger_node import JourneySegmentTriggerNode as JourneySegmentTriggerNode from .journey_throttle_static_node import JourneyThrottleStaticNode as JourneyThrottleStaticNode +from .journey_webhook_trigger_node import JourneyWebhookTriggerNode as JourneyWebhookTriggerNode from .notification_retrieve_params import NotificationRetrieveParams as NotificationRetrieveParams from .publish_preferences_response import PublishPreferencesResponse as PublishPreferencesResponse from .put_tenant_template_response import PutTenantTemplateResponse as PutTenantTemplateResponse from .routing_strategy_list_params import RoutingStrategyListParams as RoutingStrategyListParams from .subscription_topic_new_param import SubscriptionTopicNewParam as SubscriptionTopicNewParam +from .automation_run_steps_response import AutomationRunStepsResponse as AutomationRunStepsResponse from .digest_instance_list_response import DigestInstanceListResponse as DigestInstanceListResponse +from .journey_audience_trigger_node import JourneyAudienceTriggerNode as JourneyAudienceTriggerNode from .journey_condition_group_param import JourneyConditionGroupParam as JourneyConditionGroupParam from .journey_fetch_get_delete_node import JourneyFetchGetDeleteNode as JourneyFetchGetDeleteNode from .journey_template_get_response import JourneyTemplateGetResponse as JourneyTemplateGetResponse @@ -294,8 +306,10 @@ from .workspace_preference_get_response import WorkspacePreferenceGetResponse as WorkspacePreferenceGetResponse from .journey_segment_trigger_node_param import JourneySegmentTriggerNodeParam as JourneySegmentTriggerNodeParam from .journey_throttle_static_node_param import JourneyThrottleStaticNodeParam as JourneyThrottleStaticNodeParam +from .journey_webhook_trigger_node_param import JourneyWebhookTriggerNodeParam as JourneyWebhookTriggerNodeParam from .workspace_preference_create_params import WorkspacePreferenceCreateParams as WorkspacePreferenceCreateParams from .workspace_preference_list_response import WorkspacePreferenceListResponse as WorkspacePreferenceListResponse +from .journey_audience_trigger_node_param import JourneyAudienceTriggerNodeParam as JourneyAudienceTriggerNodeParam from .journey_fetch_get_delete_node_param import JourneyFetchGetDeleteNodeParam as JourneyFetchGetDeleteNodeParam from .journey_throttle_dynamic_node_param import JourneyThrottleDynamicNodeParam as JourneyThrottleDynamicNodeParam from .notification_template_payload_param import NotificationTemplatePayloadParam as NotificationTemplatePayloadParam diff --git a/src/courier/types/automation_run_list_item.py b/src/courier/types/automation_run_list_item.py new file mode 100644 index 0000000..0951b82 --- /dev/null +++ b/src/courier/types/automation_run_list_item.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["AutomationRunListItem"] + + +class AutomationRunListItem(BaseModel): + """An Automation run as it appears in a list response.""" + + run_id: str + """A unique identifier representing the run.""" + + source: List[str] + """Internal provenance strings describing what started the run, e.g. + + `invoke/` or `segment/page/Pricing Page`. Diagnostic only — the + format is unstable and should not be parsed. + """ + + created_at: Optional[str] = None + """When the run started, as an ISO 8601 timestamp.""" + + status: Optional[str] = None + """ + The state of the run: `PROCESSING`, `PROCESSED`, `WAITING`, `CANCELED`, `ERROR`, + `THROTTLED`, or `NOT PROCESSED`. Not an enum — new values have been added + before. + """ + + template_id: Optional[str] = None + """The id of the Automation Template this run belongs to.""" diff --git a/src/courier/types/automation_run_list_response.py b/src/courier/types/automation_run_list_response.py new file mode 100644 index 0000000..c637d8d --- /dev/null +++ b/src/courier/types/automation_run_list_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel +from .automation_run_list_item import AutomationRunListItem + +__all__ = ["AutomationRunListResponse"] + + +class AutomationRunListResponse(BaseModel): + """A page of Automation runs.""" + + runs: List[AutomationRunListItem] + + next_cursor: Optional[str] = None + """Pass back as `cursor` to fetch the next page. Absent on the last page.""" diff --git a/src/courier/types/automation_run_step.py b/src/courier/types/automation_run_step.py new file mode 100644 index 0000000..f743bea --- /dev/null +++ b/src/courier/types/automation_run_step.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["AutomationRunStep"] + + +class AutomationRunStep(BaseModel): + """One executed step of an Automation run.""" + + action: str + """The kind of step that ran, e.g. `send`, `delay`, or `update-profile`.""" + + status: str + """The state of the step: the seven run statuses, plus `SKIPPED` and `COMPUTING`. + + Not an enum — new values have been added before. + """ + + created_at: Optional[str] = None + """When the step started, as an ISO 8601 timestamp.""" + + message_id: Optional[str] = None + """The message this step produced, present on send steps. + + Pass it to `GET /messages/{message_id}` for delivery status. A send to a List or + an Audience yields one id for the request, not one per recipient. + """ + + step_id: Optional[str] = None + """A unique identifier representing the step.""" + + updated_at: Optional[str] = None + """When the step last changed state, as an ISO 8601 timestamp.""" diff --git a/src/courier/types/automation_run_steps_response.py b/src/courier/types/automation_run_steps_response.py new file mode 100644 index 0000000..9be054f --- /dev/null +++ b/src/courier/types/automation_run_steps_response.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from .._models import BaseModel +from .automation_run_step import AutomationRunStep + +__all__ = ["AutomationRunStepsResponse"] + + +class AutomationRunStepsResponse(BaseModel): + """Every step of an Automation run. Not paginated.""" + + steps: List[AutomationRunStep] diff --git a/src/courier/types/automations/__init__.py b/src/courier/types/automations/__init__.py index 930ff16..13b1113 100644 --- a/src/courier/types/automations/__init__.py +++ b/src/courier/types/automations/__init__.py @@ -2,5 +2,6 @@ from __future__ import annotations +from .run_list_params import RunListParams as RunListParams from .invoke_invoke_ad_hoc_params import InvokeInvokeAdHocParams as InvokeInvokeAdHocParams from .invoke_invoke_by_template_params import InvokeInvokeByTemplateParams as InvokeInvokeByTemplateParams diff --git a/src/courier/types/automations/run_list_params.py b/src/courier/types/automations/run_list_params.py new file mode 100644 index 0000000..b9a01e1 --- /dev/null +++ b/src/courier/types/automations/run_list_params.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["RunListParams"] + + +class RunListParams(TypedDict, total=False): + cursor: str + """A cursor token for pagination. + + Use the `next_cursor` from the previous response to fetch the next page of + results. Treat it as opaque. + """ + + end_date: str + """An inclusive upper bound on `created_at`, in the same format as `start_date`.""" + + limit: str + """The number of runs to return per page, between `1` and `50`. + + Defaults to `20`. Values outside the range are clamped, and a non-numeric value + falls back to `20`. + """ + + start_date: str + """An inclusive lower bound on `created_at`, as an ISO 8601 date or timestamp (e.g. + + `2026-08-18` or `2026-08-18T20:06:36.259Z`). Any other format returns `400`. + """ + + status: str + """A comma-separated list of run statuses to filter on, e.g. `PROCESSED,ERROR`.""" + + template_id: str + """A comma-separated list of Automation Template ids to filter on.""" diff --git a/src/courier/types/journey_audience_trigger_node.py b/src/courier/types/journey_audience_trigger_node.py new file mode 100644 index 0000000..f3a7107 --- /dev/null +++ b/src/courier/types/journey_audience_trigger_node.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .journey_conditions_field import JourneyConditionsField + +__all__ = ["JourneyAudienceTriggerNode"] + + +class JourneyAudienceTriggerNode(BaseModel): + """Trigger fired when a user newly matches an Audience. + + Leaving and re-joining the Audience re-enters the Journey. Membership is new-members-only: users already in the Audience when the Journey is published do not enter. Unlike the v2 Automations audience trigger, there is no member scope, event type, or frequency mode to configure, and `audience_id` must name one Audience — wildcards are not supported. + """ + + audience_id: str + """The Audience to watch. + + Must name a single Audience; wildcards are not supported. + """ + + trigger_type: Literal["audience"] + + type: Literal["trigger"] + + id: Optional[str] = None + + conditions: Optional[JourneyConditionsField] = None + """Condition spec for a journey node. + + Accepts a single condition atom, an AND/OR group, or an AND/OR nested group. + Omit the `conditions` property entirely to express "no conditions". + """ diff --git a/src/courier/types/journey_audience_trigger_node_param.py b/src/courier/types/journey_audience_trigger_node_param.py new file mode 100644 index 0000000..bb9017d --- /dev/null +++ b/src/courier/types/journey_audience_trigger_node_param.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from .journey_conditions_field_param import JourneyConditionsFieldParam + +__all__ = ["JourneyAudienceTriggerNodeParam"] + + +class JourneyAudienceTriggerNodeParam(TypedDict, total=False): + """Trigger fired when a user newly matches an Audience. + + Leaving and re-joining the Audience re-enters the Journey. Membership is new-members-only: users already in the Audience when the Journey is published do not enter. Unlike the v2 Automations audience trigger, there is no member scope, event type, or frequency mode to configure, and `audience_id` must name one Audience — wildcards are not supported. + """ + + audience_id: Required[str] + """The Audience to watch. + + Must name a single Audience; wildcards are not supported. + """ + + trigger_type: Required[Literal["audience"]] + + type: Required[Literal["trigger"]] + + id: str + + conditions: JourneyConditionsFieldParam + """Condition spec for a journey node. + + Accepts a single condition atom, an AND/OR group, or an AND/OR nested group. + Omit the `conditions` property entirely to express "no conditions". + """ diff --git a/src/courier/types/journey_node.py b/src/courier/types/journey_node.py index 33420ba..69043ba 100644 --- a/src/courier/types/journey_node.py +++ b/src/courier/types/journey_node.py @@ -15,6 +15,8 @@ from .journey_fetch_post_put_node import JourneyFetchPostPutNode from .journey_segment_trigger_node import JourneySegmentTriggerNode from .journey_throttle_static_node import JourneyThrottleStaticNode +from .journey_webhook_trigger_node import JourneyWebhookTriggerNode +from .journey_audience_trigger_node import JourneyAudienceTriggerNode from .journey_fetch_get_delete_node import JourneyFetchGetDeleteNode from .journey_throttle_dynamic_node import JourneyThrottleDynamicNode from .journey_api_invoke_trigger_node import JourneyAPIInvokeTriggerNode @@ -153,6 +155,8 @@ class JourneyBranchNode(BaseModel): JourneyNode: TypeAlias = Union[ JourneyAPIInvokeTriggerNode, JourneySegmentTriggerNode, + JourneyAudienceTriggerNode, + JourneyWebhookTriggerNode, JourneySendNode, JourneyDelayDurationNode, JourneyDelayUntilNode, diff --git a/src/courier/types/journey_node_param.py b/src/courier/types/journey_node_param.py index 65b0d1b..5d4e069 100644 --- a/src/courier/types/journey_node_param.py +++ b/src/courier/types/journey_node_param.py @@ -14,6 +14,8 @@ from .journey_fetch_post_put_node_param import JourneyFetchPostPutNodeParam from .journey_segment_trigger_node_param import JourneySegmentTriggerNodeParam from .journey_throttle_static_node_param import JourneyThrottleStaticNodeParam +from .journey_webhook_trigger_node_param import JourneyWebhookTriggerNodeParam +from .journey_audience_trigger_node_param import JourneyAudienceTriggerNodeParam from .journey_fetch_get_delete_node_param import JourneyFetchGetDeleteNodeParam from .journey_throttle_dynamic_node_param import JourneyThrottleDynamicNodeParam from .journey_api_invoke_trigger_node_param import JourneyAPIInvokeTriggerNodeParam @@ -152,6 +154,8 @@ class JourneyBranchNode(TypedDict, total=False): JourneyNodeParam: TypeAlias = Union[ JourneyAPIInvokeTriggerNodeParam, JourneySegmentTriggerNodeParam, + JourneyAudienceTriggerNodeParam, + JourneyWebhookTriggerNodeParam, JourneySendNodeParam, JourneyDelayDurationNodeParam, JourneyDelayUntilNodeParam, diff --git a/src/courier/types/journey_run.py b/src/courier/types/journey_run.py new file mode 100644 index 0000000..cc68872 --- /dev/null +++ b/src/courier/types/journey_run.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["JourneyRun"] + + +class JourneyRun(BaseModel): + """One run of a Journey. + + `status` and `created_at` are absent on a small number of legacy runs stored without them. + """ + + run_id: str + """A unique identifier representing the run.""" + + source: List[str] + """Internal provenance strings describing what started the run, e.g. + + `invoke/` or `segment/page/Pricing Page`. Diagnostic only — the + format is unstable and should not be parsed. + """ + + created_at: Optional[str] = None + """When the run started, as an ISO 8601 timestamp.""" + + status: Optional[str] = None + """ + The state of the run: `PROCESSING`, `PROCESSED`, `WAITING`, `CANCELED`, `ERROR`, + `THROTTLED`, or `NOT PROCESSED`. Not an enum — new values have been added + before. + """ + + template_id: Optional[str] = None + """The id of the Journey this run belongs to.""" + + updated_at: Optional[str] = None + """When the run last changed state, as an ISO 8601 timestamp.""" diff --git a/src/courier/types/journey_run_list_item.py b/src/courier/types/journey_run_list_item.py new file mode 100644 index 0000000..f908ecc --- /dev/null +++ b/src/courier/types/journey_run_list_item.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["JourneyRunListItem"] + + +class JourneyRunListItem(BaseModel): + """A Journey run as it appears in a list response, without `updated_at`.""" + + run_id: str + """A unique identifier representing the run.""" + + source: List[str] + """Internal provenance strings describing what started the run. Diagnostic only.""" + + created_at: Optional[str] = None + """When the run started, as an ISO 8601 timestamp.""" + + status: Optional[str] = None + """The state of the run. See `JourneyRun.status` for the values it takes.""" + + template_id: Optional[str] = None + """The id of the Journey this run belongs to.""" diff --git a/src/courier/types/journey_run_list_response.py b/src/courier/types/journey_run_list_response.py new file mode 100644 index 0000000..3033142 --- /dev/null +++ b/src/courier/types/journey_run_list_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel +from .journey_run_list_item import JourneyRunListItem + +__all__ = ["JourneyRunListResponse"] + + +class JourneyRunListResponse(BaseModel): + """A page of Journey runs.""" + + runs: List[JourneyRunListItem] + + next_cursor: Optional[str] = None + """Pass back as `cursor` to fetch the next page. Absent on the last page.""" + + prev_cursor: Optional[str] = None + """Pass back as `cursor` to fetch the previous page. Absent on the first page.""" diff --git a/src/courier/types/journey_run_response.py b/src/courier/types/journey_run_response.py new file mode 100644 index 0000000..4fa5b8e --- /dev/null +++ b/src/courier/types/journey_run_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel +from .journey_run import JourneyRun + +__all__ = ["JourneyRunResponse"] + + +class JourneyRunResponse(BaseModel): + """A single Journey run.""" + + run: JourneyRun + """One run of a Journey. + + `status` and `created_at` are absent on a small number of legacy runs stored + without them. + """ diff --git a/src/courier/types/journey_run_step.py b/src/courier/types/journey_run_step.py new file mode 100644 index 0000000..17e0c43 --- /dev/null +++ b/src/courier/types/journey_run_step.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["JourneyRunStep"] + + +class JourneyRunStep(BaseModel): + """One executed node of a Journey run. + + `node_id` is the id of the node in the published Journey, so a step maps directly onto the Journey graph. + """ + + action: str + """The kind of node that ran, e.g. `send`, `delay`, or `exit`.""" + + status: str + """The state of the step: the seven run statuses, plus `SKIPPED` and `COMPUTING`. + + Not an enum — new values have been added before. + """ + + created_at: Optional[str] = None + """When the step started, as an ISO 8601 timestamp.""" + + message_id: Optional[str] = None + """The message this step produced, present on send steps. + + Pass it to `GET /messages/{message_id}` for delivery status. A send to a List or + an Audience yields one id for the request, not one per recipient. + """ + + node_id: Optional[str] = None + """The id of the node in the published Journey that this step executed.""" + + updated_at: Optional[str] = None + """When the step last changed state, as an ISO 8601 timestamp.""" diff --git a/src/courier/types/journey_run_steps_response.py b/src/courier/types/journey_run_steps_response.py new file mode 100644 index 0000000..e02b6ff --- /dev/null +++ b/src/courier/types/journey_run_steps_response.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from .._models import BaseModel +from .journey_run_step import JourneyRunStep + +__all__ = ["JourneyRunStepsResponse"] + + +class JourneyRunStepsResponse(BaseModel): + """Every step of a Journey run. Not paginated.""" + + steps: List[JourneyRunStep] diff --git a/src/courier/types/journey_segment_trigger_node.py b/src/courier/types/journey_segment_trigger_node.py index 74fb186..bd297c5 100644 --- a/src/courier/types/journey_segment_trigger_node.py +++ b/src/courier/types/journey_segment_trigger_node.py @@ -10,9 +10,12 @@ class JourneySegmentTriggerNode(BaseModel): - """Trigger fired by a segment event (`identify`, `group`, or `track`).""" + """Trigger fired by a segment event (`identify`, `group`, `track`, or `page`). - request_type: Literal["identify", "group", "track"] + A trigger with no `event_id` fires on any event of its type — the only shape `identify` and `group` can take, and the one that catches a stock `analytics.page()` call. + """ + + request_type: Literal["identify", "group", "track", "page"] trigger_type: Literal["segment"] diff --git a/src/courier/types/journey_segment_trigger_node_param.py b/src/courier/types/journey_segment_trigger_node_param.py index 2dcbba2..af44cc8 100644 --- a/src/courier/types/journey_segment_trigger_node_param.py +++ b/src/courier/types/journey_segment_trigger_node_param.py @@ -10,9 +10,12 @@ class JourneySegmentTriggerNodeParam(TypedDict, total=False): - """Trigger fired by a segment event (`identify`, `group`, or `track`).""" + """Trigger fired by a segment event (`identify`, `group`, `track`, or `page`). - request_type: Required[Literal["identify", "group", "track"]] + A trigger with no `event_id` fires on any event of its type — the only shape `identify` and `group` can take, and the one that catches a stock `analytics.page()` call. + """ + + request_type: Required[Literal["identify", "group", "track", "page"]] trigger_type: Required[Literal["segment"]] diff --git a/src/courier/types/journey_webhook_trigger_node.py b/src/courier/types/journey_webhook_trigger_node.py new file mode 100644 index 0000000..6e13a8c --- /dev/null +++ b/src/courier/types/journey_webhook_trigger_node.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .journey_conditions_field import JourneyConditionsField + +__all__ = ["JourneyWebhookTriggerNode"] + + +class JourneyWebhookTriggerNode(BaseModel): + """ + Trigger fired when an external system POSTs to the webhook URL minted for `event_source`. Narrow it to one event with `event_id`, or omit `event_id` to accept every event delivered to the URL. + """ + + event_source: str + """The provider key the webhook URL is minted for. + + Required, and must not contain a forward slash. + """ + + trigger_type: Literal["webhook"] + + type: Literal["trigger"] + + id: Optional[str] = None + + conditions: Optional[JourneyConditionsField] = None + """Condition spec for a journey node. + + Accepts a single condition atom, an AND/OR group, or an AND/OR nested group. + Omit the `conditions` property entirely to express "no conditions". + """ + + event_id: Optional[str] = None + """An optional event filter, matched against the payload's `event` field. + + A sender that supplies no `event` matches the literal `custom`. Must not contain + a forward slash. Omit to accept every event delivered to the URL. + """ diff --git a/src/courier/types/journey_webhook_trigger_node_param.py b/src/courier/types/journey_webhook_trigger_node_param.py new file mode 100644 index 0000000..d58022f --- /dev/null +++ b/src/courier/types/journey_webhook_trigger_node_param.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from .journey_conditions_field_param import JourneyConditionsFieldParam + +__all__ = ["JourneyWebhookTriggerNodeParam"] + + +class JourneyWebhookTriggerNodeParam(TypedDict, total=False): + """ + Trigger fired when an external system POSTs to the webhook URL minted for `event_source`. Narrow it to one event with `event_id`, or omit `event_id` to accept every event delivered to the URL. + """ + + event_source: Required[str] + """The provider key the webhook URL is minted for. + + Required, and must not contain a forward slash. + """ + + trigger_type: Required[Literal["webhook"]] + + type: Required[Literal["trigger"]] + + id: str + + conditions: JourneyConditionsFieldParam + """Condition spec for a journey node. + + Accepts a single condition atom, an AND/OR group, or an AND/OR nested group. + Omit the `conditions` property entirely to express "no conditions". + """ + + event_id: str + """An optional event filter, matched against the payload's `event` field. + + A sender that supplies no `event` matches the literal `custom`. Must not contain + a forward slash. Omit to accept every event delivered to the URL. + """ diff --git a/src/courier/types/journeys/__init__.py b/src/courier/types/journeys/__init__.py index 969be02..a2bbc26 100644 --- a/src/courier/types/journeys/__init__.py +++ b/src/courier/types/journeys/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations +from .run_list_params import RunListParams as RunListParams from .template_list_params import TemplateListParams as TemplateListParams from .template_create_params import TemplateCreateParams as TemplateCreateParams from .template_publish_params import TemplatePublishParams as TemplatePublishParams diff --git a/src/courier/types/journeys/run_list_params.py b/src/courier/types/journeys/run_list_params.py new file mode 100644 index 0000000..34201aa --- /dev/null +++ b/src/courier/types/journeys/run_list_params.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["RunListParams"] + + +class RunListParams(TypedDict, total=False): + cursor: str + """A cursor token for pagination. + + Use the `next_cursor` from the previous response to fetch the next page of + results. Treat it as opaque. + """ + + end_date: str + """An inclusive upper bound on `created_at`, in the same format as `start_date`.""" + + limit: str + """The number of runs to return per page, between `1` and `50`. + + Defaults to `20`. Values outside the range are clamped, and a non-numeric value + falls back to `20`. + """ + + start_date: str + """An inclusive lower bound on `created_at`, as an ISO 8601 date or timestamp (e.g. + + `2026-08-18` or `2026-08-18T20:06:36.259Z`). Any other format returns `400`. + """ + + status: str + """A comma-separated list of run statuses to filter on, e.g. `PROCESSED,ERROR`.""" + + template_id: str + """A comma-separated list of Journey ids to filter on.""" diff --git a/tests/api_resources/automations/test_runs.py b/tests/api_resources/automations/test_runs.py new file mode 100644 index 0000000..e739d3e --- /dev/null +++ b/tests/api_resources/automations/test_runs.py @@ -0,0 +1,190 @@ +# 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 +from tests.utils import assert_matches_type +from courier.types import AutomationRunListResponse, AutomationRunStepsResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRuns: + 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_list(self, client: Courier) -> None: + run = client.automations.runs.list() + assert_matches_type(AutomationRunListResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Courier) -> None: + run = client.automations.runs.list( + cursor="cursor", + end_date="end_date", + limit="321669910225", + start_date="start_date", + status="status", + template_id="template_id", + ) + assert_matches_type(AutomationRunListResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Courier) -> None: + response = client.automations.runs.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(AutomationRunListResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Courier) -> None: + with client.automations.runs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(AutomationRunListResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_steps(self, client: Courier) -> None: + run = client.automations.runs.list_steps( + "x", + ) + assert_matches_type(AutomationRunStepsResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list_steps(self, client: Courier) -> None: + response = client.automations.runs.with_raw_response.list_steps( + "x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(AutomationRunStepsResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list_steps(self, client: Courier) -> None: + with client.automations.runs.with_streaming_response.list_steps( + "x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(AutomationRunStepsResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_list_steps(self, client: Courier) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.automations.runs.with_raw_response.list_steps( + "", + ) + + +class TestAsyncRuns: + 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_list(self, async_client: AsyncCourier) -> None: + run = await async_client.automations.runs.list() + assert_matches_type(AutomationRunListResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncCourier) -> None: + run = await async_client.automations.runs.list( + cursor="cursor", + end_date="end_date", + limit="321669910225", + start_date="start_date", + status="status", + template_id="template_id", + ) + assert_matches_type(AutomationRunListResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncCourier) -> None: + response = await async_client.automations.runs.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = await response.parse() + assert_matches_type(AutomationRunListResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncCourier) -> None: + async with async_client.automations.runs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(AutomationRunListResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_steps(self, async_client: AsyncCourier) -> None: + run = await async_client.automations.runs.list_steps( + "x", + ) + assert_matches_type(AutomationRunStepsResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list_steps(self, async_client: AsyncCourier) -> None: + response = await async_client.automations.runs.with_raw_response.list_steps( + "x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = await response.parse() + assert_matches_type(AutomationRunStepsResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list_steps(self, async_client: AsyncCourier) -> None: + async with async_client.automations.runs.with_streaming_response.list_steps( + "x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(AutomationRunStepsResponse, run, 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_list_steps(self, async_client: AsyncCourier) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.automations.runs.with_raw_response.list_steps( + "", + ) diff --git a/tests/api_resources/journeys/test_runs.py b/tests/api_resources/journeys/test_runs.py new file mode 100644 index 0000000..09871c3 --- /dev/null +++ b/tests/api_resources/journeys/test_runs.py @@ -0,0 +1,274 @@ +# 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 +from tests.utils import assert_matches_type +from courier.types import JourneyRunResponse, JourneyRunListResponse, JourneyRunStepsResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRuns: + 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_retrieve(self, client: Courier) -> None: + run = client.journeys.runs.retrieve( + "x", + ) + assert_matches_type(JourneyRunResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Courier) -> None: + response = client.journeys.runs.with_raw_response.retrieve( + "x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(JourneyRunResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Courier) -> None: + with client.journeys.runs.with_streaming_response.retrieve( + "x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(JourneyRunResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Courier) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.journeys.runs.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Courier) -> None: + run = client.journeys.runs.list() + assert_matches_type(JourneyRunListResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Courier) -> None: + run = client.journeys.runs.list( + cursor="cursor", + end_date="end_date", + limit="321669910225", + start_date="start_date", + status="status", + template_id="template_id", + ) + assert_matches_type(JourneyRunListResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Courier) -> None: + response = client.journeys.runs.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(JourneyRunListResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Courier) -> None: + with client.journeys.runs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(JourneyRunListResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_steps(self, client: Courier) -> None: + run = client.journeys.runs.list_steps( + "x", + ) + assert_matches_type(JourneyRunStepsResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list_steps(self, client: Courier) -> None: + response = client.journeys.runs.with_raw_response.list_steps( + "x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(JourneyRunStepsResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list_steps(self, client: Courier) -> None: + with client.journeys.runs.with_streaming_response.list_steps( + "x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(JourneyRunStepsResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_list_steps(self, client: Courier) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.journeys.runs.with_raw_response.list_steps( + "", + ) + + +class TestAsyncRuns: + 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_retrieve(self, async_client: AsyncCourier) -> None: + run = await async_client.journeys.runs.retrieve( + "x", + ) + assert_matches_type(JourneyRunResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncCourier) -> None: + response = await async_client.journeys.runs.with_raw_response.retrieve( + "x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = await response.parse() + assert_matches_type(JourneyRunResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncCourier) -> None: + async with async_client.journeys.runs.with_streaming_response.retrieve( + "x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(JourneyRunResponse, run, 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_retrieve(self, async_client: AsyncCourier) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.journeys.runs.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncCourier) -> None: + run = await async_client.journeys.runs.list() + assert_matches_type(JourneyRunListResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncCourier) -> None: + run = await async_client.journeys.runs.list( + cursor="cursor", + end_date="end_date", + limit="321669910225", + start_date="start_date", + status="status", + template_id="template_id", + ) + assert_matches_type(JourneyRunListResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncCourier) -> None: + response = await async_client.journeys.runs.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = await response.parse() + assert_matches_type(JourneyRunListResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncCourier) -> None: + async with async_client.journeys.runs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(JourneyRunListResponse, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_steps(self, async_client: AsyncCourier) -> None: + run = await async_client.journeys.runs.list_steps( + "x", + ) + assert_matches_type(JourneyRunStepsResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list_steps(self, async_client: AsyncCourier) -> None: + response = await async_client.journeys.runs.with_raw_response.list_steps( + "x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = await response.parse() + assert_matches_type(JourneyRunStepsResponse, run, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list_steps(self, async_client: AsyncCourier) -> None: + async with async_client.journeys.runs.with_streaming_response.list_steps( + "x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(JourneyRunStepsResponse, run, 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_list_steps(self, async_client: AsyncCourier) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.journeys.runs.with_raw_response.list_steps( + "", + )