From 175c2941424bed4cd7eafefebb4e936ba2a52344 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 14:21:14 -0500 Subject: [PATCH 01/15] python: Add the authentication.py generated by the new openapi-codegen Note: the openapi.json file I am working with does not include some deprecated methods. For now I will manually add these deprecated calls by hand --- python/svix/api/authentication.py | 90 ++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 7 deletions(-) diff --git a/python/svix/api/authentication.py b/python/svix/api/authentication.py index e18520711..f2e091396 100644 --- a/python/svix/api/authentication.py +++ b/python/svix/api/authentication.py @@ -1,13 +1,43 @@ -from .common import PostOptions, ApiBase +import typing as t +from dataclasses import dataclass +from deprecated import deprecated + +from .common import ApiBase, BaseOptions + from ..internal.openapi_client.api.authentication import ( v1_authentication_app_portal_access, v1_authentication_dashboard_access, + v1_authentication_expire_all, v1_authentication_logout, ) + from ..internal.openapi_client.models.app_portal_access_in import AppPortalAccessIn from ..internal.openapi_client.models.app_portal_access_out import AppPortalAccessOut from ..internal.openapi_client.models.dashboard_access_out import DashboardAccessOut +from ..internal.openapi_client.models.application_token_expire_in import ( + ApplicationTokenExpireIn, +) + + +@dataclass +class AuthenticationAppPortalAccessOptions(BaseOptions): + idempotency_key: t.Optional[str] = None + + +@dataclass +class AuthenticationDashboardAccessOptions(BaseOptions): + idempotency_key: t.Optional[str] = None + + +@dataclass +class AuthenticationExpireAllOptions(BaseOptions): + idempotency_key: t.Optional[str] = None + + +@dataclass +class AuthenticationLogoutOptions(BaseOptions): + idempotency_key: t.Optional[str] = None class AuthenticationAsync(ApiBase): @@ -15,8 +45,9 @@ async def app_portal_access( self, app_id: str, app_portal_access_in: AppPortalAccessIn, - options: PostOptions = PostOptions(), + options: AuthenticationAppPortalAccessOptions = AuthenticationAppPortalAccessOptions(), ) -> AppPortalAccessOut: + """Use this function to get magic links (and authentication codes) for connecting your users to the Consumer Application Portal.""" return await v1_authentication_app_portal_access.request_asyncio( client=self._client, app_id=app_id, @@ -24,14 +55,36 @@ async def app_portal_access( **options.to_dict(), ) + @deprecated async def dashboard_access( - self, app_id: str, options: PostOptions = PostOptions() + self, + app_id: str, + options: AuthenticationDashboardAccessOptions = AuthenticationDashboardAccessOptions(), ) -> DashboardAccessOut: return await v1_authentication_dashboard_access.request_asyncio( client=self._client, app_id=app_id, **options.to_dict() ) - async def logout(self, options: PostOptions = PostOptions()) -> None: + async def expire_all( + self, + app_id: str, + application_token_expire_in: ApplicationTokenExpireIn, + options: AuthenticationExpireAllOptions = AuthenticationExpireAllOptions(), + ) -> None: + """Expire all of the tokens associated with a specific application.""" + return await v1_authentication_expire_all.request_asyncio( + client=self._client, + app_id=app_id, + json_body=application_token_expire_in, + **options.to_dict(), + ) + + async def logout( + self, options: AuthenticationLogoutOptions = AuthenticationLogoutOptions() + ) -> None: + """Logout an app token. + + Trying to log out other tokens will fail.""" return await v1_authentication_logout.request_asyncio( client=self._client, **options.to_dict() ) @@ -42,8 +95,9 @@ def app_portal_access( self, app_id: str, app_portal_access_in: AppPortalAccessIn, - options: PostOptions = PostOptions(), + options: AuthenticationAppPortalAccessOptions = AuthenticationAppPortalAccessOptions(), ) -> AppPortalAccessOut: + """Use this function to get magic links (and authentication codes) for connecting your users to the Consumer Application Portal.""" return v1_authentication_app_portal_access.request_sync( client=self._client, app_id=app_id, @@ -51,14 +105,36 @@ def app_portal_access( **options.to_dict(), ) + def expire_all( + self, + app_id: str, + application_token_expire_in: ApplicationTokenExpireIn, + options: AuthenticationExpireAllOptions = AuthenticationExpireAllOptions(), + ) -> None: + """Expire all of the tokens associated with a specific application.""" + return v1_authentication_expire_all.request_sync( + client=self._client, + app_id=app_id, + json_body=application_token_expire_in, + **options.to_dict(), + ) + + @deprecated def dashboard_access( - self, app_id: str, options: PostOptions = PostOptions() + self, + app_id: str, + options: AuthenticationDashboardAccessOptions = AuthenticationDashboardAccessOptions(), ) -> DashboardAccessOut: return v1_authentication_dashboard_access.request_sync( client=self._client, app_id=app_id, **options.to_dict() ) - def logout(self, options: PostOptions = PostOptions()) -> None: + def logout( + self, options: AuthenticationLogoutOptions = AuthenticationLogoutOptions() + ) -> None: + """Logout an app token. + + Trying to log out other tokens will fail.""" return v1_authentication_logout.request_sync( client=self._client, **options.to_dict() ) From f5d7bb2656c1dd826be29e51fc7951ba6a9f0ce0 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 14:55:48 -0500 Subject: [PATCH 02/15] python: Add openapi-codegen generated endpoint.py --- python/svix/api/endpoint.py | 239 ++++++++++++++++++++++++------------ 1 file changed, 162 insertions(+), 77 deletions(-) diff --git a/python/svix/api/endpoint.py b/python/svix/api/endpoint.py index 9f95bc0f7..0f00f2ccc 100644 --- a/python/svix/api/endpoint.py +++ b/python/svix/api/endpoint.py @@ -1,69 +1,102 @@ import typing as t -from dataclasses import dataclass from datetime import datetime +from dataclasses import dataclass + +from .common import ApiBase, BaseOptions +from ..internal.openapi_client import models from ..internal.openapi_client.api.endpoint import ( + v1_endpoint_list, v1_endpoint_create, - v1_endpoint_delete, v1_endpoint_get, - v1_endpoint_get_headers, - v1_endpoint_get_secret, - v1_endpoint_get_stats, - v1_endpoint_list, + v1_endpoint_update, + v1_endpoint_delete, v1_endpoint_patch, + v1_endpoint_get_headers, + v1_endpoint_update_headers, v1_endpoint_patch_headers, v1_endpoint_recover, v1_endpoint_replay_missing, + v1_endpoint_get_secret, v1_endpoint_rotate_secret, v1_endpoint_send_example, + v1_endpoint_get_stats, v1_endpoint_transformation_get, v1_endpoint_transformation_partial_update, - v1_endpoint_update, - v1_endpoint_update_headers, ) -from ..internal.openapi_client.models.endpoint_headers_in import EndpointHeadersIn -from ..internal.openapi_client.models.endpoint_headers_out import EndpointHeadersOut -from ..internal.openapi_client.models.endpoint_headers_patch_in import ( - EndpointHeadersPatchIn, + +from ..internal.openapi_client.models.list_response_endpoint_out import ( + ListResponseEndpointOut, ) from ..internal.openapi_client.models.endpoint_in import EndpointIn from ..internal.openapi_client.models.endpoint_out import EndpointOut +from ..internal.openapi_client.models.endpoint_update import EndpointUpdate from ..internal.openapi_client.models.endpoint_patch import EndpointPatch +from ..internal.openapi_client.models.endpoint_headers_out import EndpointHeadersOut +from ..internal.openapi_client.models.endpoint_headers_in import EndpointHeadersIn +from ..internal.openapi_client.models.endpoint_headers_patch_in import ( + EndpointHeadersPatchIn, +) +from ..internal.openapi_client.models.recover_in import RecoverIn +from ..internal.openapi_client.models.recover_out import RecoverOut +from ..internal.openapi_client.models.replay_in import ReplayIn +from ..internal.openapi_client.models.replay_out import ReplayOut from ..internal.openapi_client.models.endpoint_secret_out import EndpointSecretOut from ..internal.openapi_client.models.endpoint_secret_rotate_in import ( EndpointSecretRotateIn, ) +from ..internal.openapi_client.models.event_example_in import EventExampleIn +from ..internal.openapi_client.models.message_out import MessageOut from ..internal.openapi_client.models.endpoint_stats import EndpointStats -from ..internal.openapi_client.models.endpoint_transformation_in import ( - EndpointTransformationIn, -) from ..internal.openapi_client.models.endpoint_transformation_out import ( EndpointTransformationOut, ) -from ..internal.openapi_client.models.endpoint_update import EndpointUpdate -from ..internal.openapi_client.models.event_example_in import EventExampleIn -from ..internal.openapi_client.models.list_response_endpoint_out import ( - ListResponseEndpointOut, +from ..internal.openapi_client.models.endpoint_transformation_in import ( + EndpointTransformationIn, ) -from ..internal.openapi_client.models.message_out import MessageOut -from ..internal.openapi_client.models.ordering import Ordering -from ..internal.openapi_client.models.recover_in import RecoverIn -from ..internal.openapi_client.models.recover_out import RecoverOut -from ..internal.openapi_client.models.replay_in import ReplayIn -from ..internal.openapi_client.models.replay_out import ReplayOut -from .common import ensure_tz, ListOptions, PostOptions, ApiBase + +@dataclass +class EndpointListOptions(BaseOptions): + # Limit the number of returned items + limit: t.Optional[int] = None + # The iterator returned from a prior invocation + iterator: t.Optional[str] = None + # The sorting order of the returned items + order: t.Optional[models.Ordering] = None + + +@dataclass +class EndpointCreateOptions(BaseOptions): + idempotency_key: t.Optional[str] = None + + +@dataclass +class EndpointRecoverOptions(BaseOptions): + idempotency_key: t.Optional[str] = None + + +@dataclass +class EndpointReplayMissingOptions(BaseOptions): + idempotency_key: t.Optional[str] = None + + +@dataclass +class EndpointRotateSecretOptions(BaseOptions): + idempotency_key: t.Optional[str] = None @dataclass -class EndpointListOptions(ListOptions): - order: t.Optional[Ordering] = None +class EndpointSendExampleOptions(BaseOptions): + idempotency_key: t.Optional[str] = None @dataclass -class EndpointStatsOptions: +class EndpointGetStatsOptions(BaseOptions): + # Filter the range to data starting from this date. since: t.Optional[datetime] = None + # Filter the range to data ending by this date. until: t.Optional[datetime] = None @@ -71,15 +104,20 @@ class EndpointAsync(ApiBase): async def list( self, app_id: str, options: EndpointListOptions = EndpointListOptions() ) -> ListResponseEndpointOut: + """List the application's endpoints.""" return await v1_endpoint_list.request_asyncio( - client=self._client, - app_id=app_id, - **options.to_dict(), + client=self._client, app_id=app_id, **options.to_dict() ) async def create( - self, app_id: str, endpoint_in: EndpointIn, options: PostOptions = PostOptions() + self, + app_id: str, + endpoint_in: EndpointIn, + options: EndpointCreateOptions = EndpointCreateOptions(), ) -> EndpointOut: + """Create a new endpoint for the application. + + When `secret` is `null` the secret is automatically generated (recommended).""" return await v1_endpoint_create.request_asyncio( client=self._client, app_id=app_id, @@ -88,6 +126,7 @@ async def create( ) async def get(self, app_id: str, endpoint_id: str) -> EndpointOut: + """Get an endpoint.""" return await v1_endpoint_get.request_asyncio( client=self._client, app_id=app_id, endpoint_id=endpoint_id ) @@ -95,6 +134,7 @@ async def get(self, app_id: str, endpoint_id: str) -> EndpointOut: async def update( self, app_id: str, endpoint_id: str, endpoint_update: EndpointUpdate ) -> EndpointOut: + """Update an endpoint.""" return await v1_endpoint_update.request_asyncio( client=self._client, app_id=app_id, @@ -103,15 +143,15 @@ async def update( ) async def delete(self, app_id: str, endpoint_id: str) -> None: + """Delete an endpoint.""" return await v1_endpoint_delete.request_asyncio( - client=self._client, - app_id=app_id, - endpoint_id=endpoint_id, + client=self._client, app_id=app_id, endpoint_id=endpoint_id ) async def patch( self, app_id: str, endpoint_id: str, endpoint_patch: EndpointPatch ) -> EndpointOut: + """Partially update an endpoint.""" return await v1_endpoint_patch.request_asyncio( client=self._client, app_id=app_id, @@ -120,15 +160,15 @@ async def patch( ) async def get_headers(self, app_id: str, endpoint_id: str) -> EndpointHeadersOut: + """Get the additional headers to be sent with the webhook.""" return await v1_endpoint_get_headers.request_asyncio( - client=self._client, - app_id=app_id, - endpoint_id=endpoint_id, + client=self._client, app_id=app_id, endpoint_id=endpoint_id ) async def update_headers( self, app_id: str, endpoint_id: str, endpoint_headers_in: EndpointHeadersIn ) -> None: + """Set the additional headers to be sent with the webhook.""" return await v1_endpoint_update_headers.request_asyncio( client=self._client, app_id=app_id, @@ -137,13 +177,17 @@ async def update_headers( ) async def patch_headers( - self, app_id: str, endpoint_id: str, endpoint_headers_in: EndpointHeadersPatchIn + self, + app_id: str, + endpoint_id: str, + endpoint_headers_patch_in: EndpointHeadersPatchIn, ) -> None: + """Partially set the additional headers to be sent with the webhook.""" return await v1_endpoint_patch_headers.request_asyncio( client=self._client, app_id=app_id, endpoint_id=endpoint_id, - json_body=endpoint_headers_in, + json_body=endpoint_headers_patch_in, ) async def recover( @@ -151,8 +195,11 @@ async def recover( app_id: str, endpoint_id: str, recover_in: RecoverIn, - options: PostOptions = PostOptions(), + options: EndpointRecoverOptions = EndpointRecoverOptions(), ) -> RecoverOut: + """Resend all failed messages since a given time. + + Messages that were sent successfully, even if failed initially, are not resent.""" return await v1_endpoint_recover.request_asyncio( client=self._client, app_id=app_id, @@ -166,8 +213,12 @@ async def replay_missing( app_id: str, endpoint_id: str, replay_in: ReplayIn, - options: PostOptions = PostOptions(), + options: EndpointReplayMissingOptions = EndpointReplayMissingOptions(), ) -> ReplayOut: + """Replays messages to the endpoint. + + Only messages that were created after `since` will be sent. + Messages that were previously sent to the endpoint are not resent.""" return await v1_endpoint_replay_missing.request_asyncio( client=self._client, app_id=app_id, @@ -177,10 +228,12 @@ async def replay_missing( ) async def get_secret(self, app_id: str, endpoint_id: str) -> EndpointSecretOut: + """Get the endpoint's signing secret. + + This is used to verify the authenticity of the webhook. + For more information please refer to [the consuming webhooks docs](https://docs.svix.com/consuming-webhooks/).""" return await v1_endpoint_get_secret.request_asyncio( - client=self._client, - app_id=app_id, - endpoint_id=endpoint_id, + client=self._client, app_id=app_id, endpoint_id=endpoint_id ) async def rotate_secret( @@ -188,8 +241,11 @@ async def rotate_secret( app_id: str, endpoint_id: str, endpoint_secret_rotate_in: EndpointSecretRotateIn, - options: PostOptions = PostOptions(), + options: EndpointRotateSecretOptions = EndpointRotateSecretOptions(), ) -> None: + """Rotates the endpoint's signing secret. + + The previous secret will remain valid for the next 24 hours.""" return await v1_endpoint_rotate_secret.request_asyncio( client=self._client, app_id=app_id, @@ -203,8 +259,9 @@ async def send_example( app_id: str, endpoint_id: str, event_example_in: EventExampleIn, - options: PostOptions = PostOptions(), + options: EndpointSendExampleOptions = EndpointSendExampleOptions(), ) -> MessageOut: + """Send an example message for an event.""" return await v1_endpoint_send_example.request_asyncio( client=self._client, app_id=app_id, @@ -217,19 +274,20 @@ async def get_stats( self, app_id: str, endpoint_id: str, - options: EndpointStatsOptions = EndpointStatsOptions(), + options: EndpointGetStatsOptions = EndpointGetStatsOptions(), ) -> EndpointStats: + """Get basic statistics for the endpoint.""" return await v1_endpoint_get_stats.request_asyncio( client=self._client, app_id=app_id, endpoint_id=endpoint_id, - since=ensure_tz(options.since), - until=ensure_tz(options.until), + **options.to_dict(), ) - async def transformations_get( + async def transformation_get( self, app_id: str, endpoint_id: str ) -> EndpointTransformationOut: + """Get the transformation code associated with this endpoint.""" return await v1_endpoint_transformation_get.request_asyncio( client=self._client, app_id=app_id, endpoint_id=endpoint_id ) @@ -240,7 +298,8 @@ async def transformation_partial_update( endpoint_id: str, endpoint_transformation_in: EndpointTransformationIn, ) -> None: - await v1_endpoint_transformation_partial_update.request_asyncio( + """Set or unset the transformation code associated with this endpoint.""" + return await v1_endpoint_transformation_partial_update.request_asyncio( client=self._client, app_id=app_id, endpoint_id=endpoint_id, @@ -252,15 +311,20 @@ class Endpoint(ApiBase): def list( self, app_id: str, options: EndpointListOptions = EndpointListOptions() ) -> ListResponseEndpointOut: + """List the application's endpoints.""" return v1_endpoint_list.request_sync( - client=self._client, - app_id=app_id, - **options.to_dict(), + client=self._client, app_id=app_id, **options.to_dict() ) def create( - self, app_id: str, endpoint_in: EndpointIn, options: PostOptions = PostOptions() + self, + app_id: str, + endpoint_in: EndpointIn, + options: EndpointCreateOptions = EndpointCreateOptions(), ) -> EndpointOut: + """Create a new endpoint for the application. + + When `secret` is `null` the secret is automatically generated (recommended).""" return v1_endpoint_create.request_sync( client=self._client, app_id=app_id, @@ -269,6 +333,7 @@ def create( ) def get(self, app_id: str, endpoint_id: str) -> EndpointOut: + """Get an endpoint.""" return v1_endpoint_get.request_sync( client=self._client, app_id=app_id, endpoint_id=endpoint_id ) @@ -276,6 +341,7 @@ def get(self, app_id: str, endpoint_id: str) -> EndpointOut: def update( self, app_id: str, endpoint_id: str, endpoint_update: EndpointUpdate ) -> EndpointOut: + """Update an endpoint.""" return v1_endpoint_update.request_sync( client=self._client, app_id=app_id, @@ -284,15 +350,15 @@ def update( ) def delete(self, app_id: str, endpoint_id: str) -> None: + """Delete an endpoint.""" return v1_endpoint_delete.request_sync( - client=self._client, - app_id=app_id, - endpoint_id=endpoint_id, + client=self._client, app_id=app_id, endpoint_id=endpoint_id ) def patch( self, app_id: str, endpoint_id: str, endpoint_patch: EndpointPatch ) -> EndpointOut: + """Partially update an endpoint.""" return v1_endpoint_patch.request_sync( client=self._client, app_id=app_id, @@ -301,15 +367,15 @@ def patch( ) def get_headers(self, app_id: str, endpoint_id: str) -> EndpointHeadersOut: + """Get the additional headers to be sent with the webhook.""" return v1_endpoint_get_headers.request_sync( - client=self._client, - app_id=app_id, - endpoint_id=endpoint_id, + client=self._client, app_id=app_id, endpoint_id=endpoint_id ) def update_headers( self, app_id: str, endpoint_id: str, endpoint_headers_in: EndpointHeadersIn ) -> None: + """Set the additional headers to be sent with the webhook.""" return v1_endpoint_update_headers.request_sync( client=self._client, app_id=app_id, @@ -318,13 +384,17 @@ def update_headers( ) def patch_headers( - self, app_id: str, endpoint_id: str, endpoint_headers_in: EndpointHeadersPatchIn + self, + app_id: str, + endpoint_id: str, + endpoint_headers_patch_in: EndpointHeadersPatchIn, ) -> None: + """Partially set the additional headers to be sent with the webhook.""" return v1_endpoint_patch_headers.request_sync( client=self._client, app_id=app_id, endpoint_id=endpoint_id, - json_body=endpoint_headers_in, + json_body=endpoint_headers_patch_in, ) def recover( @@ -332,8 +402,11 @@ def recover( app_id: str, endpoint_id: str, recover_in: RecoverIn, - options: PostOptions = PostOptions(), + options: EndpointRecoverOptions = EndpointRecoverOptions(), ) -> RecoverOut: + """Resend all failed messages since a given time. + + Messages that were sent successfully, even if failed initially, are not resent.""" return v1_endpoint_recover.request_sync( client=self._client, app_id=app_id, @@ -347,8 +420,12 @@ def replay_missing( app_id: str, endpoint_id: str, replay_in: ReplayIn, - options: PostOptions = PostOptions(), + options: EndpointReplayMissingOptions = EndpointReplayMissingOptions(), ) -> ReplayOut: + """Replays messages to the endpoint. + + Only messages that were created after `since` will be sent. + Messages that were previously sent to the endpoint are not resent.""" return v1_endpoint_replay_missing.request_sync( client=self._client, app_id=app_id, @@ -358,10 +435,12 @@ def replay_missing( ) def get_secret(self, app_id: str, endpoint_id: str) -> EndpointSecretOut: + """Get the endpoint's signing secret. + + This is used to verify the authenticity of the webhook. + For more information please refer to [the consuming webhooks docs](https://docs.svix.com/consuming-webhooks/).""" return v1_endpoint_get_secret.request_sync( - client=self._client, - app_id=app_id, - endpoint_id=endpoint_id, + client=self._client, app_id=app_id, endpoint_id=endpoint_id ) def rotate_secret( @@ -369,8 +448,11 @@ def rotate_secret( app_id: str, endpoint_id: str, endpoint_secret_rotate_in: EndpointSecretRotateIn, - options: PostOptions = PostOptions(), + options: EndpointRotateSecretOptions = EndpointRotateSecretOptions(), ) -> None: + """Rotates the endpoint's signing secret. + + The previous secret will remain valid for the next 24 hours.""" return v1_endpoint_rotate_secret.request_sync( client=self._client, app_id=app_id, @@ -384,8 +466,9 @@ def send_example( app_id: str, endpoint_id: str, event_example_in: EventExampleIn, - options: PostOptions = PostOptions(), + options: EndpointSendExampleOptions = EndpointSendExampleOptions(), ) -> MessageOut: + """Send an example message for an event.""" return v1_endpoint_send_example.request_sync( client=self._client, app_id=app_id, @@ -398,19 +481,20 @@ def get_stats( self, app_id: str, endpoint_id: str, - options: EndpointStatsOptions = EndpointStatsOptions(), + options: EndpointGetStatsOptions = EndpointGetStatsOptions(), ) -> EndpointStats: + """Get basic statistics for the endpoint.""" return v1_endpoint_get_stats.request_sync( client=self._client, app_id=app_id, endpoint_id=endpoint_id, - since=ensure_tz(options.since), - until=ensure_tz(options.until), + **options.to_dict(), ) - def transformations_get( + def transformation_get( self, app_id: str, endpoint_id: str ) -> EndpointTransformationOut: + """Get the transformation code associated with this endpoint.""" return v1_endpoint_transformation_get.request_sync( client=self._client, app_id=app_id, endpoint_id=endpoint_id ) @@ -421,7 +505,8 @@ def transformation_partial_update( endpoint_id: str, endpoint_transformation_in: EndpointTransformationIn, ) -> None: - v1_endpoint_transformation_partial_update.request_sync( + """Set or unset the transformation code associated with this endpoint.""" + return v1_endpoint_transformation_partial_update.request_sync( client=self._client, app_id=app_id, endpoint_id=endpoint_id, From 71bec705778e7b8f144cd8cdf4fe22509bf22790 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 14:56:12 -0500 Subject: [PATCH 03/15] python: Add endpoint.py *Options to __init__.py --- python/svix/api/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/python/svix/api/__init__.py b/python/svix/api/__init__.py index 7b808e87f..9634ddb03 100644 --- a/python/svix/api/__init__.py +++ b/python/svix/api/__init__.py @@ -11,7 +11,17 @@ ApplicationGetOrCreateOptions, ) from .authentication import AuthenticationAsync, Authentication -from .endpoint import EndpointAsync, Endpoint, EndpointListOptions +from .endpoint import ( + EndpointAsync, + Endpoint, + EndpointListOptions, + EndpointCreateOptions, + EndpointRecoverOptions, + EndpointReplayMissingOptions, + EndpointRotateSecretOptions, + EndpointSendExampleOptions, + EndpointGetStatsOptions, +) from .event_type import EventTypeAsync, EventType, EventTypeListOptions from .integration import IntegrationAsync, Integration from .message import MessageAsync, Message From 16c93a89d0a29a80bbba7b3c3fe8d993e9efafeb Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 14:59:43 -0500 Subject: [PATCH 04/15] python: Add openapi-codegen generated event_type.py --- python/svix/api/event_type.py | 153 ++++++++++++++++++++++++---------- 1 file changed, 110 insertions(+), 43 deletions(-) diff --git a/python/svix/api/event_type.py b/python/svix/api/event_type.py index d89204b7e..43183ed38 100644 --- a/python/svix/api/event_type.py +++ b/python/svix/api/event_type.py @@ -1,91 +1,139 @@ import typing as t from dataclasses import dataclass +from .common import ApiBase, BaseOptions +from ..internal.openapi_client import models + from ..internal.openapi_client.api.event_type import ( + v1_event_type_list, v1_event_type_create, - v1_event_type_delete, - v1_event_type_get, v1_event_type_import_openapi, - v1_event_type_list, - v1_event_type_patch, + v1_event_type_get, v1_event_type_update, + v1_event_type_delete, + v1_event_type_patch, ) + +from ..internal.openapi_client.models.list_response_event_type_out import ( + ListResponseEventTypeOut, +) +from ..internal.openapi_client.models.event_type_in import EventTypeIn +from ..internal.openapi_client.models.event_type_out import EventTypeOut from ..internal.openapi_client.models.event_type_import_open_api_in import ( EventTypeImportOpenApiIn, ) from ..internal.openapi_client.models.event_type_import_open_api_out import ( EventTypeImportOpenApiOut, ) -from ..internal.openapi_client.models.event_type_in import EventTypeIn -from ..internal.openapi_client.models.event_type_out import EventTypeOut -from ..internal.openapi_client.models.event_type_patch import EventTypePatch from ..internal.openapi_client.models.event_type_update import EventTypeUpdate -from ..internal.openapi_client.models.list_response_event_type_out import ( - ListResponseEventTypeOut, -) -from .common import ListOptions, PostOptions, ApiBase +from ..internal.openapi_client.models.event_type_patch import EventTypePatch @dataclass -class EventTypeListOptions(ListOptions): - with_content: t.Optional[bool] = None +class EventTypeListOptions(BaseOptions): + # Limit the number of returned items + limit: t.Optional[int] = None + # The iterator returned from a prior invocation + iterator: t.Optional[str] = None + # The sorting order of the returned items + order: t.Optional[models.Ordering] = None + # When `true` archived (deleted but not expunged) items are included in the response. include_archived: t.Optional[bool] = None + # When `true` the full item (including the schema) is included in the response. + with_content: t.Optional[bool] = None + + +@dataclass +class EventTypeCreateOptions(BaseOptions): + idempotency_key: t.Optional[str] = None + + +@dataclass +class EventTypeImportOpenapiOptions(BaseOptions): + idempotency_key: t.Optional[str] = None + + +@dataclass +class EventTypeDeleteOptions(BaseOptions): + # By default event types are archived when "deleted". Passing this to `true` deletes them entirely. + expunge: t.Optional[bool] = None class EventTypeAsync(ApiBase): async def list( self, options: EventTypeListOptions = EventTypeListOptions() ) -> ListResponseEventTypeOut: + """Return the list of event types.""" return await v1_event_type_list.request_asyncio( - client=self._client, - **options.to_dict(), + client=self._client, **options.to_dict() ) async def create( - self, event_type_in: EventTypeIn, options: PostOptions = PostOptions() + self, + event_type_in: EventTypeIn, + options: EventTypeCreateOptions = EventTypeCreateOptions(), ) -> EventTypeOut: + """Create new or unarchive existing event type. + + Unarchiving an event type will allow endpoints to filter on it and messages to be sent with it. + Endpoints filtering on the event type before archival will continue to filter on it. + This operation does not preserve the description and schemas.""" return await v1_event_type_create.request_asyncio( - client=self._client, - json_body=event_type_in, - **options.to_dict(), + client=self._client, json_body=event_type_in, **options.to_dict() ) async def import_openapi( self, - event_type_import_openapi_in: EventTypeImportOpenApiIn, - options: PostOptions = PostOptions(), + event_type_import_open_api_in: EventTypeImportOpenApiIn, + options: EventTypeImportOpenapiOptions = EventTypeImportOpenapiOptions(), ) -> EventTypeImportOpenApiOut: + """Given an OpenAPI spec, create new or update existing event types. + If an existing `archived` event type is updated, it will be unarchived. + + The importer will convert all webhooks found in the either the `webhooks` or `x-webhooks` + top-level.""" return await v1_event_type_import_openapi.request_asyncio( client=self._client, - json_body=event_type_import_openapi_in, + json_body=event_type_import_open_api_in, **options.to_dict(), ) async def get(self, event_type_name: str) -> EventTypeOut: + """Get an event type.""" return await v1_event_type_get.request_asyncio( - client=self._client, - event_type_name=event_type_name, + client=self._client, event_type_name=event_type_name ) async def update( self, event_type_name: str, event_type_update: EventTypeUpdate ) -> EventTypeOut: + """Update an event type.""" return await v1_event_type_update.request_asyncio( client=self._client, event_type_name=event_type_name, json_body=event_type_update, ) - async def delete(self, event_type_name: str) -> None: + async def delete( + self, + event_type_name: str, + options: EventTypeDeleteOptions = EventTypeDeleteOptions(), + ) -> None: + """Archive an event type. + + Endpoints already configured to filter on an event type will continue to do so after archival. + However, new messages can not be sent with it and endpoints can not filter on it. + An event type can be unarchived with the + [create operation](#operation/create_event_type_api_v1_event_type__post).""" return await v1_event_type_delete.request_asyncio( - client=self._client, - event_type_name=event_type_name, + client=self._client, event_type_name=event_type_name, **options.to_dict() ) async def patch( self, event_type_name: str, event_type_patch: EventTypePatch ) -> EventTypeOut: + """Partially update an event type.""" return await v1_event_type_patch.request_asyncio( client=self._client, event_type_name=event_type_name, @@ -97,55 +145,74 @@ class EventType(ApiBase): def list( self, options: EventTypeListOptions = EventTypeListOptions() ) -> ListResponseEventTypeOut: - return v1_event_type_list.request_sync( - client=self._client, - **options.to_dict(), - ) + """Return the list of event types.""" + return v1_event_type_list.request_sync(client=self._client, **options.to_dict()) def create( - self, event_type_in: EventTypeIn, options: PostOptions = PostOptions() + self, + event_type_in: EventTypeIn, + options: EventTypeCreateOptions = EventTypeCreateOptions(), ) -> EventTypeOut: + """Create new or unarchive existing event type. + + Unarchiving an event type will allow endpoints to filter on it and messages to be sent with it. + Endpoints filtering on the event type before archival will continue to filter on it. + This operation does not preserve the description and schemas.""" return v1_event_type_create.request_sync( - client=self._client, - json_body=event_type_in, - **options.to_dict(), + client=self._client, json_body=event_type_in, **options.to_dict() ) def import_openapi( self, - event_type_import_openapi_in: EventTypeImportOpenApiIn, - options: PostOptions = PostOptions(), + event_type_import_open_api_in: EventTypeImportOpenApiIn, + options: EventTypeImportOpenapiOptions = EventTypeImportOpenapiOptions(), ) -> EventTypeImportOpenApiOut: + """Given an OpenAPI spec, create new or update existing event types. + If an existing `archived` event type is updated, it will be unarchived. + + The importer will convert all webhooks found in the either the `webhooks` or `x-webhooks` + top-level.""" return v1_event_type_import_openapi.request_sync( client=self._client, - json_body=event_type_import_openapi_in, + json_body=event_type_import_open_api_in, **options.to_dict(), ) def get(self, event_type_name: str) -> EventTypeOut: + """Get an event type.""" return v1_event_type_get.request_sync( - client=self._client, - event_type_name=event_type_name, + client=self._client, event_type_name=event_type_name ) def update( self, event_type_name: str, event_type_update: EventTypeUpdate ) -> EventTypeOut: + """Update an event type.""" return v1_event_type_update.request_sync( client=self._client, event_type_name=event_type_name, json_body=event_type_update, ) - def delete(self, event_type_name: str) -> None: + def delete( + self, + event_type_name: str, + options: EventTypeDeleteOptions = EventTypeDeleteOptions(), + ) -> None: + """Archive an event type. + + Endpoints already configured to filter on an event type will continue to do so after archival. + However, new messages can not be sent with it and endpoints can not filter on it. + An event type can be unarchived with the + [create operation](#operation/create_event_type_api_v1_event_type__post).""" return v1_event_type_delete.request_sync( - client=self._client, - event_type_name=event_type_name, + client=self._client, event_type_name=event_type_name, **options.to_dict() ) def patch( self, event_type_name: str, event_type_patch: EventTypePatch ) -> EventTypeOut: + """Partially update an event type.""" return v1_event_type_patch.request_sync( client=self._client, event_type_name=event_type_name, From 0857c08ae83efb0ef5b8caf957e5dc2c30002cc4 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 15:00:41 -0500 Subject: [PATCH 05/15] python: Add event_type.py *Options to __init__.py --- python/svix/api/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/python/svix/api/__init__.py b/python/svix/api/__init__.py index 9634ddb03..38de3ef2e 100644 --- a/python/svix/api/__init__.py +++ b/python/svix/api/__init__.py @@ -22,7 +22,14 @@ EndpointSendExampleOptions, EndpointGetStatsOptions, ) -from .event_type import EventTypeAsync, EventType, EventTypeListOptions +from .event_type import ( + EventTypeAsync, + EventType, + EventTypeListOptions, + EventTypeCreateOptions, + EventTypeImportOpenapiOptions, + EventTypeDeleteOptions, +) from .integration import IntegrationAsync, Integration from .message import MessageAsync, Message from .message_attempt import ( From 67361cb7e279afefaf6ecb34825dcab1102c9412 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 15:05:14 -0500 Subject: [PATCH 06/15] python: Add authentication.py *Options to __init__.py --- python/svix/api/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/python/svix/api/__init__.py b/python/svix/api/__init__.py index 38de3ef2e..842f6648f 100644 --- a/python/svix/api/__init__.py +++ b/python/svix/api/__init__.py @@ -10,7 +10,14 @@ ApplicationCreateOptions, ApplicationGetOrCreateOptions, ) -from .authentication import AuthenticationAsync, Authentication +from .authentication import ( + AuthenticationAsync, + Authentication, + AuthenticationAppPortalAccessOptions, + AuthenticationDashboardAccessOptions, + AuthenticationExpireAllOptions, + AuthenticationLogoutOptions, +) from .endpoint import ( EndpointAsync, Endpoint, From e029ed5b946399671999e179af0bfb0fbb010a26 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 15:23:51 -0500 Subject: [PATCH 07/15] python: Add openapi-codegen generated integration.py Breaking change: in the create and update function the integ_(in/update) is renamed to integration_(in/update) This is due to the way that the codegen decides how to name function arg. (The type of the function arg to_snake_case) I don't think it makes sense to add a special case in the codegen to shorten `integration` to `integ` This problem is a bit more annoying, since the path param is called `integ_id` (in the `openapi.json`). So to keep consistency we may want to add the special case to the codegen Alternatively (and this would make me happy) we could change the path param name from `integ_id` to `integration_id`. (in the `openapi.json`) This will make the file consistent, and won't require a special case in the openapi-codegen --- python/svix/api/integration.py | 117 ++++++++++++++++++++------------- 1 file changed, 71 insertions(+), 46 deletions(-) diff --git a/python/svix/api/integration.py b/python/svix/api/integration.py index 85c60675e..6453139c4 100644 --- a/python/svix/api/integration.py +++ b/python/svix/api/integration.py @@ -1,72 +1,93 @@ +import typing as t from dataclasses import dataclass +from .common import ApiBase, BaseOptions +from ..internal.openapi_client import models + from ..internal.openapi_client.api.integration import ( + v1_integration_list, v1_integration_create, - v1_integration_delete, v1_integration_get, + v1_integration_update, + v1_integration_delete, v1_integration_get_key, - v1_integration_list, v1_integration_rotate_key, - v1_integration_update, ) -from ..internal.openapi_client.models.integration_in import IntegrationIn -from ..internal.openapi_client.models.integration_key_out import IntegrationKeyOut -from ..internal.openapi_client.models.integration_out import IntegrationOut -from ..internal.openapi_client.models.integration_update import IntegrationUpdate + from ..internal.openapi_client.models.list_response_integration_out import ( ListResponseIntegrationOut, ) -from .common import ListOptions, PostOptions, ApiBase +from ..internal.openapi_client.models.integration_in import IntegrationIn +from ..internal.openapi_client.models.integration_out import IntegrationOut +from ..internal.openapi_client.models.integration_update import IntegrationUpdate +from ..internal.openapi_client.models.integration_key_out import IntegrationKeyOut + + +@dataclass +class IntegrationListOptions(BaseOptions): + # Limit the number of returned items + limit: t.Optional[int] = None + # The iterator returned from a prior invocation + iterator: t.Optional[str] = None + # The sorting order of the returned items + order: t.Optional[models.Ordering] = None + + +@dataclass +class IntegrationCreateOptions(BaseOptions): + idempotency_key: t.Optional[str] = None @dataclass -class IntegrationListOptions(ListOptions): - pass +class IntegrationRotateKeyOptions(BaseOptions): + idempotency_key: t.Optional[str] = None class IntegrationAsync(ApiBase): async def list( self, app_id: str, options: IntegrationListOptions = IntegrationListOptions() ) -> ListResponseIntegrationOut: + """List the application's integrations.""" return await v1_integration_list.request_asyncio( - client=self._client, - app_id=app_id, - **options.to_dict(), + client=self._client, app_id=app_id, **options.to_dict() ) async def create( - self, app_id: str, integ_in: IntegrationIn, options: PostOptions = PostOptions() + self, + app_id: str, + integration_in: IntegrationIn, + options: IntegrationCreateOptions = IntegrationCreateOptions(), ) -> IntegrationOut: + """Create an integration.""" return await v1_integration_create.request_asyncio( client=self._client, app_id=app_id, - json_body=integ_in, + json_body=integration_in, **options.to_dict(), ) async def get(self, app_id: str, integ_id: str) -> IntegrationOut: + """Get an integration.""" return await v1_integration_get.request_asyncio( - client=self._client, - app_id=app_id, - integ_id=integ_id, + client=self._client, app_id=app_id, integ_id=integ_id ) async def update( - self, app_id: str, integ_id: str, integ_update: IntegrationUpdate + self, app_id: str, integ_id: str, integration_update: IntegrationUpdate ) -> IntegrationOut: + """Update an integration.""" return await v1_integration_update.request_asyncio( client=self._client, app_id=app_id, integ_id=integ_id, - json_body=integ_update, + json_body=integration_update, ) async def delete(self, app_id: str, integ_id: str) -> None: + """Delete an integration.""" return await v1_integration_delete.request_asyncio( - client=self._client, - app_id=app_id, - integ_id=integ_id, + client=self._client, app_id=app_id, integ_id=integ_id ) async def get_key(self, app_id: str, integ_id: str) -> IntegrationKeyOut: @@ -77,13 +98,14 @@ async def get_key(self, app_id: str, integ_id: str) -> IntegrationKeyOut: ) async def rotate_key( - self, app_id: str, integ_id: str, options: PostOptions = PostOptions() + self, + app_id: str, + integ_id: str, + options: IntegrationRotateKeyOptions = IntegrationRotateKeyOptions(), ) -> IntegrationKeyOut: + """Rotate the integration's key. The previous key will be immediately revoked.""" return await v1_integration_rotate_key.request_asyncio( - client=self._client, - app_id=app_id, - integ_id=integ_id, - **options.to_dict(), + client=self._client, app_id=app_id, integ_id=integ_id, **options.to_dict() ) @@ -91,44 +113,46 @@ class Integration(ApiBase): def list( self, app_id: str, options: IntegrationListOptions = IntegrationListOptions() ) -> ListResponseIntegrationOut: + """List the application's integrations.""" return v1_integration_list.request_sync( - client=self._client, - app_id=app_id, - **options.to_dict(), + client=self._client, app_id=app_id, **options.to_dict() ) def create( - self, app_id: str, integ_in: IntegrationIn, options: PostOptions = PostOptions() + self, + app_id: str, + integration_in: IntegrationIn, + options: IntegrationCreateOptions = IntegrationCreateOptions(), ) -> IntegrationOut: + """Create an integration.""" return v1_integration_create.request_sync( client=self._client, app_id=app_id, - json_body=integ_in, + json_body=integration_in, **options.to_dict(), ) def get(self, app_id: str, integ_id: str) -> IntegrationOut: + """Get an integration.""" return v1_integration_get.request_sync( - client=self._client, - app_id=app_id, - integ_id=integ_id, + client=self._client, app_id=app_id, integ_id=integ_id ) def update( - self, app_id: str, integ_id: str, integ_update: IntegrationUpdate + self, app_id: str, integ_id: str, integration_update: IntegrationUpdate ) -> IntegrationOut: + """Update an integration.""" return v1_integration_update.request_sync( client=self._client, app_id=app_id, integ_id=integ_id, - json_body=integ_update, + json_body=integration_update, ) def delete(self, app_id: str, integ_id: str) -> None: + """Delete an integration.""" return v1_integration_delete.request_sync( - client=self._client, - app_id=app_id, - integ_id=integ_id, + client=self._client, app_id=app_id, integ_id=integ_id ) def get_key(self, app_id: str, integ_id: str) -> IntegrationKeyOut: @@ -139,11 +163,12 @@ def get_key(self, app_id: str, integ_id: str) -> IntegrationKeyOut: ) def rotate_key( - self, app_id: str, integ_id: str, options: PostOptions = PostOptions() + self, + app_id: str, + integ_id: str, + options: IntegrationRotateKeyOptions = IntegrationRotateKeyOptions(), ) -> IntegrationKeyOut: + """Rotate the integration's key. The previous key will be immediately revoked.""" return v1_integration_rotate_key.request_sync( - client=self._client, - app_id=app_id, - integ_id=integ_id, - **options.to_dict(), + client=self._client, app_id=app_id, integ_id=integ_id, **options.to_dict() ) From 1ebccaf5eaceab40be9a652e9b3f6713c528b1c6 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 15:29:50 -0500 Subject: [PATCH 08/15] python: Add integration.py *Options to __init__.py --- python/svix/api/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/python/svix/api/__init__.py b/python/svix/api/__init__.py index 842f6648f..138837656 100644 --- a/python/svix/api/__init__.py +++ b/python/svix/api/__init__.py @@ -37,7 +37,13 @@ EventTypeImportOpenapiOptions, EventTypeDeleteOptions, ) -from .integration import IntegrationAsync, Integration +from .integration import ( + IntegrationAsync, + Integration, + IntegrationListOptions, + IntegrationCreateOptions, + IntegrationRotateKeyOptions, +) from .message import MessageAsync, Message from .message_attempt import ( MessageAttemptAsync, From 1579a3fa47c7781535372f4816d36889981b26c7 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 15:58:36 -0500 Subject: [PATCH 09/15] python: Add openapi-codegen generated message_attempt.py Braking changes: - `MessageAttemptListOptions` is not importable from `svix.api` --- python/svix/api/__init__.py | 1 - python/svix/api/message_attempt.py | 275 +++++++++++++++++++++-------- 2 files changed, 200 insertions(+), 76 deletions(-) diff --git a/python/svix/api/__init__.py b/python/svix/api/__init__.py index 138837656..6d5abd710 100644 --- a/python/svix/api/__init__.py +++ b/python/svix/api/__init__.py @@ -48,7 +48,6 @@ from .message_attempt import ( MessageAttemptAsync, MessageAttempt, - MessageAttemptListOptions, ) from .operational_webhook import ( OperationalWebhookEndpointAsync, diff --git a/python/svix/api/message_attempt.py b/python/svix/api/message_attempt.py index 81395dc82..13044bee8 100644 --- a/python/svix/api/message_attempt.py +++ b/python/svix/api/message_attempt.py @@ -1,55 +1,144 @@ import typing as t -from dataclasses import dataclass from datetime import datetime +from dataclasses import dataclass from deprecated import deprecated -from .common import ensure_tz, ListOptions, PostOptions, ApiBase +from .common import ApiBase, BaseOptions +from ..internal.openapi_client import models from ..internal.openapi_client.api.message_attempt import ( - v1_message_attempt_expunge_content, - v1_message_attempt_get, - v1_message_attempt_list_attempted_destinations, - v1_message_attempt_list_attempted_messages, v1_message_attempt_list_by_endpoint, - v1_message_attempt_list_by_endpoint_deprecated, v1_message_attempt_list_by_msg, + v1_message_attempt_list_attempted_messages, + v1_message_attempt_get, + v1_message_attempt_expunge_content, + v1_message_attempt_list_attempted_destinations, v1_message_attempt_resend, + v1_message_attempt_list_by_endpoint_deprecated, ) -from ..internal.openapi_client.models.list_response_endpoint_message_out import ( - ListResponseEndpointMessageOut, -) -from ..internal.openapi_client.models.list_response_message_attempt_endpoint_out import ( - ListResponseMessageAttemptEndpointOut, -) + from ..internal.openapi_client.models.list_response_message_attempt_out import ( ListResponseMessageAttemptOut, ) +from ..internal.openapi_client.models.list_response_endpoint_message_out import ( + ListResponseEndpointMessageOut, +) +from ..internal.openapi_client.models.message_attempt_out import MessageAttemptOut from ..internal.openapi_client.models.list_response_message_endpoint_out import ( ListResponseMessageEndpointOut, ) -from ..internal.openapi_client.models.message_attempt_out import MessageAttemptOut -from ..internal.openapi_client.models.message_status import MessageStatus -from ..internal.openapi_client.models.status_code_class import StatusCodeClass +from ..internal.openapi_client.models.list_response_message_attempt_endpoint_out import ( + ListResponseMessageAttemptEndpointOut, +) @dataclass -class MessageAttemptListOptions(ListOptions): - status: t.Optional[MessageStatus] = None +class MessageAttemptListByEndpointOptions(BaseOptions): + # Limit the number of returned items + limit: t.Optional[int] = None + # The iterator returned from a prior invocation + iterator: t.Optional[str] = None + # Filter response based on the status of the attempt: Success (0), Pending (1), Failed (2), or Sending (3) + status: t.Optional[models.MessageStatus] = None + # Filter response based on the HTTP status code + status_code_class: t.Optional[models.StatusCodeClass] = None + # Filter response based on the channel + channel: t.Optional[str] = None + # Filter response based on the tag + tag: t.Optional[str] = None + # Only include items created before a certain date + before: t.Optional[datetime] = None + # Only include items created after a certain date + after: t.Optional[datetime] = None + # When `true` attempt content is included in the response + with_content: t.Optional[bool] = None + # When `true`, the message information is included in the response + with_msg: t.Optional[bool] = None + # Filter response based on the event type + event_types: t.Optional[t.Set[str]] = None + + +@dataclass +class MessageAttemptListByMsgOptions(BaseOptions): + # Limit the number of returned items + limit: t.Optional[int] = None + # The iterator returned from a prior invocation + iterator: t.Optional[str] = None + # Filter response based on the status of the attempt: Success (0), Pending (1), Failed (2), or Sending (3) + status: t.Optional[models.MessageStatus] = None + # Filter response based on the HTTP status code + status_code_class: t.Optional[models.StatusCodeClass] = None + # Filter response based on the channel + channel: t.Optional[str] = None + # Filter response based on the tag + tag: t.Optional[str] = None + # Filter the attempts based on the attempted endpoint + endpoint_id: t.Optional[str] = None + # Only include items created before a certain date + before: t.Optional[datetime] = None + # Only include items created after a certain date + after: t.Optional[datetime] = None + # When `true` attempt content is included in the response + with_content: t.Optional[bool] = None + # Filter response based on the event type + event_types: t.Optional[t.Set[str]] = None + + +@dataclass +class MessageAttemptListAttemptedMessagesOptions(BaseOptions): + # Limit the number of returned items + limit: t.Optional[int] = None + # The iterator returned from a prior invocation + iterator: t.Optional[str] = None + # Filter response based on the channel + channel: t.Optional[str] = None + # Filter response based on the message tags + tag: t.Optional[str] = None + # Filter response based on the status of the attempt: Success (0), Pending (1), Failed (2), or Sending (3) + status: t.Optional[models.MessageStatus] = None + # Only include items created before a certain date + before: t.Optional[datetime] = None + # Only include items created after a certain date + after: t.Optional[datetime] = None + # When `true` message payloads are included in the response + with_content: t.Optional[bool] = None + # Filter response based on the event type + event_types: t.Optional[t.Set[str]] = None + + +@dataclass +class MessageAttemptListAttemptedDestinationsOptions(BaseOptions): + # Limit the number of returned items + limit: t.Optional[int] = None + # The iterator returned from a prior invocation + iterator: t.Optional[str] = None + + +@dataclass +class MessageAttemptResendOptions(BaseOptions): + idempotency_key: t.Optional[str] = None + + +@dataclass +class MessageListOptions(BaseOptions): + status: t.Optional[models.MessageStatus] = None event_types: t.Optional[t.List[str]] = None before: t.Optional[datetime] = None after: t.Optional[datetime] = None channel: t.Optional[str] = None - status_code_class: t.Optional[StatusCodeClass] = None + status_code_class: t.Optional[models.StatusCodeClass] = None + - def to_dict(self) -> t.Dict[str, t.Any]: - d = super().to_dict() - if self.before is not None: - d["before"] = ensure_tz(self.before) - if self.after is not None: - d["after"] = ensure_tz(self.after) - return d +@dataclass +class MessageListAttemptsForEndpointOptions(BaseOptions): + status: t.Optional[models.MessageStatus] = None + event_types: t.Optional[t.List[str]] = None + before: t.Optional[datetime] = None + after: t.Optional[datetime] = None + channel: t.Optional[str] = None + status_code_class: t.Optional[models.StatusCodeClass] = None class MessageAttemptAsync(ApiBase): @@ -57,8 +146,15 @@ async def list_by_endpoint( self, app_id: str, endpoint_id: str, - options: MessageAttemptListOptions = MessageAttemptListOptions(), + options: MessageAttemptListByEndpointOptions = MessageAttemptListByEndpointOptions(), ) -> ListResponseMessageAttemptOut: + """List attempts by endpoint id + + Note that by default this endpoint is limited to retrieving 90 days' worth of data + relative to now or, if an iterator is provided, 90 days before/after the time indicated + by the iterator ID. If you require data beyond those time ranges, you will need to explicitly + set the `before` or `after` parameter as appropriate. + """ return await v1_message_attempt_list_by_endpoint.request_asyncio( client=self._client, app_id=app_id, @@ -70,8 +166,14 @@ async def list_by_msg( self, app_id: str, msg_id: str, - options: MessageAttemptListOptions = MessageAttemptListOptions(), + options: MessageAttemptListByMsgOptions = MessageAttemptListByMsgOptions(), ) -> ListResponseMessageAttemptOut: + """List attempts by message ID. + + Note that by default this endpoint is limited to retrieving 90 days' worth of data + relative to now or, if an iterator is provided, 90 days before/after the time indicated + by the iterator ID. If you require data beyond those time ranges, you will need to explicitly + set the `before` or `after` parameter as appropriate.""" return await v1_message_attempt_list_by_msg.request_asyncio( client=self._client, app_id=app_id, msg_id=msg_id, **options.to_dict() ) @@ -80,8 +182,17 @@ async def list_attempted_messages( self, app_id: str, endpoint_id: str, - options: MessageAttemptListOptions = MessageAttemptListOptions(), + options: MessageAttemptListAttemptedMessagesOptions = MessageAttemptListAttemptedMessagesOptions(), ) -> ListResponseEndpointMessageOut: + """List messages for a particular endpoint. Additionally includes metadata about the latest message attempt. + + The `before` parameter lets you filter all items created before a certain date and is ignored if an iterator is passed. + + Note that by default this endpoint is limited to retrieving 90 days' worth of data + relative to now or, if an iterator is provided, 90 days before/after the time indicated + by the iterator ID. If you require data beyond those time ranges, you will need to explicitly + set the `before` or `after` parameter as appropriate. + """ return await v1_message_attempt_list_attempted_messages.request_asyncio( client=self._client, app_id=app_id, @@ -90,37 +201,32 @@ async def list_attempted_messages( ) async def get(self, app_id: str, msg_id: str, attempt_id: str) -> MessageAttemptOut: + """`msg_id`: Use a message id or a message `eventId`""" return await v1_message_attempt_get.request_asyncio( - client=self._client, - app_id=app_id, - msg_id=msg_id, - attempt_id=attempt_id, + client=self._client, app_id=app_id, msg_id=msg_id, attempt_id=attempt_id ) - async def expunge_content( - self, - app_id: str, - msg_id: str, - attempt_id: str, - ) -> None: + async def expunge_content(self, app_id: str, msg_id: str, attempt_id: str) -> None: + """Deletes the given attempt's response body. + + Useful when an endpoint accidentally returned sensitive content. + The message can't be replayed or resent once its payload has been deleted or expired.""" return await v1_message_attempt_expunge_content.request_asyncio( - client=self._client, - app_id=app_id, - msg_id=msg_id, - attempt_id=attempt_id, + client=self._client, app_id=app_id, msg_id=msg_id, attempt_id=attempt_id ) async def list_attempted_destinations( self, app_id: str, msg_id: str, - options: MessageAttemptListOptions = MessageAttemptListOptions(), + options: MessageAttemptListAttemptedDestinationsOptions = MessageAttemptListAttemptedDestinationsOptions(), ) -> ListResponseMessageEndpointOut: + """List endpoints attempted by a given message. + + Additionally includes metadata about the latest message attempt. + By default, endpoints are listed in ascending order by ID.""" return await v1_message_attempt_list_attempted_destinations.request_asyncio( - client=self._client, - app_id=app_id, - msg_id=msg_id, - **options.to_dict(), + client=self._client, app_id=app_id, msg_id=msg_id, **options.to_dict() ) async def resend( @@ -128,8 +234,9 @@ async def resend( app_id: str, msg_id: str, endpoint_id: str, - options: PostOptions = PostOptions(), + options: MessageAttemptResendOptions = MessageAttemptResendOptions(), ) -> None: + """Resend a message to the specified endpoint.""" return await v1_message_attempt_resend.request_asyncio( client=self._client, app_id=app_id, @@ -146,7 +253,7 @@ async def list_attempts_for_endpoint( app_id: str, msg_id: str, endpoint_id: str, - options: MessageAttemptListOptions = MessageAttemptListOptions(), + options: MessageListAttemptsForEndpointOptions = MessageListAttemptsForEndpointOptions(), ) -> ListResponseMessageAttemptEndpointOut: return await v1_message_attempt_list_by_endpoint_deprecated.request_asyncio( client=self._client, @@ -162,8 +269,15 @@ def list_by_endpoint( self, app_id: str, endpoint_id: str, - options: MessageAttemptListOptions = MessageAttemptListOptions(), + options: MessageAttemptListByEndpointOptions = MessageAttemptListByEndpointOptions(), ) -> ListResponseMessageAttemptOut: + """List attempts by endpoint id + + Note that by default this endpoint is limited to retrieving 90 days' worth of data + relative to now or, if an iterator is provided, 90 days before/after the time indicated + by the iterator ID. If you require data beyond those time ranges, you will need to explicitly + set the `before` or `after` parameter as appropriate. + """ return v1_message_attempt_list_by_endpoint.request_sync( client=self._client, app_id=app_id, @@ -175,8 +289,14 @@ def list_by_msg( self, app_id: str, msg_id: str, - options: MessageAttemptListOptions = MessageAttemptListOptions(), + options: MessageAttemptListByMsgOptions = MessageAttemptListByMsgOptions(), ) -> ListResponseMessageAttemptOut: + """List attempts by message ID. + + Note that by default this endpoint is limited to retrieving 90 days' worth of data + relative to now or, if an iterator is provided, 90 days before/after the time indicated + by the iterator ID. If you require data beyond those time ranges, you will need to explicitly + set the `before` or `after` parameter as appropriate.""" return v1_message_attempt_list_by_msg.request_sync( client=self._client, app_id=app_id, msg_id=msg_id, **options.to_dict() ) @@ -185,8 +305,17 @@ def list_attempted_messages( self, app_id: str, endpoint_id: str, - options: MessageAttemptListOptions = MessageAttemptListOptions(), + options: MessageAttemptListAttemptedMessagesOptions = MessageAttemptListAttemptedMessagesOptions(), ) -> ListResponseEndpointMessageOut: + """List messages for a particular endpoint. Additionally includes metadata about the latest message attempt. + + The `before` parameter lets you filter all items created before a certain date and is ignored if an iterator is passed. + + Note that by default this endpoint is limited to retrieving 90 days' worth of data + relative to now or, if an iterator is provided, 90 days before/after the time indicated + by the iterator ID. If you require data beyond those time ranges, you will need to explicitly + set the `before` or `after` parameter as appropriate. + """ return v1_message_attempt_list_attempted_messages.request_sync( client=self._client, app_id=app_id, @@ -195,37 +324,32 @@ def list_attempted_messages( ) def get(self, app_id: str, msg_id: str, attempt_id: str) -> MessageAttemptOut: + """`msg_id`: Use a message id or a message `eventId`""" return v1_message_attempt_get.request_sync( - client=self._client, - app_id=app_id, - msg_id=msg_id, - attempt_id=attempt_id, + client=self._client, app_id=app_id, msg_id=msg_id, attempt_id=attempt_id ) - def expunge_content( - self, - app_id: str, - msg_id: str, - attempt_id: str, - ) -> None: + def expunge_content(self, app_id: str, msg_id: str, attempt_id: str) -> None: + """Deletes the given attempt's response body. + + Useful when an endpoint accidentally returned sensitive content. + The message can't be replayed or resent once its payload has been deleted or expired.""" return v1_message_attempt_expunge_content.request_sync( - client=self._client, - app_id=app_id, - msg_id=msg_id, - attempt_id=attempt_id, + client=self._client, app_id=app_id, msg_id=msg_id, attempt_id=attempt_id ) def list_attempted_destinations( self, app_id: str, msg_id: str, - options: MessageAttemptListOptions = MessageAttemptListOptions(), + options: MessageAttemptListAttemptedDestinationsOptions = MessageAttemptListAttemptedDestinationsOptions(), ) -> ListResponseMessageEndpointOut: + """List endpoints attempted by a given message. + + Additionally includes metadata about the latest message attempt. + By default, endpoints are listed in ascending order by ID.""" return v1_message_attempt_list_attempted_destinations.request_sync( - client=self._client, - app_id=app_id, - msg_id=msg_id, - **options.to_dict(), + client=self._client, app_id=app_id, msg_id=msg_id, **options.to_dict() ) def resend( @@ -233,8 +357,9 @@ def resend( app_id: str, msg_id: str, endpoint_id: str, - options: PostOptions = PostOptions(), + options: MessageAttemptResendOptions = MessageAttemptResendOptions(), ) -> None: + """Resend a message to the specified endpoint.""" return v1_message_attempt_resend.request_sync( client=self._client, app_id=app_id, @@ -248,7 +373,7 @@ def list( self, app_id: str, msg_id: str, - options: MessageAttemptListOptions = MessageAttemptListOptions(), + options: MessageAttemptListByMsgOptions = MessageAttemptListByMsgOptions(), ) -> ListResponseMessageAttemptOut: return self.list_by_msg(app_id=app_id, msg_id=msg_id, options=options) @@ -260,7 +385,7 @@ def list_attempts_for_endpoint( app_id: str, msg_id: str, endpoint_id: str, - options: MessageAttemptListOptions = MessageAttemptListOptions(), + options: MessageListAttemptsForEndpointOptions = MessageListAttemptsForEndpointOptions(), ) -> ListResponseMessageAttemptEndpointOut: return v1_message_attempt_list_by_endpoint_deprecated.request_sync( client=self._client, From 3bc407c49bb1373ed0ee05e1fd8ba22eda74a526 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 16:00:08 -0500 Subject: [PATCH 10/15] python: Add message_attempt.py *Options to __init__.py --- python/svix/api/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/python/svix/api/__init__.py b/python/svix/api/__init__.py index 6d5abd710..8a6605bb6 100644 --- a/python/svix/api/__init__.py +++ b/python/svix/api/__init__.py @@ -48,6 +48,13 @@ from .message_attempt import ( MessageAttemptAsync, MessageAttempt, + MessageAttemptListByEndpointOptions, + MessageAttemptListByMsgOptions, + MessageAttemptListAttemptedMessagesOptions, + MessageAttemptListAttemptedDestinationsOptions, + MessageAttemptResendOptions, + MessageListOptions, + MessageListAttemptsForEndpointOptions, ) from .operational_webhook import ( OperationalWebhookEndpointAsync, From 69300bddbd283b1e2372d4bed434c5304a2846e0 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 16:12:27 -0500 Subject: [PATCH 11/15] python: Add openapi-codegen generated message.py Manual edits: - For `v1.message.create` manually set `ret.payload` to `message_in.payload` - For `MessageCreateOptions` set the `with_content` false by default. (this is preserve previous behavior) --- python/svix/api/message.py | 140 ++++++++++++++++++++++++++----------- 1 file changed, 101 insertions(+), 39 deletions(-) diff --git a/python/svix/api/message.py b/python/svix/api/message.py index 8963bf7b2..8f15faa17 100644 --- a/python/svix/api/message.py +++ b/python/svix/api/message.py @@ -1,38 +1,56 @@ import typing as t -from dataclasses import dataclass from datetime import datetime +from dataclasses import dataclass + +from .common import ApiBase, BaseOptions from ..internal.openapi_client.api.message import ( + v1_message_list, v1_message_create, - v1_message_expunge_content, v1_message_get, - v1_message_list, + v1_message_expunge_content, ) + from ..internal.openapi_client.models.list_response_message_out import ( ListResponseMessageOut, ) from ..internal.openapi_client.models.message_in import MessageIn from ..internal.openapi_client.models.message_out import MessageOut -from .common import ensure_tz, ListOptions, PostOptions, ApiBase - @dataclass -class MessageListOptions(ListOptions): - event_types: t.Optional[t.List[str]] = None +class MessageListOptions(BaseOptions): + # Limit the number of returned items + limit: t.Optional[int] = None + # The iterator returned from a prior invocation + iterator: t.Optional[str] = None + # Filter response based on the channel. + channel: t.Optional[str] = None + # Only include items created before a certain date. before: t.Optional[datetime] = None + # Only include items created after a certain date. after: t.Optional[datetime] = None - channel: t.Optional[str] = None + # When `true` message payloads are included in the response. + with_content: t.Optional[bool] = None + # Filter messages matching the provided tag. tag: t.Optional[str] = None + # Filter response based on the event type + event_types: t.Optional[t.Set[str]] = None - def to_dict(self) -> t.Dict[str, t.Any]: - d = super().to_dict() - if self.before is not None: - d["before"] = ensure_tz(self.before) - if self.after is not None: - d["after"] = ensure_tz(self.after) - return d + +@dataclass +class MessageCreateOptions(BaseOptions): + # When `true`, message payloads are included in the response. + with_content: t.Optional[bool] = False + + idempotency_key: t.Optional[str] = None + + +@dataclass +class MessageGetOptions(BaseOptions): + # When `true` message payloads are included in the response. + with_content: t.Optional[bool] = None def message_in_raw( @@ -71,37 +89,59 @@ class MessageAsync(ApiBase): async def list( self, app_id: str, options: MessageListOptions = MessageListOptions() ) -> ListResponseMessageOut: + """List all of the application's messages. + + The `before` and `after` parameters let you filter all items created before or after a certain date. These can be used alongside an iterator to paginate over results + within a certain window. + + Note that by default this endpoint is limited to retrieving 90 days' worth of data + relative to now or, if an iterator is provided, 90 days before/after the time indicated + by the iterator ID. If you require data beyond those time ranges, you will need to explicitly + set the `before` or `after` parameter as appropriate. + """ return await v1_message_list.request_asyncio( - client=self._client, - app_id=app_id, - **options.to_dict(), + client=self._client, app_id=app_id, **options.to_dict() ) async def create( - self, app_id: str, message_in: MessageIn, options: PostOptions = PostOptions() + self, + app_id: str, + message_in: MessageIn, + options: MessageCreateOptions = MessageCreateOptions(), ) -> MessageOut: + """Creates a new message and dispatches it to all of the application's endpoints. + + The `eventId` is an optional custom unique ID. It's verified to be unique only up to a day, after that no verification will be made. + If a message with the same `eventId` already exists for the application, a 409 conflict error will be returned. + + The `eventType` indicates the type and schema of the event. All messages of a certain `eventType` are expected to have the same schema. Endpoints can choose to only listen to specific event types. + Messages can also have `channels`, which similar to event types let endpoints filter by them. Unlike event types, messages can have multiple channels, and channels don't imply a specific message content or schema. + + The `payload` property is the webhook's body (the actual webhook message). Svix supports payload sizes of up to ~350kb, though it's generally a good idea to keep webhook payloads small, probably no larger than 40kb.""" ret = await v1_message_create.request_asyncio( client=self._client, app_id=app_id, json_body=message_in, - with_content=False, **options.to_dict(), ) ret.payload = message_in.payload return ret - async def get(self, app_id: str, msg_id: str) -> MessageOut: + async def get( + self, app_id: str, msg_id: str, options: MessageGetOptions = MessageGetOptions() + ) -> MessageOut: + """Get a message by its ID or eventID.""" return await v1_message_get.request_asyncio( - client=self._client, - app_id=app_id, - msg_id=msg_id, + client=self._client, app_id=app_id, msg_id=msg_id, **options.to_dict() ) async def expunge_content(self, app_id: str, msg_id: str) -> None: + """Delete the given message's payload. + + Useful in cases when a message was accidentally sent with sensitive content. + The message can't be replayed or resent once its payload has been deleted or expired.""" return await v1_message_expunge_content.request_asyncio( - client=self._client, - app_id=app_id, - msg_id=msg_id, + client=self._client, app_id=app_id, msg_id=msg_id ) @@ -109,35 +149,57 @@ class Message(ApiBase): def list( self, app_id: str, options: MessageListOptions = MessageListOptions() ) -> ListResponseMessageOut: + """List all of the application's messages. + + The `before` and `after` parameters let you filter all items created before or after a certain date. These can be used alongside an iterator to paginate over results + within a certain window. + + Note that by default this endpoint is limited to retrieving 90 days' worth of data + relative to now or, if an iterator is provided, 90 days before/after the time indicated + by the iterator ID. If you require data beyond those time ranges, you will need to explicitly + set the `before` or `after` parameter as appropriate. + """ return v1_message_list.request_sync( - client=self._client, - app_id=app_id, - **options.to_dict(), + client=self._client, app_id=app_id, **options.to_dict() ) def create( - self, app_id: str, message_in: MessageIn, options: PostOptions = PostOptions() + self, + app_id: str, + message_in: MessageIn, + options: MessageCreateOptions = MessageCreateOptions(), ) -> MessageOut: + """Creates a new message and dispatches it to all of the application's endpoints. + + The `eventId` is an optional custom unique ID. It's verified to be unique only up to a day, after that no verification will be made. + If a message with the same `eventId` already exists for the application, a 409 conflict error will be returned. + + The `eventType` indicates the type and schema of the event. All messages of a certain `eventType` are expected to have the same schema. Endpoints can choose to only listen to specific event types. + Messages can also have `channels`, which similar to event types let endpoints filter by them. Unlike event types, messages can have multiple channels, and channels don't imply a specific message content or schema. + + The `payload` property is the webhook's body (the actual webhook message). Svix supports payload sizes of up to ~350kb, though it's generally a good idea to keep webhook payloads small, probably no larger than 40kb.""" ret = v1_message_create.request_sync( client=self._client, app_id=app_id, json_body=message_in, - with_content=False, **options.to_dict(), ) ret.payload = message_in.payload return ret - def get(self, app_id: str, msg_id: str) -> MessageOut: + def get( + self, app_id: str, msg_id: str, options: MessageGetOptions = MessageGetOptions() + ) -> MessageOut: + """Get a message by its ID or eventID.""" return v1_message_get.request_sync( - client=self._client, - app_id=app_id, - msg_id=msg_id, + client=self._client, app_id=app_id, msg_id=msg_id, **options.to_dict() ) def expunge_content(self, app_id: str, msg_id: str) -> None: + """Delete the given message's payload. + + Useful in cases when a message was accidentally sent with sensitive content. + The message can't be replayed or resent once its payload has been deleted or expired.""" return v1_message_expunge_content.request_sync( - client=self._client, - app_id=app_id, - msg_id=msg_id, + client=self._client, app_id=app_id, msg_id=msg_id ) From 80e2b76733f08e97c5ce44cf4087ff709607ee85 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 16:20:04 -0500 Subject: [PATCH 12/15] python: Add message.py *Options to __init__.py This also switches the `MessageListOptions` import from `message_attempt` to `message`, and removes the definition in `message_attempt` since it is not used by anything (except the `from svix.api` import, which we replaced) Breaking change: - The `status_code_class` and `status` optional fields are removed from `svix.api.MessageListOptions` --- python/svix/api/__init__.py | 9 +++++++-- python/svix/api/message_attempt.py | 10 ---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/python/svix/api/__init__.py b/python/svix/api/__init__.py index 8a6605bb6..8ea4d43ea 100644 --- a/python/svix/api/__init__.py +++ b/python/svix/api/__init__.py @@ -44,7 +44,13 @@ IntegrationCreateOptions, IntegrationRotateKeyOptions, ) -from .message import MessageAsync, Message +from .message import ( + MessageAsync, + Message, + MessageListOptions, + MessageCreateOptions, + MessageGetOptions, +) from .message_attempt import ( MessageAttemptAsync, MessageAttempt, @@ -53,7 +59,6 @@ MessageAttemptListAttemptedMessagesOptions, MessageAttemptListAttemptedDestinationsOptions, MessageAttemptResendOptions, - MessageListOptions, MessageListAttemptsForEndpointOptions, ) from .operational_webhook import ( diff --git a/python/svix/api/message_attempt.py b/python/svix/api/message_attempt.py index 13044bee8..ddf4db168 100644 --- a/python/svix/api/message_attempt.py +++ b/python/svix/api/message_attempt.py @@ -121,16 +121,6 @@ class MessageAttemptResendOptions(BaseOptions): idempotency_key: t.Optional[str] = None -@dataclass -class MessageListOptions(BaseOptions): - status: t.Optional[models.MessageStatus] = None - event_types: t.Optional[t.List[str]] = None - before: t.Optional[datetime] = None - after: t.Optional[datetime] = None - channel: t.Optional[str] = None - status_code_class: t.Optional[models.StatusCodeClass] = None - - @dataclass class MessageListAttemptsForEndpointOptions(BaseOptions): status: t.Optional[models.MessageStatus] = None From 32b221d679e55fbd40f61061b0affdfb5ea5c6d2 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 16:24:52 -0500 Subject: [PATCH 13/15] python: Add openapi-codegen generated statistics.py Technically? breaking change: - the `task_id` argument is removed from the `aggregate_event_types` method. However this argument was not previously used by the inner API client at all. So this technically breaks the high-level API client, but no change to the actual outgoing api.svix.com requests --- python/svix/api/statistics.py | 56 +++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/python/svix/api/statistics.py b/python/svix/api/statistics.py index 9da16720c..9890bce56 100644 --- a/python/svix/api/statistics.py +++ b/python/svix/api/statistics.py @@ -1,10 +1,7 @@ -from ..internal.openapi_client.models.aggregate_event_types_out import ( - AggregateEventTypesOut, -) -from ..internal.openapi_client.models.app_usage_stats_in import AppUsageStatsIn -from ..internal.openapi_client.models.app_usage_stats_out import AppUsageStatsOut +import typing as t +from dataclasses import dataclass -from .common import PostOptions, ApiBase +from .common import ApiBase, BaseOptions from ..internal.openapi_client.api.statistics import ( @@ -12,18 +9,37 @@ v1_statistics_aggregate_event_types, ) +from ..internal.openapi_client.models.app_usage_stats_in import AppUsageStatsIn +from ..internal.openapi_client.models.app_usage_stats_out import AppUsageStatsOut +from ..internal.openapi_client.models.aggregate_event_types_out import ( + AggregateEventTypesOut, +) + + +@dataclass +class StatisticsAggregateAppStatsOptions(BaseOptions): + idempotency_key: t.Optional[str] = None + class StatisticsAsync(ApiBase): async def aggregate_app_stats( - self, app_usage_stats_in: AppUsageStatsIn, options: PostOptions = PostOptions() + self, + app_usage_stats_in: AppUsageStatsIn, + options: StatisticsAggregateAppStatsOptions = StatisticsAggregateAppStatsOptions(), ) -> AppUsageStatsOut: + """Creates a background task to calculate the message destinations for all applications in the environment. + + Note that this endpoint is asynchronous. You will need to poll the `Get Background Task` endpoint to + retrieve the results of the operation.""" return await v1_statistics_aggregate_app_stats.request_asyncio( - client=self._client, - json_body=app_usage_stats_in, - **options.to_dict(), + client=self._client, json_body=app_usage_stats_in, **options.to_dict() ) - async def aggregate_event_types(self, task_id: str) -> AggregateEventTypesOut: + async def aggregate_event_types(self) -> AggregateEventTypesOut: + """Creates a background task to calculate the listed event types for all apps in the organization. + + Note that this endpoint is asynchronous. You will need to poll the `Get Background Task` endpoint to + retrieve the results of the operation.""" return await v1_statistics_aggregate_event_types.request_asyncio( client=self._client ) @@ -31,13 +47,21 @@ async def aggregate_event_types(self, task_id: str) -> AggregateEventTypesOut: class Statistics(ApiBase): def aggregate_app_stats( - self, app_usage_stats_in: AppUsageStatsIn, options: PostOptions = PostOptions() + self, + app_usage_stats_in: AppUsageStatsIn, + options: StatisticsAggregateAppStatsOptions = StatisticsAggregateAppStatsOptions(), ) -> AppUsageStatsOut: + """Creates a background task to calculate the message destinations for all applications in the environment. + + Note that this endpoint is asynchronous. You will need to poll the `Get Background Task` endpoint to + retrieve the results of the operation.""" return v1_statistics_aggregate_app_stats.request_sync( - client=self._client, - json_body=app_usage_stats_in, - **options.to_dict(), + client=self._client, json_body=app_usage_stats_in, **options.to_dict() ) - def aggregate_event_types(self, task_id: str) -> AggregateEventTypesOut: + def aggregate_event_types(self) -> AggregateEventTypesOut: + """Creates a background task to calculate the listed event types for all apps in the organization. + + Note that this endpoint is asynchronous. You will need to poll the `Get Background Task` endpoint to + retrieve the results of the operation.""" return v1_statistics_aggregate_event_types.request_sync(client=self._client) From 5ebe694ea3ba9f6c24b0bc9b6674b6a024169dc1 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Thu, 16 Jan 2025 16:25:12 -0500 Subject: [PATCH 14/15] python: Add statistics.py *Options to __init__.py --- python/svix/api/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/svix/api/__init__.py b/python/svix/api/__init__.py index 8ea4d43ea..cd5fb3818 100644 --- a/python/svix/api/__init__.py +++ b/python/svix/api/__init__.py @@ -65,7 +65,7 @@ OperationalWebhookEndpointAsync, OperationalWebhookEndpoint, ) -from .statistics import StatisticsAsync, Statistics +from .statistics import StatisticsAsync, Statistics, StatisticsAggregateAppStatsOptions from svix.internal.openapi_client.models.aggregate_event_types_out import ( AggregateEventTypesOut, From 0406384d6dd39caa7e2b2cf385ea5ff7bd1e08a3 Mon Sep 17 00:00:00 2001 From: Mendy Man Date: Fri, 17 Jan 2025 14:00:33 -0500 Subject: [PATCH 15/15] Document breaking changes in Changelog --- ChangeLog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 980575ff5..5e11d5173 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,11 @@ # Changelog ## Unreleased +* Libs/Python **(Breaking)**: `PostOptions` and `ListOptions` are no longer used in methods for `Authentication`,`Endpoint`,`EventType`,`Integration`,`MessageAttempt`,`Message` and `Statistics` resources. Instead each API call now has it's own `{Resource}{Operation}Options`. (Both sync and async) +* Libs/Python: In `Application` the `dashboard_access` method is deprecated in favor of `app_portal_access`. (Both sync and async) +* Libs/Python **(Breaking)**: `EndpointStatsOptions` is renamed to `EndpointGetStatsOptions` +* Libs/Python **(Breaking)**: `MessageAttemptListOptions` is removed in favor of call specific `{Resource}{Operation}Options` +* Libs/Python **(Breaking)**: For `Statistics` in the `aggregate_event_types` method the `task_id` parameter is removed, Please note that previously this parameter was ignored and had no affect (Both sync and async) * Libs/Kotlin **(Breaking)**: Mark `api` field of all API resource classes as `private` (previously only some were private, accidentally) * Libs/Kotlin **(Breaking)**: Update `recover` to return `RecoverOut` (instead of nothing)