Instrumentación de bases de datos

Para ayudarte a entender y controlar las consultas emitidas por tu código, Django proporciona un hook para instalar funciones envolventes alrededor de la ejecución de consultas de base de datos. Por ejemplo, los envolventes pueden contar consultas, medir el tiempo de duración de las consultas, registrar consultas o incluso evitar la ejecución de consultas (por ejemplo, para asegurarte de que no se emitan consultas mientras se está renderizando un template con datos prefetichados).

Los wrappers se modelan después de middleware – son llamables que toman como uno de sus argumentos otro llamable. Llaman a ese llamable para invocar la consulta de base de datos (posiblemente envuelta) y pueden hacer lo que quieran alrededor de esa llamada. Sin embargo, se crean e instalan mediante código del usuario, por lo que no necesitan una fábrica separada como los middleware.

La instalación de un wrapper se hace en un administrador de contexto – por lo tanto, los wrappers son temporales y específicos de alguna flujo en tu código.

Como se mencionó anteriormente, un ejemplo de un wrapper es un bloqueador de ejecución de consultas. Podría verse así:

def blocker(*args):
    raise Exception("No database access allowed here.")

Y se utilizaría en una vista para bloquear las consultas desde el template como se muestra a continuación:

from django.db import connection
from django.shortcuts import render


def my_view(request):
    context = {...}  # Code to generate context with all data.
    template_name = ...
    with connection.execute_wrapper(blocker):
        return render(request, template_name, context)

Los parámetros enviados a los wrappers son:

  • execute – un llamable, que debe ser invocado con los demás parámetros en orden para ejecutar la consulta.

  • sql – una str, la consulta SQL a enviar a la base de datos.

  • params – una lista/tupla de valores de parámetro para el comando SQL, o una lista/tupla de listas/tuplas si la llamada envuelta es executemany().

  • many – un bool que indica si la llamada invocada finalmente es execute() o executemany() (y si params se espera sea una secuencia de valores, o una secuencia de secuencias de valores).

  • context – un diccionario con datos adicionales sobre el contexto de invocación. Esto incluye la conexión y cursor.

Usando los parámetros, una versión ligeramente más compleja del bloqueador podría incluir el nombre de la conexión en el mensaje de error:

def blocker(execute, sql, params, many, context):
    alias = context["connection"].alias
    raise Exception("Access to database '{}' blocked here".format(alias))

Para un ejemplo más completo, un registro de consultas podría verse así:

import time


class QueryLogger:
    def __init__(self):
        self.queries = []

    def __call__(self, execute, sql, params, many, context):
        current_query = {"sql": sql, "params": params, "many": many}
        start = time.monotonic()
        try:
            result = execute(sql, params, many, context)
        except Exception as e:
            current_query["status"] = "error"
            current_query["exception"] = e
            raise
        else:
            current_query["status"] = "ok"
            return result
        finally:
            duration = time.monotonic() - start
            current_query["duration"] = duration
            self.queries.append(current_query)

Crearías un objeto de registro y lo instalarías como un envoltorio:

from django.db import connection

ql = QueryLogger()
with connection.execute_wrapper(ql):
    do_queries()
# Now we can print the log.
print(ql.queries)

connection.execute_wrapper()

execute_wrapper(wrapper)

Devuelve un administrador de contexto que, cuando se ingresa, instala un envoltorio alrededor de las ejecuciones de consultas de base de datos, y cuando se sale, elimina el envoltorio. El envoltorio se instala en el objeto de conexión local a nivel de hilo.

wrapper es una función llamable que toma cinco argumentos. Se llama para cada ejecución de consulta en el ámbito del administrador de contexto, con los argumentos execute, sql, params, many y context descritos anteriormente. Se espera que llame a execute(sql, params, many, context) y devuelva el valor de retorno de esa llamada.