django-temporalio
A small Django app that provides helpers for integrating Temporal.io with Django.
Features
- Registry: Provides a registry that holds mappings between queue names and registered activities and workflows.
- Management Commands: Includes management commands to manage Temporal.io workers and sync schedules.
- Activity Failure Logging: An opt-in worker interceptor that logs activity failures at ERROR level with the full traceback, so errors in endlessly retried activities surface in your error tracking instead of only in the Temporal UI, plus a logging filter to throttle the SDK's own per-attempt failure records.
Installation
You can install django_temporalio using pip:
$ pip install django-temporalio
Add django_temporalio to your INSTALLED_APPS:
INSTALLED_APPS = [
...
'django_temporalio.apps.DjangoTemporalioConfig',
...
]
Add the following settings to your settings.py:
from temporalio.worker import WorkerConfig
DJANGO_TEMPORALIO = {
"CLIENT_CONFIG": {
"target_host": "localhost:7233",
},
"BASE_MODULE": "path.to.module",
"WORKER_CONFIGS": {
"main": WorkerConfig(
task_queue="MAIN_TASK_QUEUE",
...
),
...
},
}
Usage
Activities, workflows and schedules should be placed inside the base module defined by the BASE_MODULE setting,
preferably outside of any Django application, in order to keep the uses of
the imports_passed_through context manager encapsulated
inside the module, along with Temporal.io related code.
Workflow and Activity Registry
The registry is a singleton that holds mappings between queue names and registered activities and workflows.
You can register activities and workflows using the register method.
Activities and workflows should be declared in modules matching the following patterns *workflows*.py and
*activities*.py respectively.
from temporalio import activity, workflow
from django_temporalio.registry import queue_activities, queue_workflows
@queue_activities.register("HIGH_PRIORITY_TASK_QUEUE", "MAIN_TASK_QUEUE")
@activity.defn
def my_activity():
pass
@queue_workflows.register("HIGH_PRIORITY_TASK_QUEUE", "MAIN_TASK_QUEUE")
@workflow.defn
class MyWorkflow:
pass
Schedule Registry
You can register schedules using the register method.
Schedules should be declared in schedules.py module.
from django_temporalio.registry import schedules
from temporalio.client import Schedule
schedules.register("do-cool-stuff-every-hour", Schedule(...))
Heartbeat
Good practice for long-running activities is setting up a heartbeat_timeout and calling heartbeat periodically to make sure the activity is still alive.
This can be achieved by setting up providing heartbeat_timeout when starting the activity and calling activity.heartbeat() directly inside your core logic e.g. on each iteration.
If you encountered a use case where this approach does not fit your design, you can use heartbeat contextmanager. It creates a background task utilizing asyncio and calls the heartbeat with defined intervals.
from django_temporalio.utils import heartbeat
@queue_activities.register("MAIN_TASK_QUEUE")
@activity.defn
async def long_running_activity():
async with heartbeat(timedelta(seconds=10)):
await count_sheeps()
await workflow.execute_activity(
long_running_activity,
start_to_close_timeout=timedelta(minutes=20),
heartbeat_timeout=timedelta(seconds=30),
)
Activity Failure Logging
The temporalio SDK catches every activity exception to report it to the Temporal server for retry, so it never surfaces as an unhandled exception, and the SDK's own log record is a WARNING - below the threshold error trackers turn into an alert. An activity failing under an infinite retry policy is therefore invisible unless someone checks the Temporal UI.
The package ships ActivityFailureLoggingInterceptor, which logs activity failures to the
django_temporalio.activity logger at ERROR level with the full traceback, so error trackers hooked
into Python logging (e.g. Sentry) pick them up as events. Workers are started with the interceptors
declared in the INTERCEPTORS setting (none by default), followed by any in their WorkerConfig:
DJANGO_TEMPORALIO = {
...
"INTERCEPTORS": ["django_temporalio.logs.ActivityFailureLoggingInterceptor"],
}
When a failure is logged depends on the activity's retry policy:
- Limited retries (
maximum_attempts> 0): only the final attempt is logged - earlier failures will be retried and may yet succeed. - Endless retries (
maximum_attempts= 0): throttled by attempt number - the attempts listed inACTIVITY_FAILURE_LOG_ATTEMPTS, then everyACTIVITY_FAILURE_LOG_EVERY-th attempt (default 1, 10, 100, 1000, then every 1000th). Transient errors log at most once, persistent errors keep resurfacing without flooding the logs. - Non-retryable failures (
ApplicationError(non_retryable=True)or a type listed in the policy'snon_retryable_error_types) are final and logged immediately. - Cancellations and benign application errors are never logged.
- Retries cut off by
schedule_to_close_timeoutare not detectable on the worker - the final attempt of a limited-retry activity may go unlogged when the timeout, not the attempt count, ends the retries.
Each log record carries a temporal_activity dict (activity_id, activity_type, attempt,
task_queue, workflow_id, workflow_run_id, workflow_type) in extra for structured logging.
To customize the behavior, subclass the interceptor and point the INTERCEPTORS setting to your class:
from django_temporalio.logs import ActivityFailureLoggingInterceptor
class MyInterceptor(ActivityFailureLoggingInterceptor):
def should_log(self, info, err) -> bool:
if isinstance(err, SomeExpectedError):
return False
return super().should_log(info, err)
Independently of the interceptor, the SDK logs every failed attempt as a WARNING on the
temporalio.activity logger. If your logging setup routes that logger (or the root logger) to a
real sink, django_temporalio.logs.ActivityFailureThrottleFilter throttles those records with the
same rules and schedule; the attempts/every kwargs override the settings per instance
(every=0 disables). Records processed outside the activity context (e.g. behind a QueueHandler)
fall back to the attempt schedule:
LOGGING = {
...
"filters": {
"throttle_activity_failures": {
"()": "django_temporalio.logs.ActivityFailureThrottleFilter",
# optionally override the schedule: "attempts": (1, 50), "every": 500,
},
},
"loggers": {
"temporalio.activity": {
"handlers": ["console"],
"level": "WARNING",
"filters": ["throttle_activity_failures"],
},
},
}
Records that are not activity failures pass through untouched; the filter identifies them by the
__temporal_error_identifier marker the SDK sets on its failure records.
Management Commands
To see a queue's registered activities and workflows:
$ ./manage.py show_temporalio_queue_registry
To start a worker defined in the settings (for production):
$ ./manage.py start_temporalio_worker <worker_name>
To start a worker for development (starts a worker for each registered queue, WORKER_CONFIGS setting is ignored):
$ ./manage.py start_temporalio_worker --all
To sync schedules with Temporal.io:
$ ./manage.py sync_temporalio_schedules
To see what sync operation would do without actually syncing:
$ ./manage.py sync_temporal_schedules --dry-run
Configuration
You can configure the app using the following settings:
DJANGO_TEMPORALIO: A dictionary containing the following keys:
- CLIENT_CONFIG: A dictionary of kwargs that are passed to the
temporalio.client.Client.connectmethod on the client initialization, defaults to{} - WORKER_CONFIGS: A dictionary containing worker configurations.
The key is the worker name and the value is a
temporalio.worker.WorkerConfiginstance. - BASE_MODULE: A python module that holds workflows, activities and schedules, defaults to
None - INTERCEPTORS: A list of import strings of
temporalio.worker.Interceptorclasses workers are started with, defaults to() - ACTIVITY_FAILURE_LOG_ATTEMPTS: Attempt numbers on which an activity failure is logged,
defaults to
(1, 10, 100, 1000) - ACTIVITY_FAILURE_LOG_EVERY: After the attempts above, log every Nth attempt,
set to
Noneto disable, defaults to1000
Making a new release
This project makes use of RegioHelden's reusable GitHub workflows.
Make a new release by manually triggering the Open release PR workflow.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_temporalio-3.0.0.tar.gz.
File metadata
- Download URL: django_temporalio-3.0.0.tar.gz
- Upload date:
- Size: 23.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38349cc50333f32dffc277ea7aee33b20a51c2ae40dc4c293ed418b8f1f1723b
|
|
| MD5 |
df499f3253d19e5905ca6dd181c62dd7
|
|
| BLAKE2b-256 |
151feb848209e39837fc72dc0b5680942f5dd6615601e38537dfa0d393cad1d2
|
File details
Details for the file django_temporalio-3.0.0-py3-none-any.whl.
File metadata
- Download URL: django_temporalio-3.0.0-py3-none-any.whl
- Upload date:
- Size: 40.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
31e01f000cd64d5f159cef7627c5e058c418d05a276925a1d620a52976fd3df7
|
|
| MD5 |
20104ea89b407accc6798279b9410cfd
|
|
| BLAKE2b-256 |
3372fa3217b8c0a3c190785f425d31f3836f9e2c71e50d9cf958531a1936148a
|