django-outbox-pattern


Namedjango-outbox-pattern JSON
Version 3.0.7 PyPI version JSON
download
home_pagehttps://github.com/juntossomosmais/django-outbox-pattern
SummaryA django application to make it easier to use the transactional outbox pattern
upload_time2025-09-12 18:44:46
maintainerNone
docs_urlNone
authorHugo Brilhante
requires_python<4.0,>=3.10
licenseMIT
keywords transactional outbox patterns application events microservices
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage
            # Django outbox pattern

[![Build Status](https://dev.azure.com/juntos-somos-mais-loyalty/python/_apis/build/status/juntossomosmais.django-outbox-pattern?branchName=main)](https://dev.azure.com/juntos-somos-mais-loyalty/python/_build/latest?definitionId=307&branchName=main)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=juntossomosmais_django-outbox-pattern&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=juntossomosmais_django-outbox-pattern)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=juntossomosmais_django-outbox-pattern&metric=coverage)](https://sonarcloud.io/summary/new_code?id=juntossomosmais_django-outbox-pattern)
[![Code style: black](https://img.shields.io/badge/code%20style-black-black)](https://github.com/ambv/black)
[![Downloads](https://pepy.tech/badge/django-outbox-pattern)](https://pepy.tech/project/django-outbox-pattern)
[![Downloads](https://pepy.tech/badge/django-outbox-pattern/month)](https://pepy.tech/project/django-outbox-pattern/month)
[![Downloads](https://pepy.tech/badge/django-outbox-pattern/week)](https://pepy.tech/project/django-outbox-pattern/week)
[![PyPI version](https://badge.fury.io/py/django-outbox-pattern.svg)](https://badge.fury.io/py/django-outbox-pattern)
[![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/juntossomosmais/django-outbox-pattern/blob/master/LICENSE)

A django application to make it easier to use
the [transactional outbox pattern](https://microservices.io/patterns/data/transactional-outbox.html)

## Installation

Install django-outbox-pattern with pip

```bash
pip install django-outbox-pattern
```

Add to settings

```python
# settings.py

INSTALLED_APPS = [
    "django_outbox_pattern",
]

DJANGO_OUTBOX_PATTERN = {
    "DEFAULT_STOMP_HOST_AND_PORTS": [("127.0.0.1", 61613)],
    "DEFAULT_STOMP_USERNAME": "guest",
    "DEFAULT_STOMP_PASSCODE": "guest",
}

```

Rum migrations

```shell
python manage.py migrate
```

## Usage/Examples

The `publish` decorator adds
the [outbox table](https://github.com/juntossomosmais/django-outbox-pattern/blob/main/django_outbox_pattern/models.py#L14)
to the model. `publish` accepts list of Config. The Config accepts four params the `destination` which is required,
`fields` which the default are all the fields of the model, `serializer` which by default adds the `id` in the message
to be sent and `version` which by default is empty.

> Note: `fields` and `serializer` are mutually exclusive, serializer overwrites the fields.

### The Config typing

```python
from typing import List
from typing import NamedTuple
from typing import Optional


class Config(NamedTuple):
    destination: str
    fields: Optional[List[str]] = None
    serializer: Optional[str] = None
    version: Optional[str] = None
```

#### Only destination in config

```python
from django.db import models
from django_outbox_pattern.decorators import Config
from django_outbox_pattern.decorators import publish


@publish([Config(destination='/topic/my_route_key')])
class MyModel(models.Model):
    field_one = models.CharField(max_length=100)
    field_two = models.CharField(max_length=100)
```

This generates the following data to be sent.

```text
Published(destination='/topic/my_route_key', body='{"id": 1, "field_one": "Field One", "field_two": "Field Two"}')
```

#### Change destination version in config

```python
from django.db import models
from django_outbox_pattern.decorators import Config
from django_outbox_pattern.decorators import publish


@publish([Config(destination='/topic/my_route_key', version="v1")])
class MyModel(models.Model):
    field_one = models.CharField(max_length=100)
    field_two = models.CharField(max_length=100)
```

This generates the following data to be sent.

```text
Published(destination='/topic/my_route_key.v1', body='{"id": 1, "field_one": "One", "field_two": "Two"}', version="v1")
```

#### With destinations and fields

```python
from django.db import models
from django_outbox_pattern.decorators import Config
from django_outbox_pattern.decorators import publish


@publish([Config(destination='/topic/my_route_key', fields=["field_one"])])
class MyModel(models.Model):
    field_one = models.CharField(max_length=100)
    field_two = models.CharField(max_length=100)
```

This generates the following data to be sent.

```text
Published(destination='/topic/my_route_key', body='{"id": 1, "field_one": "Field One"}')
```

#### With destinations and serializer

```python
from django.db import models
from django_outbox_pattern.decorators import Config
from django_outbox_pattern.decorators import publish


@publish([Config(destination='/topic/my_route_key', serializer='my_serializer')])
class MyModel(models.Model):
    field_one = models.CharField(max_length=100)
    field_two = models.CharField(max_length=100)

    def my_serializer(self):
        return {
            "id": self.id,
            "one": self.field_one,
            "two": self.field_two
        }
```

This generates the following data to be sent.

```text
Published(destination='/topic/my_route_key', body='{"id": 1, "one": "Field One", "two": "Field Two"}')
```

#### With multi destinations and serializers

```python
from django.db import models
from django_outbox_pattern.decorators import Config
from django_outbox_pattern.decorators import publish


@publish([
    Config(destination='/topic/my_route_key_1', serializer="my_serializer_1"),
    Config(destination='/topic/my_route_key_2', serializer="my_serializer_2"),
])
class MyModel(models.Model):
    field_one = models.CharField(max_length=100)
    field_two = models.CharField(max_length=100)

    def my_serializer_1(self):
        return {
            "id": self.id,
            "one": self.field_one,
        }

    def my_serializer_2(self):
        return {
            "id": self.id,
            "two": self.field_two
        }
```

This generates the following data to be sent.

```text
Published(destination='/topic/my_route_key_1', body='{"id": 1, "one": "Field One"}')
Published(destination='/topic/my_route_key_2', body='{"id": 1, "two": "Field Two"}')
```

## Publish/Subscribe commands

##### Publish command

To send the messages added to the Published model it is necessary to start the producer with the following command.

```shell
python manage.py publish
```

##### Publish message via outbox

It is possible to use the outbox pattern with a custom logic before sending the message to the outbox table.

```python
from django.db import transaction
from django_outbox_pattern.models import Published


def custom_business_logic() -> None:
    # run your custom business logic

    with transaction.atomic():
        YourBusinessModel.objects.create()
        Published.objects.create(destination="your_destination", body={"some": "data"})

```

With this you can ensure that the messages can be published in the same database transaction of your business logic.

##### Publish message directly

It is possible to send messages directly without using the outbox table

```python
# send.py
from django_outbox_pattern.factories import factory_producer


def send_event(destination, body, headers):
    with factory_producer() as producer:
        producer.send_event(destination=destination, body=body, headers=headers)
```

##### Subscribe command

Consumers created through the library implement the idempotency pattern using the header attribute `message-id`. The
library configures it as unique in the database. This ensures a given message is only processed once, no matter what.
To correctly implement this, you must open a transaction with the database to persist the data from your logic and
execute the `save` method of the `payload` object. Once the code is performed correctly, the library guarantees the
message is removed from the broker.

If you need to discard the message due to a business rule, use the `nack` method of the Payload object. This call
removes the message from the broker. This method performs no persistence in the database and can be called outside your
database transaction. If it fails for any reason, the message is resent to the consumer.

**Alert:**

**You need to use either `save` or `nack` to process of your message. The library cannot make the decision for the
developer, and it is up to the developer to determine whether to use the `save` or `nack` method. However, in case of an
exception during the operation, the `nack` method will be triggered automatically**

**The same service (code + database) cannot consume the same message even with different consumers.**

Create a function that receives an instance of `django_outbox_pattern.payloads.Payload`

```python
# callbacks.py
from django.db import transaction
from django_outbox_pattern.payloads import Payload


def callback(payload: Payload):
    if message_is_invalid(payload.body):
        payload.nack()
        return

    with transaction.atomic():
        persist_your_data()
        payload.save()

```

To start the consumer, after creating the callback, it is necessary to execute the following command.

```shell
python manage.py subscribe 'dotted.path.to.callback` 'destination' 'queue_name'
```

The command takes three parameters:

`callback` : the path to the callback function.

`destination` : the destination where messages will be consumed following one of
the [stomp](https://www.rabbitmq.com/stomp.html) patterns

`queue_name`(optional): the name of the queue that will be consumed. If not provided, the routing_key of the destination
will be used.

## Settings

**DEFAULT_CONNECTION_CLASS**

The stomp.py class responsible for connecting to the broker. Default: `stomp.StompConnection12`

**DEFAULT_CONSUMER_LISTENER_CLASS**

The consumer listener class. Default: `django_outbox_pattern.listeners.ConsumerListener`

**DEFAULT_GENERATE_HEADERS**

A function to add headers to the message. Default: `django_outbox_pattern.headers.generate_headers`

**DEFAULT_MAXIMUM_BACKOFF**:

Maximum wait time for connection attempts in seconds. Default: `3600` (1 hour)

**DEFAULT_MAXIMUM_RETRY_ATTEMPTS**

Maximum number of message resend attempts. Default: `50`

**DEFAULT_PAUSE_FOR_RETRY**

Pausing for attempts to resend messages in seconds. Defualt: `240` (4 minutes)

**DEFAULT_WAIT_RETRY**

Time between attempts to send messages after the pause. Default: `60` (1 minute)

**DEFAULT_PRODUCER_LISTENER_CLASS**:

The producer listener class. Default: `django_outbox_pattern.listeners.ProducerListener`

**DEFAULT_STOMP_HOST_AND_PORTS**

List of host and port tuples to try to connect to the broker. Default `[("127.0.0.1", 61613)]`

**DEFAULT_STOMP_QUEUE_HEADERS**

Headers for queues. Default: `{"durable": "true", "auto-delete": "false", "prefetch-count": "1"}`

**DEFAULT_STOMP_HEARTBEATS**

Time tuples for input and output heartbeats. Default:  `(10000, 10000)`

Optional overrides:

- **DEFAULT_STOMP_OUTGOING_HEARTBEAT**: Overrides the outgoing heartbeat (ms) if set; otherwise falls back to
  DEFAULT_STOMP_HEARTBEATS and/or top-level STOMP_OUTGOING_HEARTBEAT.
- **DEFAULT_STOMP_INCOMING_HEARTBEAT**: Overrides the incoming heartbeat (ms) if set; otherwise falls back to
  DEFAULT_STOMP_HEARTBEATS and/or top-level STOMP_INCOMING_HEARTBEAT.

Top-level Django settings supported (compat with django-stomp):

- `STOMP_OUTGOING_HEARTBEAT` and `STOMP_INCOMING_HEARTBEAT` can be defined at settings.py root to control heartbeats
  without touching DJANGO_OUTBOX_PATTERN.

> warning: The heartbeat only works at consumer connection. The publisher process open and close connection for each
> batch message to publish.

**DEFAULT_STOMP_VHOST**

Virtual host. Default: "/"

**DEFAULT_STOMP_USERNAME**

Username for connection. Default: `"guest"`

**DEFAULT_STOMP_PASSCODE**

Password for connection. Default: `"guest"`

**DEFAULT_STOMP_USE_SSL**

For ssl connections. Default: False

**DEFAULT_STOMP_KEY_FILE**

The path to a X509 key file. Default: None

**DEFAULT_STOMP_CERT_FILE**

The path to a X509 certificate. Default: None

**DEFAULT_STOMP_CA_CERTS**

The path to the a file containing CA certificates to validate the server against.
If this is not set, server side certificate validation is not done. Default: None

**DEFAULT_STOMP_CERT_VALIDATOR**

Function which performs extra validation on the client certificate, for example
checking the returned certificate has a commonName attribute equal to the
hostname (to avoid man in the middle attacks).
The signature is: (OK, err_msg) = validation_function(cert, hostname)
where OK is a boolean, and cert is a certificate structure
as returned by ssl.SSLSocket.getpeercert(). Default: None

**DEFAULT_STOMP_SSL_VERSION**

SSL protocol to use for the connection. This should be one of the PROTOCOL_x
constants provided by the ssl module. The default is ssl.PROTOCOL_TLSv1.

**DEFAULT_STOMP_SSL_PASSWORD**

SSL password

**DEFAULT_EXCLUSIVE_QUEUE**

For exclusive queue feature. Default: False

**DAYS_TO_KEEP_DATA**

The total number of days that the system will keep a message in the database history. Default: 30

**REMOVE_DATA_CACHE_TTL**

This variable defines the time-to-live (TTL) value in seconds for the cache used by the `_remove_old_messages` method in
the `django_outbox_pattern` application. The cache is used to prevent the method from deleting old data every time it is
run, and the TTL value determines how long the cache entry should remain valid before being automatically deleted. It
can be customized by setting the REMOVE_DATA_CACHE_TTL variable. Default: 86400 seconds (1 day)

**OUTBOX_PATTERN_PUBLISHER_CACHE_KEY**

The `OUTBOX_PATTERN_PUBLISHER_CACHE_KEY` variable controls the key name of the cache used to store the outbox pattern
publisher. Default: `remove_old_messages_django_outbox_pattern_publisher`.

**OUTBOX_PATTERN_CONSUMER_CACHE_KEY**

The `OUTBOX_PATTERN_CONSUMER_CACHE_KEY` variable controls the key name of the cache used to store the outbox pattern
publisher. Default: `remove_old_messages_django_outbox_pattern_consumer`.

**DEFAULT_PUBLISHED_CHUNK_SIZE**

The `DEFAULT_PUBLISHED_CHUNK_SIZE` variable controls chunk size for the `publish` command in get message to publish
action. Default: 200

**DEFAULT_CONSUMER_PROCESS_MSG_ON_BACKGROUND**

Controls whether Consumer processes incoming messages on a background thread pool.
When set to `True`, `handle_incoming_message` submits work to a `ThreadPoolExecutor` and returns immediately;
The user callback (via `message_handler`) runs asynchronously. This helps keep the listener responsive (e.g.,
heartbeats) when callbacks are slow or blocking. When `False` (default), messages are processed synchronously and any
exception raised by the callback will propagate to the caller.

Default: `False`.

Notes:

- The worker pool is recreated automatically if it was previously shut down and a new message arrives.
- Ensure your callback calls `payload.save()` (or `payload.nack()` when appropriate); otherwise a warning is logged and
  the message may not be acked/nacked automatically unless an exception occurs.

Example configuration:

```python
# settings.py
DJANGO_OUTBOX_PATTERN = {
    # ... other options ...
    "DEFAULT_CONSUMER_PROCESS_MSG_ON_BACKGROUND": True
}
```

**DEFAULT_PRODUCER_WAITING_TIME**

The `DEFAULT_PRODUCER_WAITING_TIME` variable controls the waiting time in seconds for the producer to check for new
messages to be sent.
Default: 1 second

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/juntossomosmais/django-outbox-pattern",
    "name": "django-outbox-pattern",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.10",
    "maintainer_email": null,
    "keywords": "transactional outbox patterns, application events, microservices",
    "author": "Hugo Brilhante",
    "author_email": "hugobrilhante@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/87/02/c90f672f0be799ce44f3b055b2f35d51db6819c868af3bd7ba4b58393647/django_outbox_pattern-3.0.7.tar.gz",
    "platform": null,
    "description": "# Django outbox pattern\n\n[![Build Status](https://dev.azure.com/juntos-somos-mais-loyalty/python/_apis/build/status/juntossomosmais.django-outbox-pattern?branchName=main)](https://dev.azure.com/juntos-somos-mais-loyalty/python/_build/latest?definitionId=307&branchName=main)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=juntossomosmais_django-outbox-pattern&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=juntossomosmais_django-outbox-pattern)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=juntossomosmais_django-outbox-pattern&metric=coverage)](https://sonarcloud.io/summary/new_code?id=juntossomosmais_django-outbox-pattern)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-black)](https://github.com/ambv/black)\n[![Downloads](https://pepy.tech/badge/django-outbox-pattern)](https://pepy.tech/project/django-outbox-pattern)\n[![Downloads](https://pepy.tech/badge/django-outbox-pattern/month)](https://pepy.tech/project/django-outbox-pattern/month)\n[![Downloads](https://pepy.tech/badge/django-outbox-pattern/week)](https://pepy.tech/project/django-outbox-pattern/week)\n[![PyPI version](https://badge.fury.io/py/django-outbox-pattern.svg)](https://badge.fury.io/py/django-outbox-pattern)\n[![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/juntossomosmais/django-outbox-pattern/blob/master/LICENSE)\n\nA django application to make it easier to use\nthe [transactional outbox pattern](https://microservices.io/patterns/data/transactional-outbox.html)\n\n## Installation\n\nInstall django-outbox-pattern with pip\n\n```bash\npip install django-outbox-pattern\n```\n\nAdd to settings\n\n```python\n# settings.py\n\nINSTALLED_APPS = [\n    \"django_outbox_pattern\",\n]\n\nDJANGO_OUTBOX_PATTERN = {\n    \"DEFAULT_STOMP_HOST_AND_PORTS\": [(\"127.0.0.1\", 61613)],\n    \"DEFAULT_STOMP_USERNAME\": \"guest\",\n    \"DEFAULT_STOMP_PASSCODE\": \"guest\",\n}\n\n```\n\nRum migrations\n\n```shell\npython manage.py migrate\n```\n\n## Usage/Examples\n\nThe `publish` decorator adds\nthe [outbox table](https://github.com/juntossomosmais/django-outbox-pattern/blob/main/django_outbox_pattern/models.py#L14)\nto the model. `publish` accepts list of Config. The Config accepts four params the `destination` which is required,\n`fields` which the default are all the fields of the model, `serializer` which by default adds the `id` in the message\nto be sent and `version` which by default is empty.\n\n> Note: `fields` and `serializer` are mutually exclusive, serializer overwrites the fields.\n\n### The Config typing\n\n```python\nfrom typing import List\nfrom typing import NamedTuple\nfrom typing import Optional\n\n\nclass Config(NamedTuple):\n    destination: str\n    fields: Optional[List[str]] = None\n    serializer: Optional[str] = None\n    version: Optional[str] = None\n```\n\n#### Only destination in config\n\n```python\nfrom django.db import models\nfrom django_outbox_pattern.decorators import Config\nfrom django_outbox_pattern.decorators import publish\n\n\n@publish([Config(destination='/topic/my_route_key')])\nclass MyModel(models.Model):\n    field_one = models.CharField(max_length=100)\n    field_two = models.CharField(max_length=100)\n```\n\nThis generates the following data to be sent.\n\n```text\nPublished(destination='/topic/my_route_key', body='{\"id\": 1, \"field_one\": \"Field One\", \"field_two\": \"Field Two\"}')\n```\n\n#### Change destination version in config\n\n```python\nfrom django.db import models\nfrom django_outbox_pattern.decorators import Config\nfrom django_outbox_pattern.decorators import publish\n\n\n@publish([Config(destination='/topic/my_route_key', version=\"v1\")])\nclass MyModel(models.Model):\n    field_one = models.CharField(max_length=100)\n    field_two = models.CharField(max_length=100)\n```\n\nThis generates the following data to be sent.\n\n```text\nPublished(destination='/topic/my_route_key.v1', body='{\"id\": 1, \"field_one\": \"One\", \"field_two\": \"Two\"}', version=\"v1\")\n```\n\n#### With destinations and fields\n\n```python\nfrom django.db import models\nfrom django_outbox_pattern.decorators import Config\nfrom django_outbox_pattern.decorators import publish\n\n\n@publish([Config(destination='/topic/my_route_key', fields=[\"field_one\"])])\nclass MyModel(models.Model):\n    field_one = models.CharField(max_length=100)\n    field_two = models.CharField(max_length=100)\n```\n\nThis generates the following data to be sent.\n\n```text\nPublished(destination='/topic/my_route_key', body='{\"id\": 1, \"field_one\": \"Field One\"}')\n```\n\n#### With destinations and serializer\n\n```python\nfrom django.db import models\nfrom django_outbox_pattern.decorators import Config\nfrom django_outbox_pattern.decorators import publish\n\n\n@publish([Config(destination='/topic/my_route_key', serializer='my_serializer')])\nclass MyModel(models.Model):\n    field_one = models.CharField(max_length=100)\n    field_two = models.CharField(max_length=100)\n\n    def my_serializer(self):\n        return {\n            \"id\": self.id,\n            \"one\": self.field_one,\n            \"two\": self.field_two\n        }\n```\n\nThis generates the following data to be sent.\n\n```text\nPublished(destination='/topic/my_route_key', body='{\"id\": 1, \"one\": \"Field One\", \"two\": \"Field Two\"}')\n```\n\n#### With multi destinations and serializers\n\n```python\nfrom django.db import models\nfrom django_outbox_pattern.decorators import Config\nfrom django_outbox_pattern.decorators import publish\n\n\n@publish([\n    Config(destination='/topic/my_route_key_1', serializer=\"my_serializer_1\"),\n    Config(destination='/topic/my_route_key_2', serializer=\"my_serializer_2\"),\n])\nclass MyModel(models.Model):\n    field_one = models.CharField(max_length=100)\n    field_two = models.CharField(max_length=100)\n\n    def my_serializer_1(self):\n        return {\n            \"id\": self.id,\n            \"one\": self.field_one,\n        }\n\n    def my_serializer_2(self):\n        return {\n            \"id\": self.id,\n            \"two\": self.field_two\n        }\n```\n\nThis generates the following data to be sent.\n\n```text\nPublished(destination='/topic/my_route_key_1', body='{\"id\": 1, \"one\": \"Field One\"}')\nPublished(destination='/topic/my_route_key_2', body='{\"id\": 1, \"two\": \"Field Two\"}')\n```\n\n## Publish/Subscribe commands\n\n##### Publish command\n\nTo send the messages added to the Published model it is necessary to start the producer with the following command.\n\n```shell\npython manage.py publish\n```\n\n##### Publish message via outbox\n\nIt is possible to use the outbox pattern with a custom logic before sending the message to the outbox table.\n\n```python\nfrom django.db import transaction\nfrom django_outbox_pattern.models import Published\n\n\ndef custom_business_logic() -> None:\n    # run your custom business logic\n\n    with transaction.atomic():\n        YourBusinessModel.objects.create()\n        Published.objects.create(destination=\"your_destination\", body={\"some\": \"data\"})\n\n```\n\nWith this you can ensure that the messages can be published in the same database transaction of your business logic.\n\n##### Publish message directly\n\nIt is possible to send messages directly without using the outbox table\n\n```python\n# send.py\nfrom django_outbox_pattern.factories import factory_producer\n\n\ndef send_event(destination, body, headers):\n    with factory_producer() as producer:\n        producer.send_event(destination=destination, body=body, headers=headers)\n```\n\n##### Subscribe command\n\nConsumers created through the library implement the idempotency pattern using the header attribute `message-id`. The\nlibrary configures it as unique in the database. This ensures a given message is only processed once, no matter what.\nTo correctly implement this, you must open a transaction with the database to persist the data from your logic and\nexecute the `save` method of the `payload` object. Once the code is performed correctly, the library guarantees the\nmessage is removed from the broker.\n\nIf you need to discard the message due to a business rule, use the `nack` method of the Payload object. This call\nremoves the message from the broker. This method performs no persistence in the database and can be called outside your\ndatabase transaction. If it fails for any reason, the message is resent to the consumer.\n\n**Alert:**\n\n**You need to use either `save` or `nack` to process of your message. The library cannot make the decision for the\ndeveloper, and it is up to the developer to determine whether to use the `save` or `nack` method. However, in case of an\nexception during the operation, the `nack` method will be triggered automatically**\n\n**The same service (code + database) cannot consume the same message even with different consumers.**\n\nCreate a function that receives an instance of `django_outbox_pattern.payloads.Payload`\n\n```python\n# callbacks.py\nfrom django.db import transaction\nfrom django_outbox_pattern.payloads import Payload\n\n\ndef callback(payload: Payload):\n    if message_is_invalid(payload.body):\n        payload.nack()\n        return\n\n    with transaction.atomic():\n        persist_your_data()\n        payload.save()\n\n```\n\nTo start the consumer, after creating the callback, it is necessary to execute the following command.\n\n```shell\npython manage.py subscribe 'dotted.path.to.callback` 'destination' 'queue_name'\n```\n\nThe command takes three parameters:\n\n`callback` : the path to the callback function.\n\n`destination` : the destination where messages will be consumed following one of\nthe [stomp](https://www.rabbitmq.com/stomp.html) patterns\n\n`queue_name`(optional): the name of the queue that will be consumed. If not provided, the routing_key of the destination\nwill be used.\n\n## Settings\n\n**DEFAULT_CONNECTION_CLASS**\n\nThe stomp.py class responsible for connecting to the broker. Default: `stomp.StompConnection12`\n\n**DEFAULT_CONSUMER_LISTENER_CLASS**\n\nThe consumer listener class. Default: `django_outbox_pattern.listeners.ConsumerListener`\n\n**DEFAULT_GENERATE_HEADERS**\n\nA function to add headers to the message. Default: `django_outbox_pattern.headers.generate_headers`\n\n**DEFAULT_MAXIMUM_BACKOFF**:\n\nMaximum wait time for connection attempts in seconds. Default: `3600` (1 hour)\n\n**DEFAULT_MAXIMUM_RETRY_ATTEMPTS**\n\nMaximum number of message resend attempts. Default: `50`\n\n**DEFAULT_PAUSE_FOR_RETRY**\n\nPausing for attempts to resend messages in seconds. Defualt: `240` (4 minutes)\n\n**DEFAULT_WAIT_RETRY**\n\nTime between attempts to send messages after the pause. Default: `60` (1 minute)\n\n**DEFAULT_PRODUCER_LISTENER_CLASS**:\n\nThe producer listener class. Default: `django_outbox_pattern.listeners.ProducerListener`\n\n**DEFAULT_STOMP_HOST_AND_PORTS**\n\nList of host and port tuples to try to connect to the broker. Default `[(\"127.0.0.1\", 61613)]`\n\n**DEFAULT_STOMP_QUEUE_HEADERS**\n\nHeaders for queues. Default: `{\"durable\": \"true\", \"auto-delete\": \"false\", \"prefetch-count\": \"1\"}`\n\n**DEFAULT_STOMP_HEARTBEATS**\n\nTime tuples for input and output heartbeats. Default:  `(10000, 10000)`\n\nOptional overrides:\n\n- **DEFAULT_STOMP_OUTGOING_HEARTBEAT**: Overrides the outgoing heartbeat (ms) if set; otherwise falls back to\n  DEFAULT_STOMP_HEARTBEATS and/or top-level STOMP_OUTGOING_HEARTBEAT.\n- **DEFAULT_STOMP_INCOMING_HEARTBEAT**: Overrides the incoming heartbeat (ms) if set; otherwise falls back to\n  DEFAULT_STOMP_HEARTBEATS and/or top-level STOMP_INCOMING_HEARTBEAT.\n\nTop-level Django settings supported (compat with django-stomp):\n\n- `STOMP_OUTGOING_HEARTBEAT` and `STOMP_INCOMING_HEARTBEAT` can be defined at settings.py root to control heartbeats\n  without touching DJANGO_OUTBOX_PATTERN.\n\n> warning: The heartbeat only works at consumer connection. The publisher process open and close connection for each\n> batch message to publish.\n\n**DEFAULT_STOMP_VHOST**\n\nVirtual host. Default: \"/\"\n\n**DEFAULT_STOMP_USERNAME**\n\nUsername for connection. Default: `\"guest\"`\n\n**DEFAULT_STOMP_PASSCODE**\n\nPassword for connection. Default: `\"guest\"`\n\n**DEFAULT_STOMP_USE_SSL**\n\nFor ssl connections. Default: False\n\n**DEFAULT_STOMP_KEY_FILE**\n\nThe path to a X509 key file. Default: None\n\n**DEFAULT_STOMP_CERT_FILE**\n\nThe path to a X509 certificate. Default: None\n\n**DEFAULT_STOMP_CA_CERTS**\n\nThe path to the a file containing CA certificates to validate the server against.\nIf this is not set, server side certificate validation is not done. Default: None\n\n**DEFAULT_STOMP_CERT_VALIDATOR**\n\nFunction which performs extra validation on the client certificate, for example\nchecking the returned certificate has a commonName attribute equal to the\nhostname (to avoid man in the middle attacks).\nThe signature is: (OK, err_msg) = validation_function(cert, hostname)\nwhere OK is a boolean, and cert is a certificate structure\nas returned by ssl.SSLSocket.getpeercert(). Default: None\n\n**DEFAULT_STOMP_SSL_VERSION**\n\nSSL protocol to use for the connection. This should be one of the PROTOCOL_x\nconstants provided by the ssl module. The default is ssl.PROTOCOL_TLSv1.\n\n**DEFAULT_STOMP_SSL_PASSWORD**\n\nSSL password\n\n**DEFAULT_EXCLUSIVE_QUEUE**\n\nFor exclusive queue feature. Default: False\n\n**DAYS_TO_KEEP_DATA**\n\nThe total number of days that the system will keep a message in the database history. Default: 30\n\n**REMOVE_DATA_CACHE_TTL**\n\nThis variable defines the time-to-live (TTL) value in seconds for the cache used by the `_remove_old_messages` method in\nthe `django_outbox_pattern` application. The cache is used to prevent the method from deleting old data every time it is\nrun, and the TTL value determines how long the cache entry should remain valid before being automatically deleted. It\ncan be customized by setting the REMOVE_DATA_CACHE_TTL variable. Default: 86400 seconds (1 day)\n\n**OUTBOX_PATTERN_PUBLISHER_CACHE_KEY**\n\nThe `OUTBOX_PATTERN_PUBLISHER_CACHE_KEY` variable controls the key name of the cache used to store the outbox pattern\npublisher. Default: `remove_old_messages_django_outbox_pattern_publisher`.\n\n**OUTBOX_PATTERN_CONSUMER_CACHE_KEY**\n\nThe `OUTBOX_PATTERN_CONSUMER_CACHE_KEY` variable controls the key name of the cache used to store the outbox pattern\npublisher. Default: `remove_old_messages_django_outbox_pattern_consumer`.\n\n**DEFAULT_PUBLISHED_CHUNK_SIZE**\n\nThe `DEFAULT_PUBLISHED_CHUNK_SIZE` variable controls chunk size for the `publish` command in get message to publish\naction. Default: 200\n\n**DEFAULT_CONSUMER_PROCESS_MSG_ON_BACKGROUND**\n\nControls whether Consumer processes incoming messages on a background thread pool.\nWhen set to `True`, `handle_incoming_message` submits work to a `ThreadPoolExecutor` and returns immediately;\nThe user callback (via `message_handler`) runs asynchronously. This helps keep the listener responsive (e.g.,\nheartbeats) when callbacks are slow or blocking. When `False` (default), messages are processed synchronously and any\nexception raised by the callback will propagate to the caller.\n\nDefault: `False`.\n\nNotes:\n\n- The worker pool is recreated automatically if it was previously shut down and a new message arrives.\n- Ensure your callback calls `payload.save()` (or `payload.nack()` when appropriate); otherwise a warning is logged and\n  the message may not be acked/nacked automatically unless an exception occurs.\n\nExample configuration:\n\n```python\n# settings.py\nDJANGO_OUTBOX_PATTERN = {\n    # ... other options ...\n    \"DEFAULT_CONSUMER_PROCESS_MSG_ON_BACKGROUND\": True\n}\n```\n\n**DEFAULT_PRODUCER_WAITING_TIME**\n\nThe `DEFAULT_PRODUCER_WAITING_TIME` variable controls the waiting time in seconds for the producer to check for new\nmessages to be sent.\nDefault: 1 second\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A django application to make it easier to use the transactional outbox pattern",
    "version": "3.0.7",
    "project_urls": {
        "Documentation": "https://github.com/juntossomosmais/django-outbox-pattern",
        "Homepage": "https://github.com/juntossomosmais/django-outbox-pattern",
        "Repository": "https://github.com/juntossomosmais/django-outbox-pattern"
    },
    "split_keywords": [
        "transactional outbox patterns",
        " application events",
        " microservices"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e6cf8ef17e997097e2f198ab9e10b027d131c6e770ff94290f569a82125d80a4",
                "md5": "60cd019f573851645a2c15307da26271",
                "sha256": "6b341dccca7ee22e6b854b3ad91f78e77039f4906920356735f7215c9bf4b939"
            },
            "downloads": -1,
            "filename": "django_outbox_pattern-3.0.7-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "60cd019f573851645a2c15307da26271",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.10",
            "size": 23979,
            "upload_time": "2025-09-12T18:44:45",
            "upload_time_iso_8601": "2025-09-12T18:44:45.237141Z",
            "url": "https://files.pythonhosted.org/packages/e6/cf/8ef17e997097e2f198ab9e10b027d131c6e770ff94290f569a82125d80a4/django_outbox_pattern-3.0.7-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8702c90f672f0be799ce44f3b055b2f35d51db6819c868af3bd7ba4b58393647",
                "md5": "5fe816cf139f402a5a1f28f419ad28c5",
                "sha256": "4d941747383903dad323922882c2f5d6ec6511bfe193271dbdf114049ff82b7d"
            },
            "downloads": -1,
            "filename": "django_outbox_pattern-3.0.7.tar.gz",
            "has_sig": false,
            "md5_digest": "5fe816cf139f402a5a1f28f419ad28c5",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.10",
            "size": 20494,
            "upload_time": "2025-09-12T18:44:46",
            "upload_time_iso_8601": "2025-09-12T18:44:46.195478Z",
            "url": "https://files.pythonhosted.org/packages/87/02/c90f672f0be799ce44f3b055b2f35d51db6819c868af3bd7ba4b58393647/django_outbox_pattern-3.0.7.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-12 18:44:46",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "juntossomosmais",
    "github_project": "django-outbox-pattern",
    "travis_ci": false,
    "coveralls": true,
    "github_actions": false,
    "lcname": "django-outbox-pattern"
}
        
Elapsed time: 2.35812s