diff --git a/.stats.yml b/.stats.yml
index 6407960..5fe5772 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1 +1 @@
-configured_endpoints: 157
+configured_endpoints: 158
diff --git a/api.md b/api.md
index e663e78..3d159d2 100644
--- a/api.md
+++ b/api.md
@@ -578,6 +578,9 @@ Types:
```python
from courier.types import (
+ PreferenceChangeLogEntry,
+ PreferenceChangeLogValue,
+ PreferenceLogsListResponse,
PublishPreferencesRequest,
PublishPreferencesResponse,
TopicDigestCategory,
@@ -602,6 +605,7 @@ Methods:
- client.workspace_preferences.retrieve(section_id) -> WorkspacePreferenceGetResponse
- client.workspace_preferences.list() -> WorkspacePreferenceListResponse
- client.workspace_preferences.archive(section_id) -> None
+- client.workspace_preferences.list_logs(\*\*params) -> PreferenceLogsListResponse
- client.workspace_preferences.publish(\*\*params) -> PublishPreferencesResponse
- client.workspace_preferences.replace(section_id, \*\*params) -> WorkspacePreferenceGetResponse
diff --git a/src/courier/resources/workspace_preferences/workspace_preferences.py b/src/courier/resources/workspace_preferences/workspace_preferences.py
index 6239fd2..cdace88 100644
--- a/src/courier/resources/workspace_preferences/workspace_preferences.py
+++ b/src/courier/resources/workspace_preferences/workspace_preferences.py
@@ -18,6 +18,7 @@
workspace_preference_create_params,
workspace_preference_publish_params,
workspace_preference_replace_params,
+ workspace_preference_list_logs_params,
)
from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
@@ -31,6 +32,7 @@
)
from ..._base_client import make_request_options
from ...types.publish_preferences_response import PublishPreferencesResponse
+from ...types.preference_logs_list_response import PreferenceLogsListResponse
from ...types.shared.channel_classification import ChannelClassification
from ...types.workspace_preference_get_response import WorkspacePreferenceGetResponse
from ...types.workspace_preference_list_response import WorkspacePreferenceListResponse
@@ -222,6 +224,71 @@ def archive(
cast_to=NoneType,
)
+ def list_logs(
+ self,
+ *,
+ cursor: str | Omit = omit,
+ limit: int | Omit = omit,
+ since: str | Omit = omit,
+ tenant_id: str | Omit = omit,
+ user_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,
+ ) -> PreferenceLogsListResponse:
+ """
+ Returns the history of preference changes in this environment, newest first.
+ Each entry records one change a user made to one subscription topic, and carries
+ the value before it where there was one. Supply user_id to read a single user's
+ history instead of the whole environment.
+
+ Args:
+ cursor: A cursor from a previous response's paging.cursor. Continue only while
+ paging.more is true; the cursor is omitted on the last page.
+
+ limit: How many entries to return. Defaults to 25.
+
+ since: Return only changes at or after this time, as an ISO-8601 date or date-time. A
+ date alone is read as the start of that day in UTC.
+
+ tenant_id: Narrow to the changes this user made in one tenant context. Only valid together
+ with user_id.
+
+ user_id: Return only this user's changes. Omit it to read every change in the
+ environment.
+
+ 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(
+ "/preferences/logs",
+ 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,
+ "since": since,
+ "tenant_id": tenant_id,
+ "user_id": user_id,
+ },
+ workspace_preference_list_logs_params.WorkspacePreferenceListLogsParams,
+ ),
+ ),
+ cast_to=PreferenceLogsListResponse,
+ )
+
def publish(
self,
*,
@@ -524,6 +591,71 @@ async def archive(
cast_to=NoneType,
)
+ async def list_logs(
+ self,
+ *,
+ cursor: str | Omit = omit,
+ limit: int | Omit = omit,
+ since: str | Omit = omit,
+ tenant_id: str | Omit = omit,
+ user_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,
+ ) -> PreferenceLogsListResponse:
+ """
+ Returns the history of preference changes in this environment, newest first.
+ Each entry records one change a user made to one subscription topic, and carries
+ the value before it where there was one. Supply user_id to read a single user's
+ history instead of the whole environment.
+
+ Args:
+ cursor: A cursor from a previous response's paging.cursor. Continue only while
+ paging.more is true; the cursor is omitted on the last page.
+
+ limit: How many entries to return. Defaults to 25.
+
+ since: Return only changes at or after this time, as an ISO-8601 date or date-time. A
+ date alone is read as the start of that day in UTC.
+
+ tenant_id: Narrow to the changes this user made in one tenant context. Only valid together
+ with user_id.
+
+ user_id: Return only this user's changes. Omit it to read every change in the
+ environment.
+
+ 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(
+ "/preferences/logs",
+ 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,
+ "since": since,
+ "tenant_id": tenant_id,
+ "user_id": user_id,
+ },
+ workspace_preference_list_logs_params.WorkspacePreferenceListLogsParams,
+ ),
+ ),
+ cast_to=PreferenceLogsListResponse,
+ )
+
async def publish(
self,
*,
@@ -658,6 +790,9 @@ def __init__(self, workspace_preferences: WorkspacePreferencesResource) -> None:
self.archive = to_raw_response_wrapper(
workspace_preferences.archive,
)
+ self.list_logs = to_raw_response_wrapper(
+ workspace_preferences.list_logs,
+ )
self.publish = to_raw_response_wrapper(
workspace_preferences.publish,
)
@@ -686,6 +821,9 @@ def __init__(self, workspace_preferences: AsyncWorkspacePreferencesResource) ->
self.archive = async_to_raw_response_wrapper(
workspace_preferences.archive,
)
+ self.list_logs = async_to_raw_response_wrapper(
+ workspace_preferences.list_logs,
+ )
self.publish = async_to_raw_response_wrapper(
workspace_preferences.publish,
)
@@ -714,6 +852,9 @@ def __init__(self, workspace_preferences: WorkspacePreferencesResource) -> None:
self.archive = to_streamed_response_wrapper(
workspace_preferences.archive,
)
+ self.list_logs = to_streamed_response_wrapper(
+ workspace_preferences.list_logs,
+ )
self.publish = to_streamed_response_wrapper(
workspace_preferences.publish,
)
@@ -742,6 +883,9 @@ def __init__(self, workspace_preferences: AsyncWorkspacePreferencesResource) ->
self.archive = async_to_streamed_response_wrapper(
workspace_preferences.archive,
)
+ self.list_logs = async_to_streamed_response_wrapper(
+ workspace_preferences.list_logs,
+ )
self.publish = async_to_streamed_response_wrapper(
workspace_preferences.publish,
)
diff --git a/src/courier/types/__init__.py b/src/courier/types/__init__.py
index 6fedc45..eeb635d 100644
--- a/src/courier/types/__init__.py
+++ b/src/courier/types/__init__.py
@@ -268,6 +268,8 @@
from .notification_replace_params import NotificationReplaceParams as NotificationReplaceParams
from .notification_template_alias import NotificationTemplateAlias as NotificationTemplateAlias
from .notification_template_state import NotificationTemplateState as NotificationTemplateState
+from .preference_change_log_entry import PreferenceChangeLogEntry as PreferenceChangeLogEntry
+from .preference_change_log_value import PreferenceChangeLogValue as PreferenceChangeLogValue
from .tenant_template_input_param import TenantTemplateInputParam as TenantTemplateInputParam
from .topic_digest_category_param import TopicDigestCategoryParam as TopicDigestCategoryParam
from .audience_list_members_params import AudienceListMembersParams as AudienceListMembersParams
@@ -295,6 +297,7 @@
from .notification_metrics_response import NotificationMetricsResponse as NotificationMetricsResponse
from .notification_template_payload import NotificationTemplatePayload as NotificationTemplatePayload
from .notification_template_summary import NotificationTemplateSummary as NotificationTemplateSummary
+from .preference_logs_list_response import PreferenceLogsListResponse as PreferenceLogsListResponse
from .routing_strategy_get_response import RoutingStrategyGetResponse as RoutingStrategyGetResponse
from .translation_retrieve_response import TranslationRetrieveResponse as TranslationRetrieveResponse
from .audience_list_members_response import AudienceListMembersResponse as AudienceListMembersResponse
@@ -351,6 +354,9 @@
PostTenantTemplatePublishResponse as PostTenantTemplatePublishResponse,
)
from .subscribe_to_lists_request_item_param import SubscribeToListsRequestItemParam as SubscribeToListsRequestItemParam
+from .workspace_preference_list_logs_params import (
+ WorkspacePreferenceListLogsParams as WorkspacePreferenceListLogsParams,
+)
from .journey_send_node_to_slack_email_param import JourneySendNodeToSlackEmailParam as JourneySendNodeToSlackEmailParam
from .notification_content_mutation_response import (
NotificationContentMutationResponse as NotificationContentMutationResponse,
diff --git a/src/courier/types/preference_change_log_entry.py b/src/courier/types/preference_change_log_entry.py
new file mode 100644
index 0000000..4fa28ca
--- /dev/null
+++ b/src/courier/types/preference_change_log_entry.py
@@ -0,0 +1,52 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from .._models import BaseModel
+from .shared.preference_status import PreferenceStatus
+from .preference_change_log_value import PreferenceChangeLogValue
+from .shared.channel_classification import ChannelClassification
+
+__all__ = ["PreferenceChangeLogEntry"]
+
+
+class PreferenceChangeLogEntry(BaseModel):
+ id: str
+ """Unique identifier for this change."""
+
+ custom_routing: List[ChannelClassification]
+ """The channels chosen for this topic, present only when has_custom_routing is
+ true.
+
+ Empty otherwise.
+ """
+
+ has_custom_routing: bool
+ """
+ Whether specific delivery channels were chosen for this topic rather than the
+ topic's default routing.
+ """
+
+ status: PreferenceStatus
+ """The subscription status the change set."""
+
+ timestamp: str
+ """When the change was made, as an ISO-8601 date-time in UTC."""
+
+ topic_id: str
+ """The subscription topic the change applies to."""
+
+ topic_name: str
+ """The display name of that topic when the change was made."""
+
+ user_id: str
+ """The user whose preference changed."""
+
+ previous: Optional[PreferenceChangeLogValue] = None
+ """The value before this change, where it was recorded."""
+
+ tenant_id: Optional[str] = None
+ """The tenant context the change was made in.
+
+ Absent when the user set the preference outside any tenant.
+ """
diff --git a/src/courier/types/preference_change_log_value.py b/src/courier/types/preference_change_log_value.py
new file mode 100644
index 0000000..c90e88d
--- /dev/null
+++ b/src/courier/types/preference_change_log_value.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .._models import BaseModel
+from .shared.preference_status import PreferenceStatus
+from .shared.channel_classification import ChannelClassification
+
+__all__ = ["PreferenceChangeLogValue"]
+
+
+class PreferenceChangeLogValue(BaseModel):
+ custom_routing: List[ChannelClassification]
+ """The channels chosen before the change."""
+
+ has_custom_routing: bool
+ """Whether custom routing was in effect before the change."""
+
+ status: PreferenceStatus
+ """The subscription status before the change."""
diff --git a/src/courier/types/preference_logs_list_response.py b/src/courier/types/preference_logs_list_response.py
new file mode 100644
index 0000000..56587b0
--- /dev/null
+++ b/src/courier/types/preference_logs_list_response.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .._models import BaseModel
+from .shared.paging import Paging
+from .preference_change_log_entry import PreferenceChangeLogEntry
+
+__all__ = ["PreferenceLogsListResponse"]
+
+
+class PreferenceLogsListResponse(BaseModel):
+ items: List[PreferenceChangeLogEntry]
+ """One entry per preference change, newest first."""
+
+ paging: Paging
diff --git a/src/courier/types/workspace_preference_list_logs_params.py b/src/courier/types/workspace_preference_list_logs_params.py
new file mode 100644
index 0000000..6524c21
--- /dev/null
+++ b/src/courier/types/workspace_preference_list_logs_params.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["WorkspacePreferenceListLogsParams"]
+
+
+class WorkspacePreferenceListLogsParams(TypedDict, total=False):
+ cursor: str
+ """A cursor from a previous response's paging.cursor.
+
+ Continue only while paging.more is true; the cursor is omitted on the last page.
+ """
+
+ limit: int
+ """How many entries to return. Defaults to 25."""
+
+ since: str
+ """Return only changes at or after this time, as an ISO-8601 date or date-time.
+
+ A date alone is read as the start of that day in UTC.
+ """
+
+ tenant_id: str
+ """Narrow to the changes this user made in one tenant context.
+
+ Only valid together with user_id.
+ """
+
+ user_id: str
+ """Return only this user's changes.
+
+ Omit it to read every change in the environment.
+ """
diff --git a/tests/api_resources/test_workspace_preferences.py b/tests/api_resources/test_workspace_preferences.py
index 3257332..ee89394 100644
--- a/tests/api_resources/test_workspace_preferences.py
+++ b/tests/api_resources/test_workspace_preferences.py
@@ -10,6 +10,7 @@
from courier import Courier, AsyncCourier
from tests.utils import assert_matches_type
from courier.types import (
+ PreferenceLogsListResponse,
PublishPreferencesResponse,
WorkspacePreferenceGetResponse,
WorkspacePreferenceListResponse,
@@ -180,6 +181,46 @@ def test_path_params_archive(self, client: Courier) -> None:
"",
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_logs(self, client: Courier) -> None:
+ workspace_preference = client.workspace_preferences.list_logs()
+ assert_matches_type(PreferenceLogsListResponse, workspace_preference, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_logs_with_all_params(self, client: Courier) -> None:
+ workspace_preference = client.workspace_preferences.list_logs(
+ cursor="cursor",
+ limit=1,
+ since="since",
+ tenant_id="tenant_id",
+ user_id="user_id",
+ )
+ assert_matches_type(PreferenceLogsListResponse, workspace_preference, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list_logs(self, client: Courier) -> None:
+ response = client.workspace_preferences.with_raw_response.list_logs()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ workspace_preference = response.parse()
+ assert_matches_type(PreferenceLogsListResponse, workspace_preference, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list_logs(self, client: Courier) -> None:
+ with client.workspace_preferences.with_streaming_response.list_logs() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ workspace_preference = response.parse()
+ assert_matches_type(PreferenceLogsListResponse, workspace_preference, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_publish(self, client: Courier) -> None:
@@ -443,6 +484,46 @@ async def test_path_params_archive(self, async_client: AsyncCourier) -> None:
"",
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_logs(self, async_client: AsyncCourier) -> None:
+ workspace_preference = await async_client.workspace_preferences.list_logs()
+ assert_matches_type(PreferenceLogsListResponse, workspace_preference, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_logs_with_all_params(self, async_client: AsyncCourier) -> None:
+ workspace_preference = await async_client.workspace_preferences.list_logs(
+ cursor="cursor",
+ limit=1,
+ since="since",
+ tenant_id="tenant_id",
+ user_id="user_id",
+ )
+ assert_matches_type(PreferenceLogsListResponse, workspace_preference, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list_logs(self, async_client: AsyncCourier) -> None:
+ response = await async_client.workspace_preferences.with_raw_response.list_logs()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ workspace_preference = await response.parse()
+ assert_matches_type(PreferenceLogsListResponse, workspace_preference, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list_logs(self, async_client: AsyncCourier) -> None:
+ async with async_client.workspace_preferences.with_streaming_response.list_logs() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ workspace_preference = await response.parse()
+ assert_matches_type(PreferenceLogsListResponse, workspace_preference, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_publish(self, async_client: AsyncCourier) -> None: