django-webhook-subscriber 0.4.0__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_webhook_subscriber/__init__.py +7 -1
- django_webhook_subscriber/admin.py +831 -182
- django_webhook_subscriber/apps.py +3 -20
- django_webhook_subscriber/conf.py +11 -24
- django_webhook_subscriber/delivery.py +414 -159
- django_webhook_subscriber/http.py +51 -0
- django_webhook_subscriber/management/commands/webhook.py +169 -0
- django_webhook_subscriber/management/commands/webhook_cache.py +173 -0
- django_webhook_subscriber/management/commands/webhook_logs.py +226 -0
- django_webhook_subscriber/management/commands/webhook_performance_test.py +469 -0
- django_webhook_subscriber/management/commands/webhook_send.py +96 -0
- django_webhook_subscriber/management/commands/webhook_status.py +139 -0
- django_webhook_subscriber/managers.py +36 -14
- django_webhook_subscriber/migrations/0002_remove_webhookregistry_content_type_and_more.py +192 -0
- django_webhook_subscriber/models.py +291 -114
- django_webhook_subscriber/serializers.py +16 -50
- django_webhook_subscriber/tasks.py +434 -56
- django_webhook_subscriber/tests/factories.py +40 -0
- django_webhook_subscriber/tests/settings.py +27 -8
- django_webhook_subscriber/tests/test_delivery.py +453 -190
- django_webhook_subscriber/tests/test_http.py +32 -0
- django_webhook_subscriber/tests/test_managers.py +26 -37
- django_webhook_subscriber/tests/test_models.py +341 -251
- django_webhook_subscriber/tests/test_serializers.py +22 -56
- django_webhook_subscriber/tests/test_tasks.py +477 -189
- django_webhook_subscriber/tests/test_utils.py +98 -94
- django_webhook_subscriber/utils.py +87 -69
- django_webhook_subscriber/validators.py +53 -0
- django_webhook_subscriber-2.0.0.dist-info/METADATA +774 -0
- django_webhook_subscriber-2.0.0.dist-info/RECORD +38 -0
- django_webhook_subscriber/management/commands/check_webhook_tasks.py +0 -113
- django_webhook_subscriber/management/commands/clean_webhook_logs.py +0 -65
- django_webhook_subscriber/management/commands/test_webhook.py +0 -96
- django_webhook_subscriber/signals.py +0 -152
- django_webhook_subscriber/testing.py +0 -14
- django_webhook_subscriber/tests/test_signals.py +0 -268
- django_webhook_subscriber-0.4.0.dist-info/METADATA +0 -448
- django_webhook_subscriber-0.4.0.dist-info/RECORD +0 -33
- {django_webhook_subscriber-0.4.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/WHEEL +0 -0
- {django_webhook_subscriber-0.4.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {django_webhook_subscriber-0.4.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-webhook-subscriber
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: A Django package designed to handle webhook creation, management, and delivery.
|
|
5
|
+
Author-email: 42 Portugal <root@42porto.com>
|
|
6
|
+
Classifier: Environment :: Web Environment
|
|
7
|
+
Classifier: Framework :: Django
|
|
8
|
+
Classifier: Framework :: Django :: 5.0
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
20
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: Django<5.3,>=5.0
|
|
25
|
+
Requires-Dist: djangorestframework<3.17,>=3.15.0
|
|
26
|
+
Requires-Dist: requests<2.33,>=2.32.3
|
|
27
|
+
Requires-Dist: celery<5.6,>=5.0.0
|
|
28
|
+
Requires-Dist: redis<6.5,>=6.4
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest<8.4,>=8.3.4; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-django<5.0,>=4.9.0; extra == "test"
|
|
32
|
+
Requires-Dist: pytest-cov<6.1,>=6.0.0; extra == "test"
|
|
33
|
+
Requires-Dist: pytest-factoryboy<2.9,>=2.8; extra == "test"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# Django Webhook Subscriber
|
|
37
|
+
|
|
38
|
+
A Django package for managing webhook subscriptions and deliveries with robust retry logic, performance monitoring, and comprehensive admin interface.
|
|
39
|
+
|
|
40
|
+
## Table of Contents
|
|
41
|
+
|
|
42
|
+
- [Installation](#installation)
|
|
43
|
+
- [Quick Start](#quick-start)
|
|
44
|
+
- [Configuration](#configuration)
|
|
45
|
+
- [Basic Usage](#basic-usage)
|
|
46
|
+
- [Models Reference](#models-reference)
|
|
47
|
+
- [API Reference](#api-reference)
|
|
48
|
+
- [Admin Interface](#admin-interface)
|
|
49
|
+
- [Management Commands](#management-commands)
|
|
50
|
+
- [Performance Testing](#performance-testing)
|
|
51
|
+
- [Production Deployment](#production-deployment)
|
|
52
|
+
- [Troubleshooting](#troubleshooting)
|
|
53
|
+
- [Contributing](#contributing)
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
### Requirements
|
|
58
|
+
|
|
59
|
+
- Python 3.8+
|
|
60
|
+
- Django 3.2+
|
|
61
|
+
- Django REST Framework 3.12+
|
|
62
|
+
- Celery 5.0+ (for async webhook delivery)
|
|
63
|
+
- Redis or another cache backend (recommended)
|
|
64
|
+
|
|
65
|
+
### Install Package
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install django-webhook-subscriber
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Django Settings
|
|
72
|
+
|
|
73
|
+
Add to your `INSTALLED_APPS`:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
INSTALLED_APPS = [
|
|
77
|
+
"django_webhook_subscriber",
|
|
78
|
+
# ... your apps
|
|
79
|
+
]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Database Migration
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
python manage.py migrate django_webhook_subscriber
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Celery Configuration
|
|
89
|
+
|
|
90
|
+
Configure Celery for async webhook delivery:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
# settings.py
|
|
94
|
+
CELERY_BROKER_URL = "redis://localhost:6379/0"
|
|
95
|
+
CELERY_RESULT_BACKEND = "redis://localhost:6379/0"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
# celery.py
|
|
100
|
+
from celery import Celery
|
|
101
|
+
import os
|
|
102
|
+
|
|
103
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
|
|
104
|
+
|
|
105
|
+
app = Celery("myproject")
|
|
106
|
+
app.config_from_object("django.conf:settings", namespace="CELERY")
|
|
107
|
+
app.autodiscover_tasks()
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Quick Start
|
|
111
|
+
|
|
112
|
+
### 1. Create a Webhook Subscriber
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from django.contrib.contenttypes.models import ContentType
|
|
116
|
+
from django_webhook_subscriber.models import WebhookSubscriber, WebhookSubscription
|
|
117
|
+
from django.contrib.auth import get_user_model
|
|
118
|
+
|
|
119
|
+
User = get_user_model()
|
|
120
|
+
|
|
121
|
+
# Create subscriber for User model
|
|
122
|
+
user_content_type = ContentType.objects.get_for_model(User)
|
|
123
|
+
|
|
124
|
+
subscriber = WebhookSubscriber.objects.create(
|
|
125
|
+
name="My External API",
|
|
126
|
+
description="Receives user events",
|
|
127
|
+
content_type=user_content_type,
|
|
128
|
+
target_url="https://api.example.com/webhooks",
|
|
129
|
+
max_retries=3,
|
|
130
|
+
retry_delay=60,
|
|
131
|
+
timeout=30
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Create subscription for 'created' events
|
|
135
|
+
subscription = WebhookSubscription.objects.create(
|
|
136
|
+
subscriber=subscriber,
|
|
137
|
+
event_name="created",
|
|
138
|
+
is_active=True
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 2. Send Webhooks
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from django_webhook_subscriber import send_webhooks
|
|
146
|
+
from django.contrib.auth import get_user_model
|
|
147
|
+
|
|
148
|
+
User = get_user_model()
|
|
149
|
+
|
|
150
|
+
# Create a user and send webhook
|
|
151
|
+
user = User.objects.create(name="John Doe", email="john@example.com")
|
|
152
|
+
|
|
153
|
+
# Send webhook for "created" event
|
|
154
|
+
send_webhooks(user, "created")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 3. Custom Serialization
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from rest_framework import serializers
|
|
161
|
+
from django.contrib.auth import get_user_model
|
|
162
|
+
|
|
163
|
+
User = get_user_model()
|
|
164
|
+
|
|
165
|
+
class UserWebhookSerializer(serializers.ModelSerializer):
|
|
166
|
+
class Meta:
|
|
167
|
+
model = User
|
|
168
|
+
fields = ["id", "name", "email"]
|
|
169
|
+
|
|
170
|
+
# Update subscriber to use custom serializer
|
|
171
|
+
subscriber.serializer_class = "myapp.serializers.UserWebhookSerializer"
|
|
172
|
+
subscriber.save()
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Configuration
|
|
176
|
+
|
|
177
|
+
Configure the package in your Django settings:
|
|
178
|
+
|
|
179
|
+
<!-- TODO: confirm these are all the settings -->
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
WEBHOOK_SUBSCRIBER = {
|
|
183
|
+
# Log retention
|
|
184
|
+
"LOG_RETENTION_DAYS": 30,
|
|
185
|
+
"AUTO_CLEANUP": True,
|
|
186
|
+
|
|
187
|
+
# Performance settings
|
|
188
|
+
"MAX_WEBHOOK_BATCH_SIZE": 50,
|
|
189
|
+
"WEBHOOK_CACHE_TTL": 300,
|
|
190
|
+
|
|
191
|
+
# Defaults for new subscribers
|
|
192
|
+
"DEFAULT_MAX_RETRIES": 3,
|
|
193
|
+
"DEFAULT_RETRY_DELAY": 60,
|
|
194
|
+
"REQUEST_TIMEOUT": 30,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# Recommended cache configuration
|
|
199
|
+
CACHES = {
|
|
200
|
+
'default': {
|
|
201
|
+
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
|
|
202
|
+
'LOCATION': 'redis://127.0.0.1:6379/1',
|
|
203
|
+
'OPTIONS': {
|
|
204
|
+
'CONNECTION_POOL_KWARGS': {'max_connections': 20},
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# Optional: Disable webhooks globally
|
|
211
|
+
DISABLE_WEBHOOKS = False # Set to True to disable all webhook sending
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Basic Usage
|
|
215
|
+
|
|
216
|
+
### Sending Webhooks
|
|
217
|
+
|
|
218
|
+
The main API is the `send_webhooks()` function:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from django_webhook_subscriber import send_webhooks
|
|
222
|
+
|
|
223
|
+
# Basic usage
|
|
224
|
+
send_webhooks(instance, 'event_name')
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Common Integration Patterns
|
|
228
|
+
|
|
229
|
+
#### Django Signals
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
# signals.py
|
|
233
|
+
from django.db.models.signals import post_save
|
|
234
|
+
from django.dispatch import receiver
|
|
235
|
+
from django_webhook_subscriber import send_webhooks
|
|
236
|
+
from django.contrib.auth import get_user_model
|
|
237
|
+
|
|
238
|
+
User = get_user_model()
|
|
239
|
+
|
|
240
|
+
@receiver(post_save, sender=User)
|
|
241
|
+
def user_saved(sender, instance, created, **kwargs):
|
|
242
|
+
event_name = "created" if created else "updated"
|
|
243
|
+
send_webhooks(instance, event_name)
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### Django Lifecycle
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
# models.py
|
|
251
|
+
from django_lifecycle import LifecycleModel, hook, AFTER_CREATE, AFTER_UPDATE
|
|
252
|
+
from django_webhook_subscriber import send_webhooks
|
|
253
|
+
|
|
254
|
+
class User(LifecycleModel):
|
|
255
|
+
name = models.CharField(max_length=100)
|
|
256
|
+
email = models.EmailField()
|
|
257
|
+
|
|
258
|
+
@hook(AFTER_CREATE)
|
|
259
|
+
def on_create(self):
|
|
260
|
+
send_webhooks(self, 'created')
|
|
261
|
+
|
|
262
|
+
@hook(AFTER_UPDATE, when='email', has_changed=True)
|
|
263
|
+
def on_email_update(self):
|
|
264
|
+
send_webhooks(self, 'email_updated')
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
#### Manual Integration
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
# myapp/views.py
|
|
271
|
+
from django_webhook_subscriber import send_webhooks
|
|
272
|
+
|
|
273
|
+
class UserViewSet(viewsets.ModelViewSet):
|
|
274
|
+
def perform_create(self, serializer):
|
|
275
|
+
user = serializer.save()
|
|
276
|
+
send_webhooks(user, "created")
|
|
277
|
+
|
|
278
|
+
def perform_update(self, serializer):
|
|
279
|
+
user = serializer.save()
|
|
280
|
+
send_webhooks(user, "updated")
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Disabling Webhooks Temporarily
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
from django_webhook_subscriber.utils import disable_webhooks
|
|
287
|
+
|
|
288
|
+
# Disable webhooks for bulk operations
|
|
289
|
+
with disable_webhooks():
|
|
290
|
+
User.objects.bulk_create([...]) # No webhooks sent
|
|
291
|
+
|
|
292
|
+
# Webhooks resume normal operation after the context
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Models Reference
|
|
296
|
+
|
|
297
|
+
### WebhookSubscriber
|
|
298
|
+
|
|
299
|
+
Represents an external service that wants to receive webhooks.
|
|
300
|
+
|
|
301
|
+
**Key Fields:**
|
|
302
|
+
|
|
303
|
+
- `name`: Human-readable name for the subscriber
|
|
304
|
+
- `target_url`: Base URL for webhook delivery
|
|
305
|
+
- `content_type`: Django model this subscriber watches
|
|
306
|
+
- `serializer_class`: Optional custom DRF serializer
|
|
307
|
+
- `max_retries`: Maximum delivery attempts
|
|
308
|
+
- `retry_delay`: Seconds between retries
|
|
309
|
+
- `timeout`: Request timeout
|
|
310
|
+
- `auto_disable_after_failures`: Auto-disable threshold
|
|
311
|
+
|
|
312
|
+
**Methods:**
|
|
313
|
+
|
|
314
|
+
- `record_success()`: Mark successful delivery
|
|
315
|
+
- `record_failure()`: Mark failed delivery, handle auto-disable
|
|
316
|
+
|
|
317
|
+
### WebhookSubscription
|
|
318
|
+
|
|
319
|
+
Individual event subscription for a subscriber.
|
|
320
|
+
|
|
321
|
+
**Key Fields:**
|
|
322
|
+
|
|
323
|
+
- `subscriber`: Foreign key to WebhookSubscriber
|
|
324
|
+
- `event_name`: Event to subscribe to (e.g., 'created', 'updated')
|
|
325
|
+
- `custom_endpoint`: Optional endpoint override
|
|
326
|
+
- `success_rate`: Calculated property showing delivery success percentage
|
|
327
|
+
|
|
328
|
+
**Properties:**
|
|
329
|
+
|
|
330
|
+
- `endpoint`: Full URL for this subscription
|
|
331
|
+
- `success_rate`: Percentage of successful deliveries
|
|
332
|
+
|
|
333
|
+
**Methods:**
|
|
334
|
+
|
|
335
|
+
- `record_delivery_attempt(success, response_text)`: Track delivery stats
|
|
336
|
+
|
|
337
|
+
### WebhookDeliveryLog
|
|
338
|
+
|
|
339
|
+
Log of individual webhook delivery attempts.
|
|
340
|
+
|
|
341
|
+
**Key Fields:**
|
|
342
|
+
|
|
343
|
+
- `subscription`: Foreign key to WebhookSubscription
|
|
344
|
+
- `attempt_number`: Retry attempt number
|
|
345
|
+
- `response_status`: HTTP response status
|
|
346
|
+
- `response_body`: HTTP response body (truncated)
|
|
347
|
+
- `delivery_duration_ms`: Delivery time in milliseconds
|
|
348
|
+
- `error_message`: Exception details if delivery failed
|
|
349
|
+
|
|
350
|
+
**Properties:**
|
|
351
|
+
|
|
352
|
+
- `is_success`: True if delivery was successful (2xx status)
|
|
353
|
+
- `is_client_error`: True for 4xx status codes
|
|
354
|
+
- `is_server_error`: True for 5xx status codes
|
|
355
|
+
|
|
356
|
+
## API Reference
|
|
357
|
+
|
|
358
|
+
### Core Functions
|
|
359
|
+
|
|
360
|
+
#### `send_webhooks(instance, event_name, context=None, **kwargs)`
|
|
361
|
+
|
|
362
|
+
Send webhooks for a model instance and event.
|
|
363
|
+
|
|
364
|
+
**Parameters:**
|
|
365
|
+
|
|
366
|
+
- `instance`: Django model instance
|
|
367
|
+
- `event_name`: String event name
|
|
368
|
+
- `context`: Optional dict with additional data
|
|
369
|
+
- `**kwargs`: Additional arguments
|
|
370
|
+
|
|
371
|
+
**Returns:**
|
|
372
|
+
|
|
373
|
+
- Dict with processing summary
|
|
374
|
+
|
|
375
|
+
**Example:**
|
|
376
|
+
|
|
377
|
+
```python
|
|
378
|
+
result = send_webhooks(user, 'created')
|
|
379
|
+
# Returns: {'processed': 3, 'batches': 1, 'task_ids': [...]}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
#### `clear_webhook_cache(content_type=None, event_name=None)`
|
|
383
|
+
|
|
384
|
+
Clear cached webhook subscription data.
|
|
385
|
+
|
|
386
|
+
**Parameters:**
|
|
387
|
+
|
|
388
|
+
- `content_type`: Optional ContentType to limit clearing
|
|
389
|
+
- `event_name`: Optional event name to limit clearing
|
|
390
|
+
|
|
391
|
+
#### `get_webhook_cache_stats()`
|
|
392
|
+
|
|
393
|
+
Get detailed cache statistics.
|
|
394
|
+
|
|
395
|
+
**Returns:**
|
|
396
|
+
|
|
397
|
+
- Dict with cache hit ratios, key counts, etc.
|
|
398
|
+
|
|
399
|
+
### Utility Functions
|
|
400
|
+
|
|
401
|
+
#### `webhooks_disabled()`
|
|
402
|
+
|
|
403
|
+
Check if webhooks are currently disabled.
|
|
404
|
+
|
|
405
|
+
#### `disable_webhooks()` (context manager)
|
|
406
|
+
|
|
407
|
+
Temporarily disable webhook sending.
|
|
408
|
+
|
|
409
|
+
#### `generate_secret()`
|
|
410
|
+
|
|
411
|
+
Generate a new webhook secret key.
|
|
412
|
+
|
|
413
|
+
## Admin Interface
|
|
414
|
+
|
|
415
|
+
The package provides a comprehensive Django admin interface:
|
|
416
|
+
|
|
417
|
+
### WebhookSubscriber Admin
|
|
418
|
+
|
|
419
|
+
- **Health indicators**: Visual status showing healthy/warning/critical
|
|
420
|
+
- **Performance tracking**: Success rates and failure counts
|
|
421
|
+
- **Connectivity testing**: Test endpoints via admin actions
|
|
422
|
+
- **Cache management**: Clear cache for specific subscribers
|
|
423
|
+
|
|
424
|
+
**Available Actions:**
|
|
425
|
+
|
|
426
|
+
- Activate/deactivate subscribers
|
|
427
|
+
- Test endpoint connectivity
|
|
428
|
+
- Reset failure counters
|
|
429
|
+
- Clear cache
|
|
430
|
+
|
|
431
|
+
### WebhookSubscription Admin
|
|
432
|
+
|
|
433
|
+
- **Performance metrics**: Success rates with color coding
|
|
434
|
+
- **Recent deliveries**: Visual icons showing last 5 attempts
|
|
435
|
+
- **Statistics management**: Reset performance counters
|
|
436
|
+
|
|
437
|
+
### WebhookDeliveryLog Admin
|
|
438
|
+
|
|
439
|
+
- **Detailed logging**: Formatted JSON payloads and responses
|
|
440
|
+
- **Performance analysis**: Delivery times and retry information
|
|
441
|
+
- **Error categorization**: Grouped error analysis
|
|
442
|
+
|
|
443
|
+
## Management Commands
|
|
444
|
+
|
|
445
|
+
### Cache Management
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
# Show cache statistics
|
|
449
|
+
python manage.py webhook_cache stats
|
|
450
|
+
|
|
451
|
+
# Clear all cache
|
|
452
|
+
python manage.py webhook_cache clear
|
|
453
|
+
|
|
454
|
+
# Clear specific content type
|
|
455
|
+
python manage.py webhook_cache clear --content-type=myapp.User
|
|
456
|
+
|
|
457
|
+
# Pre-warm cache
|
|
458
|
+
python manage.py webhook_cache warm
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Testing Endpoints
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
# Test all active subscribers
|
|
465
|
+
python manage.py webhook_test --all
|
|
466
|
+
|
|
467
|
+
# Test with custom payload
|
|
468
|
+
python manage.py webhook_test --subscriber-id=1 --method=POST --payload='{"test": true}'
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Log Management
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
# Clean up old logs
|
|
475
|
+
python manage.py webhook_logs cleanup --days=30
|
|
476
|
+
|
|
477
|
+
# Show statistics
|
|
478
|
+
python manage.py webhook_logs stats --days=7
|
|
479
|
+
|
|
480
|
+
# Show recent errors
|
|
481
|
+
python manage.py webhook_logs errors --limit=20
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### System Status
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
# Overview
|
|
488
|
+
python manage.py webhook_status
|
|
489
|
+
|
|
490
|
+
# Detailed per-subscriber status
|
|
491
|
+
python manage.py webhook_status --detailed
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Manual Webhook Sending
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
# Send test webhook
|
|
498
|
+
python manage.py webhook_send myapp.User 123 created
|
|
499
|
+
|
|
500
|
+
# With custom context
|
|
501
|
+
python manage.py webhook_send myapp.Order 456 paid --context='{"payment_method": "stripe"}'
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## Performance Testing
|
|
505
|
+
|
|
506
|
+
Test your webhook system under load:
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
# Basic performance test
|
|
510
|
+
python manage.py webhook_performance_test \
|
|
511
|
+
--model=myapp.User \
|
|
512
|
+
--event=created \
|
|
513
|
+
--object-count=20 \
|
|
514
|
+
--concurrent-webhooks=10
|
|
515
|
+
|
|
516
|
+
# High-load test with delivery measurement
|
|
517
|
+
python manage.py webhook_performance_test \
|
|
518
|
+
--model=myapp.Order \
|
|
519
|
+
--event=paid \
|
|
520
|
+
--object-count=50 \
|
|
521
|
+
--concurrent-webhooks=20 \
|
|
522
|
+
--measure-delivery \
|
|
523
|
+
--timeout=60 \
|
|
524
|
+
--warmup
|
|
525
|
+
|
|
526
|
+
# Sustained load test
|
|
527
|
+
python manage.py webhook_performance_test \
|
|
528
|
+
--model=myapp.Product \
|
|
529
|
+
--event=updated \
|
|
530
|
+
--object-count=15 \
|
|
531
|
+
--batches=5 \
|
|
532
|
+
--batch-delay=2.0
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
**Performance Metrics Provided:**
|
|
536
|
+
|
|
537
|
+
- Send throughput (webhooks/second)
|
|
538
|
+
- Delivery timing statistics
|
|
539
|
+
- Success/failure rates
|
|
540
|
+
- Error categorization
|
|
541
|
+
- Concurrency analysis
|
|
542
|
+
|
|
543
|
+
## Production Deployment
|
|
544
|
+
|
|
545
|
+
### Celery Setup
|
|
546
|
+
|
|
547
|
+
Ensure Celery is properly configured for production:
|
|
548
|
+
|
|
549
|
+
```python
|
|
550
|
+
# celery.py
|
|
551
|
+
from celery import Celery
|
|
552
|
+
|
|
553
|
+
app = Celery('myproject')
|
|
554
|
+
app.config_from_object('django.conf:settings', namespace='CELERY')
|
|
555
|
+
|
|
556
|
+
# Configure for production
|
|
557
|
+
app.conf.update(
|
|
558
|
+
task_serializer='json',
|
|
559
|
+
accept_content=['json'],
|
|
560
|
+
result_serializer='json',
|
|
561
|
+
timezone='UTC',
|
|
562
|
+
enable_utc=True,
|
|
563
|
+
task_track_started=True,
|
|
564
|
+
worker_hijack_root_logger=False,
|
|
565
|
+
worker_log_format='[%(asctime)s: %(levelname)s/%(processName)s] %(message)s',
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
app.autodiscover_tasks()
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Monitoring Setup
|
|
572
|
+
|
|
573
|
+
Configure logging for webhook monitoring:
|
|
574
|
+
|
|
575
|
+
```python
|
|
576
|
+
# settings.py
|
|
577
|
+
LOGGING = {
|
|
578
|
+
'version': 1,
|
|
579
|
+
'disable_existing_loggers': False,
|
|
580
|
+
'handlers': {
|
|
581
|
+
'webhook_file': {
|
|
582
|
+
'level': 'INFO',
|
|
583
|
+
'class': 'logging.FileHandler',
|
|
584
|
+
'filename': '/var/log/django/webhooks.log',
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
'loggers': {
|
|
588
|
+
'django_webhook_subscriber': {
|
|
589
|
+
'handlers': ['webhook_file'],
|
|
590
|
+
'level': 'INFO',
|
|
591
|
+
'propagate': True,
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### Automated Maintenance
|
|
598
|
+
|
|
599
|
+
Set up cron jobs for maintenance:
|
|
600
|
+
|
|
601
|
+
```bash
|
|
602
|
+
# Daily log cleanup at 2 AM
|
|
603
|
+
0 2 * * * cd /path/to/project && python manage.py webhook_logs cleanup
|
|
604
|
+
|
|
605
|
+
# Weekly health check report
|
|
606
|
+
0 9 * * 1 cd /path/to/project && python manage.py webhook_status --detailed
|
|
607
|
+
|
|
608
|
+
# Cache warming after deployments
|
|
609
|
+
@reboot cd /path/to/project && python manage.py webhook_cache warm
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### Performance Optimization
|
|
613
|
+
|
|
614
|
+
1. **Use Redis for caching:**
|
|
615
|
+
|
|
616
|
+
```python
|
|
617
|
+
CACHES = {
|
|
618
|
+
'default': {
|
|
619
|
+
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
|
|
620
|
+
'LOCATION': 'redis://127.0.0.1:6379/1',
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
2. **Configure appropriate batch sizes:**
|
|
626
|
+
|
|
627
|
+
```python
|
|
628
|
+
WEBHOOK_SUBSCRIBER = {
|
|
629
|
+
'MAX_WEBHOOK_BATCH_SIZE': 25, # Adjust based on your load
|
|
630
|
+
'WEBHOOK_CACHE_TTL': 600, # 10 minutes
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
3. **Monitor delivery performance:**
|
|
635
|
+
```bash
|
|
636
|
+
# Regular performance testing
|
|
637
|
+
python manage.py webhook_performance_test --model=myapp.User --event=test --object-count=10
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
## Troubleshooting
|
|
641
|
+
|
|
642
|
+
### Common Issues
|
|
643
|
+
|
|
644
|
+
#### 1. Webhooks Not Sending
|
|
645
|
+
|
|
646
|
+
**Check:**
|
|
647
|
+
|
|
648
|
+
- Webhooks globally enabled: `DISABLE_WEBHOOKS = False`
|
|
649
|
+
- Active subscriptions exist for the model/event
|
|
650
|
+
- Celery workers are running
|
|
651
|
+
|
|
652
|
+
**Debug:**
|
|
653
|
+
|
|
654
|
+
```bash
|
|
655
|
+
python manage.py webhook_status --detailed
|
|
656
|
+
python manage.py webhook_cache stats
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
#### 2. High Delivery Failures
|
|
660
|
+
|
|
661
|
+
**Check:**
|
|
662
|
+
|
|
663
|
+
- Endpoint accessibility
|
|
664
|
+
- Network connectivity
|
|
665
|
+
- Authentication headers
|
|
666
|
+
|
|
667
|
+
**Debug:**
|
|
668
|
+
|
|
669
|
+
```bash
|
|
670
|
+
python manage.py webhook_test --all
|
|
671
|
+
python manage.py webhook_logs errors --limit=50
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
#### 3. Performance Issues
|
|
675
|
+
|
|
676
|
+
**Check:**
|
|
677
|
+
|
|
678
|
+
- Cache hit ratios
|
|
679
|
+
- Batch sizes
|
|
680
|
+
- Celery worker count
|
|
681
|
+
|
|
682
|
+
**Debug:**
|
|
683
|
+
|
|
684
|
+
```bash
|
|
685
|
+
python manage.py webhook_cache stats
|
|
686
|
+
python manage.py webhook_performance_test --model=myapp.User --event=test --object-count=5
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
#### 4. Memory Usage
|
|
690
|
+
|
|
691
|
+
**Solutions:**
|
|
692
|
+
|
|
693
|
+
- Reduce `LOG_RETENTION_DAYS`
|
|
694
|
+
- Increase cleanup frequency
|
|
695
|
+
- Limit response body storage
|
|
696
|
+
|
|
697
|
+
```bash
|
|
698
|
+
python manage.py webhook_logs cleanup --days=7
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### Error Reference
|
|
702
|
+
|
|
703
|
+
#### Common Error Messages
|
|
704
|
+
|
|
705
|
+
- **"No subscriptions found"**: No active subscriptions for model/event
|
|
706
|
+
- **"Webhook disabled"**: Global or context-specific disabling active
|
|
707
|
+
- **"Subscription not found"**: Subscription deleted during processing
|
|
708
|
+
- **"Connection error"**: Network connectivity issues
|
|
709
|
+
- **"Timeout"**: Endpoint response too slow
|
|
710
|
+
|
|
711
|
+
#### Performance Warnings
|
|
712
|
+
|
|
713
|
+
- **Cache miss ratio > 50%**: Consider warming cache more frequently
|
|
714
|
+
- **Delivery time > 5s**: Endpoint performance issues
|
|
715
|
+
- **Success rate < 95%**: Check endpoint reliability
|
|
716
|
+
|
|
717
|
+
### Debug Mode
|
|
718
|
+
|
|
719
|
+
Enable detailed logging for debugging:
|
|
720
|
+
|
|
721
|
+
```python
|
|
722
|
+
# settings.py
|
|
723
|
+
LOGGING = {
|
|
724
|
+
'loggers': {
|
|
725
|
+
'django_webhook_subscriber': {
|
|
726
|
+
'level': 'DEBUG',
|
|
727
|
+
'handlers': ['console'],
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
## Contributing
|
|
734
|
+
|
|
735
|
+
### Development Setup
|
|
736
|
+
|
|
737
|
+
1. Clone the repository
|
|
738
|
+
2. Install development dependencies: `pip install -e .[dev]`
|
|
739
|
+
3. Run tests: `python -m pytest`
|
|
740
|
+
4. Run linting: `flake8 django_webhook_subscriber/`
|
|
741
|
+
|
|
742
|
+
### Running Tests
|
|
743
|
+
|
|
744
|
+
```bash
|
|
745
|
+
# Run all tests
|
|
746
|
+
python -m pytest
|
|
747
|
+
|
|
748
|
+
# Run specific test file
|
|
749
|
+
python -m pytest tests/test_delivery.py
|
|
750
|
+
|
|
751
|
+
# Run with coverage
|
|
752
|
+
python -m pytest --cov=django_webhook_subscriber
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### Contributing Guidelines
|
|
756
|
+
|
|
757
|
+
1. Fork the repository
|
|
758
|
+
2. Create a feature branch
|
|
759
|
+
3. Add tests for new functionality
|
|
760
|
+
4. Ensure all tests pass
|
|
761
|
+
5. Submit a pull request
|
|
762
|
+
|
|
763
|
+
### Reporting Issues
|
|
764
|
+
|
|
765
|
+
Please include:
|
|
766
|
+
|
|
767
|
+
- Django version
|
|
768
|
+
- Package version
|
|
769
|
+
- Full error traceback
|
|
770
|
+
- Minimal reproduction case
|
|
771
|
+
|
|
772
|
+
## License
|
|
773
|
+
|
|
774
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|