Database instrumentation

To help you understand and control the queries issued by your code, Djangoprovides a hook for installing wrapper functions around the execution ofdatabase queries. For example, wrappers can count queries, measure queryduration, log queries, or even prevent query execution (e.g. to make sure thatno queries are issued while rendering a template with prefetched data).

The wrappers are modeled after middleware –they are callables which take another callable as one of their arguments. Theycall that callable to invoke the (possibly wrapped) database query, and theycan do what they want around that call. They are, however, created andinstalled by user code, and so don’t need a separate factory like middleware do.

Installing a wrapper is done in a context manager – so the wrappers aretemporary and specific to some flow in your code.

As mentioned above, an example of a wrapper is a query execution blocker. Itcould look like this:

  1. def blocker(*args):
  2. raise Exception('No database access allowed here.')

And it would be used in a view to block queries from the template like so:

  1. from django.db import connection
  2. from django.shortcuts import render
  3.  
  4. def my_view(request):
  5. context = {...} # Code to generate context with all data.
  6. template_name = ...
  7. with connection.execute_wrapper(blocker):
  8. return render(request, template_name, context)

The parameters sent to the wrappers are:

  • execute – a callable, which should be invoked with the rest of theparameters in order to execute the query.
  • sql – a str, the SQL query to be sent to the database.
  • params – a list/tuple of parameter values for the SQL command, or alist/tuple of lists/tuples if the wrapped call is executemany().
  • many – a bool indicating whether the ultimately invoked call isexecute() or executemany() (and whether params is expected to bea sequence of values, or a sequence of sequences of values).
  • context – a dictionary with further data about the context ofinvocation. This includes the connection and cursor.Using the parameters, a slightly more complex version of the blocker couldinclude the connection name in the error message:
  1. def blocker(execute, sql, params, many, context):
  2. alias = context['connection'].alias
  3. raise Exception("Access to database '{}' blocked here".format(alias))

For a more complete example, a query logger could look like this:

  1. import time
  2.  
  3. class QueryLogger:
  4.  
  5. def __init__(self):
  6. self.queries = []
  7.  
  8. def __call__(self, execute, sql, params, many, context):
  9. current_query = {'sql': sql, 'params': params, 'many': many}
  10. start = time.monotonic()
  11. try:
  12. result = execute(sql, params, many, context)
  13. except Exception as e:
  14. current_query['status'] = 'error'
  15. current_query['exception'] = e
  16. raise
  17. else:
  18. current_query['status'] = 'ok'
  19. return result
  20. finally:
  21. duration = time.monotonic() - start
  22. current_query['duration'] = duration
  23. self.queries.append(current_query)

To use this, you would create a logger object and install it as a wrapper:

  1. from django.db import connection
  2.  
  3. ql = QueryLogger()
  4. with connection.execute_wrapper(ql):
  5. do_queries()
  6. # Now we can print the log.
  7. print(ql.queries)

connection.execute_wrapper()

  • executewrapper(_wrapper)
  • Returns a context manager which, when entered, installs a wrapper arounddatabase query executions, and when exited, removes the wrapper. The wrapper isinstalled on the thread-local connection object.

wrapper is a callable taking five arguments. It is called for every queryexecution in the scope of the context manager, with arguments execute,sql, params, many, and context as described above. It’sexpected to call execute(sql, params, many, context) and return the returnvalue of that call.