From a1760f41b2ca681bf983ae3b24c49516156fa7bd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 22:25:43 +0000 Subject: [PATCH 01/17] chore(internal): change ci workflow machines --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92f0514..87d9206 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: permissions: contents: read id-token: write - runs-on: depot-ubuntu-24.04 + runs-on: ${{ github.repository == 'stainless-sdks/knock-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 From 66ba143f5c6e28cb7e04c0682f1da3dbe0a82f55 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:03:54 +0000 Subject: [PATCH 02/17] fix: avoid newer type syntax --- src/knockapi/_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/knockapi/_models.py b/src/knockapi/_models.py index b8387ce..92f7c10 100644 --- a/src/knockapi/_models.py +++ b/src/knockapi/_models.py @@ -304,7 +304,7 @@ def model_dump( exclude_none=exclude_none, ) - return cast(dict[str, Any], json_safe(dumped)) if mode == "json" else dumped + return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped @override def model_dump_json( From 40513b513e482356c3abf8c4427e133da91f8a85 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 21:53:48 +0000 Subject: [PATCH 03/17] chore(internal): update pyright exclude list --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 06e3288..11143e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -148,6 +148,7 @@ exclude = [ "_dev", ".venv", ".nox", + ".git", ] reportImplicitOverride = true From b9007c9da8d172eeb43c0acb509ec12c7c3d0d9a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 19:22:37 +0000 Subject: [PATCH 04/17] feat(api): api update --- .stats.yml | 4 +-- src/knockapi/types/message.py | 54 +++++++++++++++++------------------ 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/.stats.yml b/.stats.yml index 232dd9d..ac88494 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 89 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-e4ea1ded040ebfa923df0d24ef37ae3c742383828cda85e1489bc2cb5e14da29.yml -openapi_spec_hash: 4cfd1f5f0d42e1b821f70ba12089b606 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-de67f68640a0cbc35fd85134ee7d220975b45873653129e857c2b09bd5a03a4b.yml +openapi_spec_hash: b53d828f85ef1e06c0af004a25a6576c config_hash: 5c872aa99cad9b9602e84668f5b38a8a diff --git a/src/knockapi/types/message.py b/src/knockapi/types/message.py index bb136ea..7411742 100644 --- a/src/knockapi/types/message.py +++ b/src/knockapi/types/message.py @@ -29,12 +29,36 @@ class Source(BaseModel): class Message(BaseModel): - id: Optional[str] = None + id: str """The unique identifier for the message.""" - api_typename: Optional[str] = FieldInfo(alias="__typename", default=None) + api_typename: str = FieldInfo(alias="__typename") """The typename of the schema.""" + channel_id: str + """The ID for the channel the message was sent through.""" + + engagement_statuses: List[Literal["seen", "read", "interacted", "link_clicked", "archived"]] + """A list of engagement statuses.""" + + inserted_at: datetime + """Timestamp when the resource was created.""" + + recipient: RecipientReference + """ + A reference to a recipient, either a user identifier (string) or an object + reference (ID, collection). + """ + + source: Source + """The workflow that triggered the message.""" + + status: Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"] + """The message delivery status.""" + + updated_at: datetime + """The timestamp when the resource was last updated.""" + actors: Optional[List[RecipientReference]] = None """One or more actors that are associated with this message. @@ -45,9 +69,6 @@ class Message(BaseModel): archived_at: Optional[datetime] = None """Timestamp when the message was archived.""" - channel_id: Optional[str] = None - """The ID for the channel the message was sent through.""" - clicked_at: Optional[datetime] = None """Timestamp when the message was clicked.""" @@ -61,12 +82,6 @@ class Message(BaseModel): `data` from the most-recent trigger request (the final `activity` in the batch). """ - engagement_statuses: Optional[List[Literal["seen", "read", "interacted", "link_clicked", "archived"]]] = None - """A list of engagement statuses.""" - - inserted_at: Optional[datetime] = None - """Timestamp when the resource was created.""" - interacted_at: Optional[datetime] = None """Timestamp when the message was interacted with.""" @@ -79,34 +94,17 @@ class Message(BaseModel): read_at: Optional[datetime] = None """Timestamp when the message was read.""" - recipient: Optional[RecipientReference] = None - """ - A reference to a recipient, either a user identifier (string) or an object - reference (ID, collection). - """ - scheduled_at: Optional[datetime] = None """Timestamp when the message was scheduled to be sent.""" seen_at: Optional[datetime] = None """Timestamp when the message was seen.""" - source: Optional[Source] = None - """The workflow that triggered the message.""" - - status: Optional[ - Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"] - ] = None - """The message delivery status.""" - tenant: Optional[str] = None """The ID of the `tenant` associated with the message. Only present when a `tenant` is provided on a workflow trigger request. """ - updated_at: Optional[datetime] = None - """The timestamp when the resource was last updated.""" - workflow: Optional[str] = None """The key of the workflow that generated the message.""" From df20d5158a8a0df0600147be6497c56feed1a257 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 15:00:02 +0000 Subject: [PATCH 05/17] feat(api): api update --- .stats.yml | 4 ++-- src/knockapi/types/message.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index ac88494..9cbdb2d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 89 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-de67f68640a0cbc35fd85134ee7d220975b45873653129e857c2b09bd5a03a4b.yml -openapi_spec_hash: b53d828f85ef1e06c0af004a25a6576c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-8d6bb1a0b221b8b9fb9aae94419c46e2e1ca4732281924f24c6539c006d12cb8.yml +openapi_spec_hash: 5dd6c9f3cac4f8cc602c0d1543ec4de4 config_hash: 5c872aa99cad9b9602e84668f5b38a8a diff --git a/src/knockapi/types/message.py b/src/knockapi/types/message.py index 7411742..c2cee77 100644 --- a/src/knockapi/types/message.py +++ b/src/knockapi/types/message.py @@ -25,7 +25,7 @@ class Source(BaseModel): """The ID of the version of the workflow that triggered the message.""" step_ref: Optional[str] = None - """The step reference for the step in the workflow that generated the message""" + """The step reference for the step in the workflow that generated the message.""" class Message(BaseModel): From da8cdc052fe12a158e2f3e8081182fa0cada65f0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 17:14:46 +0000 Subject: [PATCH 06/17] feat(api): api update --- .stats.yml | 4 +-- src/knockapi/types/message.py | 56 ++++++++++++++++++----------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9cbdb2d..232dd9d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 89 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-8d6bb1a0b221b8b9fb9aae94419c46e2e1ca4732281924f24c6539c006d12cb8.yml -openapi_spec_hash: 5dd6c9f3cac4f8cc602c0d1543ec4de4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-e4ea1ded040ebfa923df0d24ef37ae3c742383828cda85e1489bc2cb5e14da29.yml +openapi_spec_hash: 4cfd1f5f0d42e1b821f70ba12089b606 config_hash: 5c872aa99cad9b9602e84668f5b38a8a diff --git a/src/knockapi/types/message.py b/src/knockapi/types/message.py index c2cee77..bb136ea 100644 --- a/src/knockapi/types/message.py +++ b/src/knockapi/types/message.py @@ -25,40 +25,16 @@ class Source(BaseModel): """The ID of the version of the workflow that triggered the message.""" step_ref: Optional[str] = None - """The step reference for the step in the workflow that generated the message.""" + """The step reference for the step in the workflow that generated the message""" class Message(BaseModel): - id: str + id: Optional[str] = None """The unique identifier for the message.""" - api_typename: str = FieldInfo(alias="__typename") + api_typename: Optional[str] = FieldInfo(alias="__typename", default=None) """The typename of the schema.""" - channel_id: str - """The ID for the channel the message was sent through.""" - - engagement_statuses: List[Literal["seen", "read", "interacted", "link_clicked", "archived"]] - """A list of engagement statuses.""" - - inserted_at: datetime - """Timestamp when the resource was created.""" - - recipient: RecipientReference - """ - A reference to a recipient, either a user identifier (string) or an object - reference (ID, collection). - """ - - source: Source - """The workflow that triggered the message.""" - - status: Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"] - """The message delivery status.""" - - updated_at: datetime - """The timestamp when the resource was last updated.""" - actors: Optional[List[RecipientReference]] = None """One or more actors that are associated with this message. @@ -69,6 +45,9 @@ class Message(BaseModel): archived_at: Optional[datetime] = None """Timestamp when the message was archived.""" + channel_id: Optional[str] = None + """The ID for the channel the message was sent through.""" + clicked_at: Optional[datetime] = None """Timestamp when the message was clicked.""" @@ -82,6 +61,12 @@ class Message(BaseModel): `data` from the most-recent trigger request (the final `activity` in the batch). """ + engagement_statuses: Optional[List[Literal["seen", "read", "interacted", "link_clicked", "archived"]]] = None + """A list of engagement statuses.""" + + inserted_at: Optional[datetime] = None + """Timestamp when the resource was created.""" + interacted_at: Optional[datetime] = None """Timestamp when the message was interacted with.""" @@ -94,17 +79,34 @@ class Message(BaseModel): read_at: Optional[datetime] = None """Timestamp when the message was read.""" + recipient: Optional[RecipientReference] = None + """ + A reference to a recipient, either a user identifier (string) or an object + reference (ID, collection). + """ + scheduled_at: Optional[datetime] = None """Timestamp when the message was scheduled to be sent.""" seen_at: Optional[datetime] = None """Timestamp when the message was seen.""" + source: Optional[Source] = None + """The workflow that triggered the message.""" + + status: Optional[ + Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"] + ] = None + """The message delivery status.""" + tenant: Optional[str] = None """The ID of the `tenant` associated with the message. Only present when a `tenant` is provided on a workflow trigger request. """ + updated_at: Optional[datetime] = None + """The timestamp when the resource was last updated.""" + workflow: Optional[str] = None """The key of the workflow that generated the message.""" From 2032a76fac21550c5198347c3f5a392e86616ca4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 19:18:39 +0000 Subject: [PATCH 07/17] chore(internal): add Sequence related utils --- src/knockapi/_types.py | 36 ++++++++++++++++++++++++++++++++- src/knockapi/_utils/__init__.py | 1 + src/knockapi/_utils/_typing.py | 5 +++++ tests/utils.py | 10 ++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/knockapi/_types.py b/src/knockapi/_types.py index 44bb250..cda4f53 100644 --- a/src/knockapi/_types.py +++ b/src/knockapi/_types.py @@ -13,10 +13,21 @@ Mapping, TypeVar, Callable, + Iterator, Optional, Sequence, ) -from typing_extensions import Set, Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable +from typing_extensions import ( + Set, + Literal, + Protocol, + TypeAlias, + TypedDict, + SupportsIndex, + overload, + override, + runtime_checkable, +) import httpx import pydantic @@ -217,3 +228,26 @@ class _GenericAlias(Protocol): class HttpxSendArgs(TypedDict, total=False): auth: httpx.Auth follow_redirects: bool + + +_T_co = TypeVar("_T_co", covariant=True) + + +if TYPE_CHECKING: + # This works because str.__contains__ does not accept object (either in typeshed or at runtime) + # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 + class SequenceNotStr(Protocol[_T_co]): + @overload + def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... + @overload + def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... + def __contains__(self, value: object, /) -> bool: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[_T_co]: ... + def index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ... + def count(self, value: Any, /) -> int: ... + def __reversed__(self) -> Iterator[_T_co]: ... +else: + # just point this to a normal `Sequence` at runtime to avoid having to special case + # deserializing our custom sequence type + SequenceNotStr = Sequence diff --git a/src/knockapi/_utils/__init__.py b/src/knockapi/_utils/__init__.py index d4fda26..ca547ce 100644 --- a/src/knockapi/_utils/__init__.py +++ b/src/knockapi/_utils/__init__.py @@ -38,6 +38,7 @@ extract_type_arg as extract_type_arg, is_iterable_type as is_iterable_type, is_required_type as is_required_type, + is_sequence_type as is_sequence_type, is_annotated_type as is_annotated_type, is_type_alias_type as is_type_alias_type, strip_annotated_type as strip_annotated_type, diff --git a/src/knockapi/_utils/_typing.py b/src/knockapi/_utils/_typing.py index 1bac954..845cd6b 100644 --- a/src/knockapi/_utils/_typing.py +++ b/src/knockapi/_utils/_typing.py @@ -26,6 +26,11 @@ def is_list_type(typ: type) -> bool: return (get_origin(typ) or typ) == list +def is_sequence_type(typ: type) -> bool: + origin = get_origin(typ) or typ + return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence + + def is_iterable_type(typ: type) -> bool: """If the given type is `typing.Iterable[T]`""" origin = get_origin(typ) or typ diff --git a/tests/utils.py b/tests/utils.py index 6212bd0..23be870 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,7 +4,7 @@ import inspect import traceback import contextlib -from typing import Any, TypeVar, Iterator, cast +from typing import Any, TypeVar, Iterator, Sequence, cast from datetime import date, datetime from typing_extensions import Literal, get_args, get_origin, assert_type @@ -15,6 +15,7 @@ is_list_type, is_union_type, extract_type_arg, + is_sequence_type, is_annotated_type, is_type_alias_type, ) @@ -71,6 +72,13 @@ def assert_matches_type( if is_list_type(type_): return _assert_list_type(type_, value) + if is_sequence_type(type_): + assert isinstance(value, Sequence) + inner_type = get_args(type_)[0] + for entry in value: # type: ignore + assert_type(inner_type, entry) # type: ignore + return + if origin == str: assert isinstance(value, str) elif origin == int: From e19113cded8eccae475f30b36ae5585427bffe57 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 15:15:04 +0000 Subject: [PATCH 08/17] feat(types): replace List[str] with SequenceNotStr in params --- src/knockapi/_utils/_transform.py | 6 ++++ src/knockapi/resources/channels/bulk.py | 16 ++++----- src/knockapi/resources/messages/batch.py | 36 +++++++++---------- src/knockapi/resources/messages/messages.py | 10 +++--- src/knockapi/resources/objects/bulk.py | 8 ++--- src/knockapi/resources/objects/objects.py | 22 ++++++------ src/knockapi/resources/schedules/schedules.py | 20 +++++------ src/knockapi/resources/tenants/bulk.py | 12 +++---- src/knockapi/resources/users/bulk.py | 12 +++---- src/knockapi/resources/users/feeds.py | 7 ++-- src/knockapi/resources/users/users.py | 14 ++++---- src/knockapi/resources/workflows.py | 12 +++---- .../bulk_update_message_status_params.py | 9 ++--- src/knockapi/types/message_list_params.py | 6 ++-- .../types/messages/batch_archive_params.py | 5 +-- .../messages/batch_get_content_params.py | 5 +-- .../batch_mark_as_interacted_params.py | 6 ++-- .../messages/batch_mark_as_read_params.py | 5 +-- .../messages/batch_mark_as_seen_params.py | 5 +-- .../messages/batch_mark_as_unread_params.py | 5 +-- .../messages/batch_mark_as_unseen_params.py | 5 +-- .../types/messages/batch_unarchive_params.py | 5 +-- .../types/object_add_subscriptions_params.py | 5 +-- .../object_delete_subscriptions_params.py | 4 +-- .../types/object_list_messages_params.py | 6 ++-- .../types/object_list_subscriptions_params.py | 3 +- .../objects/bulk_add_subscriptions_params.py | 5 +-- .../types/objects/bulk_delete_params.py | 5 +-- .../one_signal_channel_data_param.py | 5 +-- .../recipients/push_channel_data_param.py | 5 +-- src/knockapi/types/schedule_create_params.py | 5 +-- src/knockapi/types/schedule_delete_params.py | 5 +-- src/knockapi/types/schedule_list_params.py | 4 +-- src/knockapi/types/schedule_update_params.py | 5 +-- .../types/tenants/bulk_delete_params.py | 5 +-- src/knockapi/types/tenants/bulk_set_params.py | 4 +-- .../types/user_list_messages_params.py | 6 ++-- .../types/user_list_subscriptions_params.py | 3 +- .../types/users/bulk_delete_params.py | 5 +-- .../users/bulk_set_preferences_params.py | 4 +-- .../types/users/feed_list_items_params.py | 5 +-- src/knockapi/types/workflow_cancel_params.py | 5 +-- src/knockapi/types/workflow_trigger_params.py | 5 +-- 43 files changed, 182 insertions(+), 148 deletions(-) diff --git a/src/knockapi/_utils/_transform.py b/src/knockapi/_utils/_transform.py index b0cc20a..f0bcefd 100644 --- a/src/knockapi/_utils/_transform.py +++ b/src/knockapi/_utils/_transform.py @@ -16,6 +16,7 @@ lru_cache, is_mapping, is_iterable, + is_sequence, ) from .._files import is_base64_file_input from ._typing import ( @@ -24,6 +25,7 @@ extract_type_arg, is_iterable_type, is_required_type, + is_sequence_type, is_annotated_type, strip_annotated_type, ) @@ -184,6 +186,8 @@ def _transform_recursive( (is_list_type(stripped_type) and is_list(data)) # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) ): # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually # intended as an iterable, so we don't transform it. @@ -346,6 +350,8 @@ async def _async_transform_recursive( (is_list_type(stripped_type) and is_list(data)) # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) ): # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually # intended as an iterable, so we don't transform it. diff --git a/src/knockapi/resources/channels/bulk.py b/src/knockapi/resources/channels/bulk.py index d179ddf..edd1712 100644 --- a/src/knockapi/resources/channels/bulk.py +++ b/src/knockapi/resources/channels/bulk.py @@ -2,13 +2,13 @@ from __future__ import annotations -from typing import List, Union +from typing import Union from datetime import datetime from typing_extensions import Literal import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -64,10 +64,10 @@ def update_message_status( has_tenant: bool | NotGiven = NOT_GIVEN, newer_than: Union[str, datetime] | NotGiven = NOT_GIVEN, older_than: Union[str, datetime] | NotGiven = NOT_GIVEN, - recipient_ids: List[str] | NotGiven = NOT_GIVEN, - tenants: List[str] | NotGiven = NOT_GIVEN, + recipient_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, + tenants: SequenceNotStr[str] | NotGiven = NOT_GIVEN, trigger_data: str | NotGiven = NOT_GIVEN, - workflows: List[str] | NotGiven = NOT_GIVEN, + workflows: SequenceNotStr[str] | NotGiven = NOT_GIVEN, # 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, @@ -180,10 +180,10 @@ async def update_message_status( has_tenant: bool | NotGiven = NOT_GIVEN, newer_than: Union[str, datetime] | NotGiven = NOT_GIVEN, older_than: Union[str, datetime] | NotGiven = NOT_GIVEN, - recipient_ids: List[str] | NotGiven = NOT_GIVEN, - tenants: List[str] | NotGiven = NOT_GIVEN, + recipient_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, + tenants: SequenceNotStr[str] | NotGiven = NOT_GIVEN, trigger_data: str | NotGiven = NOT_GIVEN, - workflows: List[str] | NotGiven = NOT_GIVEN, + workflows: SequenceNotStr[str] | NotGiven = NOT_GIVEN, # 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, diff --git a/src/knockapi/resources/messages/batch.py b/src/knockapi/resources/messages/batch.py index 4702a5b..874448d 100644 --- a/src/knockapi/resources/messages/batch.py +++ b/src/knockapi/resources/messages/batch.py @@ -2,11 +2,11 @@ from __future__ import annotations -from typing import Dict, List, Optional +from typing import Dict, Optional import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -62,7 +62,7 @@ def with_streaming_response(self) -> BatchResourceWithStreamingResponse: def archive( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, @@ -98,7 +98,7 @@ def archive( def get_content( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, @@ -135,7 +135,7 @@ def get_content( def mark_as_interacted( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[str], metadata: Optional[Dict[str, object]] | NotGiven = NOT_GIVEN, # 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. @@ -183,7 +183,7 @@ def mark_as_interacted( def mark_as_read( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, @@ -219,7 +219,7 @@ def mark_as_read( def mark_as_seen( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, @@ -256,7 +256,7 @@ def mark_as_seen( def mark_as_unread( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, @@ -293,7 +293,7 @@ def mark_as_unread( def mark_as_unseen( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, @@ -330,7 +330,7 @@ def mark_as_unseen( def unarchive( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, @@ -388,7 +388,7 @@ def with_streaming_response(self) -> AsyncBatchResourceWithStreamingResponse: async def archive( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, @@ -424,7 +424,7 @@ async def archive( async def get_content( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, @@ -463,7 +463,7 @@ async def get_content( async def mark_as_interacted( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[str], metadata: Optional[Dict[str, object]] | NotGiven = NOT_GIVEN, # 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. @@ -511,7 +511,7 @@ async def mark_as_interacted( async def mark_as_read( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, @@ -549,7 +549,7 @@ async def mark_as_read( async def mark_as_seen( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, @@ -588,7 +588,7 @@ async def mark_as_seen( async def mark_as_unread( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, @@ -627,7 +627,7 @@ async def mark_as_unread( async def mark_as_unseen( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, @@ -666,7 +666,7 @@ async def mark_as_unseen( async def unarchive( self, *, - message_ids: List[str], + message_ids: SequenceNotStr[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, diff --git a/src/knockapi/resources/messages/messages.py b/src/knockapi/resources/messages/messages.py index 69f8519..ff8cb46 100644 --- a/src/knockapi/resources/messages/messages.py +++ b/src/knockapi/resources/messages/messages.py @@ -22,7 +22,7 @@ message_list_delivery_logs_params, message_mark_as_interacted_params, ) -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -78,14 +78,14 @@ def list( ] | NotGiven = NOT_GIVEN, inserted_at: message_list_params.InsertedAt | NotGiven = NOT_GIVEN, - message_ids: List[str] | NotGiven = NOT_GIVEN, + message_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, page_size: int | NotGiven = NOT_GIVEN, source: str | NotGiven = NOT_GIVEN, status: List[Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"]] | NotGiven = NOT_GIVEN, tenant: str | NotGiven = NOT_GIVEN, trigger_data: str | NotGiven = NOT_GIVEN, - workflow_categories: List[str] | NotGiven = NOT_GIVEN, + workflow_categories: SequenceNotStr[str] | NotGiven = NOT_GIVEN, workflow_recipient_run_id: str | NotGiven = NOT_GIVEN, workflow_run_id: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -694,14 +694,14 @@ def list( ] | NotGiven = NOT_GIVEN, inserted_at: message_list_params.InsertedAt | NotGiven = NOT_GIVEN, - message_ids: List[str] | NotGiven = NOT_GIVEN, + message_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, page_size: int | NotGiven = NOT_GIVEN, source: str | NotGiven = NOT_GIVEN, status: List[Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"]] | NotGiven = NOT_GIVEN, tenant: str | NotGiven = NOT_GIVEN, trigger_data: str | NotGiven = NOT_GIVEN, - workflow_categories: List[str] | NotGiven = NOT_GIVEN, + workflow_categories: SequenceNotStr[str] | NotGiven = NOT_GIVEN, workflow_recipient_run_id: str | NotGiven = NOT_GIVEN, workflow_run_id: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. diff --git a/src/knockapi/resources/objects/bulk.py b/src/knockapi/resources/objects/bulk.py index ec19ad2..d284b4b 100644 --- a/src/knockapi/resources/objects/bulk.py +++ b/src/knockapi/resources/objects/bulk.py @@ -2,11 +2,11 @@ from __future__ import annotations -from typing import List, Iterable +from typing import Iterable import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -47,7 +47,7 @@ def delete( self, collection: str, *, - object_ids: List[str], + object_ids: SequenceNotStr[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, @@ -186,7 +186,7 @@ async def delete( self, collection: str, *, - object_ids: List[str], + object_ids: SequenceNotStr[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, diff --git a/src/knockapi/resources/objects/objects.py b/src/knockapi/resources/objects/objects.py index ee644f7..25a20ef 100644 --- a/src/knockapi/resources/objects/objects.py +++ b/src/knockapi/resources/objects/objects.py @@ -26,7 +26,7 @@ object_list_subscriptions_params, object_delete_subscriptions_params, ) -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -183,7 +183,7 @@ def add_subscriptions( collection: str, object_id: str, *, - recipients: List[RecipientRequestParam], + recipients: SequenceNotStr[RecipientRequestParam], properties: Optional[Dict[str, object]] | NotGiven = NOT_GIVEN, # 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. @@ -237,7 +237,7 @@ def delete_subscriptions( collection: str, object_id: str, *, - recipients: List[RecipientReferenceParam], + recipients: SequenceNotStr[RecipientReferenceParam], # 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, @@ -406,14 +406,14 @@ def list_messages( ] | NotGiven = NOT_GIVEN, inserted_at: object_list_messages_params.InsertedAt | NotGiven = NOT_GIVEN, - message_ids: List[str] | NotGiven = NOT_GIVEN, + message_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, page_size: int | NotGiven = NOT_GIVEN, source: str | NotGiven = NOT_GIVEN, status: List[Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"]] | NotGiven = NOT_GIVEN, tenant: str | NotGiven = NOT_GIVEN, trigger_data: str | NotGiven = NOT_GIVEN, - workflow_categories: List[str] | NotGiven = NOT_GIVEN, + workflow_categories: SequenceNotStr[str] | NotGiven = NOT_GIVEN, workflow_recipient_run_id: str | NotGiven = NOT_GIVEN, workflow_run_id: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -614,7 +614,7 @@ def list_subscriptions( mode: Literal["recipient", "object"] | NotGiven = NOT_GIVEN, objects: Iterable[object_list_subscriptions_params.Object] | NotGiven = NOT_GIVEN, page_size: int | NotGiven = NOT_GIVEN, - recipients: List[RecipientReferenceParam] | NotGiven = NOT_GIVEN, + recipients: SequenceNotStr[RecipientReferenceParam] | NotGiven = NOT_GIVEN, # 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, @@ -1030,7 +1030,7 @@ async def add_subscriptions( collection: str, object_id: str, *, - recipients: List[RecipientRequestParam], + recipients: SequenceNotStr[RecipientRequestParam], properties: Optional[Dict[str, object]] | NotGiven = NOT_GIVEN, # 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. @@ -1084,7 +1084,7 @@ async def delete_subscriptions( collection: str, object_id: str, *, - recipients: List[RecipientReferenceParam], + recipients: SequenceNotStr[RecipientReferenceParam], # 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, @@ -1253,14 +1253,14 @@ def list_messages( ] | NotGiven = NOT_GIVEN, inserted_at: object_list_messages_params.InsertedAt | NotGiven = NOT_GIVEN, - message_ids: List[str] | NotGiven = NOT_GIVEN, + message_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, page_size: int | NotGiven = NOT_GIVEN, source: str | NotGiven = NOT_GIVEN, status: List[Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"]] | NotGiven = NOT_GIVEN, tenant: str | NotGiven = NOT_GIVEN, trigger_data: str | NotGiven = NOT_GIVEN, - workflow_categories: List[str] | NotGiven = NOT_GIVEN, + workflow_categories: SequenceNotStr[str] | NotGiven = NOT_GIVEN, workflow_recipient_run_id: str | NotGiven = NOT_GIVEN, workflow_run_id: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -1461,7 +1461,7 @@ def list_subscriptions( mode: Literal["recipient", "object"] | NotGiven = NOT_GIVEN, objects: Iterable[object_list_subscriptions_params.Object] | NotGiven = NOT_GIVEN, page_size: int | NotGiven = NOT_GIVEN, - recipients: List[RecipientReferenceParam] | NotGiven = NOT_GIVEN, + recipients: SequenceNotStr[RecipientReferenceParam] | NotGiven = NOT_GIVEN, # 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, diff --git a/src/knockapi/resources/schedules/schedules.py b/src/knockapi/resources/schedules/schedules.py index bf836a7..d6b3d1c 100644 --- a/src/knockapi/resources/schedules/schedules.py +++ b/src/knockapi/resources/schedules/schedules.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, List, Union, Iterable, Optional +from typing import Dict, Union, Iterable, Optional from datetime import datetime import httpx @@ -21,7 +21,7 @@ schedule_delete_params, schedule_update_params, ) -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -72,7 +72,7 @@ def with_streaming_response(self) -> SchedulesResourceWithStreamingResponse: def create( self, *, - recipients: List[RecipientRequestParam], + recipients: SequenceNotStr[RecipientRequestParam], workflow: str, actor: Optional[RecipientRequestParam] | NotGiven = NOT_GIVEN, data: Optional[Dict[str, object]] | NotGiven = NOT_GIVEN, @@ -148,7 +148,7 @@ def create( def update( self, *, - schedule_ids: List[str], + schedule_ids: SequenceNotStr[str], actor: Optional[RecipientReferenceParam] | NotGiven = NOT_GIVEN, data: Optional[Dict[str, object]] | NotGiven = NOT_GIVEN, ending_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, @@ -223,7 +223,7 @@ def list( after: str | NotGiven = NOT_GIVEN, before: str | NotGiven = NOT_GIVEN, page_size: int | NotGiven = NOT_GIVEN, - recipients: List[RecipientReferenceParam] | NotGiven = NOT_GIVEN, + recipients: SequenceNotStr[RecipientReferenceParam] | NotGiven = NOT_GIVEN, tenant: str | NotGiven = NOT_GIVEN, # 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. @@ -283,7 +283,7 @@ def list( def delete( self, *, - schedule_ids: List[str], + schedule_ids: SequenceNotStr[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, @@ -343,7 +343,7 @@ def with_streaming_response(self) -> AsyncSchedulesResourceWithStreamingResponse async def create( self, *, - recipients: List[RecipientRequestParam], + recipients: SequenceNotStr[RecipientRequestParam], workflow: str, actor: Optional[RecipientRequestParam] | NotGiven = NOT_GIVEN, data: Optional[Dict[str, object]] | NotGiven = NOT_GIVEN, @@ -419,7 +419,7 @@ async def create( async def update( self, *, - schedule_ids: List[str], + schedule_ids: SequenceNotStr[str], actor: Optional[RecipientReferenceParam] | NotGiven = NOT_GIVEN, data: Optional[Dict[str, object]] | NotGiven = NOT_GIVEN, ending_at: Union[str, datetime, None] | NotGiven = NOT_GIVEN, @@ -494,7 +494,7 @@ def list( after: str | NotGiven = NOT_GIVEN, before: str | NotGiven = NOT_GIVEN, page_size: int | NotGiven = NOT_GIVEN, - recipients: List[RecipientReferenceParam] | NotGiven = NOT_GIVEN, + recipients: SequenceNotStr[RecipientReferenceParam] | NotGiven = NOT_GIVEN, tenant: str | NotGiven = NOT_GIVEN, # 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. @@ -554,7 +554,7 @@ def list( async def delete( self, *, - schedule_ids: List[str], + schedule_ids: SequenceNotStr[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, diff --git a/src/knockapi/resources/tenants/bulk.py b/src/knockapi/resources/tenants/bulk.py index 2deec89..795b038 100644 --- a/src/knockapi/resources/tenants/bulk.py +++ b/src/knockapi/resources/tenants/bulk.py @@ -2,11 +2,9 @@ from __future__ import annotations -from typing import List - import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -47,7 +45,7 @@ def with_streaming_response(self) -> BulkResourceWithStreamingResponse: def delete( self, *, - tenant_ids: List[str], + tenant_ids: SequenceNotStr[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, @@ -85,7 +83,7 @@ def delete( def set( self, *, - tenants: List[InlineTenantRequestParam], + tenants: SequenceNotStr[InlineTenantRequestParam], # 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, @@ -140,7 +138,7 @@ def with_streaming_response(self) -> AsyncBulkResourceWithStreamingResponse: async def delete( self, *, - tenant_ids: List[str], + tenant_ids: SequenceNotStr[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, @@ -178,7 +176,7 @@ async def delete( async def set( self, *, - tenants: List[InlineTenantRequestParam], + tenants: SequenceNotStr[InlineTenantRequestParam], # 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, diff --git a/src/knockapi/resources/users/bulk.py b/src/knockapi/resources/users/bulk.py index 27e7bf2..e043149 100644 --- a/src/knockapi/resources/users/bulk.py +++ b/src/knockapi/resources/users/bulk.py @@ -2,11 +2,11 @@ from __future__ import annotations -from typing import List, Iterable +from typing import Iterable import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -48,7 +48,7 @@ def with_streaming_response(self) -> BulkResourceWithStreamingResponse: def delete( self, *, - user_ids: List[str], + user_ids: SequenceNotStr[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, @@ -120,7 +120,7 @@ def set_preferences( self, *, preferences: PreferenceSetRequestParam, - user_ids: List[str], + user_ids: SequenceNotStr[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, @@ -188,7 +188,7 @@ def with_streaming_response(self) -> AsyncBulkResourceWithStreamingResponse: async def delete( self, *, - user_ids: List[str], + user_ids: SequenceNotStr[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, @@ -260,7 +260,7 @@ async def set_preferences( self, *, preferences: PreferenceSetRequestParam, - user_ids: List[str], + user_ids: SequenceNotStr[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, diff --git a/src/knockapi/resources/users/feeds.py b/src/knockapi/resources/users/feeds.py index ef47808..e9bb789 100644 --- a/src/knockapi/resources/users/feeds.py +++ b/src/knockapi/resources/users/feeds.py @@ -2,12 +2,11 @@ from __future__ import annotations -from typing import List from typing_extensions import Literal import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -96,7 +95,7 @@ def list_items( status: Literal["unread", "read", "unseen", "seen", "all"] | NotGiven = NOT_GIVEN, tenant: str | NotGiven = NOT_GIVEN, trigger_data: str | NotGiven = NOT_GIVEN, - workflow_categories: List[str] | NotGiven = NOT_GIVEN, + workflow_categories: SequenceNotStr[str] | NotGiven = NOT_GIVEN, # 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, @@ -251,7 +250,7 @@ def list_items( status: Literal["unread", "read", "unseen", "seen", "all"] | NotGiven = NOT_GIVEN, tenant: str | NotGiven = NOT_GIVEN, trigger_data: str | NotGiven = NOT_GIVEN, - workflow_categories: List[str] | NotGiven = NOT_GIVEN, + workflow_categories: SequenceNotStr[str] | NotGiven = NOT_GIVEN, # 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, diff --git a/src/knockapi/resources/users/users.py b/src/knockapi/resources/users/users.py index cf2f084..14b6417 100644 --- a/src/knockapi/resources/users/users.py +++ b/src/knockapi/resources/users/users.py @@ -43,7 +43,7 @@ user_set_channel_data_params, user_list_subscriptions_params, ) -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -400,14 +400,14 @@ def list_messages( ] | NotGiven = NOT_GIVEN, inserted_at: user_list_messages_params.InsertedAt | NotGiven = NOT_GIVEN, - message_ids: List[str] | NotGiven = NOT_GIVEN, + message_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, page_size: int | NotGiven = NOT_GIVEN, source: str | NotGiven = NOT_GIVEN, status: List[Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"]] | NotGiven = NOT_GIVEN, tenant: str | NotGiven = NOT_GIVEN, trigger_data: str | NotGiven = NOT_GIVEN, - workflow_categories: List[str] | NotGiven = NOT_GIVEN, + workflow_categories: SequenceNotStr[str] | NotGiven = NOT_GIVEN, workflow_recipient_run_id: str | NotGiven = NOT_GIVEN, workflow_run_id: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -597,7 +597,7 @@ def list_subscriptions( after: str | NotGiven = NOT_GIVEN, before: str | NotGiven = NOT_GIVEN, include: List[Literal["preferences"]] | NotGiven = NOT_GIVEN, - objects: List[RecipientReferenceParam] | NotGiven = NOT_GIVEN, + objects: SequenceNotStr[RecipientReferenceParam] | NotGiven = NOT_GIVEN, page_size: int | NotGiven = NOT_GIVEN, # 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. @@ -1167,14 +1167,14 @@ def list_messages( ] | NotGiven = NOT_GIVEN, inserted_at: user_list_messages_params.InsertedAt | NotGiven = NOT_GIVEN, - message_ids: List[str] | NotGiven = NOT_GIVEN, + message_ids: SequenceNotStr[str] | NotGiven = NOT_GIVEN, page_size: int | NotGiven = NOT_GIVEN, source: str | NotGiven = NOT_GIVEN, status: List[Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"]] | NotGiven = NOT_GIVEN, tenant: str | NotGiven = NOT_GIVEN, trigger_data: str | NotGiven = NOT_GIVEN, - workflow_categories: List[str] | NotGiven = NOT_GIVEN, + workflow_categories: SequenceNotStr[str] | NotGiven = NOT_GIVEN, workflow_recipient_run_id: str | NotGiven = NOT_GIVEN, workflow_run_id: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -1364,7 +1364,7 @@ def list_subscriptions( after: str | NotGiven = NOT_GIVEN, before: str | NotGiven = NOT_GIVEN, include: List[Literal["preferences"]] | NotGiven = NOT_GIVEN, - objects: List[RecipientReferenceParam] | NotGiven = NOT_GIVEN, + objects: SequenceNotStr[RecipientReferenceParam] | NotGiven = NOT_GIVEN, page_size: int | NotGiven = NOT_GIVEN, # 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. diff --git a/src/knockapi/resources/workflows.py b/src/knockapi/resources/workflows.py index e23b0f0..6b065cb 100644 --- a/src/knockapi/resources/workflows.py +++ b/src/knockapi/resources/workflows.py @@ -2,12 +2,12 @@ from __future__ import annotations -from typing import Dict, List, Optional +from typing import Dict, Optional import httpx from ..types import workflow_cancel_params, workflow_trigger_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -51,7 +51,7 @@ def cancel( key: str, *, cancellation_key: str, - recipients: Optional[List[RecipientReferenceParam]] | NotGiven = NOT_GIVEN, + recipients: Optional[SequenceNotStr[RecipientReferenceParam]] | NotGiven = NOT_GIVEN, # 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, @@ -103,7 +103,7 @@ def trigger( self, key: str, *, - recipients: List[RecipientRequestParam], + recipients: SequenceNotStr[RecipientRequestParam], actor: Optional[RecipientRequestParam] | NotGiven = NOT_GIVEN, cancellation_key: Optional[str] | NotGiven = NOT_GIVEN, data: Optional[Dict[str, object]] | NotGiven = NOT_GIVEN, @@ -197,7 +197,7 @@ async def cancel( key: str, *, cancellation_key: str, - recipients: Optional[List[RecipientReferenceParam]] | NotGiven = NOT_GIVEN, + recipients: Optional[SequenceNotStr[RecipientReferenceParam]] | NotGiven = NOT_GIVEN, # 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, @@ -249,7 +249,7 @@ async def trigger( self, key: str, *, - recipients: List[RecipientRequestParam], + recipients: SequenceNotStr[RecipientRequestParam], actor: Optional[RecipientRequestParam] | NotGiven = NOT_GIVEN, cancellation_key: Optional[str] | NotGiven = NOT_GIVEN, data: Optional[Dict[str, object]] | NotGiven = NOT_GIVEN, diff --git a/src/knockapi/types/channels/bulk_update_message_status_params.py b/src/knockapi/types/channels/bulk_update_message_status_params.py index fdb7e71..87bf7a1 100644 --- a/src/knockapi/types/channels/bulk_update_message_status_params.py +++ b/src/knockapi/types/channels/bulk_update_message_status_params.py @@ -2,10 +2,11 @@ from __future__ import annotations -from typing import List, Union +from typing import Union from datetime import datetime from typing_extensions import Literal, Annotated, TypedDict +from ..._types import SequenceNotStr from ..._utils import PropertyInfo __all__ = ["BulkUpdateMessageStatusParams"] @@ -32,10 +33,10 @@ class BulkUpdateMessageStatusParams(TypedDict, total=False): older_than: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] """Limits the results to messages inserted before the given date.""" - recipient_ids: List[str] + recipient_ids: SequenceNotStr[str] """Limits the results to messages with the given recipient IDs.""" - tenants: List[str] + tenants: SequenceNotStr[str] """Limits the results to messages with the given tenant IDs.""" trigger_data: str @@ -45,5 +46,5 @@ class BulkUpdateMessageStatusParams(TypedDict, total=False): more information. """ - workflows: List[str] + workflows: SequenceNotStr[str] """Limits the results to messages with the given workflow keys.""" diff --git a/src/knockapi/types/message_list_params.py b/src/knockapi/types/message_list_params.py index 703a2ab..f90a5d1 100644 --- a/src/knockapi/types/message_list_params.py +++ b/src/knockapi/types/message_list_params.py @@ -5,6 +5,8 @@ from typing import List from typing_extensions import Literal, TypedDict +from .._types import SequenceNotStr + __all__ = ["MessageListParams", "InsertedAt"] @@ -25,7 +27,7 @@ class MessageListParams(TypedDict, total=False): inserted_at: InsertedAt - message_ids: List[str] + message_ids: SequenceNotStr[str] """Limits the results to only the message IDs given (max 50). Note: when using this option, the results will be subject to any other filters @@ -51,7 +53,7 @@ class MessageListParams(TypedDict, total=False): more information. """ - workflow_categories: List[str] + workflow_categories: SequenceNotStr[str] """Limits the results to messages related to any of the provided categories.""" workflow_recipient_run_id: str diff --git a/src/knockapi/types/messages/batch_archive_params.py b/src/knockapi/types/messages/batch_archive_params.py index 6b091e1..01d7306 100644 --- a/src/knockapi/types/messages/batch_archive_params.py +++ b/src/knockapi/types/messages/batch_archive_params.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["BatchArchiveParams"] class BatchArchiveParams(TypedDict, total=False): - message_ids: Required[List[str]] + message_ids: Required[SequenceNotStr[str]] """The message IDs to update the status of.""" diff --git a/src/knockapi/types/messages/batch_get_content_params.py b/src/knockapi/types/messages/batch_get_content_params.py index 2262eff..a352ca9 100644 --- a/src/knockapi/types/messages/batch_get_content_params.py +++ b/src/knockapi/types/messages/batch_get_content_params.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["BatchGetContentParams"] class BatchGetContentParams(TypedDict, total=False): - message_ids: Required[List[str]] + message_ids: Required[SequenceNotStr[str]] """The IDs of the messages to fetch contents of.""" diff --git a/src/knockapi/types/messages/batch_mark_as_interacted_params.py b/src/knockapi/types/messages/batch_mark_as_interacted_params.py index 391c702..df91778 100644 --- a/src/knockapi/types/messages/batch_mark_as_interacted_params.py +++ b/src/knockapi/types/messages/batch_mark_as_interacted_params.py @@ -2,14 +2,16 @@ from __future__ import annotations -from typing import Dict, List, Optional +from typing import Dict, Optional from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["BatchMarkAsInteractedParams"] class BatchMarkAsInteractedParams(TypedDict, total=False): - message_ids: Required[List[str]] + message_ids: Required[SequenceNotStr[str]] """The message IDs to batch mark as interacted with.""" metadata: Optional[Dict[str, object]] diff --git a/src/knockapi/types/messages/batch_mark_as_read_params.py b/src/knockapi/types/messages/batch_mark_as_read_params.py index dc14cb7..2e653bd 100644 --- a/src/knockapi/types/messages/batch_mark_as_read_params.py +++ b/src/knockapi/types/messages/batch_mark_as_read_params.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["BatchMarkAsReadParams"] class BatchMarkAsReadParams(TypedDict, total=False): - message_ids: Required[List[str]] + message_ids: Required[SequenceNotStr[str]] """The message IDs to update the status of.""" diff --git a/src/knockapi/types/messages/batch_mark_as_seen_params.py b/src/knockapi/types/messages/batch_mark_as_seen_params.py index 137eb1c..850c3ce 100644 --- a/src/knockapi/types/messages/batch_mark_as_seen_params.py +++ b/src/knockapi/types/messages/batch_mark_as_seen_params.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["BatchMarkAsSeenParams"] class BatchMarkAsSeenParams(TypedDict, total=False): - message_ids: Required[List[str]] + message_ids: Required[SequenceNotStr[str]] """The message IDs to update the status of.""" diff --git a/src/knockapi/types/messages/batch_mark_as_unread_params.py b/src/knockapi/types/messages/batch_mark_as_unread_params.py index a9828cc..b24417d 100644 --- a/src/knockapi/types/messages/batch_mark_as_unread_params.py +++ b/src/knockapi/types/messages/batch_mark_as_unread_params.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["BatchMarkAsUnreadParams"] class BatchMarkAsUnreadParams(TypedDict, total=False): - message_ids: Required[List[str]] + message_ids: Required[SequenceNotStr[str]] """The message IDs to update the status of.""" diff --git a/src/knockapi/types/messages/batch_mark_as_unseen_params.py b/src/knockapi/types/messages/batch_mark_as_unseen_params.py index 82a6dcd..0b8592b 100644 --- a/src/knockapi/types/messages/batch_mark_as_unseen_params.py +++ b/src/knockapi/types/messages/batch_mark_as_unseen_params.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["BatchMarkAsUnseenParams"] class BatchMarkAsUnseenParams(TypedDict, total=False): - message_ids: Required[List[str]] + message_ids: Required[SequenceNotStr[str]] """The message IDs to update the status of.""" diff --git a/src/knockapi/types/messages/batch_unarchive_params.py b/src/knockapi/types/messages/batch_unarchive_params.py index 2bcca3d..640a77d 100644 --- a/src/knockapi/types/messages/batch_unarchive_params.py +++ b/src/knockapi/types/messages/batch_unarchive_params.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["BatchUnarchiveParams"] class BatchUnarchiveParams(TypedDict, total=False): - message_ids: Required[List[str]] + message_ids: Required[SequenceNotStr[str]] """The message IDs to update the status of.""" diff --git a/src/knockapi/types/object_add_subscriptions_params.py b/src/knockapi/types/object_add_subscriptions_params.py index f66f7d2..48cf377 100644 --- a/src/knockapi/types/object_add_subscriptions_params.py +++ b/src/knockapi/types/object_add_subscriptions_params.py @@ -2,16 +2,17 @@ from __future__ import annotations -from typing import Dict, List, Optional +from typing import Dict, Optional from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr from .recipient_request_param import RecipientRequestParam __all__ = ["ObjectAddSubscriptionsParams"] class ObjectAddSubscriptionsParams(TypedDict, total=False): - recipients: Required[List[RecipientRequestParam]] + recipients: Required[SequenceNotStr[RecipientRequestParam]] """The recipients of the subscription. You can subscribe up to 100 recipients to an object at a time. diff --git a/src/knockapi/types/object_delete_subscriptions_params.py b/src/knockapi/types/object_delete_subscriptions_params.py index b00f365..6b72eac 100644 --- a/src/knockapi/types/object_delete_subscriptions_params.py +++ b/src/knockapi/types/object_delete_subscriptions_params.py @@ -2,16 +2,16 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr from .recipient_reference_param import RecipientReferenceParam __all__ = ["ObjectDeleteSubscriptionsParams"] class ObjectDeleteSubscriptionsParams(TypedDict, total=False): - recipients: Required[List[RecipientReferenceParam]] + recipients: Required[SequenceNotStr[RecipientReferenceParam]] """The recipients of the subscription. You can subscribe up to 100 recipients to an object at a time. diff --git a/src/knockapi/types/object_list_messages_params.py b/src/knockapi/types/object_list_messages_params.py index ca70806..29ad12e 100644 --- a/src/knockapi/types/object_list_messages_params.py +++ b/src/knockapi/types/object_list_messages_params.py @@ -5,6 +5,8 @@ from typing import List from typing_extensions import Literal, TypedDict +from .._types import SequenceNotStr + __all__ = ["ObjectListMessagesParams", "InsertedAt"] @@ -25,7 +27,7 @@ class ObjectListMessagesParams(TypedDict, total=False): inserted_at: InsertedAt - message_ids: List[str] + message_ids: SequenceNotStr[str] """Limits the results to only the message IDs given (max 50). Note: when using this option, the results will be subject to any other filters @@ -51,7 +53,7 @@ class ObjectListMessagesParams(TypedDict, total=False): more information. """ - workflow_categories: List[str] + workflow_categories: SequenceNotStr[str] """Limits the results to messages related to any of the provided categories.""" workflow_recipient_run_id: str diff --git a/src/knockapi/types/object_list_subscriptions_params.py b/src/knockapi/types/object_list_subscriptions_params.py index f7d97fd..f8a45fc 100644 --- a/src/knockapi/types/object_list_subscriptions_params.py +++ b/src/knockapi/types/object_list_subscriptions_params.py @@ -5,6 +5,7 @@ from typing import List, Iterable from typing_extensions import Literal, TypedDict +from .._types import SequenceNotStr from .recipient_reference_param import RecipientReferenceParam __all__ = ["ObjectListSubscriptionsParams", "Object"] @@ -33,7 +34,7 @@ class ObjectListSubscriptionsParams(TypedDict, total=False): page_size: int """The number of items per page (defaults to 50).""" - recipients: List[RecipientReferenceParam] + recipients: SequenceNotStr[RecipientReferenceParam] """Recipients to filter by (only used if mode is `object`).""" diff --git a/src/knockapi/types/objects/bulk_add_subscriptions_params.py b/src/knockapi/types/objects/bulk_add_subscriptions_params.py index b83523d..6198b30 100644 --- a/src/knockapi/types/objects/bulk_add_subscriptions_params.py +++ b/src/knockapi/types/objects/bulk_add_subscriptions_params.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import Dict, List, Iterable, Optional +from typing import Dict, Iterable, Optional from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr from ..recipient_request_param import RecipientRequestParam __all__ = ["BulkAddSubscriptionsParams", "Subscription"] @@ -16,7 +17,7 @@ class BulkAddSubscriptionsParams(TypedDict, total=False): class Subscription(TypedDict, total=False): - recipients: Required[List[RecipientRequestParam]] + recipients: Required[SequenceNotStr[RecipientRequestParam]] """The recipients of the subscription. You can subscribe up to 100 recipients to an object at a time. diff --git a/src/knockapi/types/objects/bulk_delete_params.py b/src/knockapi/types/objects/bulk_delete_params.py index dd8b472..0d0f0a6 100644 --- a/src/knockapi/types/objects/bulk_delete_params.py +++ b/src/knockapi/types/objects/bulk_delete_params.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["BulkDeleteParams"] class BulkDeleteParams(TypedDict, total=False): - object_ids: Required[List[str]] + object_ids: Required[SequenceNotStr[str]] """List of object IDs to delete.""" diff --git a/src/knockapi/types/recipients/one_signal_channel_data_param.py b/src/knockapi/types/recipients/one_signal_channel_data_param.py index f59727c..5ea9141 100644 --- a/src/knockapi/types/recipients/one_signal_channel_data_param.py +++ b/src/knockapi/types/recipients/one_signal_channel_data_param.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["OneSignalChannelDataParam"] class OneSignalChannelDataParam(TypedDict, total=False): - player_ids: Required[List[str]] + player_ids: Required[SequenceNotStr[str]] """A list of OneSignal player IDs.""" diff --git a/src/knockapi/types/recipients/push_channel_data_param.py b/src/knockapi/types/recipients/push_channel_data_param.py index ad8b8be..f826156 100644 --- a/src/knockapi/types/recipients/push_channel_data_param.py +++ b/src/knockapi/types/recipients/push_channel_data_param.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["PushChannelDataParam"] class PushChannelDataParam(TypedDict, total=False): - tokens: Required[List[str]] + tokens: Required[SequenceNotStr[str]] """A list of push channel tokens.""" diff --git a/src/knockapi/types/schedule_create_params.py b/src/knockapi/types/schedule_create_params.py index b9956fa..a55aa90 100644 --- a/src/knockapi/types/schedule_create_params.py +++ b/src/knockapi/types/schedule_create_params.py @@ -2,10 +2,11 @@ from __future__ import annotations -from typing import Dict, List, Union, Iterable, Optional +from typing import Dict, Union, Iterable, Optional from datetime import datetime from typing_extensions import Required, Annotated, TypedDict +from .._types import SequenceNotStr from .._utils import PropertyInfo from .recipient_request_param import RecipientRequestParam from .schedule_repeat_rule_param import ScheduleRepeatRuleParam @@ -15,7 +16,7 @@ class ScheduleCreateParams(TypedDict, total=False): - recipients: Required[List[RecipientRequestParam]] + recipients: Required[SequenceNotStr[RecipientRequestParam]] """The recipients to set the schedule for. Limited to 100 recipients per request.""" workflow: Required[str] diff --git a/src/knockapi/types/schedule_delete_params.py b/src/knockapi/types/schedule_delete_params.py index a6d5f44..af60432 100644 --- a/src/knockapi/types/schedule_delete_params.py +++ b/src/knockapi/types/schedule_delete_params.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr + __all__ = ["ScheduleDeleteParams"] class ScheduleDeleteParams(TypedDict, total=False): - schedule_ids: Required[List[str]] + schedule_ids: Required[SequenceNotStr[str]] """A list of schedule IDs.""" diff --git a/src/knockapi/types/schedule_list_params.py b/src/knockapi/types/schedule_list_params.py index 710182f..9c84045 100644 --- a/src/knockapi/types/schedule_list_params.py +++ b/src/knockapi/types/schedule_list_params.py @@ -2,9 +2,9 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr from .recipient_reference_param import RecipientReferenceParam __all__ = ["ScheduleListParams"] @@ -23,7 +23,7 @@ class ScheduleListParams(TypedDict, total=False): page_size: int """The number of items per page (defaults to 50).""" - recipients: List[RecipientReferenceParam] + recipients: SequenceNotStr[RecipientReferenceParam] """Filter by recipient references.""" tenant: str diff --git a/src/knockapi/types/schedule_update_params.py b/src/knockapi/types/schedule_update_params.py index 8863edd..ee7890f 100644 --- a/src/knockapi/types/schedule_update_params.py +++ b/src/knockapi/types/schedule_update_params.py @@ -2,10 +2,11 @@ from __future__ import annotations -from typing import Dict, List, Union, Iterable, Optional +from typing import Dict, Union, Iterable, Optional from datetime import datetime from typing_extensions import Required, Annotated, TypedDict +from .._types import SequenceNotStr from .._utils import PropertyInfo from .recipient_reference_param import RecipientReferenceParam from .schedule_repeat_rule_param import ScheduleRepeatRuleParam @@ -15,7 +16,7 @@ class ScheduleUpdateParams(TypedDict, total=False): - schedule_ids: Required[List[str]] + schedule_ids: Required[SequenceNotStr[str]] """A list of schedule IDs.""" actor: Optional[RecipientReferenceParam] diff --git a/src/knockapi/types/tenants/bulk_delete_params.py b/src/knockapi/types/tenants/bulk_delete_params.py index 69cf77d..01ae68a 100644 --- a/src/knockapi/types/tenants/bulk_delete_params.py +++ b/src/knockapi/types/tenants/bulk_delete_params.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["BulkDeleteParams"] class BulkDeleteParams(TypedDict, total=False): - tenant_ids: Required[List[str]] + tenant_ids: Required[SequenceNotStr[str]] """The IDs of the tenants to delete.""" diff --git a/src/knockapi/types/tenants/bulk_set_params.py b/src/knockapi/types/tenants/bulk_set_params.py index b636880..4307b13 100644 --- a/src/knockapi/types/tenants/bulk_set_params.py +++ b/src/knockapi/types/tenants/bulk_set_params.py @@ -2,14 +2,14 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr from ..inline_tenant_request_param import InlineTenantRequestParam __all__ = ["BulkSetParams"] class BulkSetParams(TypedDict, total=False): - tenants: Required[List[InlineTenantRequestParam]] + tenants: Required[SequenceNotStr[InlineTenantRequestParam]] """The tenants to be upserted.""" diff --git a/src/knockapi/types/user_list_messages_params.py b/src/knockapi/types/user_list_messages_params.py index 28c7aef..2b2e2ee 100644 --- a/src/knockapi/types/user_list_messages_params.py +++ b/src/knockapi/types/user_list_messages_params.py @@ -5,6 +5,8 @@ from typing import List from typing_extensions import Literal, TypedDict +from .._types import SequenceNotStr + __all__ = ["UserListMessagesParams", "InsertedAt"] @@ -25,7 +27,7 @@ class UserListMessagesParams(TypedDict, total=False): inserted_at: InsertedAt - message_ids: List[str] + message_ids: SequenceNotStr[str] """Limits the results to only the message IDs given (max 50). Note: when using this option, the results will be subject to any other filters @@ -51,7 +53,7 @@ class UserListMessagesParams(TypedDict, total=False): more information. """ - workflow_categories: List[str] + workflow_categories: SequenceNotStr[str] """Limits the results to messages related to any of the provided categories.""" workflow_recipient_run_id: str diff --git a/src/knockapi/types/user_list_subscriptions_params.py b/src/knockapi/types/user_list_subscriptions_params.py index 8700c6f..c17e938 100644 --- a/src/knockapi/types/user_list_subscriptions_params.py +++ b/src/knockapi/types/user_list_subscriptions_params.py @@ -5,6 +5,7 @@ from typing import List from typing_extensions import Literal, TypedDict +from .._types import SequenceNotStr from .recipient_reference_param import RecipientReferenceParam __all__ = ["UserListSubscriptionsParams"] @@ -20,7 +21,7 @@ class UserListSubscriptionsParams(TypedDict, total=False): include: List[Literal["preferences"]] """Associated resources to include in the response.""" - objects: List[RecipientReferenceParam] + objects: SequenceNotStr[RecipientReferenceParam] """Only returns subscriptions for the specified object references.""" page_size: int diff --git a/src/knockapi/types/users/bulk_delete_params.py b/src/knockapi/types/users/bulk_delete_params.py index 09f8f4c..7174742 100644 --- a/src/knockapi/types/users/bulk_delete_params.py +++ b/src/knockapi/types/users/bulk_delete_params.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["BulkDeleteParams"] class BulkDeleteParams(TypedDict, total=False): - user_ids: Required[List[str]] + user_ids: Required[SequenceNotStr[str]] """A list of user IDs.""" diff --git a/src/knockapi/types/users/bulk_set_preferences_params.py b/src/knockapi/types/users/bulk_set_preferences_params.py index ac3c20f..a954ede 100644 --- a/src/knockapi/types/users/bulk_set_preferences_params.py +++ b/src/knockapi/types/users/bulk_set_preferences_params.py @@ -2,9 +2,9 @@ from __future__ import annotations -from typing import List from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr from ..recipients.preference_set_request_param import PreferenceSetRequestParam __all__ = ["BulkSetPreferencesParams"] @@ -14,5 +14,5 @@ class BulkSetPreferencesParams(TypedDict, total=False): preferences: Required[PreferenceSetRequestParam] """A request to set a preference set for a recipient.""" - user_ids: Required[List[str]] + user_ids: Required[SequenceNotStr[str]] """A list of user IDs.""" diff --git a/src/knockapi/types/users/feed_list_items_params.py b/src/knockapi/types/users/feed_list_items_params.py index a21b120..cc5a3b7 100644 --- a/src/knockapi/types/users/feed_list_items_params.py +++ b/src/knockapi/types/users/feed_list_items_params.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import List from typing_extensions import Literal, TypedDict +from ..._types import SequenceNotStr + __all__ = ["FeedListItemsParams"] @@ -36,5 +37,5 @@ class FeedListItemsParams(TypedDict, total=False): trigger_data: str """The trigger data of the feed items (as a JSON string).""" - workflow_categories: List[str] + workflow_categories: SequenceNotStr[str] """The workflow categories of the feed items.""" diff --git a/src/knockapi/types/workflow_cancel_params.py b/src/knockapi/types/workflow_cancel_params.py index bbf90fc..ad674e6 100644 --- a/src/knockapi/types/workflow_cancel_params.py +++ b/src/knockapi/types/workflow_cancel_params.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import List, Optional +from typing import Optional from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr from .recipient_reference_param import RecipientReferenceParam __all__ = ["WorkflowCancelParams"] @@ -20,7 +21,7 @@ class WorkflowCancelParams(TypedDict, total=False): unintentional cancellations. """ - recipients: Optional[List[RecipientReferenceParam]] + recipients: Optional[SequenceNotStr[RecipientReferenceParam]] """A list of recipients to cancel the notification for. If omitted, cancels for all recipients associated with the cancellation key. diff --git a/src/knockapi/types/workflow_trigger_params.py b/src/knockapi/types/workflow_trigger_params.py index 9f73210..604f319 100644 --- a/src/knockapi/types/workflow_trigger_params.py +++ b/src/knockapi/types/workflow_trigger_params.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import Dict, List, Optional +from typing import Dict, Optional from typing_extensions import Required, TypedDict +from .._types import SequenceNotStr from .recipient_request_param import RecipientRequestParam from .inline_tenant_request_param import InlineTenantRequestParam @@ -12,7 +13,7 @@ class WorkflowTriggerParams(TypedDict, total=False): - recipients: Required[List[RecipientRequestParam]] + recipients: Required[SequenceNotStr[RecipientRequestParam]] """The recipients to trigger the workflow for. Can inline identify users, objects, or use a list of user IDs. Limited to 1,000 From b6b52d7259d8c48f92f208d99756eb7fd234b0c4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 20:41:51 +0000 Subject: [PATCH 09/17] feat(api): api update --- .stats.yml | 4 +-- src/knockapi/types/message.py | 56 +++++++++++++++++------------------ 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/.stats.yml b/.stats.yml index 232dd9d..9cbdb2d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 89 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-e4ea1ded040ebfa923df0d24ef37ae3c742383828cda85e1489bc2cb5e14da29.yml -openapi_spec_hash: 4cfd1f5f0d42e1b821f70ba12089b606 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-8d6bb1a0b221b8b9fb9aae94419c46e2e1ca4732281924f24c6539c006d12cb8.yml +openapi_spec_hash: 5dd6c9f3cac4f8cc602c0d1543ec4de4 config_hash: 5c872aa99cad9b9602e84668f5b38a8a diff --git a/src/knockapi/types/message.py b/src/knockapi/types/message.py index bb136ea..c2cee77 100644 --- a/src/knockapi/types/message.py +++ b/src/knockapi/types/message.py @@ -25,16 +25,40 @@ class Source(BaseModel): """The ID of the version of the workflow that triggered the message.""" step_ref: Optional[str] = None - """The step reference for the step in the workflow that generated the message""" + """The step reference for the step in the workflow that generated the message.""" class Message(BaseModel): - id: Optional[str] = None + id: str """The unique identifier for the message.""" - api_typename: Optional[str] = FieldInfo(alias="__typename", default=None) + api_typename: str = FieldInfo(alias="__typename") """The typename of the schema.""" + channel_id: str + """The ID for the channel the message was sent through.""" + + engagement_statuses: List[Literal["seen", "read", "interacted", "link_clicked", "archived"]] + """A list of engagement statuses.""" + + inserted_at: datetime + """Timestamp when the resource was created.""" + + recipient: RecipientReference + """ + A reference to a recipient, either a user identifier (string) or an object + reference (ID, collection). + """ + + source: Source + """The workflow that triggered the message.""" + + status: Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"] + """The message delivery status.""" + + updated_at: datetime + """The timestamp when the resource was last updated.""" + actors: Optional[List[RecipientReference]] = None """One or more actors that are associated with this message. @@ -45,9 +69,6 @@ class Message(BaseModel): archived_at: Optional[datetime] = None """Timestamp when the message was archived.""" - channel_id: Optional[str] = None - """The ID for the channel the message was sent through.""" - clicked_at: Optional[datetime] = None """Timestamp when the message was clicked.""" @@ -61,12 +82,6 @@ class Message(BaseModel): `data` from the most-recent trigger request (the final `activity` in the batch). """ - engagement_statuses: Optional[List[Literal["seen", "read", "interacted", "link_clicked", "archived"]]] = None - """A list of engagement statuses.""" - - inserted_at: Optional[datetime] = None - """Timestamp when the resource was created.""" - interacted_at: Optional[datetime] = None """Timestamp when the message was interacted with.""" @@ -79,34 +94,17 @@ class Message(BaseModel): read_at: Optional[datetime] = None """Timestamp when the message was read.""" - recipient: Optional[RecipientReference] = None - """ - A reference to a recipient, either a user identifier (string) or an object - reference (ID, collection). - """ - scheduled_at: Optional[datetime] = None """Timestamp when the message was scheduled to be sent.""" seen_at: Optional[datetime] = None """Timestamp when the message was seen.""" - source: Optional[Source] = None - """The workflow that triggered the message.""" - - status: Optional[ - Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"] - ] = None - """The message delivery status.""" - tenant: Optional[str] = None """The ID of the `tenant` associated with the message. Only present when a `tenant` is provided on a workflow trigger request. """ - updated_at: Optional[datetime] = None - """The timestamp when the resource was last updated.""" - workflow: Optional[str] = None """The key of the workflow that generated the message.""" From 122f574b1018dededf82d02d01f56d055115ce40 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:16:12 +0000 Subject: [PATCH 10/17] feat(api): api update --- .stats.yml | 4 +-- src/knockapi/types/message.py | 56 ++++++++++++++++++----------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9cbdb2d..232dd9d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 89 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-8d6bb1a0b221b8b9fb9aae94419c46e2e1ca4732281924f24c6539c006d12cb8.yml -openapi_spec_hash: 5dd6c9f3cac4f8cc602c0d1543ec4de4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-e4ea1ded040ebfa923df0d24ef37ae3c742383828cda85e1489bc2cb5e14da29.yml +openapi_spec_hash: 4cfd1f5f0d42e1b821f70ba12089b606 config_hash: 5c872aa99cad9b9602e84668f5b38a8a diff --git a/src/knockapi/types/message.py b/src/knockapi/types/message.py index c2cee77..bb136ea 100644 --- a/src/knockapi/types/message.py +++ b/src/knockapi/types/message.py @@ -25,40 +25,16 @@ class Source(BaseModel): """The ID of the version of the workflow that triggered the message.""" step_ref: Optional[str] = None - """The step reference for the step in the workflow that generated the message.""" + """The step reference for the step in the workflow that generated the message""" class Message(BaseModel): - id: str + id: Optional[str] = None """The unique identifier for the message.""" - api_typename: str = FieldInfo(alias="__typename") + api_typename: Optional[str] = FieldInfo(alias="__typename", default=None) """The typename of the schema.""" - channel_id: str - """The ID for the channel the message was sent through.""" - - engagement_statuses: List[Literal["seen", "read", "interacted", "link_clicked", "archived"]] - """A list of engagement statuses.""" - - inserted_at: datetime - """Timestamp when the resource was created.""" - - recipient: RecipientReference - """ - A reference to a recipient, either a user identifier (string) or an object - reference (ID, collection). - """ - - source: Source - """The workflow that triggered the message.""" - - status: Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"] - """The message delivery status.""" - - updated_at: datetime - """The timestamp when the resource was last updated.""" - actors: Optional[List[RecipientReference]] = None """One or more actors that are associated with this message. @@ -69,6 +45,9 @@ class Message(BaseModel): archived_at: Optional[datetime] = None """Timestamp when the message was archived.""" + channel_id: Optional[str] = None + """The ID for the channel the message was sent through.""" + clicked_at: Optional[datetime] = None """Timestamp when the message was clicked.""" @@ -82,6 +61,12 @@ class Message(BaseModel): `data` from the most-recent trigger request (the final `activity` in the batch). """ + engagement_statuses: Optional[List[Literal["seen", "read", "interacted", "link_clicked", "archived"]]] = None + """A list of engagement statuses.""" + + inserted_at: Optional[datetime] = None + """Timestamp when the resource was created.""" + interacted_at: Optional[datetime] = None """Timestamp when the message was interacted with.""" @@ -94,17 +79,34 @@ class Message(BaseModel): read_at: Optional[datetime] = None """Timestamp when the message was read.""" + recipient: Optional[RecipientReference] = None + """ + A reference to a recipient, either a user identifier (string) or an object + reference (ID, collection). + """ + scheduled_at: Optional[datetime] = None """Timestamp when the message was scheduled to be sent.""" seen_at: Optional[datetime] = None """Timestamp when the message was seen.""" + source: Optional[Source] = None + """The workflow that triggered the message.""" + + status: Optional[ + Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"] + ] = None + """The message delivery status.""" + tenant: Optional[str] = None """The ID of the `tenant` associated with the message. Only present when a `tenant` is provided on a workflow trigger request. """ + updated_at: Optional[datetime] = None + """The timestamp when the resource was last updated.""" + workflow: Optional[str] = None """The key of the workflow that generated the message.""" From c2a6ebbc4de4158cabbc9a22f60c2cee346c3531 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 18:29:15 +0000 Subject: [PATCH 11/17] feat(api): api update --- .stats.yml | 4 +- src/knockapi/types/message.py | 56 +++++++++---------- .../types/object_set_channel_data_params.py | 13 ++++- src/knockapi/types/recipients/channel_data.py | 24 +++++++- .../inline_channel_data_request_param.py | 20 ++++++- .../types/user_set_channel_data_params.py | 13 ++++- 6 files changed, 92 insertions(+), 38 deletions(-) diff --git a/.stats.yml b/.stats.yml index 232dd9d..bfde468 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 89 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-e4ea1ded040ebfa923df0d24ef37ae3c742383828cda85e1489bc2cb5e14da29.yml -openapi_spec_hash: 4cfd1f5f0d42e1b821f70ba12089b606 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-2d3aac5f3ddf05db97231694fc7fab3f0f2abac4691944dd52bf5a250a4edaa5.yml +openapi_spec_hash: 6418b750ca4a74b7248e3913fefb0bed config_hash: 5c872aa99cad9b9602e84668f5b38a8a diff --git a/src/knockapi/types/message.py b/src/knockapi/types/message.py index bb136ea..c2cee77 100644 --- a/src/knockapi/types/message.py +++ b/src/knockapi/types/message.py @@ -25,16 +25,40 @@ class Source(BaseModel): """The ID of the version of the workflow that triggered the message.""" step_ref: Optional[str] = None - """The step reference for the step in the workflow that generated the message""" + """The step reference for the step in the workflow that generated the message.""" class Message(BaseModel): - id: Optional[str] = None + id: str """The unique identifier for the message.""" - api_typename: Optional[str] = FieldInfo(alias="__typename", default=None) + api_typename: str = FieldInfo(alias="__typename") """The typename of the schema.""" + channel_id: str + """The ID for the channel the message was sent through.""" + + engagement_statuses: List[Literal["seen", "read", "interacted", "link_clicked", "archived"]] + """A list of engagement statuses.""" + + inserted_at: datetime + """Timestamp when the resource was created.""" + + recipient: RecipientReference + """ + A reference to a recipient, either a user identifier (string) or an object + reference (ID, collection). + """ + + source: Source + """The workflow that triggered the message.""" + + status: Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"] + """The message delivery status.""" + + updated_at: datetime + """The timestamp when the resource was last updated.""" + actors: Optional[List[RecipientReference]] = None """One or more actors that are associated with this message. @@ -45,9 +69,6 @@ class Message(BaseModel): archived_at: Optional[datetime] = None """Timestamp when the message was archived.""" - channel_id: Optional[str] = None - """The ID for the channel the message was sent through.""" - clicked_at: Optional[datetime] = None """Timestamp when the message was clicked.""" @@ -61,12 +82,6 @@ class Message(BaseModel): `data` from the most-recent trigger request (the final `activity` in the batch). """ - engagement_statuses: Optional[List[Literal["seen", "read", "interacted", "link_clicked", "archived"]]] = None - """A list of engagement statuses.""" - - inserted_at: Optional[datetime] = None - """Timestamp when the resource was created.""" - interacted_at: Optional[datetime] = None """Timestamp when the message was interacted with.""" @@ -79,34 +94,17 @@ class Message(BaseModel): read_at: Optional[datetime] = None """Timestamp when the message was read.""" - recipient: Optional[RecipientReference] = None - """ - A reference to a recipient, either a user identifier (string) or an object - reference (ID, collection). - """ - scheduled_at: Optional[datetime] = None """Timestamp when the message was scheduled to be sent.""" seen_at: Optional[datetime] = None """Timestamp when the message was seen.""" - source: Optional[Source] = None - """The workflow that triggered the message.""" - - status: Optional[ - Literal["queued", "sent", "delivered", "delivery_attempted", "undelivered", "not_sent", "bounced"] - ] = None - """The message delivery status.""" - tenant: Optional[str] = None """The ID of the `tenant` associated with the message. Only present when a `tenant` is provided on a workflow trigger request. """ - updated_at: Optional[datetime] = None - """The timestamp when the resource was last updated.""" - workflow: Optional[str] = None """The key of the workflow that generated the message.""" diff --git a/src/knockapi/types/object_set_channel_data_params.py b/src/knockapi/types/object_set_channel_data_params.py index 9c5d4fd..e4b6c89 100644 --- a/src/knockapi/types/object_set_channel_data_params.py +++ b/src/knockapi/types/object_set_channel_data_params.py @@ -5,13 +5,14 @@ from typing import Union from typing_extensions import Required, TypeAlias, TypedDict +from .._types import SequenceNotStr from .recipients.push_channel_data_param import PushChannelDataParam from .recipients.slack_channel_data_param import SlackChannelDataParam from .recipients.discord_channel_data_param import DiscordChannelDataParam from .recipients.ms_teams_channel_data_param import MsTeamsChannelDataParam from .recipients.one_signal_channel_data_param import OneSignalChannelDataParam -__all__ = ["ObjectSetChannelDataParams", "Data"] +__all__ = ["ObjectSetChannelDataParams", "Data", "DataAwsSnsPushChannelData"] class ObjectSetChannelDataParams(TypedDict, total=False): @@ -19,9 +20,19 @@ class ObjectSetChannelDataParams(TypedDict, total=False): """Channel data for a given channel type.""" +class DataAwsSnsPushChannelData(TypedDict, total=False): + target_arns: Required[SequenceNotStr[str]] + """A list of platform endpoint ARNs. + + See + [Setting up an Amazon SNS platform endpoint for mobile notifications](https://docs.aws.amazon.com/sns/latest/dg/mobile-platform-endpoint.html). + """ + + Data: TypeAlias = Union[ PushChannelDataParam, OneSignalChannelDataParam, + DataAwsSnsPushChannelData, SlackChannelDataParam, MsTeamsChannelDataParam, DiscordChannelDataParam, diff --git a/src/knockapi/types/recipients/channel_data.py b/src/knockapi/types/recipients/channel_data.py index 4f598c6..3c35931 100644 --- a/src/knockapi/types/recipients/channel_data.py +++ b/src/knockapi/types/recipients/channel_data.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Union, Optional +from typing import List, Union, Optional from typing_extensions import Literal, TypeAlias from pydantic import Field as FieldInfo @@ -12,9 +12,26 @@ from .ms_teams_channel_data import MsTeamsChannelData from .one_signal_channel_data import OneSignalChannelData -__all__ = ["ChannelData", "Data"] +__all__ = ["ChannelData", "Data", "DataAwsSnsPushChannelData"] -Data: TypeAlias = Union[PushChannelData, SlackChannelData, MsTeamsChannelData, DiscordChannelData, OneSignalChannelData] + +class DataAwsSnsPushChannelData(BaseModel): + target_arns: List[str] + """A list of platform endpoint ARNs. + + See + [Setting up an Amazon SNS platform endpoint for mobile notifications](https://docs.aws.amazon.com/sns/latest/dg/mobile-platform-endpoint.html). + """ + + +Data: TypeAlias = Union[ + PushChannelData, + SlackChannelData, + MsTeamsChannelData, + DiscordChannelData, + OneSignalChannelData, + DataAwsSnsPushChannelData, +] class ChannelData(BaseModel): @@ -31,6 +48,7 @@ class ChannelData(BaseModel): Literal[ "push_fcm", "push_apns", + "push_aws_sns", "push_expo", "push_one_signal", "chat_slack", diff --git a/src/knockapi/types/recipients/inline_channel_data_request_param.py b/src/knockapi/types/recipients/inline_channel_data_request_param.py index df54436..b210e2c 100644 --- a/src/knockapi/types/recipients/inline_channel_data_request_param.py +++ b/src/knockapi/types/recipients/inline_channel_data_request_param.py @@ -3,19 +3,35 @@ from __future__ import annotations from typing import Dict, Union -from typing_extensions import TypeAlias +from typing_extensions import Required, TypeAlias, TypedDict +from ..._types import SequenceNotStr from .push_channel_data_param import PushChannelDataParam from .slack_channel_data_param import SlackChannelDataParam from .discord_channel_data_param import DiscordChannelDataParam from .ms_teams_channel_data_param import MsTeamsChannelDataParam from .one_signal_channel_data_param import OneSignalChannelDataParam -__all__ = ["InlineChannelDataRequestParam", "InlineChannelDataRequestParamItem"] +__all__ = [ + "InlineChannelDataRequestParam", + "InlineChannelDataRequestParamItem", + "InlineChannelDataRequestParamItemAwsSnsPushChannelData", +] + + +class InlineChannelDataRequestParamItemAwsSnsPushChannelData(TypedDict, total=False): + target_arns: Required[SequenceNotStr[str]] + """A list of platform endpoint ARNs. + + See + [Setting up an Amazon SNS platform endpoint for mobile notifications](https://docs.aws.amazon.com/sns/latest/dg/mobile-platform-endpoint.html). + """ + InlineChannelDataRequestParamItem: TypeAlias = Union[ PushChannelDataParam, OneSignalChannelDataParam, + InlineChannelDataRequestParamItemAwsSnsPushChannelData, SlackChannelDataParam, MsTeamsChannelDataParam, DiscordChannelDataParam, diff --git a/src/knockapi/types/user_set_channel_data_params.py b/src/knockapi/types/user_set_channel_data_params.py index 3024cfd..ed3dec2 100644 --- a/src/knockapi/types/user_set_channel_data_params.py +++ b/src/knockapi/types/user_set_channel_data_params.py @@ -5,13 +5,14 @@ from typing import Union from typing_extensions import Required, TypeAlias, TypedDict +from .._types import SequenceNotStr from .recipients.push_channel_data_param import PushChannelDataParam from .recipients.slack_channel_data_param import SlackChannelDataParam from .recipients.discord_channel_data_param import DiscordChannelDataParam from .recipients.ms_teams_channel_data_param import MsTeamsChannelDataParam from .recipients.one_signal_channel_data_param import OneSignalChannelDataParam -__all__ = ["UserSetChannelDataParams", "Data"] +__all__ = ["UserSetChannelDataParams", "Data", "DataAwsSnsPushChannelData"] class UserSetChannelDataParams(TypedDict, total=False): @@ -19,9 +20,19 @@ class UserSetChannelDataParams(TypedDict, total=False): """Channel data for a given channel type.""" +class DataAwsSnsPushChannelData(TypedDict, total=False): + target_arns: Required[SequenceNotStr[str]] + """A list of platform endpoint ARNs. + + See + [Setting up an Amazon SNS platform endpoint for mobile notifications](https://docs.aws.amazon.com/sns/latest/dg/mobile-platform-endpoint.html). + """ + + Data: TypeAlias = Union[ PushChannelDataParam, OneSignalChannelDataParam, + DataAwsSnsPushChannelData, SlackChannelDataParam, MsTeamsChannelDataParam, DiscordChannelDataParam, From 125d0c3e2d2cbfaaee8b8c2460b9328fa7f1a47a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 21:55:24 +0000 Subject: [PATCH 12/17] feat: improve future compat with pydantic v3 --- src/knockapi/_base_client.py | 6 +- src/knockapi/_compat.py | 96 ++++++++--------- src/knockapi/_models.py | 80 +++++++------- src/knockapi/_utils/__init__.py | 10 +- src/knockapi/_utils/_compat.py | 45 ++++++++ src/knockapi/_utils/_datetime_parse.py | 136 ++++++++++++++++++++++++ src/knockapi/_utils/_transform.py | 6 +- src/knockapi/_utils/_typing.py | 2 +- src/knockapi/_utils/_utils.py | 1 - tests/test_models.py | 48 ++++----- tests/test_transform.py | 16 +-- tests/test_utils/test_datetime_parse.py | 110 +++++++++++++++++++ tests/utils.py | 8 +- 13 files changed, 432 insertions(+), 132 deletions(-) create mode 100644 src/knockapi/_utils/_compat.py create mode 100644 src/knockapi/_utils/_datetime_parse.py create mode 100644 tests/test_utils/test_datetime_parse.py diff --git a/src/knockapi/_base_client.py b/src/knockapi/_base_client.py index b7bf082..d7362ba 100644 --- a/src/knockapi/_base_client.py +++ b/src/knockapi/_base_client.py @@ -59,7 +59,7 @@ ModelBuilderProtocol, ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping -from ._compat import PYDANTIC_V2, model_copy, model_dump +from ._compat import PYDANTIC_V1, model_copy, model_dump from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type from ._response import ( APIResponse, @@ -232,7 +232,7 @@ def _set_private_attributes( model: Type[_T], options: FinalRequestOptions, ) -> None: - if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: self.__pydantic_private__ = {} self._model = model @@ -320,7 +320,7 @@ def _set_private_attributes( client: AsyncAPIClient, options: FinalRequestOptions, ) -> None: - if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: self.__pydantic_private__ = {} self._model = model diff --git a/src/knockapi/_compat.py b/src/knockapi/_compat.py index 92d9ee6..bdef67f 100644 --- a/src/knockapi/_compat.py +++ b/src/knockapi/_compat.py @@ -12,14 +12,13 @@ _T = TypeVar("_T") _ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel) -# --------------- Pydantic v2 compatibility --------------- +# --------------- Pydantic v2, v3 compatibility --------------- # Pyright incorrectly reports some of our functions as overriding a method when they don't # pyright: reportIncompatibleMethodOverride=false -PYDANTIC_V2 = pydantic.VERSION.startswith("2.") +PYDANTIC_V1 = pydantic.VERSION.startswith("1.") -# v1 re-exports if TYPE_CHECKING: def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001 @@ -44,90 +43,92 @@ def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001 ... else: - if PYDANTIC_V2: - from pydantic.v1.typing import ( + # v1 re-exports + if PYDANTIC_V1: + from pydantic.typing import ( get_args as get_args, is_union as is_union, get_origin as get_origin, is_typeddict as is_typeddict, is_literal_type as is_literal_type, ) - from pydantic.v1.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime + from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime else: - from pydantic.typing import ( + from ._utils import ( get_args as get_args, is_union as is_union, get_origin as get_origin, + parse_date as parse_date, is_typeddict as is_typeddict, + parse_datetime as parse_datetime, is_literal_type as is_literal_type, ) - from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime # refactored config if TYPE_CHECKING: from pydantic import ConfigDict as ConfigDict else: - if PYDANTIC_V2: - from pydantic import ConfigDict - else: + if PYDANTIC_V1: # TODO: provide an error message here? ConfigDict = None + else: + from pydantic import ConfigDict as ConfigDict # renamed methods / properties def parse_obj(model: type[_ModelT], value: object) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(value) - else: + if PYDANTIC_V1: return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + else: + return model.model_validate(value) def field_is_required(field: FieldInfo) -> bool: - if PYDANTIC_V2: - return field.is_required() - return field.required # type: ignore + if PYDANTIC_V1: + return field.required # type: ignore + return field.is_required() def field_get_default(field: FieldInfo) -> Any: value = field.get_default() - if PYDANTIC_V2: - from pydantic_core import PydanticUndefined - - if value == PydanticUndefined: - return None + if PYDANTIC_V1: return value + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None return value def field_outer_type(field: FieldInfo) -> Any: - if PYDANTIC_V2: - return field.annotation - return field.outer_type_ # type: ignore + if PYDANTIC_V1: + return field.outer_type_ # type: ignore + return field.annotation def get_model_config(model: type[pydantic.BaseModel]) -> Any: - if PYDANTIC_V2: - return model.model_config - return model.__config__ # type: ignore + if PYDANTIC_V1: + return model.__config__ # type: ignore + return model.model_config def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]: - if PYDANTIC_V2: - return model.model_fields - return model.__fields__ # type: ignore + if PYDANTIC_V1: + return model.__fields__ # type: ignore + return model.model_fields def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT: - if PYDANTIC_V2: - return model.model_copy(deep=deep) - return model.copy(deep=deep) # type: ignore + if PYDANTIC_V1: + return model.copy(deep=deep) # type: ignore + return model.model_copy(deep=deep) def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str: - if PYDANTIC_V2: - return model.model_dump_json(indent=indent) - return model.json(indent=indent) # type: ignore + if PYDANTIC_V1: + return model.json(indent=indent) # type: ignore + return model.model_dump_json(indent=indent) def model_dump( @@ -139,14 +140,14 @@ def model_dump( warnings: bool = True, mode: Literal["json", "python"] = "python", ) -> dict[str, Any]: - if PYDANTIC_V2 or hasattr(model, "model_dump"): + if (not PYDANTIC_V1) or hasattr(model, "model_dump"): return model.model_dump( mode=mode, exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, # warnings are not supported in Pydantic v1 - warnings=warnings if PYDANTIC_V2 else True, + warnings=True if PYDANTIC_V1 else warnings, ) return cast( "dict[str, Any]", @@ -159,9 +160,9 @@ def model_dump( def model_parse(model: type[_ModelT], data: Any) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(data) - return model.parse_obj(data) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return model.parse_obj(data) # pyright: ignore[reportDeprecated] + return model.model_validate(data) # generic models @@ -170,17 +171,16 @@ def model_parse(model: type[_ModelT], data: Any) -> _ModelT: class GenericModel(pydantic.BaseModel): ... else: - if PYDANTIC_V2: + if PYDANTIC_V1: + import pydantic.generics + + class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... + else: # there no longer needs to be a distinction in v2 but # we still have to create our own subclass to avoid # inconsistent MRO ordering errors class GenericModel(pydantic.BaseModel): ... - else: - import pydantic.generics - - class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... - # cached properties if TYPE_CHECKING: diff --git a/src/knockapi/_models.py b/src/knockapi/_models.py index 92f7c10..3a6017e 100644 --- a/src/knockapi/_models.py +++ b/src/knockapi/_models.py @@ -50,7 +50,7 @@ strip_annotated_type, ) from ._compat import ( - PYDANTIC_V2, + PYDANTIC_V1, ConfigDict, GenericModel as BaseGenericModel, get_args, @@ -81,11 +81,7 @@ class _ConfigProtocol(Protocol): class BaseModel(pydantic.BaseModel): - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict( - extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) - ) - else: + if PYDANTIC_V1: @property @override @@ -95,6 +91,10 @@ def model_fields_set(self) -> set[str]: class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] extra: Any = pydantic.Extra.allow # type: ignore + else: + model_config: ClassVar[ConfigDict] = ConfigDict( + extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) + ) def to_dict( self, @@ -215,25 +215,25 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] if key not in model_fields: parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value - if PYDANTIC_V2: - _extra[key] = parsed - else: + if PYDANTIC_V1: _fields_set.add(key) fields_values[key] = parsed + else: + _extra[key] = parsed object.__setattr__(m, "__dict__", fields_values) - if PYDANTIC_V2: - # these properties are copied from Pydantic's `model_construct()` method - object.__setattr__(m, "__pydantic_private__", None) - object.__setattr__(m, "__pydantic_extra__", _extra) - object.__setattr__(m, "__pydantic_fields_set__", _fields_set) - else: + if PYDANTIC_V1: # init_private_attributes() does not exist in v2 m._init_private_attributes() # type: ignore # copied from Pydantic v1's `construct()` method object.__setattr__(m, "__fields_set__", _fields_set) + else: + # these properties are copied from Pydantic's `model_construct()` method + object.__setattr__(m, "__pydantic_private__", None) + object.__setattr__(m, "__pydantic_extra__", _extra) + object.__setattr__(m, "__pydantic_fields_set__", _fields_set) return m @@ -243,7 +243,7 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] # although not in practice model_construct = construct - if not PYDANTIC_V2: + if PYDANTIC_V1: # we define aliases for some of the new pydantic v2 methods so # that we can just document these methods without having to specify # a specific pydantic version as some users may not know which @@ -363,10 +363,10 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: if value is None: return field_get_default(field) - if PYDANTIC_V2: - type_ = field.annotation - else: + if PYDANTIC_V1: type_ = cast(type, field.outer_type_) # type: ignore + else: + type_ = field.annotation # type: ignore if type_ is None: raise RuntimeError(f"Unexpected field type is None for {key}") @@ -375,7 +375,7 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: - if not PYDANTIC_V2: + if PYDANTIC_V1: # TODO return None @@ -628,30 +628,30 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, for variant in get_args(union): variant = strip_annotated_type(variant) if is_basemodel_type(variant): - if PYDANTIC_V2: - field = _extract_field_schema_pv2(variant, discriminator_field_name) - if not field: + if PYDANTIC_V1: + field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + if not field_info: continue # Note: if one variant defines an alias then they all should - discriminator_alias = field.get("serialization_alias") - - field_schema = field["schema"] + discriminator_alias = field_info.alias - if field_schema["type"] == "literal": - for entry in cast("LiteralSchema", field_schema)["expected"]: + if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): + for entry in get_args(annotation): if isinstance(entry, str): mapping[entry] = variant else: - field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - if not field_info: + field = _extract_field_schema_pv2(variant, discriminator_field_name) + if not field: continue # Note: if one variant defines an alias then they all should - discriminator_alias = field_info.alias + discriminator_alias = field.get("serialization_alias") - if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): - for entry in get_args(annotation): + field_schema = field["schema"] + + if field_schema["type"] == "literal": + for entry in cast("LiteralSchema", field_schema)["expected"]: if isinstance(entry, str): mapping[entry] = variant @@ -714,7 +714,7 @@ class GenericModel(BaseGenericModel, BaseModel): pass -if PYDANTIC_V2: +if not PYDANTIC_V1: from pydantic import TypeAdapter as _TypeAdapter _CachedTypeAdapter = cast("TypeAdapter[object]", lru_cache(maxsize=None)(_TypeAdapter)) @@ -782,12 +782,12 @@ class FinalRequestOptions(pydantic.BaseModel): json_data: Union[Body, None] = None extra_json: Union[AnyMapping, None] = None - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) - else: + if PYDANTIC_V1: class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] arbitrary_types_allowed: bool = True + else: + model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) def get_max_retries(self, max_retries: int) -> int: if isinstance(self.max_retries, NotGiven): @@ -820,9 +820,9 @@ def construct( # type: ignore key: strip_not_given(value) for key, value in values.items() } - if PYDANTIC_V2: - return super().model_construct(_fields_set, **kwargs) - return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + return super().model_construct(_fields_set, **kwargs) if not TYPE_CHECKING: # type checkers incorrectly complain about this assignment diff --git a/src/knockapi/_utils/__init__.py b/src/knockapi/_utils/__init__.py index ca547ce..dc64e29 100644 --- a/src/knockapi/_utils/__init__.py +++ b/src/knockapi/_utils/__init__.py @@ -10,7 +10,6 @@ lru_cache as lru_cache, is_mapping as is_mapping, is_tuple_t as is_tuple_t, - parse_date as parse_date, is_iterable as is_iterable, is_sequence as is_sequence, coerce_float as coerce_float, @@ -23,7 +22,6 @@ coerce_boolean as coerce_boolean, coerce_integer as coerce_integer, file_from_path as file_from_path, - parse_datetime as parse_datetime, strip_not_given as strip_not_given, deepcopy_minimal as deepcopy_minimal, get_async_library as get_async_library, @@ -32,6 +30,13 @@ maybe_coerce_boolean as maybe_coerce_boolean, maybe_coerce_integer as maybe_coerce_integer, ) +from ._compat import ( + get_args as get_args, + is_union as is_union, + get_origin as get_origin, + is_typeddict as is_typeddict, + is_literal_type as is_literal_type, +) from ._typing import ( is_list_type as is_list_type, is_union_type as is_union_type, @@ -56,3 +61,4 @@ function_has_argument as function_has_argument, assert_signatures_in_sync as assert_signatures_in_sync, ) +from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime diff --git a/src/knockapi/_utils/_compat.py b/src/knockapi/_utils/_compat.py new file mode 100644 index 0000000..dd70323 --- /dev/null +++ b/src/knockapi/_utils/_compat.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import sys +import typing_extensions +from typing import Any, Type, Union, Literal, Optional +from datetime import date, datetime +from typing_extensions import get_args as _get_args, get_origin as _get_origin + +from .._types import StrBytesIntFloat +from ._datetime_parse import parse_date as _parse_date, parse_datetime as _parse_datetime + +_LITERAL_TYPES = {Literal, typing_extensions.Literal} + + +def get_args(tp: type[Any]) -> tuple[Any, ...]: + return _get_args(tp) + + +def get_origin(tp: type[Any]) -> type[Any] | None: + return _get_origin(tp) + + +def is_union(tp: Optional[Type[Any]]) -> bool: + if sys.version_info < (3, 10): + return tp is Union # type: ignore[comparison-overlap] + else: + import types + + return tp is Union or tp is types.UnionType + + +def is_typeddict(tp: Type[Any]) -> bool: + return typing_extensions.is_typeddict(tp) + + +def is_literal_type(tp: Type[Any]) -> bool: + return get_origin(tp) in _LITERAL_TYPES + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + return _parse_date(value) + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + return _parse_datetime(value) diff --git a/src/knockapi/_utils/_datetime_parse.py b/src/knockapi/_utils/_datetime_parse.py new file mode 100644 index 0000000..7cb9d9e --- /dev/null +++ b/src/knockapi/_utils/_datetime_parse.py @@ -0,0 +1,136 @@ +""" +This file contains code from https://github.com/pydantic/pydantic/blob/main/pydantic/v1/datetime_parse.py +without the Pydantic v1 specific errors. +""" + +from __future__ import annotations + +import re +from typing import Dict, Union, Optional +from datetime import date, datetime, timezone, timedelta + +from .._types import StrBytesIntFloat + +date_expr = r"(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})" +time_expr = ( + r"(?P\d{1,2}):(?P\d{1,2})" + r"(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?" + r"(?PZ|[+-]\d{2}(?::?\d{2})?)?$" +) + +date_re = re.compile(f"{date_expr}$") +datetime_re = re.compile(f"{date_expr}[T ]{time_expr}") + + +EPOCH = datetime(1970, 1, 1) +# if greater than this, the number is in ms, if less than or equal it's in seconds +# (in seconds this is 11th October 2603, in ms it's 20th August 1970) +MS_WATERSHED = int(2e10) +# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9 +MAX_NUMBER = int(3e20) + + +def _get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[None, int, float]: + if isinstance(value, (int, float)): + return value + try: + return float(value) + except ValueError: + return None + except TypeError: + raise TypeError(f"invalid type; expected {native_expected_type}, string, bytes, int or float") from None + + +def _from_unix_seconds(seconds: Union[int, float]) -> datetime: + if seconds > MAX_NUMBER: + return datetime.max + elif seconds < -MAX_NUMBER: + return datetime.min + + while abs(seconds) > MS_WATERSHED: + seconds /= 1000 + dt = EPOCH + timedelta(seconds=seconds) + return dt.replace(tzinfo=timezone.utc) + + +def _parse_timezone(value: Optional[str]) -> Union[None, int, timezone]: + if value == "Z": + return timezone.utc + elif value is not None: + offset_mins = int(value[-2:]) if len(value) > 3 else 0 + offset = 60 * int(value[1:3]) + offset_mins + if value[0] == "-": + offset = -offset + return timezone(timedelta(minutes=offset)) + else: + return None + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + """ + Parse a datetime/int/float/string and return a datetime.datetime. + + This function supports time zone offsets. When the input contains one, + the output uses a timezone with a fixed offset from UTC. + + Raise ValueError if the input is well formatted but not a valid datetime. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, datetime): + return value + + number = _get_numeric(value, "datetime") + if number is not None: + return _from_unix_seconds(number) + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + + match = datetime_re.match(value) + if match is None: + raise ValueError("invalid datetime format") + + kw = match.groupdict() + if kw["microsecond"]: + kw["microsecond"] = kw["microsecond"].ljust(6, "0") + + tzinfo = _parse_timezone(kw.pop("tzinfo")) + kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None} + kw_["tzinfo"] = tzinfo + + return datetime(**kw_) # type: ignore + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + """ + Parse a date/int/float/string and return a datetime.date. + + Raise ValueError if the input is well formatted but not a valid date. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, date): + if isinstance(value, datetime): + return value.date() + else: + return value + + number = _get_numeric(value, "date") + if number is not None: + return _from_unix_seconds(number).date() + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + match = date_re.match(value) + if match is None: + raise ValueError("invalid date format") + + kw = {k: int(v) for k, v in match.groupdict().items()} + + try: + return date(**kw) + except ValueError: + raise ValueError("invalid date format") from None diff --git a/src/knockapi/_utils/_transform.py b/src/knockapi/_utils/_transform.py index f0bcefd..c19124f 100644 --- a/src/knockapi/_utils/_transform.py +++ b/src/knockapi/_utils/_transform.py @@ -19,6 +19,7 @@ is_sequence, ) from .._files import is_base64_file_input +from ._compat import get_origin, is_typeddict from ._typing import ( is_list_type, is_union_type, @@ -29,7 +30,6 @@ is_annotated_type, strip_annotated_type, ) -from .._compat import get_origin, model_dump, is_typeddict _T = TypeVar("_T") @@ -169,6 +169,8 @@ def _transform_recursive( Defaults to the same value as the `annotation` argument. """ + from .._compat import model_dump + if inner_type is None: inner_type = annotation @@ -333,6 +335,8 @@ async def _async_transform_recursive( Defaults to the same value as the `annotation` argument. """ + from .._compat import model_dump + if inner_type is None: inner_type = annotation diff --git a/src/knockapi/_utils/_typing.py b/src/knockapi/_utils/_typing.py index 845cd6b..193109f 100644 --- a/src/knockapi/_utils/_typing.py +++ b/src/knockapi/_utils/_typing.py @@ -15,7 +15,7 @@ from ._utils import lru_cache from .._types import InheritsGeneric -from .._compat import is_union as _is_union +from ._compat import is_union as _is_union def is_annotated_type(typ: type) -> bool: diff --git a/src/knockapi/_utils/_utils.py b/src/knockapi/_utils/_utils.py index ea3cf3f..f081859 100644 --- a/src/knockapi/_utils/_utils.py +++ b/src/knockapi/_utils/_utils.py @@ -22,7 +22,6 @@ import sniffio from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike -from .._compat import parse_date as parse_date, parse_datetime as parse_datetime _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) diff --git a/tests/test_models.py b/tests/test_models.py index 30bc9b0..4de7852 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -8,7 +8,7 @@ from pydantic import Field from knockapi._utils import PropertyInfo -from knockapi._compat import PYDANTIC_V2, parse_obj, model_dump, model_json +from knockapi._compat import PYDANTIC_V1, parse_obj, model_dump, model_json from knockapi._models import BaseModel, construct_type @@ -294,12 +294,12 @@ class Model(BaseModel): assert cast(bool, m.foo) is True m = Model.construct(foo={"name": 3}) - if PYDANTIC_V2: - assert isinstance(m.foo, Submodel1) - assert m.foo.name == 3 # type: ignore - else: + if PYDANTIC_V1: assert isinstance(m.foo, Submodel2) assert m.foo.name == "3" + else: + assert isinstance(m.foo, Submodel1) + assert m.foo.name == 3 # type: ignore def test_list_of_unions() -> None: @@ -426,10 +426,10 @@ class Model(BaseModel): expected = datetime(2019, 12, 27, 18, 11, 19, 117000, tzinfo=timezone.utc) - if PYDANTIC_V2: - expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' - else: + if PYDANTIC_V1: expected_json = '{"created_at": "2019-12-27T18:11:19.117000+00:00"}' + else: + expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' model = Model.construct(created_at="2019-12-27T18:11:19.117Z") assert model.created_at == expected @@ -531,7 +531,7 @@ class Model2(BaseModel): assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)} assert m4.to_dict(mode="json") == {"created_at": time_str} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): m.to_dict(warnings=False) @@ -556,7 +556,7 @@ class Model(BaseModel): assert m3.model_dump() == {"foo": None} assert m3.model_dump(exclude_none=True) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump(round_trip=True) @@ -580,10 +580,10 @@ class Model(BaseModel): assert json.loads(m.to_json()) == {"FOO": "hello"} assert json.loads(m.to_json(use_api_names=False)) == {"foo": "hello"} - if PYDANTIC_V2: - assert m.to_json(indent=None) == '{"FOO":"hello"}' - else: + if PYDANTIC_V1: assert m.to_json(indent=None) == '{"FOO": "hello"}' + else: + assert m.to_json(indent=None) == '{"FOO":"hello"}' m2 = Model() assert json.loads(m2.to_json()) == {} @@ -595,7 +595,7 @@ class Model(BaseModel): assert json.loads(m3.to_json()) == {"FOO": None} assert json.loads(m3.to_json(exclude_none=True)) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): m.to_json(warnings=False) @@ -622,7 +622,7 @@ class Model(BaseModel): assert json.loads(m3.model_dump_json()) == {"foo": None} assert json.loads(m3.model_dump_json(exclude_none=True)) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump_json(round_trip=True) @@ -679,12 +679,12 @@ class B(BaseModel): ) assert isinstance(m, A) assert m.type == "a" - if PYDANTIC_V2: - assert m.data == 100 # type: ignore[comparison-overlap] - else: + if PYDANTIC_V1: # pydantic v1 automatically converts inputs to strings # if the expected type is a str assert m.data == "100" + else: + assert m.data == 100 # type: ignore[comparison-overlap] def test_discriminated_unions_unknown_variant() -> None: @@ -768,12 +768,12 @@ class B(BaseModel): ) assert isinstance(m, A) assert m.foo_type == "a" - if PYDANTIC_V2: - assert m.data == 100 # type: ignore[comparison-overlap] - else: + if PYDANTIC_V1: # pydantic v1 automatically converts inputs to strings # if the expected type is a str assert m.data == "100" + else: + assert m.data == 100 # type: ignore[comparison-overlap] def test_discriminated_unions_overlapping_discriminators_invalid_data() -> None: @@ -833,7 +833,7 @@ class B(BaseModel): assert UnionType.__discriminator__ is discriminator -@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") def test_type_alias_type() -> None: Alias = TypeAliasType("Alias", str) # pyright: ignore @@ -849,7 +849,7 @@ class Model(BaseModel): assert m.union == "bar" -@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") def test_field_named_cls() -> None: class Model(BaseModel): cls: str @@ -936,7 +936,7 @@ class Type2(BaseModel): assert isinstance(model.value, InnerType2) -@pytest.mark.skipif(not PYDANTIC_V2, reason="this is only supported in pydantic v2 for now") +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2 for now") def test_extra_properties() -> None: class Item(BaseModel): prop: int diff --git a/tests/test_transform.py b/tests/test_transform.py index b64246c..c6af18a 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -15,7 +15,7 @@ parse_datetime, async_transform as _async_transform, ) -from knockapi._compat import PYDANTIC_V2 +from knockapi._compat import PYDANTIC_V1 from knockapi._models import BaseModel _T = TypeVar("_T") @@ -189,7 +189,7 @@ class DateModel(BaseModel): @pytest.mark.asyncio async def test_iso8601_format(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - tz = "Z" if PYDANTIC_V2 else "+00:00" + tz = "+00:00" if PYDANTIC_V1 else "Z" assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692" + tz} # type: ignore[comparison-overlap] @@ -297,11 +297,11 @@ async def test_pydantic_unknown_field(use_async: bool) -> None: @pytest.mark.asyncio async def test_pydantic_mismatched_types(use_async: bool) -> None: model = MyModel.construct(foo=True) - if PYDANTIC_V2: + if PYDANTIC_V1: + params = await transform(model, Any, use_async) + else: with pytest.warns(UserWarning): params = await transform(model, Any, use_async) - else: - params = await transform(model, Any, use_async) assert cast(Any, params) == {"foo": True} @@ -309,11 +309,11 @@ async def test_pydantic_mismatched_types(use_async: bool) -> None: @pytest.mark.asyncio async def test_pydantic_mismatched_object_type(use_async: bool) -> None: model = MyModel.construct(foo=MyModel.construct(hello="world")) - if PYDANTIC_V2: + if PYDANTIC_V1: + params = await transform(model, Any, use_async) + else: with pytest.warns(UserWarning): params = await transform(model, Any, use_async) - else: - params = await transform(model, Any, use_async) assert cast(Any, params) == {"foo": {"hello": "world"}} diff --git a/tests/test_utils/test_datetime_parse.py b/tests/test_utils/test_datetime_parse.py new file mode 100644 index 0000000..74095e0 --- /dev/null +++ b/tests/test_utils/test_datetime_parse.py @@ -0,0 +1,110 @@ +""" +Copied from https://github.com/pydantic/pydantic/blob/v1.10.22/tests/test_datetime_parse.py +with modifications so it works without pydantic v1 imports. +""" + +from typing import Type, Union +from datetime import date, datetime, timezone, timedelta + +import pytest + +from knockapi._utils import parse_date, parse_datetime + + +def create_tz(minutes: int) -> timezone: + return timezone(timedelta(minutes=minutes)) + + +@pytest.mark.parametrize( + "value,result", + [ + # Valid inputs + ("1494012444.883309", date(2017, 5, 5)), + (b"1494012444.883309", date(2017, 5, 5)), + (1_494_012_444.883_309, date(2017, 5, 5)), + ("1494012444", date(2017, 5, 5)), + (1_494_012_444, date(2017, 5, 5)), + (0, date(1970, 1, 1)), + ("2012-04-23", date(2012, 4, 23)), + (b"2012-04-23", date(2012, 4, 23)), + ("2012-4-9", date(2012, 4, 9)), + (date(2012, 4, 9), date(2012, 4, 9)), + (datetime(2012, 4, 9, 12, 15), date(2012, 4, 9)), + # Invalid inputs + ("x20120423", ValueError), + ("2012-04-56", ValueError), + (19_999_999_999, date(2603, 10, 11)), # just before watershed + (20_000_000_001, date(1970, 8, 20)), # just after watershed + (1_549_316_052, date(2019, 2, 4)), # nowish in s + (1_549_316_052_104, date(2019, 2, 4)), # nowish in ms + (1_549_316_052_104_324, date(2019, 2, 4)), # nowish in μs + (1_549_316_052_104_324_096, date(2019, 2, 4)), # nowish in ns + ("infinity", date(9999, 12, 31)), + ("inf", date(9999, 12, 31)), + (float("inf"), date(9999, 12, 31)), + ("infinity ", date(9999, 12, 31)), + (int("1" + "0" * 100), date(9999, 12, 31)), + (1e1000, date(9999, 12, 31)), + ("-infinity", date(1, 1, 1)), + ("-inf", date(1, 1, 1)), + ("nan", ValueError), + ], +) +def test_date_parsing(value: Union[str, bytes, int, float], result: Union[date, Type[Exception]]) -> None: + if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] + with pytest.raises(result): + parse_date(value) + else: + assert parse_date(value) == result + + +@pytest.mark.parametrize( + "value,result", + [ + # Valid inputs + # values in seconds + ("1494012444.883309", datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), + (1_494_012_444.883_309, datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), + ("1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + (b"1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + (1_494_012_444, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + # values in ms + ("1494012444000.883309", datetime(2017, 5, 5, 19, 27, 24, 883, tzinfo=timezone.utc)), + ("-1494012444000.883309", datetime(1922, 8, 29, 4, 32, 35, 999117, tzinfo=timezone.utc)), + (1_494_012_444_000, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + ("2012-04-23T09:15:00", datetime(2012, 4, 23, 9, 15)), + ("2012-4-9 4:8:16", datetime(2012, 4, 9, 4, 8, 16)), + ("2012-04-23T09:15:00Z", datetime(2012, 4, 23, 9, 15, 0, 0, timezone.utc)), + ("2012-4-9 4:8:16-0320", datetime(2012, 4, 9, 4, 8, 16, 0, create_tz(-200))), + ("2012-04-23T10:20:30.400+02:30", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(150))), + ("2012-04-23T10:20:30.400+02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(120))), + ("2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), + (b"2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), + (datetime(2017, 5, 5), datetime(2017, 5, 5)), + (0, datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc)), + # Invalid inputs + ("x20120423091500", ValueError), + ("2012-04-56T09:15:90", ValueError), + ("2012-04-23T11:05:00-25:00", ValueError), + (19_999_999_999, datetime(2603, 10, 11, 11, 33, 19, tzinfo=timezone.utc)), # just before watershed + (20_000_000_001, datetime(1970, 8, 20, 11, 33, 20, 1000, tzinfo=timezone.utc)), # just after watershed + (1_549_316_052, datetime(2019, 2, 4, 21, 34, 12, 0, tzinfo=timezone.utc)), # nowish in s + (1_549_316_052_104, datetime(2019, 2, 4, 21, 34, 12, 104_000, tzinfo=timezone.utc)), # nowish in ms + (1_549_316_052_104_324, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in μs + (1_549_316_052_104_324_096, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in ns + ("infinity", datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("inf", datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("inf ", datetime(9999, 12, 31, 23, 59, 59, 999999)), + (1e50, datetime(9999, 12, 31, 23, 59, 59, 999999)), + (float("inf"), datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("-infinity", datetime(1, 1, 1, 0, 0)), + ("-inf", datetime(1, 1, 1, 0, 0)), + ("nan", ValueError), + ], +) +def test_datetime_parsing(value: Union[str, bytes, int, float], result: Union[datetime, Type[Exception]]) -> None: + if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] + with pytest.raises(result): + parse_datetime(value) + else: + assert parse_datetime(value) == result diff --git a/tests/utils.py b/tests/utils.py index 23be870..ac9e9f6 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -19,7 +19,7 @@ is_annotated_type, is_type_alias_type, ) -from knockapi._compat import PYDANTIC_V2, field_outer_type, get_model_fields +from knockapi._compat import PYDANTIC_V1, field_outer_type, get_model_fields from knockapi._models import BaseModel BaseModelT = TypeVar("BaseModelT", bound=BaseModel) @@ -28,12 +28,12 @@ def assert_matches_model(model: type[BaseModelT], value: BaseModelT, *, path: list[str]) -> bool: for name, field in get_model_fields(model).items(): field_value = getattr(value, name) - if PYDANTIC_V2: - allow_none = False - else: + if PYDANTIC_V1: # in v1 nullability was structured differently # https://docs.pydantic.dev/2.0/migration/#required-optional-and-nullable-fields allow_none = getattr(field, "allow_none", False) + else: + allow_none = False assert_matches_type( field_outer_type(field), From c9193ff2a63d7b83981dea86f0b7cfa11542c4cd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 14:47:27 +0000 Subject: [PATCH 13/17] chore(internal): move mypy configurations to `pyproject.toml` file --- mypy.ini | 50 ------------------------------------------------ pyproject.toml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 50 deletions(-) delete mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 565bd29..0000000 --- a/mypy.ini +++ /dev/null @@ -1,50 +0,0 @@ -[mypy] -pretty = True -show_error_codes = True - -# Exclude _files.py because mypy isn't smart enough to apply -# the correct type narrowing and as this is an internal module -# it's fine to just use Pyright. -# -# We also exclude our `tests` as mypy doesn't always infer -# types correctly and Pyright will still catch any type errors. -exclude = ^(src/knockapi/_files\.py|_dev/.*\.py|tests/.*)$ - -strict_equality = True -implicit_reexport = True -check_untyped_defs = True -no_implicit_optional = True - -warn_return_any = True -warn_unreachable = True -warn_unused_configs = True - -# Turn these options off as it could cause conflicts -# with the Pyright options. -warn_unused_ignores = False -warn_redundant_casts = False - -disallow_any_generics = True -disallow_untyped_defs = True -disallow_untyped_calls = True -disallow_subclassing_any = True -disallow_incomplete_defs = True -disallow_untyped_decorators = True -cache_fine_grained = True - -# By default, mypy reports an error if you assign a value to the result -# of a function call that doesn't return anything. We do this in our test -# cases: -# ``` -# result = ... -# assert result is None -# ``` -# Changing this codegen to make mypy happy would increase complexity -# and would not be worth it. -disable_error_code = func-returns-value,overload-cannot-match - -# https://github.com/python/mypy/issues/12162 -[mypy.overrides] -module = "black.files.*" -ignore_errors = true -ignore_missing_imports = true diff --git a/pyproject.toml b/pyproject.toml index 11143e4..399e909 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -157,6 +157,58 @@ reportOverlappingOverload = false reportImportCycles = false reportPrivateUsage = false +[tool.mypy] +pretty = true +show_error_codes = true + +# Exclude _files.py because mypy isn't smart enough to apply +# the correct type narrowing and as this is an internal module +# it's fine to just use Pyright. +# +# We also exclude our `tests` as mypy doesn't always infer +# types correctly and Pyright will still catch any type errors. +exclude = ['src/knockapi/_files.py', '_dev/.*.py', 'tests/.*'] + +strict_equality = true +implicit_reexport = true +check_untyped_defs = true +no_implicit_optional = true + +warn_return_any = true +warn_unreachable = true +warn_unused_configs = true + +# Turn these options off as it could cause conflicts +# with the Pyright options. +warn_unused_ignores = false +warn_redundant_casts = false + +disallow_any_generics = true +disallow_untyped_defs = true +disallow_untyped_calls = true +disallow_subclassing_any = true +disallow_incomplete_defs = true +disallow_untyped_decorators = true +cache_fine_grained = true + +# By default, mypy reports an error if you assign a value to the result +# of a function call that doesn't return anything. We do this in our test +# cases: +# ``` +# result = ... +# assert result is None +# ``` +# Changing this codegen to make mypy happy would increase complexity +# and would not be worth it. +disable_error_code = "func-returns-value,overload-cannot-match" + +# https://github.com/python/mypy/issues/12162 +[[tool.mypy.overrides]] +module = "black.files.*" +ignore_errors = true +ignore_missing_imports = true + + [tool.ruff] line-length = 120 output-format = "grouped" From 78563fd72a7c20144d49770a2fb1d444cfa96d83 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 22:00:21 +0000 Subject: [PATCH 14/17] feat(api): api update --- .stats.yml | 4 ++-- .../types/object_set_channel_data_params.py | 13 +--------- src/knockapi/types/recipients/channel_data.py | 24 +++---------------- .../inline_channel_data_request_param.py | 20 ++-------------- .../types/user_set_channel_data_params.py | 13 +--------- 5 files changed, 9 insertions(+), 65 deletions(-) diff --git a/.stats.yml b/.stats.yml index bfde468..9cbdb2d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 89 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-2d3aac5f3ddf05db97231694fc7fab3f0f2abac4691944dd52bf5a250a4edaa5.yml -openapi_spec_hash: 6418b750ca4a74b7248e3913fefb0bed +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-8d6bb1a0b221b8b9fb9aae94419c46e2e1ca4732281924f24c6539c006d12cb8.yml +openapi_spec_hash: 5dd6c9f3cac4f8cc602c0d1543ec4de4 config_hash: 5c872aa99cad9b9602e84668f5b38a8a diff --git a/src/knockapi/types/object_set_channel_data_params.py b/src/knockapi/types/object_set_channel_data_params.py index e4b6c89..9c5d4fd 100644 --- a/src/knockapi/types/object_set_channel_data_params.py +++ b/src/knockapi/types/object_set_channel_data_params.py @@ -5,14 +5,13 @@ from typing import Union from typing_extensions import Required, TypeAlias, TypedDict -from .._types import SequenceNotStr from .recipients.push_channel_data_param import PushChannelDataParam from .recipients.slack_channel_data_param import SlackChannelDataParam from .recipients.discord_channel_data_param import DiscordChannelDataParam from .recipients.ms_teams_channel_data_param import MsTeamsChannelDataParam from .recipients.one_signal_channel_data_param import OneSignalChannelDataParam -__all__ = ["ObjectSetChannelDataParams", "Data", "DataAwsSnsPushChannelData"] +__all__ = ["ObjectSetChannelDataParams", "Data"] class ObjectSetChannelDataParams(TypedDict, total=False): @@ -20,19 +19,9 @@ class ObjectSetChannelDataParams(TypedDict, total=False): """Channel data for a given channel type.""" -class DataAwsSnsPushChannelData(TypedDict, total=False): - target_arns: Required[SequenceNotStr[str]] - """A list of platform endpoint ARNs. - - See - [Setting up an Amazon SNS platform endpoint for mobile notifications](https://docs.aws.amazon.com/sns/latest/dg/mobile-platform-endpoint.html). - """ - - Data: TypeAlias = Union[ PushChannelDataParam, OneSignalChannelDataParam, - DataAwsSnsPushChannelData, SlackChannelDataParam, MsTeamsChannelDataParam, DiscordChannelDataParam, diff --git a/src/knockapi/types/recipients/channel_data.py b/src/knockapi/types/recipients/channel_data.py index 3c35931..4f598c6 100644 --- a/src/knockapi/types/recipients/channel_data.py +++ b/src/knockapi/types/recipients/channel_data.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Union, Optional +from typing import Union, Optional from typing_extensions import Literal, TypeAlias from pydantic import Field as FieldInfo @@ -12,26 +12,9 @@ from .ms_teams_channel_data import MsTeamsChannelData from .one_signal_channel_data import OneSignalChannelData -__all__ = ["ChannelData", "Data", "DataAwsSnsPushChannelData"] +__all__ = ["ChannelData", "Data"] - -class DataAwsSnsPushChannelData(BaseModel): - target_arns: List[str] - """A list of platform endpoint ARNs. - - See - [Setting up an Amazon SNS platform endpoint for mobile notifications](https://docs.aws.amazon.com/sns/latest/dg/mobile-platform-endpoint.html). - """ - - -Data: TypeAlias = Union[ - PushChannelData, - SlackChannelData, - MsTeamsChannelData, - DiscordChannelData, - OneSignalChannelData, - DataAwsSnsPushChannelData, -] +Data: TypeAlias = Union[PushChannelData, SlackChannelData, MsTeamsChannelData, DiscordChannelData, OneSignalChannelData] class ChannelData(BaseModel): @@ -48,7 +31,6 @@ class ChannelData(BaseModel): Literal[ "push_fcm", "push_apns", - "push_aws_sns", "push_expo", "push_one_signal", "chat_slack", diff --git a/src/knockapi/types/recipients/inline_channel_data_request_param.py b/src/knockapi/types/recipients/inline_channel_data_request_param.py index b210e2c..df54436 100644 --- a/src/knockapi/types/recipients/inline_channel_data_request_param.py +++ b/src/knockapi/types/recipients/inline_channel_data_request_param.py @@ -3,35 +3,19 @@ from __future__ import annotations from typing import Dict, Union -from typing_extensions import Required, TypeAlias, TypedDict +from typing_extensions import TypeAlias -from ..._types import SequenceNotStr from .push_channel_data_param import PushChannelDataParam from .slack_channel_data_param import SlackChannelDataParam from .discord_channel_data_param import DiscordChannelDataParam from .ms_teams_channel_data_param import MsTeamsChannelDataParam from .one_signal_channel_data_param import OneSignalChannelDataParam -__all__ = [ - "InlineChannelDataRequestParam", - "InlineChannelDataRequestParamItem", - "InlineChannelDataRequestParamItemAwsSnsPushChannelData", -] - - -class InlineChannelDataRequestParamItemAwsSnsPushChannelData(TypedDict, total=False): - target_arns: Required[SequenceNotStr[str]] - """A list of platform endpoint ARNs. - - See - [Setting up an Amazon SNS platform endpoint for mobile notifications](https://docs.aws.amazon.com/sns/latest/dg/mobile-platform-endpoint.html). - """ - +__all__ = ["InlineChannelDataRequestParam", "InlineChannelDataRequestParamItem"] InlineChannelDataRequestParamItem: TypeAlias = Union[ PushChannelDataParam, OneSignalChannelDataParam, - InlineChannelDataRequestParamItemAwsSnsPushChannelData, SlackChannelDataParam, MsTeamsChannelDataParam, DiscordChannelDataParam, diff --git a/src/knockapi/types/user_set_channel_data_params.py b/src/knockapi/types/user_set_channel_data_params.py index ed3dec2..3024cfd 100644 --- a/src/knockapi/types/user_set_channel_data_params.py +++ b/src/knockapi/types/user_set_channel_data_params.py @@ -5,14 +5,13 @@ from typing import Union from typing_extensions import Required, TypeAlias, TypedDict -from .._types import SequenceNotStr from .recipients.push_channel_data_param import PushChannelDataParam from .recipients.slack_channel_data_param import SlackChannelDataParam from .recipients.discord_channel_data_param import DiscordChannelDataParam from .recipients.ms_teams_channel_data_param import MsTeamsChannelDataParam from .recipients.one_signal_channel_data_param import OneSignalChannelDataParam -__all__ = ["UserSetChannelDataParams", "Data", "DataAwsSnsPushChannelData"] +__all__ = ["UserSetChannelDataParams", "Data"] class UserSetChannelDataParams(TypedDict, total=False): @@ -20,19 +19,9 @@ class UserSetChannelDataParams(TypedDict, total=False): """Channel data for a given channel type.""" -class DataAwsSnsPushChannelData(TypedDict, total=False): - target_arns: Required[SequenceNotStr[str]] - """A list of platform endpoint ARNs. - - See - [Setting up an Amazon SNS platform endpoint for mobile notifications](https://docs.aws.amazon.com/sns/latest/dg/mobile-platform-endpoint.html). - """ - - Data: TypeAlias = Union[ PushChannelDataParam, OneSignalChannelDataParam, - DataAwsSnsPushChannelData, SlackChannelDataParam, MsTeamsChannelDataParam, DiscordChannelDataParam, From 1cdaf26e8af57ab84654b48b30fd54afc5c721ac Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 16:55:28 +0000 Subject: [PATCH 15/17] feat(api): api update --- .stats.yml | 4 ++-- .../types/object_set_channel_data_params.py | 13 +++++++++- src/knockapi/types/recipients/channel_data.py | 24 ++++++++++++++++--- .../inline_channel_data_request_param.py | 20 ++++++++++++++-- .../types/user_set_channel_data_params.py | 13 +++++++++- 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9cbdb2d..bfde468 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 89 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-8d6bb1a0b221b8b9fb9aae94419c46e2e1ca4732281924f24c6539c006d12cb8.yml -openapi_spec_hash: 5dd6c9f3cac4f8cc602c0d1543ec4de4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/knock%2Fknock-2d3aac5f3ddf05db97231694fc7fab3f0f2abac4691944dd52bf5a250a4edaa5.yml +openapi_spec_hash: 6418b750ca4a74b7248e3913fefb0bed config_hash: 5c872aa99cad9b9602e84668f5b38a8a diff --git a/src/knockapi/types/object_set_channel_data_params.py b/src/knockapi/types/object_set_channel_data_params.py index 9c5d4fd..e4b6c89 100644 --- a/src/knockapi/types/object_set_channel_data_params.py +++ b/src/knockapi/types/object_set_channel_data_params.py @@ -5,13 +5,14 @@ from typing import Union from typing_extensions import Required, TypeAlias, TypedDict +from .._types import SequenceNotStr from .recipients.push_channel_data_param import PushChannelDataParam from .recipients.slack_channel_data_param import SlackChannelDataParam from .recipients.discord_channel_data_param import DiscordChannelDataParam from .recipients.ms_teams_channel_data_param import MsTeamsChannelDataParam from .recipients.one_signal_channel_data_param import OneSignalChannelDataParam -__all__ = ["ObjectSetChannelDataParams", "Data"] +__all__ = ["ObjectSetChannelDataParams", "Data", "DataAwsSnsPushChannelData"] class ObjectSetChannelDataParams(TypedDict, total=False): @@ -19,9 +20,19 @@ class ObjectSetChannelDataParams(TypedDict, total=False): """Channel data for a given channel type.""" +class DataAwsSnsPushChannelData(TypedDict, total=False): + target_arns: Required[SequenceNotStr[str]] + """A list of platform endpoint ARNs. + + See + [Setting up an Amazon SNS platform endpoint for mobile notifications](https://docs.aws.amazon.com/sns/latest/dg/mobile-platform-endpoint.html). + """ + + Data: TypeAlias = Union[ PushChannelDataParam, OneSignalChannelDataParam, + DataAwsSnsPushChannelData, SlackChannelDataParam, MsTeamsChannelDataParam, DiscordChannelDataParam, diff --git a/src/knockapi/types/recipients/channel_data.py b/src/knockapi/types/recipients/channel_data.py index 4f598c6..3c35931 100644 --- a/src/knockapi/types/recipients/channel_data.py +++ b/src/knockapi/types/recipients/channel_data.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Union, Optional +from typing import List, Union, Optional from typing_extensions import Literal, TypeAlias from pydantic import Field as FieldInfo @@ -12,9 +12,26 @@ from .ms_teams_channel_data import MsTeamsChannelData from .one_signal_channel_data import OneSignalChannelData -__all__ = ["ChannelData", "Data"] +__all__ = ["ChannelData", "Data", "DataAwsSnsPushChannelData"] -Data: TypeAlias = Union[PushChannelData, SlackChannelData, MsTeamsChannelData, DiscordChannelData, OneSignalChannelData] + +class DataAwsSnsPushChannelData(BaseModel): + target_arns: List[str] + """A list of platform endpoint ARNs. + + See + [Setting up an Amazon SNS platform endpoint for mobile notifications](https://docs.aws.amazon.com/sns/latest/dg/mobile-platform-endpoint.html). + """ + + +Data: TypeAlias = Union[ + PushChannelData, + SlackChannelData, + MsTeamsChannelData, + DiscordChannelData, + OneSignalChannelData, + DataAwsSnsPushChannelData, +] class ChannelData(BaseModel): @@ -31,6 +48,7 @@ class ChannelData(BaseModel): Literal[ "push_fcm", "push_apns", + "push_aws_sns", "push_expo", "push_one_signal", "chat_slack", diff --git a/src/knockapi/types/recipients/inline_channel_data_request_param.py b/src/knockapi/types/recipients/inline_channel_data_request_param.py index df54436..b210e2c 100644 --- a/src/knockapi/types/recipients/inline_channel_data_request_param.py +++ b/src/knockapi/types/recipients/inline_channel_data_request_param.py @@ -3,19 +3,35 @@ from __future__ import annotations from typing import Dict, Union -from typing_extensions import TypeAlias +from typing_extensions import Required, TypeAlias, TypedDict +from ..._types import SequenceNotStr from .push_channel_data_param import PushChannelDataParam from .slack_channel_data_param import SlackChannelDataParam from .discord_channel_data_param import DiscordChannelDataParam from .ms_teams_channel_data_param import MsTeamsChannelDataParam from .one_signal_channel_data_param import OneSignalChannelDataParam -__all__ = ["InlineChannelDataRequestParam", "InlineChannelDataRequestParamItem"] +__all__ = [ + "InlineChannelDataRequestParam", + "InlineChannelDataRequestParamItem", + "InlineChannelDataRequestParamItemAwsSnsPushChannelData", +] + + +class InlineChannelDataRequestParamItemAwsSnsPushChannelData(TypedDict, total=False): + target_arns: Required[SequenceNotStr[str]] + """A list of platform endpoint ARNs. + + See + [Setting up an Amazon SNS platform endpoint for mobile notifications](https://docs.aws.amazon.com/sns/latest/dg/mobile-platform-endpoint.html). + """ + InlineChannelDataRequestParamItem: TypeAlias = Union[ PushChannelDataParam, OneSignalChannelDataParam, + InlineChannelDataRequestParamItemAwsSnsPushChannelData, SlackChannelDataParam, MsTeamsChannelDataParam, DiscordChannelDataParam, diff --git a/src/knockapi/types/user_set_channel_data_params.py b/src/knockapi/types/user_set_channel_data_params.py index 3024cfd..ed3dec2 100644 --- a/src/knockapi/types/user_set_channel_data_params.py +++ b/src/knockapi/types/user_set_channel_data_params.py @@ -5,13 +5,14 @@ from typing import Union from typing_extensions import Required, TypeAlias, TypedDict +from .._types import SequenceNotStr from .recipients.push_channel_data_param import PushChannelDataParam from .recipients.slack_channel_data_param import SlackChannelDataParam from .recipients.discord_channel_data_param import DiscordChannelDataParam from .recipients.ms_teams_channel_data_param import MsTeamsChannelDataParam from .recipients.one_signal_channel_data_param import OneSignalChannelDataParam -__all__ = ["UserSetChannelDataParams", "Data"] +__all__ = ["UserSetChannelDataParams", "Data", "DataAwsSnsPushChannelData"] class UserSetChannelDataParams(TypedDict, total=False): @@ -19,9 +20,19 @@ class UserSetChannelDataParams(TypedDict, total=False): """Channel data for a given channel type.""" +class DataAwsSnsPushChannelData(TypedDict, total=False): + target_arns: Required[SequenceNotStr[str]] + """A list of platform endpoint ARNs. + + See + [Setting up an Amazon SNS platform endpoint for mobile notifications](https://docs.aws.amazon.com/sns/latest/dg/mobile-platform-endpoint.html). + """ + + Data: TypeAlias = Union[ PushChannelDataParam, OneSignalChannelDataParam, + DataAwsSnsPushChannelData, SlackChannelDataParam, MsTeamsChannelDataParam, DiscordChannelDataParam, From e6d99e582689202e25a841d02d225d72c9d5ca0b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 21:25:39 +0000 Subject: [PATCH 16/17] chore(tests): simplify `get_platform` test `nest_asyncio` is archived and broken on some platforms so it's not worth keeping in our test suite. --- pyproject.toml | 1 - requirements-dev.lock | 1 - tests/test_client.py | 53 +++++-------------------------------------- 3 files changed, 6 insertions(+), 49 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 399e909..7d74476 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,6 @@ dev-dependencies = [ "dirty-equals>=0.6.0", "importlib-metadata>=6.7.0", "rich>=13.7.1", - "nest_asyncio==1.6.0", "pytest-xdist>=3.6.1", ] diff --git a/requirements-dev.lock b/requirements-dev.lock index ed67aae..19a8ee5 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -75,7 +75,6 @@ multidict==6.4.4 mypy==1.14.1 mypy-extensions==1.0.0 # via mypy -nest-asyncio==1.6.0 nodeenv==1.8.0 # via pyright nox==2023.4.22 diff --git a/tests/test_client.py b/tests/test_client.py index 380fc02..5b0a5f3 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -6,13 +6,10 @@ import os import sys import json -import time import asyncio import inspect -import subprocess import tracemalloc from typing import Any, Union, cast -from textwrap import dedent from unittest import mock from typing_extensions import Literal @@ -23,14 +20,17 @@ from knockapi import Knock, AsyncKnock, APIResponseValidationError from knockapi._types import Omit +from knockapi._utils import asyncify from knockapi._models import BaseModel, FinalRequestOptions from knockapi._exceptions import KnockError, APIStatusError, APITimeoutError, APIResponseValidationError from knockapi._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, + OtherPlatform, DefaultHttpxClient, DefaultAsyncHttpxClient, + get_platform, make_request_options, ) @@ -1621,50 +1621,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" - def test_get_platform(self) -> None: - # A previous implementation of asyncify could leave threads unterminated when - # used with nest_asyncio. - # - # Since nest_asyncio.apply() is global and cannot be un-applied, this - # test is run in a separate process to avoid affecting other tests. - test_code = dedent(""" - import asyncio - import nest_asyncio - import threading - - from knockapi._utils import asyncify - from knockapi._base_client import get_platform - - async def test_main() -> None: - result = await asyncify(get_platform)() - print(result) - for thread in threading.enumerate(): - print(thread.name) - - nest_asyncio.apply() - asyncio.run(test_main()) - """) - with subprocess.Popen( - [sys.executable, "-c", test_code], - text=True, - ) as process: - timeout = 10 # seconds - - start_time = time.monotonic() - while True: - return_code = process.poll() - if return_code is not None: - if return_code != 0: - raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") - - # success - break - - if time.monotonic() - start_time > timeout: - process.kill() - raise AssertionError("calling get_platform using asyncify resulted in a hung process") - - time.sleep(0.1) + async def test_get_platform(self) -> None: + platform = await asyncify(get_platform)() + assert isinstance(platform, (str, OtherPlatform)) async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly From 83160118d7dd4ea9f71fb3ff29640d9264393b8e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 21:25:57 +0000 Subject: [PATCH 17/17] release: 1.13.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/knockapi/_version.py | 2 +- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ffb929a..f94eeca 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.12.1" + ".": "1.13.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 23e33af..f29abfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## 1.13.0 (2025-09-05) + +Full Changelog: [v1.12.1...v1.13.0](https://github.com/knocklabs/knock-python/compare/v1.12.1...v1.13.0) + +### Features + +* **api:** api update ([1cdaf26](https://github.com/knocklabs/knock-python/commit/1cdaf26e8af57ab84654b48b30fd54afc5c721ac)) +* **api:** api update ([78563fd](https://github.com/knocklabs/knock-python/commit/78563fd72a7c20144d49770a2fb1d444cfa96d83)) +* **api:** api update ([c2a6ebb](https://github.com/knocklabs/knock-python/commit/c2a6ebbc4de4158cabbc9a22f60c2cee346c3531)) +* **api:** api update ([122f574](https://github.com/knocklabs/knock-python/commit/122f574b1018dededf82d02d01f56d055115ce40)) +* **api:** api update ([b6b52d7](https://github.com/knocklabs/knock-python/commit/b6b52d7259d8c48f92f208d99756eb7fd234b0c4)) +* **api:** api update ([da8cdc0](https://github.com/knocklabs/knock-python/commit/da8cdc052fe12a158e2f3e8081182fa0cada65f0)) +* **api:** api update ([df20d51](https://github.com/knocklabs/knock-python/commit/df20d5158a8a0df0600147be6497c56feed1a257)) +* **api:** api update ([b9007c9](https://github.com/knocklabs/knock-python/commit/b9007c9da8d172eeb43c0acb509ec12c7c3d0d9a)) +* improve future compat with pydantic v3 ([125d0c3](https://github.com/knocklabs/knock-python/commit/125d0c3e2d2cbfaaee8b8c2460b9328fa7f1a47a)) +* **types:** replace List[str] with SequenceNotStr in params ([e19113c](https://github.com/knocklabs/knock-python/commit/e19113cded8eccae475f30b36ae5585427bffe57)) + + +### Bug Fixes + +* avoid newer type syntax ([66ba143](https://github.com/knocklabs/knock-python/commit/66ba143f5c6e28cb7e04c0682f1da3dbe0a82f55)) + + +### Chores + +* **internal:** add Sequence related utils ([2032a76](https://github.com/knocklabs/knock-python/commit/2032a76fac21550c5198347c3f5a392e86616ca4)) +* **internal:** change ci workflow machines ([a1760f4](https://github.com/knocklabs/knock-python/commit/a1760f41b2ca681bf983ae3b24c49516156fa7bd)) +* **internal:** move mypy configurations to `pyproject.toml` file ([c9193ff](https://github.com/knocklabs/knock-python/commit/c9193ff2a63d7b83981dea86f0b7cfa11542c4cd)) +* **internal:** update pyright exclude list ([40513b5](https://github.com/knocklabs/knock-python/commit/40513b513e482356c3abf8c4427e133da91f8a85)) +* **tests:** simplify `get_platform` test ([e6d99e5](https://github.com/knocklabs/knock-python/commit/e6d99e582689202e25a841d02d225d72c9d5ca0b)) + ## 1.12.1 (2025-08-21) Full Changelog: [v1.12.0...v1.12.1](https://github.com/knocklabs/knock-python/compare/v1.12.0...v1.12.1) diff --git a/pyproject.toml b/pyproject.toml index 7d74476..a57789f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "knockapi" -version = "1.12.1" +version = "1.13.0" description = "The official Python library for the knock API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/knockapi/_version.py b/src/knockapi/_version.py index cd72e74..791389c 100644 --- a/src/knockapi/_version.py +++ b/src/knockapi/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "knockapi" -__version__ = "1.12.1" # x-release-please-version +__version__ = "1.13.0" # x-release-please-version