Skip to content

Commit cde48b9

Browse files
authored
Merge pull request #1169 from jawad-khan/jawad/webhooks-api
Add support for webhooks api
2 parents 87cf84b + 233b981 commit cde48b9

File tree

6 files changed

+278
-0
lines changed

6 files changed

+278
-0
lines changed

.code-samples.meilisearch.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,3 +767,19 @@ get_batch_1: |-
767767
client.get_batch(BATCH_UID)
768768
get_similar_post_1: |-
769769
client.index("INDEX_NAME").get_similar_documents({"id": "TARGET_DOCUMENT_ID", "embedder": "default"})
770+
webhooks_get_1: |-
771+
client.get_webhooks()
772+
webhooks_get_single_1: |-
773+
client.get_webhook('WEBHOOK_UID')
774+
webhooks_post_1: |-
775+
client.create_webhook({
776+
'url': 'https://example.com/webhook',
777+
'headers': {"Authorization":"", "X-Custom-Header":"test"},
778+
})
779+
webhooks_patch_1: |-
780+
client.update_webhook('WEBHOOK_UID', {
781+
'url': 'https://example.com/new-webhook',
782+
'headers': {"Authorization":"", "X-Custom-Header":"test"},
783+
})
784+
webhooks_delete_1: |-
785+
client.delete_webhook('WEBHOOK_UID')

meilisearch/client.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from meilisearch.index import Index
3333
from meilisearch.models.key import Key, KeysResults
3434
from meilisearch.models.task import Batch, BatchResults, Task, TaskInfo, TaskResults
35+
from meilisearch.models.webhook import Webhook, WebhooksResults
3536
from meilisearch.task import TaskHandler
3637

3738

@@ -465,6 +466,119 @@ def delete_key(self, key_or_uid: str) -> int:
465466

466467
return response.status_code
467468

