Repository navigation
python(feat): webhooks resource #789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
fd02304
python(feature): add webhooks resource and webhook rule actions
brandonshippy-sift bc48cac
update wording
brandonshippy-sift 908b578
fix ci tests
brandonshippy-sift 0ab123c
pyright model
brandonshippy-sift 037fad1
address review: keep RuleAction.annotation, drop Ref alias, accept As…
brandonshippy-sift d04aed5
python: address changes
brandonshippy-sift 48dee53
fix ci, mkdocstrings err
brandonshippy-sift cdee238
python: update WebhookTest
brandonshippy-sift 0f1514c
python: accept a Webhook directly as a rule action
brandonshippy-sift f5d645a
python: raise valueerror
brandonshippy-sift d3a014b
python: merge conflicts
brandonshippy-sift File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
197 changes: 197 additions & 0 deletions
197
python/lib/sift_client/_internal/low_level_wrappers/webhooks.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| from typing import TYPE_CHECKING, Any, cast | ||
|
|
||
| from sift.webhooks.v1.webhooks_pb2 import ( | ||
| CreateWebhookResponse, | ||
| GetWebhookRequest, | ||
| GetWebhookResponse, | ||
| ListWebhooksRequest, | ||
| ListWebhooksResponse, | ||
| TestWebhookRequest, | ||
| TestWebhookResponse, | ||
| UpdateWebhookRequest, | ||
| UpdateWebhookResponse, | ||
| ) | ||
| from sift.webhooks.v1.webhooks_pb2 import Webhook as WebhookProto | ||
| from sift.webhooks.v1.webhooks_pb2_grpc import WebhookServiceStub | ||
|
|
||
| from sift_client._internal.low_level_wrappers.base import DEFAULT_PAGE_SIZE, LowLevelClientBase | ||
| from sift_client.sift_types.webhook import ( | ||
| Webhook, | ||
| WebhookCreate, | ||
| WebhookTest, | ||
| WebhookUpdate, | ||
| ) | ||
| from sift_client.transport import WithGrpcClient | ||
|
|
||
| if TYPE_CHECKING: | ||
| from sift_client.transport.grpc_transport import GrpcClient | ||
|
|
||
| # Configure logging | ||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class WebhooksLowLevelClient(LowLevelClientBase, WithGrpcClient): | ||
| """Low-level client for the WebhooksAPI. | ||
|
|
||
| A thin wrapper around the autogenerated bindings for the WebhookService. | ||
| """ | ||
|
|
||
| def __init__(self, grpc_client: GrpcClient): | ||
| """Initialize the WebhooksLowLevelClient. | ||
|
|
||
| Args: | ||
| grpc_client: The gRPC client to use for making API calls. | ||
| """ | ||
| super().__init__(grpc_client) | ||
|
|
||
| async def get_webhook(self, webhook_id: str) -> Webhook: | ||
| """Get a webhook by webhook_id. | ||
|
|
||
| Args: | ||
| webhook_id: The webhook ID to get. | ||
|
|
||
| Returns: | ||
| The Webhook. | ||
| """ | ||
| return Webhook._from_proto(await self._get_webhook_proto(webhook_id)) | ||
|
|
||
| async def _get_webhook_proto(self, webhook_id: str) -> WebhookProto: | ||
| request = GetWebhookRequest(webhook_id=webhook_id) | ||
| response = await self._grpc_client.get_stub(WebhookServiceStub).GetWebhook(request) | ||
| return cast("GetWebhookResponse", response).webhook | ||
|
|
||
| async def list_webhooks( | ||
| self, | ||
| *, | ||
| page_size: int | None = DEFAULT_PAGE_SIZE, | ||
| page_token: str | None = None, | ||
| query_filter: str | None = None, | ||
| order_by: str | None = None, | ||
| ) -> tuple[list[Webhook], str]: | ||
| """List webhooks with optional filtering and pagination. | ||
|
|
||
| Args: | ||
| page_size: The maximum number of webhooks to return. | ||
| page_token: A page token for pagination. | ||
| query_filter: A CEL filter string. | ||
| order_by: How to order the retrieved webhooks. | ||
|
|
||
| Returns: | ||
| A tuple of (webhooks, next_page_token). | ||
| """ | ||
| request_kwargs: dict[str, Any] = {} | ||
| if page_size is not None: | ||
| request_kwargs["page_size"] = page_size | ||
| if page_token is not None: | ||
| request_kwargs["page_token"] = page_token | ||
| if query_filter is not None: | ||
| request_kwargs["filter"] = query_filter | ||
| if order_by is not None: | ||
| request_kwargs["order_by"] = order_by | ||
|
|
||
| request = ListWebhooksRequest(**request_kwargs) | ||
| response = await self._grpc_client.get_stub(WebhookServiceStub).ListWebhooks(request) | ||
| response = cast("ListWebhooksResponse", response) | ||
|
|
||
| webhooks = [Webhook._from_proto(webhook) for webhook in response.webhooks] | ||
| return webhooks, response.next_page_token | ||
|
|
||
| async def list_all_webhooks( | ||
| self, | ||
| *, | ||
| query_filter: str | None = None, | ||
| order_by: str | None = None, | ||
| page_size: int | None = DEFAULT_PAGE_SIZE, | ||
| max_results: int | None = None, | ||
| ) -> list[Webhook]: | ||
| """List all webhooks with optional filtering. | ||
|
|
||
| Args: | ||
| query_filter: A CEL filter string. | ||
| order_by: How to order the retrieved webhooks. | ||
| page_size: The number of results to fetch per request. | ||
| max_results: Maximum number of results to return. | ||
|
|
||
| Returns: | ||
| A list of all matching webhooks. | ||
| """ | ||
| return await self._handle_pagination( | ||
| self.list_webhooks, | ||
| kwargs={"query_filter": query_filter}, | ||
| order_by=order_by, | ||
| max_results=max_results, | ||
| page_size=page_size, | ||
| ) | ||
|
|
||
| async def create_webhook(self, *, create: WebhookCreate) -> Webhook: | ||
| """Create a webhook. | ||
|
|
||
| Args: | ||
| create: The webhook definition. | ||
|
|
||
| Returns: | ||
| The created Webhook. | ||
| """ | ||
| request_proto = create.to_proto() | ||
| response = await self._grpc_client.get_stub(WebhookServiceStub).CreateWebhook(request_proto) | ||
| grpc_webhook = cast("CreateWebhookResponse", response).webhook | ||
| return Webhook._from_proto(grpc_webhook) | ||
|
|
||
| async def update_webhook(self, update: WebhookUpdate) -> Webhook: | ||
| """Update select fields of a webhook. | ||
|
|
||
| Reads the webhook first and merges the patch over it. UpdateWebhook runs | ||
| protovalidate across the whole `Webhook` message and ignores the update mask, so | ||
| a sparse patch fails with `webhook.organization_id: value must be a valid UUID` | ||
| and three similar errors. The cost is a round trip, and concurrent updates | ||
| overwrite each other. Remove this once the service validates only masked fields. | ||
|
|
||
| Args: | ||
| update: The updates to apply. Its `resource_id` must be set. | ||
|
|
||
| Returns: | ||
| The updated Webhook. | ||
| """ | ||
| patch, update_mask = update.to_proto_with_mask() | ||
| grpc_webhook = await self._get_webhook_proto(patch.webhook_id) | ||
| update_mask.MergeMessage(patch, grpc_webhook, replace_repeated_field=True) | ||
| request = UpdateWebhookRequest(webhook=grpc_webhook, update_mask=update_mask) | ||
| response = await self._grpc_client.get_stub(WebhookServiceStub).UpdateWebhook(request) | ||
| updated_grpc_webhook = cast("UpdateWebhookResponse", response).webhook | ||
| return Webhook._from_proto(updated_grpc_webhook) | ||
|
|
||
| async def test_webhook( | ||
| self, | ||
| *, | ||
| webhook_id: str | None = None, | ||
| create: WebhookCreate | None = None, | ||
| ) -> WebhookTest: | ||
| """Send a real request to a webhook's target URL. Pass exactly one identifier. | ||
|
|
||
| Args: | ||
| webhook_id: The ID of an existing webhook to test. | ||
| create: An unsaved webhook definition to test. | ||
|
|
||
| Returns: | ||
| The response the target URL returned. | ||
|
|
||
| Raises: | ||
| ValueError: If neither or both arguments are provided. | ||
| """ | ||
| if (webhook_id is None) == (create is None): | ||
| raise ValueError("Exactly one of webhook_id or create must be provided") | ||
|
|
||
| if webhook_id is not None: | ||
| request = TestWebhookRequest(webhook_id=webhook_id) | ||
| else: | ||
| request = TestWebhookRequest(create_request=cast("WebhookCreate", create).to_proto()) | ||
|
|
||
| response = await self._grpc_client.get_stub(WebhookServiceStub).TestWebhook(request) | ||
| response = cast("TestWebhookResponse", response) | ||
| return WebhookTest( | ||
| http_response_code=response.http_response_code, | ||
| http_response_body=response.http_response_body, | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.