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.
Files changed (41) hide show
  1. django_webhook_subscriber/__init__.py +7 -1
  2. django_webhook_subscriber/admin.py +831 -182
  3. django_webhook_subscriber/apps.py +3 -20
  4. django_webhook_subscriber/conf.py +11 -24
  5. django_webhook_subscriber/delivery.py +414 -159
  6. django_webhook_subscriber/http.py +51 -0
  7. django_webhook_subscriber/management/commands/webhook.py +169 -0
  8. django_webhook_subscriber/management/commands/webhook_cache.py +173 -0
  9. django_webhook_subscriber/management/commands/webhook_logs.py +226 -0
  10. django_webhook_subscriber/management/commands/webhook_performance_test.py +469 -0
  11. django_webhook_subscriber/management/commands/webhook_send.py +96 -0
  12. django_webhook_subscriber/management/commands/webhook_status.py +139 -0
  13. django_webhook_subscriber/managers.py +36 -14
  14. django_webhook_subscriber/migrations/0002_remove_webhookregistry_content_type_and_more.py +192 -0
  15. django_webhook_subscriber/models.py +291 -114
  16. django_webhook_subscriber/serializers.py +16 -50
  17. django_webhook_subscriber/tasks.py +434 -56
  18. django_webhook_subscriber/tests/factories.py +40 -0
  19. django_webhook_subscriber/tests/settings.py +27 -8
  20. django_webhook_subscriber/tests/test_delivery.py +453 -190
  21. django_webhook_subscriber/tests/test_http.py +32 -0
  22. django_webhook_subscriber/tests/test_managers.py +26 -37
  23. django_webhook_subscriber/tests/test_models.py +341 -251
  24. django_webhook_subscriber/tests/test_serializers.py +22 -56
  25. django_webhook_subscriber/tests/test_tasks.py +477 -189
  26. django_webhook_subscriber/tests/test_utils.py +98 -94
  27. django_webhook_subscriber/utils.py +87 -69
  28. django_webhook_subscriber/validators.py +53 -0
  29. django_webhook_subscriber-2.0.0.dist-info/METADATA +774 -0
  30. django_webhook_subscriber-2.0.0.dist-info/RECORD +38 -0
  31. django_webhook_subscriber/management/commands/check_webhook_tasks.py +0 -113
  32. django_webhook_subscriber/management/commands/clean_webhook_logs.py +0 -65
  33. django_webhook_subscriber/management/commands/test_webhook.py +0 -96
  34. django_webhook_subscriber/signals.py +0 -152
  35. django_webhook_subscriber/testing.py +0 -14
  36. django_webhook_subscriber/tests/test_signals.py +0 -268
  37. django_webhook_subscriber-0.4.0.dist-info/METADATA +0 -448
  38. django_webhook_subscriber-0.4.0.dist-info/RECORD +0 -33
  39. {django_webhook_subscriber-0.4.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/WHEEL +0 -0
  40. {django_webhook_subscriber-0.4.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/licenses/LICENSE +0 -0
  41. {django_webhook_subscriber-0.4.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,38 @@
1
+ django_webhook_subscriber/__init__.py,sha256=t0izlsRXKHd46fRIwhDv8Cd59tb7MqAQ7DWJRLLWgtw,213
2
+ django_webhook_subscriber/admin.py,sha256=BGFV-1-wymm_qUsOlG0HBUY3rmAqKH59PbKaFEtAEI4,30100
3
+ django_webhook_subscriber/apps.py,sha256=U6GLuMB2xhtvUzn2IsK6rjaxN73LU3fvDoGGO1IZIr4,170
4
+ django_webhook_subscriber/conf.py,sha256=0lFW2L_s_85UIszXnUnnMCdb0iy99WnenPWeixonf-I,1759
5
+ django_webhook_subscriber/delivery.py,sha256=mjNtIMeVsXoVnWfEBmW8WsVDDPYfKcz8HIMkJ40PHjQ,15207
6
+ django_webhook_subscriber/http.py,sha256=0CoK1OMm7lCIJ3zzpNH3cdVuIgDnAB4KUqLRM_KRGQs,1191
7
+ django_webhook_subscriber/managers.py,sha256=w2QZCYs858XMlcI-WSZJ0H8vN_IzpTvu5_H3bmiy7zM,2322
8
+ django_webhook_subscriber/models.py,sha256=IdG0eBE3JPUmPLWqpR62GLtA4xVF8caIU92ourgRzVk,11528
9
+ django_webhook_subscriber/serializers.py,sha256=9QHzlTXvk-PibMSoztaGsDoZkVhCXJkuQMDDTPCKjak,1416
10
+ django_webhook_subscriber/tasks.py,sha256=jlE8Mdor387gcWfHlrMe8OGoegjt2xn8gsVVL8LkHDY,14269
11
+ django_webhook_subscriber/utils.py,sha256=SdogR0VmeMMl7tgl-DD5_4yP7vt3OcgI8IpghoCIVXQ,2950
12
+ django_webhook_subscriber/validators.py,sha256=_qlBi0rS3DaW3jKRhdpnfHzuqupqwaWtiNuZAWpIAkU,1848
13
+ django_webhook_subscriber/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ django_webhook_subscriber/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ django_webhook_subscriber/management/commands/webhook.py,sha256=p1vHy3-Ux3cfcGRkkFpmkHIfdE17kyWrJatC5mEEDKI,5378
16
+ django_webhook_subscriber/management/commands/webhook_cache.py,sha256=g5bTRWzcJk6xUWCe9mYF8oQHcm1FXsTStY4RZLaFprc,5662
17
+ django_webhook_subscriber/management/commands/webhook_logs.py,sha256=ZBw5JmfEYsV2X6bODq4y3ZeN41-k12tcM73c-sEbLNk,7249
18
+ django_webhook_subscriber/management/commands/webhook_performance_test.py,sha256=8_yb1f57bZHBfmq-Ypj7-h4HYmWTXCTsiawtVZuz-Qw,16096
19
+ django_webhook_subscriber/management/commands/webhook_send.py,sha256=M1G_VnXwjkTw4UKoCbWVExRS18bbIYTbQY7g5Cngud0,3073
20
+ django_webhook_subscriber/management/commands/webhook_status.py,sha256=_s5nTA2NAxvihAXpEP5NguEcCC_uS6RjKhSYxx02ogU,4701
21
+ django_webhook_subscriber/migrations/0001_initial.py,sha256=HZ4CF8U0aoQNMIswHv039HR32eLIEB0rttvcjrAma-Y,4160
22
+ django_webhook_subscriber/migrations/0002_remove_webhookregistry_content_type_and_more.py,sha256=ut1diKCSNAIag_KxZLvUkMLjHEVoD3LW3OOOB87kwP4,9602
23
+ django_webhook_subscriber/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ django_webhook_subscriber/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ django_webhook_subscriber/tests/factories.py,sha256=z3fXJqt_3csAFrvM5qdKwzsVu0v_e72racLvMgejEKA,1277
26
+ django_webhook_subscriber/tests/settings.py,sha256=jBy9X7QhmoQ9hnaJk4WDGWXqpRydpHlrROaXU5t7KZI,825
27
+ django_webhook_subscriber/tests/test_delivery.py,sha256=W7JrNurpTb0TA0JDyumd6UMytaQYPJ6DohS0pDNjyJw,18380
28
+ django_webhook_subscriber/tests/test_http.py,sha256=-zBNRjpYxBDCC5NvWfdYc36aFdzCwN-1p8yuQsLGfz8,1262
29
+ django_webhook_subscriber/tests/test_managers.py,sha256=z_c49z0UjkAnldXy-KU_XoECC66dr7orE77_xFvnM4A,3062
30
+ django_webhook_subscriber/tests/test_models.py,sha256=RHE14IuaMtthiaRZ8g4h2EZ4VilWOrs9UrkArDbvcDg,15209
31
+ django_webhook_subscriber/tests/test_serializers.py,sha256=UuN-cDDmsu8ZFB-aPesffT74Ck6uIuDo9ehkq8y43Mw,1831
32
+ django_webhook_subscriber/tests/test_tasks.py,sha256=wcYlYqrRC9G3C4leEAKrt7x7D1uhlqx9mfwkfQYnb8c,20622
33
+ django_webhook_subscriber/tests/test_utils.py,sha256=zFXDjr81wB8emeJKNjlwA8ezfJTfCMSw46-S2dB_fO0,4279
34
+ django_webhook_subscriber-2.0.0.dist-info/licenses/LICENSE,sha256=Cqe_H97kNjzh70ZIee47TV5t0GQyath_iH2i0Yp47SU,1064
35
+ django_webhook_subscriber-2.0.0.dist-info/METADATA,sha256=tzvYtcH4spdVL7BVftgDOBm_udSa3A3R--NFNcgdxlE,17789
36
+ django_webhook_subscriber-2.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ django_webhook_subscriber-2.0.0.dist-info/top_level.txt,sha256=t2_XjlYcTMLR9OfBGdsW0xucs3OeRWrJ7ypETMXqrvw,26
38
+ django_webhook_subscriber-2.0.0.dist-info/RECORD,,
@@ -1,113 +0,0 @@
1
- """Check the status of recent webhook deliveries."""
2
-
3
- from django.core.management.base import BaseCommand
4
- from django.utils import timezone
5
- from django.db.models import Q
6
-
7
-
8
- class Command(BaseCommand):
9
- """Check the status of recent webhook deliveries.
10
-
11
- This command checks the status of recent webhook deliveries and
12
- provides a summary of successful and failed deliveries. It can be
13
- filtered to show only failed deliveries or all deliveries within a
14
- specified time period.
15
-
16
- Usage:
17
- python manage.py check_webhook_tasks --hours 24
18
- python manage.py check_webhook_tasks --failed-only
19
- python manage.py check_webhook_tasks --hours 48 --failed-only
20
- """
21
-
22
- help = 'Check status of recent webhook deliveries'
23
-
24
- def add_arguments(self, parser):
25
- parser.add_argument(
26
- '--hours',
27
- type=int,
28
- default=24,
29
- help='Hours to look back for delivery logs',
30
- )
31
- parser.add_argument(
32
- '--failed-only',
33
- action='store_true',
34
- help='Show only failed deliveries',
35
- )
36
-
37
- def handle(self, *args, **options):
38
- hours = options['hours']
39
- failed_only = options['failed_only']
40
-
41
- cutoff = timezone.now() - timezone.timedelta(hours=hours)
42
-
43
- from django_webhook_subscriber.models import WebhookRegistry
44
-
45
- # Get webhooks with delivery status
46
- webhooks = WebhookRegistry.objects.all()
47
-
48
- for webhook in webhooks:
49
- # Get recent delivery logs
50
- logs = webhook.delivery_logs.filter(created_at__gte=cutoff)
51
-
52
- if failed_only:
53
- logs = logs.filter(
54
- Q(error_message__isnull=False)
55
- | Q(response_status__lt=200)
56
- | Q(response_status__gte=300)
57
- )
58
-
59
- if not logs.exists():
60
- if not failed_only:
61
- self.stdout.write(f'Webhook: {webhook.name}')
62
- self.stdout.write(
63
- ' No deliveries in the specified time period.'
64
- )
65
- continue
66
- self.stdout.write(f'Webhook: {webhook.name} ({webhook.endpoint})')
67
- self.stdout.write(f' Total deliveries: {logs.count()}')
68
-
69
- # Count successes and failures
70
- successes = logs.filter(
71
- error_message__isnull=True,
72
- response_status__gte=200,
73
- response_status__lt=300,
74
- ).count()
75
-
76
- failures = logs.count() - successes
77
-
78
- if successes > 0:
79
- self.stdout.write(
80
- self.style.SUCCESS(f' Successful: {successes}')
81
- )
82
-
83
- if failures > 0:
84
- self.stdout.write(self.style.ERROR(f' Failed: {failures}'))
85
-
86
- # Show most recent failure
87
- if failures > 0:
88
- most_recent = (
89
- logs.filter(
90
- Q(error_message__isnull=False)
91
- | Q(response_status__lt=200)
92
- | Q(response_status__gte=300)
93
- )
94
- .order_by('-created_at')
95
- .first()
96
- )
97
-
98
- self.stdout.write(
99
- f' Most recent failure at: {most_recent.created_at}'
100
- )
101
- if most_recent.error_message:
102
- self.stdout.write(
103
- f' Error: {most_recent.error_message}'
104
- )
105
- else:
106
- self.stdout.write(
107
- f' Status: {most_recent.response_status}'
108
- )
109
- self.stdout.write(
110
- f' Response: {most_recent.response_body[:100]}...'
111
- )
112
-
113
- self.stdout.write('')
@@ -1,65 +0,0 @@
1
- """Clean up old webhook delivery logs."""
2
-
3
- from django.core.management.base import BaseCommand
4
- from django.utils import timezone
5
- from django.conf import settings
6
- from datetime import timedelta
7
-
8
-
9
- class Command(BaseCommand):
10
- """Clean up old webhook delivery logs.
11
-
12
- This command deletes webhook delivery logs older than a specified
13
- number of days. If no number of days is specified, it defaults to
14
- the value set in the WEBHOOK_SUBSCRIBER_LOG_RETENTION_DAYS setting.
15
-
16
- Usage:
17
- python manage.py clean_webhook_logs --days 30
18
- python manage.py clean_webhook_logs --dry-run
19
- """
20
-
21
- help = 'Clean up old webhook delivery logs'
22
-
23
- def add_arguments(self, parser):
24
- parser.add_argument(
25
- '--days',
26
- type=int,
27
- default=None,
28
- help='Delete logs older than this many days',
29
- )
30
- parser.add_argument(
31
- '--dry-run',
32
- action='store_true',
33
- help='Only print what would be deleted without actually deleting',
34
- )
35
-
36
- def handle(self, *args, **options):
37
- # Get retention period from command line or settings
38
- days = options['days']
39
- if days is None:
40
- days = getattr(
41
- settings, 'WEBHOOK_SUBSCRIBER_LOG_RETENTION_DAYS', 30
42
- )
43
-
44
- # Calculate cutoff date
45
- cutoff_date = timezone.now() - timedelta(days=days)
46
-
47
- # Get logs to delete
48
- from django_webhook_subscriber.models import WebhookDeliveryLog
49
-
50
- logs_to_delete = WebhookDeliveryLog.objects.filter(
51
- created_at__lt=cutoff_date
52
- )
53
- count = logs_to_delete.count()
54
-
55
- if options['dry_run']:
56
- self.stdout.write(
57
- f'Would delete {count} logs older than {cutoff_date}'
58
- )
59
- else:
60
- logs_to_delete.delete()
61
- self.stdout.write(
62
- self.style.SUCCESS(
63
- f'Deleted {count} logs older than {cutoff_date}'
64
- )
65
- )
@@ -1,96 +0,0 @@
1
- """Test a webhook by sending a test payload to the specified endpoint."""
2
-
3
- import json
4
-
5
- from django.core.management.base import BaseCommand, CommandError
6
-
7
- from django_webhook_subscriber.delivery import deliver_webhook
8
-
9
-
10
- class Command(BaseCommand):
11
- """Test a webhook by sending a test payload to the specified endpoint.
12
-
13
- This command sends a test payload to the specified webhook endpoint
14
- and displays the response status and body. It can be used to verify
15
- that the webhook is correctly configured and that the endpoint is
16
- reachable.
17
-
18
- Usage:
19
- python manage.py test_webhook <webhook_id> --event <event_signal>
20
- python manage.py test_webhook <webhook_id> --payload <custom_payload>
21
- """
22
-
23
- help = 'Test a webhook by sending a test payload'
24
-
25
- def add_arguments(self, parser):
26
- parser.add_argument(
27
- 'webhook_id', type=int, help='The ID of the webhook to test'
28
- )
29
- parser.add_argument(
30
- '--event',
31
- type=str,
32
- default='test',
33
- help='Event type to use (test, created, updated, deleted)',
34
- )
35
- parser.add_argument(
36
- '--payload',
37
- type=str,
38
- help='Custom payload to send (as JSON string)',
39
- )
40
-
41
- def handle(self, *args, **options):
42
- webhook_id = options['webhook_id']
43
- event_signal = options['event']
44
- custom_payload = options['payload']
45
-
46
- from django_webhook_subscriber.models import WebhookRegistry
47
-
48
- try:
49
- webhook = WebhookRegistry.objects.get(pk=webhook_id)
50
- except WebhookRegistry.DoesNotExist:
51
- raise CommandError(f'Webhook with ID {webhook_id} does not exist')
52
-
53
- # Prepare payload
54
- if custom_payload:
55
- try:
56
- # Use provided payload
57
- payload = custom_payload
58
- # Validate it's valid JSON
59
- json.loads(payload)
60
- except json.JSONDecodeError:
61
- raise CommandError('The provided payload is not valid JSON')
62
- else:
63
- # Generate a test payload
64
- model_name = webhook.content_type.model
65
- app_label = webhook.content_type.app_label
66
-
67
- payload = json.dumps(
68
- {
69
- 'event': event_signal,
70
- 'model': f'{app_label}.{model_name}',
71
- 'test': True,
72
- 'message': f'Test webhook for {webhook.name}',
73
- }
74
- )
75
-
76
- # Deliver the webhook
77
- self.stdout.write(f'Sending test webhook to {webhook.endpoint}...')
78
- log = deliver_webhook(webhook, payload, event_signal)
79
-
80
- # Display results
81
- if log.error_message:
82
- self.stdout.write(self.style.ERROR(f'Error: {log.error_message}'))
83
- elif log.response_status and 200 <= log.response_status < 300:
84
- self.stdout.write(
85
- self.style.SUCCESS(
86
- f'Success! Status code: {log.response_status}'
87
- )
88
- )
89
- self.stdout.write(f'Response: {log.response_body}')
90
- else:
91
- self.stdout.write(
92
- self.style.WARNING(
93
- f'Received status code: {log.response_status}'
94
- )
95
- )
96
- self.stdout.write(f'Response: {log.response_body}')
@@ -1,152 +0,0 @@
1
- """Signal handlers for processing webhooks.
2
-
3
- It includes functions to register models, connect signals, and process webhook
4
- events.
5
- """
6
-
7
- from django.apps import apps
8
- from django.conf import settings
9
- from django.utils.module_loading import import_string
10
- from django.db.models.signals import post_save, post_delete
11
-
12
- from django_webhook_subscriber.delivery import process_and_deliver_webhook
13
- from django_webhook_subscriber.serializers import serialize_instance
14
- from django_webhook_subscriber.utils import (
15
- get_webhook_config,
16
- register_model_config,
17
- )
18
-
19
-
20
- def process_webhook_event(instance, event_signal, **kwargs):
21
- """Process a webhook event by serializing the instance and delivering the
22
- webhook.
23
-
24
- This function checks the event type and the model class of the instance in
25
- the registry. If the event type is registered, it serializes the instance
26
- using the specified serializer (if any) and calls the delivery function to
27
- send the webhook.
28
- """
29
-
30
- # Skip if webhooks are disabled in settings
31
- if getattr(settings, 'DISABLE_WEBHOOKS', False):
32
- return
33
-
34
- model_class = instance.__class__
35
-
36
- # Get the webhook configuration for this model
37
- config = get_webhook_config(model_class)
38
-
39
- event_mappings = {
40
- 'created': 'CREATE',
41
- 'updated': 'UPDATE',
42
- 'deleted': 'DELETE',
43
- }
44
- if config and event_mappings[event_signal] in config.get('events', []):
45
- # Get the serializer if configured, otherwise None (default will be
46
- # used)
47
- serializer = config.get('serializer')
48
-
49
- # Serialize the instance using either custom or default serializer
50
- payload = serialize_instance(
51
- instance,
52
- event_signal,
53
- field_serializer=serializer,
54
- )
55
-
56
- # Deliver webhooks
57
- process_and_deliver_webhook(
58
- instance,
59
- event_signal,
60
- serialized_payload=payload,
61
- )
62
-
63
-
64
- def register_webhook_signals(model_class, serializer=None, events=None):
65
- """Register webhook signals for a specific model class."""
66
-
67
- # Register the configuration
68
- config = register_model_config(
69
- model_class,
70
- serializer=serializer,
71
- events=events,
72
- )
73
-
74
- # Connect signals based on requested events with a unique identifier
75
- if 'CREATE' in config['events'] or 'UPDATE' in config['events']:
76
- post_save.connect(
77
- webhook_post_save,
78
- sender=model_class,
79
- dispatch_uid=f'webhook_post_save_{model_class.__name__}',
80
- )
81
-
82
- if 'DELETE' in config['events']:
83
- post_delete.connect(
84
- webhook_post_delete,
85
- sender=model_class,
86
- dispatch_uid=f'webhook_post_delete_{model_class.__name__}',
87
- )
88
-
89
-
90
- def webhook_post_save(sender, instance, created, **kwargs):
91
- """Handle post_save signals for webhook processing."""
92
-
93
- event_signal = 'created' if created else 'updated'
94
- process_webhook_event(
95
- instance=instance, event_signal=event_signal, **kwargs
96
- )
97
-
98
-
99
- def webhook_post_delete(sender, instance, **kwargs):
100
- """Handle post_delete signals for webhook processing."""
101
-
102
- process_webhook_event(instance=instance, event_signal='deleted', **kwargs)
103
-
104
-
105
- def register_webhooks_from_settings():
106
- """Register webhooks based on settings configuration.
107
-
108
- This function is called in AppConfig.ready() to ensure that webhooks are
109
- registered when the application starts.
110
- """
111
-
112
- from django.conf import settings
113
-
114
- # Skip registration if webhooks are disabled
115
- if getattr(settings, 'DISABLE_WEBHOOKS', False):
116
- return
117
-
118
- # Check if WEBHOOK_SUBSCRIBER is defined in settings
119
- if not hasattr(settings, 'WEBHOOK_SUBSCRIBER'):
120
- return
121
-
122
- # Getting WEBHOOKS_MODELS from settings
123
- webhook_settings = settings.WEBHOOK_SUBSCRIBER
124
- webhook_models = webhook_settings.get('WEBHOOK_MODELS', {})
125
-
126
- for model_path, config in webhook_models.items():
127
- # Split the model path into app_label and model_name
128
- try:
129
- app_label, model_name = model_path.split('.')
130
- model_class = apps.get_model(app_label, model_name)
131
- except Exception:
132
- # If model path is invalid or model doesn't exist, skip it
133
- continue
134
-
135
- # Get serializer if specified
136
- field_serializer = None
137
- if 'serializer' in config:
138
- try:
139
- field_serializer = import_string(config['serializer'])
140
- except ImportError:
141
- # If serializer import fails, skip it
142
- pass
143
-
144
- # Get events
145
- events = config.get('events', None)
146
-
147
- # Register the webhook
148
- register_webhook_signals(
149
- model_class,
150
- serializer=field_serializer,
151
- events=events,
152
- )
@@ -1,14 +0,0 @@
1
- """Testing utilities for Django Webhook Subscriber."""
2
-
3
- from django.test.utils import override_settings
4
-
5
- # Context manager for disabling webhooks in tests
6
- disabled_webhooks = override_settings(DISABLE_WEBHOOKS=True)
7
-
8
-
9
- # Function to use in setUp methods
10
- def disable_webhooks_for_testing():
11
- """Disable webhooks for test purposes by unregistering all signals."""
12
- from django_webhook_subscriber.utils import unregister_webhook_signals
13
-
14
- unregister_webhook_signals()