469+
# WEBHOOKS ROUTES
470+
471+
def get_webhooks(self) -> WebhooksResults:
472+
"""Get all webhooks.
473+
474+
Returns
475+
-------
476+
webhooks:
477+
WebhooksResults instance containing list of webhooks and pagination info.
478+
https://www.meilisearch.com/docs/reference/api/webhooks
479+
480+
Raises
481+
------
482+
MeilisearchApiError
483+
An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://www.meilisearch.com/docs/reference/errors/error_codes#meilisearch-errors
484+
"""
485+
webhooks = self.http.get(f"{self.config.paths.webhooks}")
486+
return WebhooksResults(**webhooks)
487+
488+
def get_webhook(self, webhook_uuid: str) -> Webhook:
489+
"""Get information about a specific webhook.
490+
491+
Parameters
492+
----------
493+
webhook_uuid:
494+
The uuid of the webhook to retrieve.
495+
496+
Returns
497+
-------
498+
webhook:
499+
The webhook information.
500+
https://www.meilisearch.com/docs/reference/api/webhooks#get-one-webhook
501+
502+
Raises
503+
------
504+
MeilisearchApiError
505+
An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://www.meilisearch.com/docs/reference/errors/error_codes#meilisearch-errors
506+
"""
507+
webhook = self.http.get(f"{self.config.paths.webhooks}/{webhook_uuid}")
508+
return Webhook(**webhook)
509+
510+
def create_webhook(self, options: Mapping[str, Any]) -> Webhook:
511+
"""Create a new webhook.
512+
513+
Parameters
514+
----------
515+
options:
516+
The webhook configuration. Can include:
517+
- url: The URL to send the webhook to
518+
- headers: Dictionary of HTTP headers to include in webhook requests
519+
520+
Returns
521+
-------
522+
webhook:
523+
The newly created webhook.
524+
https://www.meilisearch.com/docs/reference/api/webhooks#create-a-webhook
525+
526+
Raises
527+
------
528+
MeilisearchApiError
529+
An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://www.meilisearch.com/docs/reference/errors/error_codes#meilisearch-errors
530+
"""
531+
webhook = self.http.post(self.config.paths.webhooks, options)
532+
return Webhook(**webhook)
533+
534+
def update_webhook(self, webhook_uuid: str, options: Mapping[str, Any]) -> Webhook:
535+
"""Update an existing webhook.
536+
537+
Parameters
538+
----------
539+
webhook_uuid:
540+
The uuid of the webhook to update.
541+
options:
542+
The webhook fields to update. Can include:
543+
- url: The URL to send the webhook to
544+
- headers: Dictionary of HTTP headers to include in webhook requests
545+
546+
Returns
547+
-------
548+
webhook:
549+
The updated webhook.
550+
https://www.meilisearch.com/docs/reference/api/webhooks#update-a-webhook
551+
552+
Raises
553+
------
554+
MeilisearchApiError
555+
An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://www.meilisearch.com/docs/reference/errors/error_codes#meilisearch-errors
556+
"""
557+
webhook = self.http.patch(f"{self.config.paths.webhooks}/{webhook_uuid}", options)
558+
return Webhook(**webhook)
559+
560+
def delete_webhook(self, webhook_uuid: str) -> int:
561+
"""Delete a webhook.
562+
563+
Parameters
564+
----------
565+
webhook_uuid:
566+
The uuid of the webhook to delete.
567+
568+
Returns
569+
-------
570+
status_code:
571+
The Response status code. 204 signifies a successful delete.
572+
https://www.meilisearch.com/docs/reference/api/webhooks#delete-a-webhook
573+
574+
Raises
575+
------
576+
MeilisearchApiError
577+
An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://www.meilisearch.com/docs/reference/errors/error_codes#meilisearch-errors
578+
"""
579+
response = self.http.delete(f"{self.config.paths.webhooks}/{webhook_uuid}")
580+
return response.status_code
581+
468582
def get_version(self) -> Dict[str, str]:
469583
"""Get version Meilisearch
470584

meilisearch/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class Paths:
4747
localized_attributes = "localized-attributes"
4848
edit = "edit"
4949
network = "network"
50+
webhooks = "webhooks"
5051

5152
def __init__(
5253
self,

meilisearch/models/webhook.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from typing import Any, Dict, List, Optional
2+
3+
from camel_converter.pydantic_base import CamelBase
4+
from pydantic import ConfigDict
5+
6+
7+
class Webhook(CamelBase):
8+
"""Model for a Meilisearch webhook."""
9+
10+
model_config = ConfigDict(arbitrary_types_allowed=True)
11+
12+
uuid: str
13+
url: str
14+
headers: Optional[Dict[str, Any]] = None
15+
isEditable: bool
16+
17+
18+
class WebhooksResults(CamelBase):
19+
"""Model for webhooks list results."""
20+
21+
model_config = ConfigDict(arbitrary_types_allowed=True)
22+
results: List[Webhook]
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""Tests for webhook management endpoints."""
2+
3+
import pytest
4+
5+
from meilisearch.errors import MeilisearchApiError
6+
7+
8+
def test_get_webhooks_empty(client):
9+
"""Test getting webhooks when none exist."""
10+
webhooks = client.get_webhooks()
11+
assert webhooks.results is not None
12+
assert isinstance(webhooks.results, list)
13+
assert len(webhooks.results) == 0
14+
15+
16+
def test_create_webhook(client):
17+
"""Test creating a webhook."""
18+
webhook_data = {
19+
"url": "https://example.com/webhook",
20+
"headers": {
21+
"Authorization": "Bearer secret-token",
22+
"X-Custom-Header": "custom-value",
23+
},
24+
}
25+
26+
webhook = client.create_webhook(webhook_data)
27+
28+
assert webhook.uuid is not None
29+
assert webhook.url == webhook_data["url"]
30+
assert webhook.headers["X-Custom-Header"] == webhook_data["headers"]["X-Custom-Header"]
31+
32+
33+
def test_get_webhook(client):
34+
"""Test getting a single webhook."""
35+
# Create a webhook first
36+
webhook_data = {
37+
"url": "https://example.com/webhook",
38+
}
39+
40+
created_webhook = client.create_webhook(webhook_data)
41+
42+
# Get the webhook
43+
webhook = client.get_webhook(created_webhook.uuid)
44+
45+
assert webhook.uuid == created_webhook.uuid
46+
assert webhook.url == webhook_data["url"]
47+
48+
49+
def test_get_webhook_not_found(client):
50+
"""Test getting a webhook that doesn't exist."""
51+
with pytest.raises(MeilisearchApiError):
52+
client.get_webhook("non-existent-uid")
53+
54+
55+
def test_update_webhook(client):
56+
"""Test updating a webhook."""
57+
# Create a webhook first
58+
webhook_data = {
59+
"url": "https://example.com/webhook",
60+
}
61+
62+
created_webhook = client.create_webhook(webhook_data)
63+
64+
# Update the webhook
65+
update_data = {
66+
"url": "https://example.com/updated-webhook",
67+
}
68+
69+
updated_webhook = client.update_webhook(created_webhook.uuid, update_data)
70+
71+
assert updated_webhook.uuid == created_webhook.uuid
72+
assert updated_webhook.url == update_data["url"]
73+
74+
75+
def test_delete_webhook(client):
76+
"""Test deleting a webhook."""
77+
# Create a webhook first
78+
webhook_data = {
79+
"url": "https://example.com/webhook",
80+
}
81+
82+
created_webhook = client.create_webhook(webhook_data)
83+
84+
# Delete the webhook
85+
status_code = client.delete_webhook(created_webhook.uuid)
86+
87+
assert status_code == 204
88+
89+
# Verify it's deleted
90+
with pytest.raises(MeilisearchApiError):
91+
client.get_webhook(created_webhook.uuid)
92+
93+
94+
def test_delete_webhook_not_found(client):
95+
"""Test deleting a webhook that doesn't exist."""
96+
with pytest.raises(MeilisearchApiError):
97+
client.delete_webhook("non-existent-uid")
98+
99+
100+
def test_create_webhook_missing_required_fields(client):
101+
"""Test creating a webhook without required fields."""
102+
# Missing 'url' field
103+
webhook_data = {
104+
"headers": {
105+
"Authorization": "Bearer secret-token",
106+
},
107+
}
108+
109+
with pytest.raises(MeilisearchApiError):
110+
client.create_webhook(webhook_data)

tests/conftest.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ def clear_indexes(client):
3131
client.wait_for_task(task.task_uid)
3232

3333

34+
@fixture(autouse=True)
35+
def clear_webhooks(client):
36+
"""
37+
Auto-clears the webhooks after each test function run.
38+
Makes all the test functions independent.
39+
"""
40+
# Yields back to the test function.
41+
42+
yield
43+
# Deletes all the webhooks in the Meilisearch instance.
44+
webhooks = client.get_webhooks()
45+
for webhook in webhooks.results:
46+
client.delete_webhook(webhook.uuid)
47+
48+
3449
@fixture(autouse=True)
3550
def clear_all_tasks(client):
3651
"""

0 commit comments

Comments
 (0)