richardhapb/django-check: Static N+1 query detection for Django (LSP-based)


Static N+1 query detection for Django (LSP-based)

django-check is a static analyzer and Language Server that detects N+1 query patterns in Django code before runtime. It inspects queryset construction and related-field access to warn when relations are accessed without proper prefetching (select_related, prefetch_related).

image

It works inside the editor or directly from CLI.

Warning

This project is in active development and not yet production-ready.

  • APIs are unstable
  • Diagnostics may be incomplete or incorrect
  • Expect breaking changes without notice

Django’s ORM makes it easy to accidentally introduce N+1 queries that:

  • pass tests,
  • look correct in code review,
  • only show up under load.

Runtime tools (django-silk, nplusone) are focuesd in runtime optimiezation. django-check make static analysis before the runtime.

  • Compute the graph of the Models in the app
  • Zero runtime overhead
  • LSP-based diagnostics at edit time
  • No code instrumentation required
  • Works with any editor that supports LSP

Not available yet, should be compiled from source.

Usage: djch 

Commands:
  server  Start as a Language Server (normally handled by the IDE)
  check   Analyze the current directory tree for N+1 queries
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help (see more with '--help')
  -V, --version  Print version

Check from CLI using djch check. You will get an output like this:

app/foo/bar/views/tier1.py:210:22: [N+1] rp.ticker_benchmark
Potential N+1 query: accessing `rp.ticker_benchmark` inside loop

app/apps/crawler/tasks.py:48:17: [N+1] ticker.industry
Potential N+1 query: accessing `ticker.industry` inside loop

app/apps/crawler/views.py:62:20: [N+1] stream.streamer
Potential N+1 query: accessing `stream.streamer` inside loop

app/apps/foo/selectors/anointed.py:43:19: [N+1] anointed.pattern
Potential N+1 query: accessing `anointed.pattern` inside loop

Neovim 0.11+ (native LSP)

Neovim 0.11 ships with a stable built-in LSP client.

Minimal setup:

-- lua/init.lua
vim.lsp.enable("djch")
-- lsp/djch.lua
return {
  cmd = { "djch", "server" },
  filetypes = { "python" },
  root_markers = { 'manage.py', 'pyproject.toml', '.git' }
}

This registers django-check as a first-class LSP server.

If you are already attaching multiple LSPs to Python buffers (e.g. Pyright), Neovim will merge diagnostics correctly.

You can install the VSCode extension from, or directly in VSCode Extensions Market Place.

https://marketplace.visualstudio.com/items?itemName=richardhapb.Django-Check

# N+1 query detected
users = User.objects.all()
profiles = [user.profile in user for users]  # N+1

Explanation:

  • users is evaluated once
  • user.profile triggers one query per iteration
users = User.objects.select_related("profile").all()
profiles = [user.profile in user for users]

users = User.objects.all()
for user in users:
    user.profile.bio # N+1

Explanation:

  • users is evaluated once
  • user.profile triggers one query per iteration
users = User.objects.select_related("profile").all()
for user in users:
    user.profile.bio

django-check will clear the diagnostic once the relation is prefetched.

How it works (high level)

  1. Parse Python source into an AST
  2. Identify Django model classes and relationships
  3. Track QuerySet-producing expressions
  4. Track iteration boundaries
  5. Detect attribute access that implies ORM resolution
  6. Verify whether the required relation is prefetched
  • Zero config
  • Just works out of the box
  • Editor feedback must be actionable, not noisy

Limitations (work in progress)

  • Interprocedural analysis requires type hints on QuerySet parameters
  • Limited understanding of:
    • annotate, aggregate
    • complex custom managers
  • Custom queryset method summaries
  • Templates integration

This project lives at the intersection of:

  • Python AST
  • Django ORM semantics
  • LSP protocol design

If you are interested in any of those, contributions are welcome.

Documentation contributions are welcome, the goal is make this tool easy to use.

MIT



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *