diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2ecf001..e20563e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.13.0" + ".": "7.14.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 1692bab..6ee7bef 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 117 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-545ac9b590445e90e11113a1bdeb893a94389d69d95e0a7e5c6450bb15f5453a.yml -openapi_spec_hash: 9e243ec62800fb4a2e443eb6481afa30 -config_hash: 10bd597dd6cc89023541bc551b6532b8 +configured_endpoints: 119 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-a64bb97c3455b0689de7f6a297ba1dc1e747561ce310ddb18b9c4a5f4d3d510a.yml +openapi_spec_hash: 6a3b89f3ea7600e784902f61680f8f1a +config_hash: 822a92efc80e63cdb2d496dbd6176620 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b657bc..3387b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 7.14.0 (2026-06-12) + +Full Changelog: [v7.13.0...v7.14.0](https://github.com/trycourier/courier-python/compare/v7.13.0...v7.14.0) + +### Features + +* **digests:** document digest REST endpoints in OpenAPI spec ([d88d2ae](https://github.com/trycourier/courier-python/commit/d88d2ae2d08af0abb7334e5ba1228619a85e9e3b)) + ## 7.13.0 (2026-05-28) Full Changelog: [v7.12.0...v7.13.0](https://github.com/trycourier/courier-python/compare/v7.12.0...v7.13.0) diff --git a/api.md b/api.md index d564293..cc16218 100644 --- a/api.md +++ b/api.md @@ -310,6 +310,21 @@ Methods: - client.bulk.retrieve_job(job_id) -> BulkRetrieveJobResponse - client.bulk.run_job(job_id) -> None +# Digests + +Types: + +```python +from courier.types import DigestCategory, DigestInstance, DigestInstanceListResponse +``` + +## Schedules + +Methods: + +- client.digests.schedules.list_instances(schedule_id, \*\*params) -> DigestInstanceListResponse +- client.digests.schedules.release(schedule_id) -> None + # Inbound Types: diff --git a/pyproject.toml b/pyproject.toml index a828b21..10d2fb0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "trycourier" -version = "7.13.0" +version = "7.14.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 30317ee..64fa136 100644 --- a/src/courier/_client.py +++ b/src/courier/_client.py @@ -42,6 +42,7 @@ lists, users, brands, + digests, inbound, tenants, journeys, @@ -68,6 +69,7 @@ from .resources.users.users import UsersResource, AsyncUsersResource from .resources.audit_events import AuditEventsResource, AsyncAuditEventsResource from .resources.translations import TranslationsResource, AsyncTranslationsResource + from .resources.digests.digests import DigestsResource, AsyncDigestsResource from .resources.tenants.tenants import TenantsResource, AsyncTenantsResource from .resources.journeys.journeys import JourneysResource, AsyncJourneysResource from .resources.profiles.profiles import ProfilesResource, AsyncProfilesResource @@ -197,6 +199,12 @@ def bulk(self) -> BulkResource: return BulkResource(self) + @cached_property + def digests(self) -> DigestsResource: + from .resources.digests import DigestsResource + + return DigestsResource(self) + @cached_property def inbound(self) -> InboundResource: from .resources.inbound import InboundResource @@ -488,6 +496,12 @@ def bulk(self) -> AsyncBulkResource: return AsyncBulkResource(self) + @cached_property + def digests(self) -> AsyncDigestsResource: + from .resources.digests import AsyncDigestsResource + + return AsyncDigestsResource(self) + @cached_property def inbound(self) -> AsyncInboundResource: from .resources.inbound import AsyncInboundResource @@ -721,6 +735,12 @@ def bulk(self) -> bulk.BulkResourceWithRawResponse: return BulkResourceWithRawResponse(self._client.bulk) + @cached_property + def digests(self) -> digests.DigestsResourceWithRawResponse: + from .resources.digests import DigestsResourceWithRawResponse + + return DigestsResourceWithRawResponse(self._client.digests) + @cached_property def inbound(self) -> inbound.InboundResourceWithRawResponse: from .resources.inbound import InboundResourceWithRawResponse @@ -842,6 +862,12 @@ def bulk(self) -> bulk.AsyncBulkResourceWithRawResponse: return AsyncBulkResourceWithRawResponse(self._client.bulk) + @cached_property + def digests(self) -> digests.AsyncDigestsResourceWithRawResponse: + from .resources.digests import AsyncDigestsResourceWithRawResponse + + return AsyncDigestsResourceWithRawResponse(self._client.digests) + @cached_property def inbound(self) -> inbound.AsyncInboundResourceWithRawResponse: from .resources.inbound import AsyncInboundResourceWithRawResponse @@ -963,6 +989,12 @@ def bulk(self) -> bulk.BulkResourceWithStreamingResponse: return BulkResourceWithStreamingResponse(self._client.bulk) + @cached_property + def digests(self) -> digests.DigestsResourceWithStreamingResponse: + from .resources.digests import DigestsResourceWithStreamingResponse + + return DigestsResourceWithStreamingResponse(self._client.digests) + @cached_property def inbound(self) -> inbound.InboundResourceWithStreamingResponse: from .resources.inbound import InboundResourceWithStreamingResponse @@ -1084,6 +1116,12 @@ def bulk(self) -> bulk.AsyncBulkResourceWithStreamingResponse: return AsyncBulkResourceWithStreamingResponse(self._client.bulk) + @cached_property + def digests(self) -> digests.AsyncDigestsResourceWithStreamingResponse: + from .resources.digests import AsyncDigestsResourceWithStreamingResponse + + return AsyncDigestsResourceWithStreamingResponse(self._client.digests) + @cached_property def inbound(self) -> inbound.AsyncInboundResourceWithStreamingResponse: from .resources.inbound import AsyncInboundResourceWithStreamingResponse diff --git a/src/courier/_version.py b/src/courier/_version.py index 79fa551..070136e 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.13.0" # x-release-please-version +__version__ = "7.14.0" # x-release-please-version diff --git a/src/courier/resources/__init__.py b/src/courier/resources/__init__.py index 99d3168..3933d0a 100644 --- a/src/courier/resources/__init__.py +++ b/src/courier/resources/__init__.py @@ -48,6 +48,14 @@ BrandsResourceWithStreamingResponse, AsyncBrandsResourceWithStreamingResponse, ) +from .digests import ( + DigestsResource, + AsyncDigestsResource, + DigestsResourceWithRawResponse, + AsyncDigestsResourceWithRawResponse, + DigestsResourceWithStreamingResponse, + AsyncDigestsResourceWithStreamingResponse, +) from .inbound import ( InboundResource, AsyncInboundResource, @@ -208,6 +216,12 @@ "AsyncBulkResourceWithRawResponse", "BulkResourceWithStreamingResponse", "AsyncBulkResourceWithStreamingResponse", + "DigestsResource", + "AsyncDigestsResource", + "DigestsResourceWithRawResponse", + "AsyncDigestsResourceWithRawResponse", + "DigestsResourceWithStreamingResponse", + "AsyncDigestsResourceWithStreamingResponse", "InboundResource", "AsyncInboundResource", "InboundResourceWithRawResponse", diff --git a/src/courier/resources/digests/__init__.py b/src/courier/resources/digests/__init__.py new file mode 100644 index 0000000..d1ec65b --- /dev/null +++ b/src/courier/resources/digests/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .digests import ( + DigestsResource, + AsyncDigestsResource, + DigestsResourceWithRawResponse, + AsyncDigestsResourceWithRawResponse, + DigestsResourceWithStreamingResponse, + AsyncDigestsResourceWithStreamingResponse, +) +from .schedules import ( + SchedulesResource, + AsyncSchedulesResource, + SchedulesResourceWithRawResponse, + AsyncSchedulesResourceWithRawResponse, + SchedulesResourceWithStreamingResponse, + AsyncSchedulesResourceWithStreamingResponse, +) + +__all__ = [ + "SchedulesResource", + "AsyncSchedulesResource", + "SchedulesResourceWithRawResponse", + "AsyncSchedulesResourceWithRawResponse", + "SchedulesResourceWithStreamingResponse", + "AsyncSchedulesResourceWithStreamingResponse", + "DigestsResource", + "AsyncDigestsResource", + "DigestsResourceWithRawResponse", + "AsyncDigestsResourceWithRawResponse", + "DigestsResourceWithStreamingResponse", + "AsyncDigestsResourceWithStreamingResponse", +] diff --git a/src/courier/resources/digests/digests.py b/src/courier/resources/digests/digests.py new file mode 100644 index 0000000..b50742c --- /dev/null +++ b/src/courier/resources/digests/digests.py @@ -0,0 +1,102 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ..._compat import cached_property +from .schedules import ( + SchedulesResource, + AsyncSchedulesResource, + SchedulesResourceWithRawResponse, + AsyncSchedulesResourceWithRawResponse, + SchedulesResourceWithStreamingResponse, + AsyncSchedulesResourceWithStreamingResponse, +) +from ..._resource import SyncAPIResource, AsyncAPIResource + +__all__ = ["DigestsResource", "AsyncDigestsResource"] + + +class DigestsResource(SyncAPIResource): + @cached_property + def schedules(self) -> SchedulesResource: + return SchedulesResource(self._client) + + @cached_property + def with_raw_response(self) -> DigestsResourceWithRawResponse: + """ + 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 DigestsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> DigestsResourceWithStreamingResponse: + """ + 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 DigestsResourceWithStreamingResponse(self) + + +class AsyncDigestsResource(AsyncAPIResource): + @cached_property + def schedules(self) -> AsyncSchedulesResource: + return AsyncSchedulesResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncDigestsResourceWithRawResponse: + """ + 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 AsyncDigestsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncDigestsResourceWithStreamingResponse: + """ + 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 AsyncDigestsResourceWithStreamingResponse(self) + + +class DigestsResourceWithRawResponse: + def __init__(self, digests: DigestsResource) -> None: + self._digests = digests + + @cached_property + def schedules(self) -> SchedulesResourceWithRawResponse: + return SchedulesResourceWithRawResponse(self._digests.schedules) + + +class AsyncDigestsResourceWithRawResponse: + def __init__(self, digests: AsyncDigestsResource) -> None: + self._digests = digests + + @cached_property + def schedules(self) -> AsyncSchedulesResourceWithRawResponse: + return AsyncSchedulesResourceWithRawResponse(self._digests.schedules) + + +class DigestsResourceWithStreamingResponse: + def __init__(self, digests: DigestsResource) -> None: + self._digests = digests + + @cached_property + def schedules(self) -> SchedulesResourceWithStreamingResponse: + return SchedulesResourceWithStreamingResponse(self._digests.schedules) + + +class AsyncDigestsResourceWithStreamingResponse: + def __init__(self, digests: AsyncDigestsResource) -> None: + self._digests = digests + + @cached_property + def schedules(self) -> AsyncSchedulesResourceWithStreamingResponse: + return AsyncSchedulesResourceWithStreamingResponse(self._digests.schedules) diff --git a/src/courier/resources/digests/schedules.py b/src/courier/resources/digests/schedules.py new file mode 100644 index 0000000..d92f765 --- /dev/null +++ b/src/courier/resources/digests/schedules.py @@ -0,0 +1,287 @@ +# 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, NoneType, 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.digests import schedule_list_instances_params +from ...types.digest_instance_list_response import DigestInstanceListResponse + +__all__ = ["SchedulesResource", "AsyncSchedulesResource"] + + +class SchedulesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SchedulesResourceWithRawResponse: + """ + 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 SchedulesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SchedulesResourceWithStreamingResponse: + """ + 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 SchedulesResourceWithStreamingResponse(self) + + def list_instances( + self, + schedule_id: str, + *, + cursor: str | Omit = omit, + limit: int | 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, + ) -> DigestInstanceListResponse: + """List the digest instances for a schedule. + + Each instance represents the events + accumulated for a single user against the schedule, and can be used to monitor + digest accumulation before the digest is released. + + Args: + cursor: A cursor token from a previous response, used to fetch the next page of results. + + limit: The maximum number of digest instances to return. Defaults to 20, with a maximum + of 100. + + 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 schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + return self._get( + path_template("/digests/schedules/{schedule_id}/instances", schedule_id=schedule_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "cursor": cursor, + "limit": limit, + }, + schedule_list_instances_params.ScheduleListInstancesParams, + ), + ), + cast_to=DigestInstanceListResponse, + ) + + def release( + self, + schedule_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: + """ + Send a digest now instead of waiting for its scheduled time, so your users get + what they have collected so far right away. + + 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 schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + path_template("/digests/schedules/{schedule_id}/trigger", schedule_id=schedule_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class AsyncSchedulesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSchedulesResourceWithRawResponse: + """ + 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 AsyncSchedulesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSchedulesResourceWithStreamingResponse: + """ + 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 AsyncSchedulesResourceWithStreamingResponse(self) + + async def list_instances( + self, + schedule_id: str, + *, + cursor: str | Omit = omit, + limit: int | 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, + ) -> DigestInstanceListResponse: + """List the digest instances for a schedule. + + Each instance represents the events + accumulated for a single user against the schedule, and can be used to monitor + digest accumulation before the digest is released. + + Args: + cursor: A cursor token from a previous response, used to fetch the next page of results. + + limit: The maximum number of digest instances to return. Defaults to 20, with a maximum + of 100. + + 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 schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + return await self._get( + path_template("/digests/schedules/{schedule_id}/instances", schedule_id=schedule_id), + 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, + "limit": limit, + }, + schedule_list_instances_params.ScheduleListInstancesParams, + ), + ), + cast_to=DigestInstanceListResponse, + ) + + async def release( + self, + schedule_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: + """ + Send a digest now instead of waiting for its scheduled time, so your users get + what they have collected so far right away. + + 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 schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + path_template("/digests/schedules/{schedule_id}/trigger", schedule_id=schedule_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class SchedulesResourceWithRawResponse: + def __init__(self, schedules: SchedulesResource) -> None: + self._schedules = schedules + + self.list_instances = to_raw_response_wrapper( + schedules.list_instances, + ) + self.release = to_raw_response_wrapper( + schedules.release, + ) + + +class AsyncSchedulesResourceWithRawResponse: + def __init__(self, schedules: AsyncSchedulesResource) -> None: + self._schedules = schedules + + self.list_instances = async_to_raw_response_wrapper( + schedules.list_instances, + ) + self.release = async_to_raw_response_wrapper( + schedules.release, + ) + + +class SchedulesResourceWithStreamingResponse: + def __init__(self, schedules: SchedulesResource) -> None: + self._schedules = schedules + + self.list_instances = to_streamed_response_wrapper( + schedules.list_instances, + ) + self.release = to_streamed_response_wrapper( + schedules.release, + ) + + +class AsyncSchedulesResourceWithStreamingResponse: + def __init__(self, schedules: AsyncSchedulesResource) -> None: + self._schedules = schedules + + self.list_instances = async_to_streamed_response_wrapper( + schedules.list_instances, + ) + self.release = async_to_streamed_response_wrapper( + schedules.release, + ) diff --git a/src/courier/types/__init__.py b/src/courier/types/__init__.py index 89b0b95..22ef85d 100644 --- a/src/courier/types/__init__.py +++ b/src/courier/types/__init__.py @@ -117,6 +117,8 @@ from .brand_settings import BrandSettings as BrandSettings from .brand_snippets import BrandSnippets as BrandSnippets from .brand_template import BrandTemplate as BrandTemplate +from .digest_category import DigestCategory as DigestCategory +from .digest_instance import DigestInstance as DigestInstance from .journey_ai_node import JourneyAINode as JourneyAINode from .message_details import MessageDetails as MessageDetails from .base_check_param import BaseCheckParam as BaseCheckParam @@ -236,6 +238,7 @@ 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 .digest_instance_list_response import DigestInstanceListResponse as DigestInstanceListResponse 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 diff --git a/src/courier/types/digest_category.py b/src/courier/types/digest_category.py new file mode 100644 index 0000000..700bbfa --- /dev/null +++ b/src/courier/types/digest_category.py @@ -0,0 +1,22 @@ +# 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 + +__all__ = ["DigestCategory"] + + +class DigestCategory(BaseModel): + category_key: str + """The key that identifies the category within the digest.""" + + retain: Literal["first", "last", "highest", "lowest", "none"] + """ + Which events to keep when the number of events exceeds the retention limit for + the category. + """ + + sort_key: Optional[str] = None + """The data key used to order events when `retain` is `highest` or `lowest`.""" diff --git a/src/courier/types/digest_instance.py b/src/courier/types/digest_instance.py new file mode 100644 index 0000000..e279712 --- /dev/null +++ b/src/courier/types/digest_instance.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .digest_category import DigestCategory + +__all__ = ["DigestInstance"] + + +class DigestInstance(BaseModel): + digest_instance_id: str + """A unique identifier for the digest instance.""" + + event_count: int + """The total number of events received for this instance.""" + + status: Literal["IN_PROGRESS", "COMPLETED"] + """The status of the digest instance. + + `IN_PROGRESS` instances are still accumulating events; `COMPLETED` instances + have been released. + """ + + user_id: str + """The ID of the user this digest instance belongs to.""" + + categories: Optional[List[DigestCategory]] = None + """The categories configured for the digest.""" + + category_key_counts: Optional[Dict[str, int]] = None + """A map of category key to the number of events received for that category.""" + + created_at: Optional[str] = None + """An ISO 8601 timestamp of when the digest instance was created.""" + + disabled: Optional[bool] = None + """Whether the digest instance has been disabled.""" + + tenant_id: Optional[str] = None + """The ID of the tenant this digest instance belongs to, if any.""" diff --git a/src/courier/types/digest_instance_list_response.py b/src/courier/types/digest_instance_list_response.py new file mode 100644 index 0000000..cc1b11e --- /dev/null +++ b/src/courier/types/digest_instance_list_response.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .digest_instance import DigestInstance + +__all__ = ["DigestInstanceListResponse"] + + +class DigestInstanceListResponse(BaseModel): + has_more: bool + """Whether there are more digest instances to fetch using the cursor.""" + + items: List[DigestInstance] + """The digest instances for this page of results.""" + + type: Literal["list"] + """Always `list` for a paginated list response.""" + + cursor: Optional[str] = None + """ + A cursor token for fetching the next page of results, or null when there are + none. + """ + + next_url: Optional[str] = None + """The path to fetch the next page of results, or null when there are none.""" + + url: Optional[str] = None + """The path of the current request.""" diff --git a/src/courier/types/digests/__init__.py b/src/courier/types/digests/__init__.py new file mode 100644 index 0000000..5b1c71f --- /dev/null +++ b/src/courier/types/digests/__init__.py @@ -0,0 +1,5 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .schedule_list_instances_params import ScheduleListInstancesParams as ScheduleListInstancesParams diff --git a/src/courier/types/digests/schedule_list_instances_params.py b/src/courier/types/digests/schedule_list_instances_params.py new file mode 100644 index 0000000..e1c820d --- /dev/null +++ b/src/courier/types/digests/schedule_list_instances_params.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["ScheduleListInstancesParams"] + + +class ScheduleListInstancesParams(TypedDict, total=False): + cursor: str + """ + A cursor token from a previous response, used to fetch the next page of results. + """ + + limit: int + """The maximum number of digest instances to return. + + Defaults to 20, with a maximum of 100. + """ diff --git a/tests/api_resources/digests/__init__.py b/tests/api_resources/digests/__init__.py new file mode 100644 index 0000000..fd8019a --- /dev/null +++ b/tests/api_resources/digests/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/digests/test_schedules.py b/tests/api_resources/digests/test_schedules.py new file mode 100644 index 0000000..eac3666 --- /dev/null +++ b/tests/api_resources/digests/test_schedules.py @@ -0,0 +1,212 @@ +# 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 DigestInstanceListResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSchedules: + 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_instances(self, client: Courier) -> None: + schedule = client.digests.schedules.list_instances( + schedule_id="schedule_id", + ) + assert_matches_type(DigestInstanceListResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_instances_with_all_params(self, client: Courier) -> None: + schedule = client.digests.schedules.list_instances( + schedule_id="schedule_id", + cursor="cursor", + limit=100, + ) + assert_matches_type(DigestInstanceListResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list_instances(self, client: Courier) -> None: + response = client.digests.schedules.with_raw_response.list_instances( + schedule_id="schedule_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = response.parse() + assert_matches_type(DigestInstanceListResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list_instances(self, client: Courier) -> None: + with client.digests.schedules.with_streaming_response.list_instances( + schedule_id="schedule_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = response.parse() + assert_matches_type(DigestInstanceListResponse, schedule, 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_instances(self, client: Courier) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + client.digests.schedules.with_raw_response.list_instances( + schedule_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_release(self, client: Courier) -> None: + schedule = client.digests.schedules.release( + "schedule_id", + ) + assert schedule is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_release(self, client: Courier) -> None: + response = client.digests.schedules.with_raw_response.release( + "schedule_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = response.parse() + assert schedule is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_release(self, client: Courier) -> None: + with client.digests.schedules.with_streaming_response.release( + "schedule_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = response.parse() + assert schedule is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_release(self, client: Courier) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + client.digests.schedules.with_raw_response.release( + "", + ) + + +class TestAsyncSchedules: + 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_instances(self, async_client: AsyncCourier) -> None: + schedule = await async_client.digests.schedules.list_instances( + schedule_id="schedule_id", + ) + assert_matches_type(DigestInstanceListResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_instances_with_all_params(self, async_client: AsyncCourier) -> None: + schedule = await async_client.digests.schedules.list_instances( + schedule_id="schedule_id", + cursor="cursor", + limit=100, + ) + assert_matches_type(DigestInstanceListResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list_instances(self, async_client: AsyncCourier) -> None: + response = await async_client.digests.schedules.with_raw_response.list_instances( + schedule_id="schedule_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = await response.parse() + assert_matches_type(DigestInstanceListResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list_instances(self, async_client: AsyncCourier) -> None: + async with async_client.digests.schedules.with_streaming_response.list_instances( + schedule_id="schedule_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = await response.parse() + assert_matches_type(DigestInstanceListResponse, schedule, 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_instances(self, async_client: AsyncCourier) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + await async_client.digests.schedules.with_raw_response.list_instances( + schedule_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_release(self, async_client: AsyncCourier) -> None: + schedule = await async_client.digests.schedules.release( + "schedule_id", + ) + assert schedule is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_release(self, async_client: AsyncCourier) -> None: + response = await async_client.digests.schedules.with_raw_response.release( + "schedule_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = await response.parse() + assert schedule is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_release(self, async_client: AsyncCourier) -> None: + async with async_client.digests.schedules.with_streaming_response.release( + "schedule_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = await response.parse() + assert schedule 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_release(self, async_client: AsyncCourier) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + await async_client.digests.schedules.with_raw_response.release( + "", + )