Skip to main content

Django Connection Signals

In this section, we will explore Django's connection signals, which allow you to respond to database connection events in your Django applications.

Introduction to Connection Signals

Django provides a set of signals specifically designed to track database connection activities. These signals are particularly useful when you need to perform certain actions when database connections are established, closed, or when queries are executed.

Connection signals are part of Django's signals framework but specifically focus on database interaction events. They enable you to hook into the database connection lifecycle, which can be valuable for monitoring, logging, debugging, or implementing specific behaviors that need to respond to database connection states.

Available Connection Signals

Django provides several connection-related signals, all defined in django.db.backends.signals:

SignalDescription
connection_createdSent when a database connection is created
connection_initializedSent after a connection is initialized

Additionally, there are query execution related signals:

SignalDescription
pre_executeSent before a SQL query is executed
post_executeSent after a SQL query is executed

Let's explore each of these signals in detail.

The connection_created Signal

This signal is sent when a database connection is first created. This happens when Django needs to interact with the database for the first time in a process.

How to Connect to connection_created

python
from django.db.backends.signals import connection_created
from django.dispatch import receiver

@receiver(connection_created)
def handle_connection_created(sender, connection, **kwargs):
print(f"A database connection was created! Database: {connection.settings_dict['NAME']}")
# You could configure connection settings here
# For example, setting SQLite pragma options:
if connection.vendor == 'sqlite':
# Set SQLite to use foreign keys
cursor = connection.cursor()
cursor.execute('PRAGMA foreign_keys = ON;')

In this example, whenever a new database connection is created, the function handle_connection_created is called. It prints information about the connection and configures SQLite to enforce foreign key constraints if the database is SQLite.

Practical Application: Connection Timeouts

A real-world application might involve setting connection timeouts for PostgreSQL:

python
@receiver(connection_created)
def configure_postgres_connection(sender, connection, **kwargs):
if connection.vendor == 'postgresql':
# Set statement timeout to 30 seconds
with connection.cursor() as cursor:
cursor.execute("SET statement_timeout = 30000;") # milliseconds
print("PostgreSQL statement timeout set to 30 seconds")

Working with connection_initialized

The connection_initialized signal is fired after a connection is fully initialized. This can be useful for setting up database-specific configuration that needs to happen after initialization.

python
from django.db.backends.signals import connection_initialized

@receiver(connection_initialized)
def handle_connection_initialized(sender, connection, **kwargs):
print(f"A database connection was initialized for {connection.alias}")
# Perform actions after connection initialization is complete

Query Execution Signals

Django also provides signals that are dispatched before and after SQL queries are executed.

Using pre_execute Signal

The pre_execute signal is sent just before a SQL query is executed, allowing you to inspect or modify the query.

python
from django.db.backends.signals import pre_execute

@receiver(pre_execute)
def log_query_before_execution(sender, sql, params, hints, **kwargs):
print(f"About to execute SQL: {sql}")
print(f"With parameters: {params}")
# You could log queries here or perform additional checks

Using post_execute Signal

The post_execute signal is sent after a SQL query has been executed, letting you respond to the query's completion.

python
from django.db.backends.signals import post_execute

@receiver(post_execute)
def log_query_after_execution(sender, sql, params, hints, has_result, **kwargs):
print(f"Executed SQL: {sql}")
print(f"Query {'returned results' if has_result else 'did not return results'}")

Real-world Example: Query Performance Monitoring

Here's a practical example of using connection signals to monitor query performance:

python
import time
from django.db.backends.signals import pre_execute, post_execute
from django.dispatch import receiver

# Dictionary to store query start times
query_times = {}

@receiver(pre_execute)
def query_start(sender, sql, **kwargs):
# Generate a unique identifier for this query execution
query_id = id(sql)
query_times[query_id] = time.time()

@receiver(post_execute)
def query_end(sender, sql, **kwargs):
query_id = id(sql)
if query_id in query_times:
execution_time = time.time() - query_times[query_id]
# Log slow queries (more than 100ms)
if execution_time > 0.1:
print(f"Slow query detected! Execution time: {execution_time:.4f}s")
print(f"Query: {sql}")
# Clean up
del query_times[query_id]

This example creates a simple performance monitoring system that detects and logs slow SQL queries. It works by:

  1. Recording the start time when a query is about to be executed
  2. Calculating the execution time when the query completes
  3. Logging queries that take longer than 100ms

Disconnecting Connection Signals

As with all Django signals, it's important to disconnect signal handlers when you no longer need them, especially in test environments or when a signal handler is only needed temporarily:

python
from django.db.backends.signals import connection_created

# Store the function reference
def my_callback(sender, connection, **kwargs):
# Do something with the connection
pass

# Connect the signal
connection_created.connect(my_callback)

# Later, disconnect the signal
connection_created.disconnect(my_callback)

Important Considerations

When working with connection signals, keep these important points in mind:

  1. Performance Impact: Handlers for these signals are called for each connection or query operation, which could introduce overhead if your code is complex.

  2. Thread Safety: Make sure your signal handlers are thread-safe, as database connections might be shared across threads in some configurations.

  3. Connection Pooling: If you're using connection pooling, note that connection_created may not fire for each HTTP request since connections are reused.

  4. Testing: Connection signals can affect test isolation. Always clean up by disconnecting signals in your test teardown methods.

Summary

Django's connection signals provide a powerful mechanism for responding to database connection events, including:

  • Monitoring when connections are created and initialized
  • Intercepting and logging SQL queries before and after execution
  • Configuring database-specific settings when connections are established
  • Building performance monitoring tools for database operations

These signals are particularly useful for debugging, monitoring database activity, and implementing cross-cutting concerns that involve database connections.

Additional Resources

Exercises

  1. Create a signal handler that logs all database queries to a file, including their execution time.

  2. Implement a system that uses connection signals to ensure that certain database settings (like time zone or transaction isolation level) are consistent across all connections.

  3. Build a middleware that uses connection signals to track the number of database queries per HTTP request and logs requests that exceed a threshold.

  4. Use connection signals to implement a feature that forces all connections to use UTC timestamps in a PostgreSQL database.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)