API Reference¶
Auto-generated documentation
This page uses mkdocstrings to render API documentation directly from source code docstrings. Some references may not render if module paths differ from the documented structure.
Core¶
QueryDoctorMiddleware¶
The Django middleware that intercepts queries for each request and runs them through the analysis pipeline.
QueryDoctorMiddleware(get_response)
¶
Django middleware that activates query diagnosis per request.
Installs an execute_wrapper on the database connection to capture all SQL queries. After the response is generated, runs all enabled analyzers and sends reports to configured reporters.
Works under both WSGI and ASGI. The class declares async_capable = False
deliberately: Django then adapts it with sync_to_async(thread_sensitive=True),
which runs it in the same thread-sensitive executor Django runs all ORM work
in. Because connections["default"] is thread-local, that co-location is
what lets the execute_wrapper installed here see the queries the view issues.
Declaring async_capable = True places the middleware on the event loop
thread instead, where it wraps a connection object the ORM never touches and
captures nothing. This is ordinary behaviour for any sync-capable middleware
under ASGI, not a query-doctor compromise.
Initialize the middleware.
The async predicate comes from asgiref.sync, which is the one Django
itself uses. inspect.iscoroutinefunction does not recognise
asgiref-wrapped callables before Python 3.12, so it would put a
directly-instantiated middleware on the sync path and run the analysis
stage before the view body.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
get_response
|
Callable[..., Any]
|
The next middleware or view in the chain. |
required |
Source code in src/query_doctor/middleware.py
__acall__(request)
async
¶
Process an async request through the query doctor pipeline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
HttpRequest
|
The incoming HTTP request. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The HTTP response from the async view. |
Source code in src/query_doctor/middleware.py
__call__(request)
¶
Process a request through the query doctor pipeline.
Routes to sync or async path based on the get_response type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
HttpRequest
|
The incoming HTTP request. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The HTTP response from the view. |
Source code in src/query_doctor/middleware.py
Context Managers¶
Context managers for targeted query analysis outside of the middleware.
context_managers
¶
Context managers for targeted query diagnosis.
Provides diagnose_queries() for diagnosing queries within a specific code block rather than an entire request.
diagnose_queries()
¶
Context manager for targeted query diagnosis.
Captures and analyzes all SQL queries executed within the context. The DiagnosisReport is yielded and populated after the context exits.
Synchronous only. Entering the block from a coroutine emits a
QueryDoctorWarning and captures nothing -- use the middleware for
async views. See docs/guides/async-support.md.
Usage
with diagnose_queries() as report: # ... your ORM code here ... print(report.issues)
Source code in src/query_doctor/context_managers.py
Decorators¶
Function and method decorators for query diagnosis and budgets.
decorators
¶
Decorators for query diagnosis and budget enforcement.
Provides @diagnose for wrapping functions with automatic query analysis, and @query_budget for enforcing query count and time limits.
diagnose(func)
¶
Decorator that diagnoses queries executed within a function.
Wraps the function with diagnose_queries() context manager. After execution, the DiagnosisReport is attached as func._query_doctor_report.
Usage
@diagnose def my_view(request): return Book.objects.all()
Source code in src/query_doctor/decorators.py
query_budget(max_queries=None, max_time_ms=None)
¶
Decorator that enforces query budget limits on a function.
Raises QueryBudgetError if the function exceeds the specified query count or time limits. Falls back to config defaults if no explicit limits are provided.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_queries
|
int | None
|
Maximum number of queries allowed. None means no limit. |
None
|
max_time_ms
|
float | None
|
Maximum total query time in milliseconds. None means no limit. |
None
|
Usage
@query_budget(max_queries=10, max_time_ms=100) def my_view(request): return Book.objects.all()
Source code in src/query_doctor/decorators.py
Data Types¶
Severity¶
Severity levels for prescriptions.
Severity
¶
Bases: Enum
Severity level for a diagnosed issue.
Prescription¶
The data class returned by analyzers describing a detected issue and its fix.
Prescription(issue_type, severity, description, fix_suggestion, callsite, query_count=0, time_saved_ms=0, fingerprint='', extra=dict())
dataclass
¶
A diagnosed issue with an actionable fix.
CapturedQuery¶
Information captured for each SQL query during interception.
CapturedQuery(sql, params, duration_ms, fingerprint, normalized_sql, callsite, is_select, tables)
dataclass
¶
A single SQL query captured during a request.
DiagnosisReport¶
Aggregated report of all prescriptions for a request or command.
DiagnosisReport(prescriptions=list(), total_queries=0, total_time_ms=0, captured_queries=list())
dataclass
¶
Analyzers¶
BaseAnalyzer¶
The abstract base class that all analyzers implement. Subclass this to create custom analyzers.
BaseAnalyzer
¶
Bases: ABC
Base class for all query analyzers.
analyze(queries, models_meta=None)
abstractmethod
¶
Analyze captured queries and return prescriptions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
list[CapturedQuery]
|
List of captured SQL queries to analyze. |
required |
models_meta
|
dict[str, Any] | None
|
Reserved, and currently always None. Nothing in
the package passes it: the single call site,
|
None
|
Returns:
| Type | Description |
|---|---|
list[Prescription]
|
List of Prescription objects describing detected issues and fixes. |
Source code in src/query_doctor/analyzers/base.py
is_enabled()
¶
Check if this analyzer is enabled in config.
Source code in src/query_doctor/analyzers/base.py
NPlusOneAnalyzer¶
Detects N+1 query patterns using fingerprint-based grouping.
NPlusOneAnalyzer
¶
Bases: BaseAnalyzer
Analyzer that detects N+1 query patterns.
Groups queries by fingerprint and identifies repeated SELECT queries that indicate missing select_related() or prefetch_related() calls.
analyze(queries, models_meta=None)
¶
Analyze queries for N+1 patterns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
list[CapturedQuery]
|
List of captured queries to analyze. |
required |
models_meta
|
dict[str, Any] | None
|
Optional model metadata (not used currently). |
None
|
Returns:
| Type | Description |
|---|---|
list[Prescription]
|
List of prescriptions for detected N+1 issues. |
Source code in src/query_doctor/analyzers/nplusone.py
DuplicateAnalyzer¶
Detects exact-duplicate queries (same SQL and same bound parameters) executed more than once within a single request.
DuplicateAnalyzer
¶
Bases: BaseAnalyzer
Analyzer that detects exact-duplicate queries.
Groups queries by a hash of SQL text + params. A query executed with
the same SQL and the same parameter values more than threshold times
is flagged. Queries with the same SQL structure but different parameter
values are not considered duplicates by this analyzer.
analyze(queries, models_meta=None)
¶
Analyze queries for duplicate patterns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
list[CapturedQuery]
|
List of captured queries to analyze. |
required |
models_meta
|
dict[str, Any] | None
|
Optional model metadata (not used). |
None
|
Returns:
| Type | Description |
|---|---|
list[Prescription]
|
List of prescriptions for detected duplicate issues. |
Source code in src/query_doctor/analyzers/duplicate.py
MissingIndexAnalyzer¶
Detects queries filtering or ordering on non-indexed columns.
MissingIndexAnalyzer
¶
Bases: BaseAnalyzer
Analyzer that detects queries on non-indexed columns.
Examines WHERE and ORDER BY clauses to find columns that lack database indexes, and suggests adding Meta.indexes with models.Index().
analyze(queries, models_meta=None)
¶
Analyze queries for missing index issues.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
list[CapturedQuery]
|
List of captured queries to analyze. |
required |
models_meta
|
dict[str, Any] | None
|
Optional model metadata (not used). |
None
|
Returns:
| Type | Description |
|---|---|
list[Prescription]
|
List of prescriptions for detected missing index issues. |
Source code in src/query_doctor/analyzers/missing_index.py
FatSelectAnalyzer¶
Detects queries selecting more columns than necessary.
FatSelectAnalyzer(field_count_threshold=None)
¶
Bases: BaseAnalyzer
Analyzer that detects overly broad SELECT queries.
Flags queries that select many columns and suggests using .only() or .defer() to reduce data transfer.
Initialize the analyzer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
field_count_threshold
|
int | None
|
Minimum number of columns to flag. Defaults to config or 8. |
None
|
Source code in src/query_doctor/analyzers/fat_select.py
analyze(queries, models_meta=None)
¶
Analyze queries for fat SELECT patterns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
list[CapturedQuery]
|
List of captured queries to analyze. |
required |
models_meta
|
dict[str, Any] | None
|
Optional model metadata (not used currently). |
None
|
Returns:
| Type | Description |
|---|---|
list[Prescription]
|
List of prescriptions for detected fat SELECT issues. |
Source code in src/query_doctor/analyzers/fat_select.py
QuerySetEvalAnalyzer¶
Detects unintended queryset evaluation patterns.
QuerySetEvalAnalyzer
¶
Bases: BaseAnalyzer
Analyzer that detects inefficient queryset evaluation patterns.
Inspects call site code context to find patterns where Django provides more efficient alternatives (count, exists, first).
analyze(queries, models_meta=None)
¶
Analyze queries for inefficient evaluation patterns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
list[CapturedQuery]
|
List of captured queries to analyze. |
required |
models_meta
|
dict[str, Any] | None
|
Optional model metadata (not used). |
None
|
Returns:
| Type | Description |
|---|---|
list[Prescription]
|
List of prescriptions for detected evaluation issues. |
Source code in src/query_doctor/analyzers/queryset_eval.py
QueryComplexityAnalyzer¶
Scores queries by complexity and flags those above threshold.
QueryComplexityAnalyzer
¶
Bases: BaseAnalyzer
Analyzes SQL queries for excessive complexity.
Scores queries based on structural patterns (JOINs, subqueries, aggregations, etc.) and flags those exceeding a configurable threshold.
analyze(queries, models_meta=None)
¶
Analyze captured queries for excessive complexity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
list[CapturedQuery]
|
List of captured SQL queries to analyze. |
required |
models_meta
|
dict[str, Any] | None
|
Optional Django model metadata (unused). |
None
|
Returns:
| Type | Description |
|---|---|
list[Prescription]
|
List of Prescription objects for overly complex queries. |
Source code in src/query_doctor/analyzers/complexity.py
WriteNPlusOneAnalyzer¶
Detects repeated single-row writes and prescribes the bulk equivalent.
WriteNPlusOneAnalyzer
¶
Bases: BaseAnalyzer
Analyzer that detects repeated single-row writes.
Groups non-SELECT queries by fingerprint and reports groups at or over the configured threshold, prescribing the bulk statement that replaces them.
analyze(queries, models_meta=None)
¶
Analyze queries for repeated single-row writes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
list[CapturedQuery]
|
List of captured queries to analyze. |
required |
models_meta
|
dict[str, Any] | None
|
Optional model metadata (not used currently). |
None
|
Returns:
| Type | Description |
|---|---|
list[Prescription]
|
List of prescriptions for detected write N+1 issues. |
Source code in src/query_doctor/analyzers/write_nplusone.py
SerializerMethodAnalyzer¶
Static AST analysis of DRF SerializerMethodField bodies. Unlike the seven
analyzers above it does not read captured queries: analyze() conforms to the
BaseAnalyzer interface and always returns []. Drive it through
analyze_serializer(), or through the check_serializers management command.
SerializerMethodAnalyzer
¶
Bases: BaseAnalyzer
Analyzes DRF serializer classes for SerializerMethodField methods.
Detects methods that may cause N+1 queries.
This is a STATIC analyzer -- it reads source code, not runtime queries.
It should be invoked separately from the runtime query interception pipeline
via the analyze_serializer() method.
Inherits BaseAnalyzer for plugin API compatibility. The analyze() method
(required by BaseAnalyzer) returns an empty list since this analyzer operates
on serializer classes, not captured queries. Use analyze_serializer()
for actual analysis.
analyze(queries, models_meta=None)
¶
Conform to BaseAnalyzer interface. Returns empty list.
This is a static analyzer that operates on serializer classes, not
runtime queries. Use analyze_serializer() instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
list[CapturedQuery]
|
Ignored -- this analyzer does not use captured queries. |
required |
models_meta
|
dict[str, Any] | None
|
Ignored. |
None
|
Returns:
| Type | Description |
|---|---|
list[Prescription]
|
Always an empty list. |
Source code in src/query_doctor/analyzers/serializer_method.py
analyze_serializer(serializer_cls)
¶
Analyze a single serializer class for N+1 patterns.
Finds all SerializerMethodField declarations, locates the corresponding
get_
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
serializer_cls
|
Any
|
A DRF serializer class to analyze. |
required |
Returns:
| Type | Description |
|---|---|
list[Prescription]
|
List of Prescription objects describing detected issues. |
Source code in src/query_doctor/analyzers/serializer_method.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | |
Reporters¶
ConsoleReporter¶
Terminal output with Rich formatting and plain-text fallback.
ConsoleReporter(stream=None, group_by=None)
¶
Formats and prints diagnosis reports to the console.
Uses Rich for styled output if available, otherwise plain text. Supports grouped output mode for related prescriptions.
Initialize the console reporter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stream
|
Any
|
Output stream (file-like object). Defaults to sys.stderr. Accepts TextIO, Django's OutputWrapper, or any writable stream. |
None
|
group_by
|
str | None
|
If set, group prescriptions by this strategy ("file_analyzer", "root_cause", "view"). |
None
|
Source code in src/query_doctor/reporters/console.py
render(report)
¶
Render a diagnosis report as a formatted string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
DiagnosisReport
|
The diagnosis report to render. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Formatted string representation of the report. |
Source code in src/query_doctor/reporters/console.py
report(report)
¶
Print the diagnosis report to the configured stream.
If group_by was set during init, groups related prescriptions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
DiagnosisReport
|
The diagnosis report to print. |
required |
Source code in src/query_doctor/reporters/console.py
JSONReporter¶
Structured JSON output for CI/CD pipelines.
JSONReporter(output_path=None)
¶
Formats diagnosis reports as structured JSON.
Optionally writes the JSON to a file for CI/CD integration.
Initialize the JSON reporter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_path
|
str | None
|
Optional file path to write JSON output. If None, output is only available via render(). |
None
|
Source code in src/query_doctor/reporters/json_reporter.py
render(report)
¶
Render a diagnosis report as a JSON string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
DiagnosisReport
|
The diagnosis report to render. |
required |
Returns:
| Type | Description |
|---|---|
str
|
JSON string representation of the report. |
Source code in src/query_doctor/reporters/json_reporter.py
report(report)
¶
Write the diagnosis report to the configured output path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
DiagnosisReport
|
The diagnosis report to output. |
required |
Source code in src/query_doctor/reporters/json_reporter.py
HTMLReporter¶
Interactive HTML dashboard report.
HTMLReporter(output_path=None)
¶
Generates standalone HTML reports for query diagnosis.
Produces a single HTML file with inline CSS suitable for saving, sharing, or viewing in a browser.
Initialize the HTML reporter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_path
|
str | None
|
Optional file path to write HTML output. |
None
|
Source code in src/query_doctor/reporters/html_reporter.py
render(report)
¶
Render a diagnosis report as a standalone HTML string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
DiagnosisReport
|
The diagnosis report to render. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Complete HTML document as a string. |
Source code in src/query_doctor/reporters/html_reporter.py
report(report)
¶
Write the diagnosis report to the configured output path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
DiagnosisReport
|
The diagnosis report to output. |
required |
Source code in src/query_doctor/reporters/html_reporter.py
LogReporter¶
Python logging integration for production monitoring.
LogReporter
¶
Sends diagnosis reports to Python's logging system.
Each prescription is logged at the appropriate level based on severity.
report(report)
¶
Log the diagnosis report.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
DiagnosisReport
|
The diagnosis report to log. |
required |
Source code in src/query_doctor/reporters/log_reporter.py
OTelReporter¶
OpenTelemetry span export for observability platforms.
OTelReporter(tracer=None)
¶
Reports query diagnosis data via OpenTelemetry spans.
Creates a span for each diagnosis run with attributes for summary metrics and events for each prescription. Sets span status to ERROR if critical issues are found.
If OpenTelemetry is not installed, all operations are no-ops.
Initialize the OTel reporter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tracer
|
Any
|
Optional pre-configured OTel tracer. If None, one is created from the global TracerProvider. |
None
|
Source code in src/query_doctor/reporters/otel_exporter.py
has_otel
property
¶
Whether OpenTelemetry is available.
report(report)
¶
Export diagnosis report as OTel span data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
DiagnosisReport
|
The diagnosis report to export. |
required |
Source code in src/query_doctor/reporters/otel_exporter.py
Configuration¶
get_config¶
Access django-query-doctor settings with defaults.
get_config()
cached
¶
Return the merged configuration, cached after first call.
Reads the QUERY_DOCTOR setting from Django settings and deep-merges it with DEFAULT_CONFIG. The result is cached for performance.
Source code in src/query_doctor/conf.py
Fingerprinting¶
SQL Fingerprinting¶
Normalize and hash SQL statements for grouping.
fingerprint
¶
SQL normalization and fingerprinting for query pattern detection.
Provides functions to normalize SQL queries (replacing literals with placeholders), generate deterministic fingerprints for grouping similar queries, and extract table names from SQL statements.
extract_tables(sql)
¶
Extract the table names a SQL statement reads or writes.
Handles: FROM table, JOIN table, FROM table AS alias, FROM "quoted_table", and subqueries; plus the write forms UPDATE table, INSERT INTO table and DELETE FROM table. Returns a deduplicated list of table names (without quotes or aliases).
Source code in src/query_doctor/fingerprint.py
fingerprint(sql)
¶
Generate a SHA-256 fingerprint (first 16 hex chars) for a SQL query.
Two queries with the same structure but different parameter values will produce the same fingerprint.
Source code in src/query_doctor/fingerprint.py
normalize_sql(sql)
¶
Normalize a SQL query by replacing literals with placeholders.
Replaces quoted strings, numbers, booleans, and IN-clause lists with '?', collapses whitespace, strips semicolons, and lowercases everything.
Source code in src/query_doctor/fingerprint.py
Stack Tracing¶
Source Code Mapping¶
Map captured queries back to user source code locations.
stack_tracer
¶
Stack trace capture for mapping SQL queries to user source code.
Walks the call stack to find the first frame in user code (filtering out Django internals, this package, and stdlib modules) so each query can be attributed to a specific file:line in the application.
capture_callsite(exclude_modules=None)
¶
Walk the stack and find the first frame in user code.
Filters out frames from query_doctor, Django internals, and stdlib. Returns the last remaining frame (closest to the query trigger), or None if no user code frame is found.
Source code in src/query_doctor/stack_tracer.py
Exceptions¶
All exceptions raised by django-query-doctor inherit from QueryDoctorError:
exceptions
¶
Exception hierarchy and warning category for django-query-doctor.
All package exceptions inherit from QueryDoctorError to allow callers to catch any query-doctor-specific error with a single except clause. Runtime advisories are emitted as QueryDoctorWarning so they can be filtered by category without touching other UserWarnings.
QueryBudgetError(message, report=None)
¶
Bases: QueryDoctorError
Raised when a function exceeds its query budget.
Attributes:
| Name | Type | Description |
|---|---|---|
report |
The DiagnosisReport from the function execution. |
Initialize with a message and optional report.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
Human-readable description of the budget violation. |
required |
report
|
DiagnosisReport | None
|
The DiagnosisReport from the function execution. |
None
|
Source code in src/query_doctor/exceptions.py
QueryDoctorError
¶
Bases: Exception
Base exception for all django-query-doctor errors.
QueryDoctorWarning
¶
Bases: UserWarning
Base warning category for query_doctor runtime advisories.
Emitted where query_doctor detects usage that silently does not do
what it appears to do. Suppress this category alone with
-W ignore::query_doctor.QueryDoctorWarning (or the equivalent
filterwarnings entry) without silencing other UserWarnings.