Events¶
This module provides a simple interface allowing django to absorb events, eg from Pub/Sub push subscriptions or EventArc.
Events are communicated using django’s signals framework. They can be handled by any app (not just django-gcp) simply by creating a signal receiver.
Warning
Please see Authenticating Webhooks and PubSub messages to learn about authenticating incoming messages
Events Endpoints¶
If you have django_gcp
installed correctly (see Add the endpoints), using python manage.py show_urls
will show the endpoints for events.
Endpoints are POST
-only and require two URL parameters, an event_kind
and an event_reference
. The body of the POST
request forms the event_payload
.
So, if you POST
data to https://your-server.com/django-gcp/events/my-kind/my-reference/
then a signal will be dispatched
with event_kind="my-event"
and event_reference="my-reference"
.
Creating A Receiver¶
This is how you attach your handler. In your-app/signals.py
file, do:
import logging
from django.dispatch import receiver
from django_gcp.events.signals import event_received
from django_gcp.events.utils import decode_pubsub_message
logger = logging.getLogger(__name__)
@receiver(event_received)
def receive_event(sender, event_kind, event_reference, event_payload, event_parameters):
"""Handle question updates received via pubsub
:param event_kind (str): A kind/variety allowing you to determine the handler to use (eg "something-update"). Required.
:param event_reference (str): A reference value provided by the client allowing events to be sorted/filtered. Required.
:param event_payload (dict, array): The event payload to process, already decoded.
:param event_parameters (dict): Extra parameters passed to the endpoint using URL query parameters
:return: None
"""
# There could be many different event types, from your own or other apps, and
# django-gcp itself (when we get going with some more advanced features)
# so make sure you only act on the specific kind(s) you want to handle
if event_kind is "something-important":
# Here is where you handle the event using whatever logic you want
# CAREFUL: See the tip above about authentication (verifying the payload is not malicious)
print("DO SOMETHING IMPORTANT WITH THE PAYLOAD:", event_payload)
#
# Your payload can be from any arbitrary source, and is in the form of decoded json.
# However, if the source is Eventarc or Pub/Sub, the payload contains a formatted message
# with base64 encoded data; we provide a utility to further decode this into something sensible:
message = decode_pubsub_message(event_payload)
print("DECODED PUBSUB MESSAGE:" message)
Tip
To handle a range of events, use a uniform prefix for all their kinds, eg:
if event_kind.startswith("my-"):
my_handler(event_kind, event_reference, event_payload)
Generating Endpoint URLs¶
A utility is provided to help generate URLs for the events endpoint.
This is similar to, but easier than, generating URLs with django’s built-in reverse()
function.
It generates absolute URLs by default, because integration with external systems is the most common use case.
import logging
from django_gcp.events.utils import get_event_url
logger = logging.getLogger(__name__)
get_event_url(
'the-kind',
'the-reference',
event_parameters={"a":"parameter"}, # These get encoded as a querystring, and are decoded back to a dict by the events endpoint. Keep it short!
url_namespace="gcp-events", # You only need to edit this if you define your own urlpatterns with a different namespace
)
Tip
By default, get_event_url
generates an absolute URL, using the configured settings.BASE_URL
.
To specify a different base url, you can pass it explicitly:
relative_url = get_event_url(
'the-kind',
'the-reference',
base_url=''
)
non_default_base_url = get_event_url(
'the-kind',
'the-reference',
base_url='https://somewhere.else.com'
)
Generating and Consuming Pub/Sub Messages¶
When hooked up to GCP Pub/Sub or eventarc, the event payload is in the form of a Pub/Sub message.
These messages have a specific format (see https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage).
To allow you to interact directly with Pub/Sub (i.e. publish messages to a topic), or for the purposes of testing your signals,
django-gcp
includes a make_pubsub_message utility that provides an easy and pythonic way of constructing a Pub/Sub message.
For example, to test the signal receiver above with a replica of a real pubsub message payload, you might do:
from django_gcp.events.utils import make_pubsub_message
from datetime import datetime
class YourTests(TestCase):
def test_your_code_handles_a_payload_from_pubsub(self):
payload = make_pubsub_message({"my": "data"}, publish_time=datetime.now())
response = self.client.post(
reverse("gcp-events", args=["the-event-kind", "the-event-reference"]),
data=payload,
content_type="application/json",
)
self.assertEqual(response.status_code, 201)
Exception Handling¶
Any exception that gets raised in the handlers will be hidden from the user to prevent disclosure of information that may lead to attack.
Instead, a BAD_REQUEST (400)
status code is returned with a generic error message.
Note
We’ll work on adding a way of returning more useful information to the end user, which will probably be based on raising a ValidationError or similar, a bit like using DRF serialisers.
However, this is low priority right now so as always, if you need this feature, ping us on GitHub!