Skip to main content

Static N+1 query detector for Django ORM – fast Rust-powered checker

Project description

django-check

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

Why this exists

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.

Key properties

  • 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

What it detects (today)

  • Iteration over QuerySets followed by related-field access

  • Missing select_related / prefetch_related for:

    • ForeignKey
    • OneToOne
    • ManyToMany
    • Inheritance
    • Reverse relations (with related_name explicit or not)
    • Complex chained relations like model__relation__child_relation__depth
    • Prefetch usage: Prefetch("relation", queryset=Relation.objects.select_related("child")

Installation

pip install djch

Or with uv:

uv pip install djch

This installs the djch binary to your PATH.

CLI

Usage: djch <COMMAND>

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

Editor integration (LSP)

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.

VS Code (Extension)

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

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

Examples

Problematic code

# 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

Corrected code

users = User.objects.select_related("profile").all()
profiles = [user.profile in user for users]

Problematic code

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

Corrected code

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

Design philosophy

  • 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

Roadmap

  • Custom queryset method summaries
  • Templates integration

Contributing

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.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

djch-0.1.2-py3-none-win_amd64.whl (2.5 MB view details)

Uploaded Python 3Windows x86-64

djch-0.1.2-py3-none-musllinux_1_2_aarch64.whl (2.4 MB view details)

Uploaded Python 3musllinux: musl 1.2+ ARM64

djch-0.1.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (2.3 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARMv7l

djch-0.1.2-py3-none-macosx_11_0_arm64.whl (2.3 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

File details

Details for the file djch-0.1.2-py3-none-win_amd64.whl.

File metadata

  • Download URL: djch-0.1.2-py3-none-win_amd64.whl
  • Upload date:
  • Size: 2.5 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for djch-0.1.2-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 2bd6157752de6c562ef109f186886c7b90942f0e09b97ea6a74526970aed0d02
MD5 e07e80dc084202278ae45ba5910a341e
BLAKE2b-256 6fd7bdf41d5acb8cb7683f5921e845f1a28074250f122044a7a97d564cfc669e

See more details on using hashes here.

File details

Details for the file djch-0.1.2-py3-none-musllinux_1_2_aarch64.whl.

File metadata

  • Download URL: djch-0.1.2-py3-none-musllinux_1_2_aarch64.whl
  • Upload date:
  • Size: 2.4 MB
  • Tags: Python 3, musllinux: musl 1.2+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for djch-0.1.2-py3-none-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 636f2ec0077e051b0d955d959cbbbc7083192bf0f1342d0b8a9bac0407aea245
MD5 5c63ae0e5042ebdfd2fd4c2ae7ab4de7
BLAKE2b-256 ecc5d9cf135a9ccb916ce368a83257426a103e8f5bd41c53cf01cb3f87a5796b

See more details on using hashes here.

File details

Details for the file djch-0.1.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl.

File metadata

  • Download URL: djch-0.1.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
  • Upload date:
  • Size: 2.3 MB
  • Tags: Python 3, manylinux: glibc 2.17+ ARMv7l
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for djch-0.1.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm Hash digest
SHA256 6d8378ed10535b18a50e3f9f60f7dc3fa9cbfdb775c093412165d7fdd47d6731
MD5 3322cdabed82710340db291f75653539
BLAKE2b-256 8c5666ae28629c37cd3ed48f3b3ae0046c79e81c2bf37f212d0921c218465748

See more details on using hashes here.

File details

Details for the file djch-0.1.2-py3-none-macosx_11_0_arm64.whl.

File metadata

  • Download URL: djch-0.1.2-py3-none-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 2.3 MB
  • Tags: Python 3, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for djch-0.1.2-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1bdfa67790fff38ffb46f0909c24afe706f4c59eedad26ede820ac7386e4b981
MD5 a36a8b0593963bdfe2a5573fd6d1500c
BLAKE2b-256 ff6673a9735f41e9c393f3b6d72c43fa126146c553b9ceefda60ddc8793241ff

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page