Skip to main content

django-safeql — run untrusted or AI-generated SQL safely

PyPI CI Python Django License

django-safeql

A whitelisted SQL-to-QuerySet transpiler for Django.

django-safeql parses a restricted subset of PostgreSQL SQL, validates it against a schema you declare (which tables, columns, functions and operators are allowed), and compiles it straight into a real Django QuerySet — no raw SQL ever touches the database. It's built for situations where a SQL query comes from an untrusted or semi-trusted source — an LLM answering questions over your data, an end-user-facing query box, a saved-report feature — and has to run safely against your existing Django models.

  • No SQL injection surface — the input is parsed into an AST and compiled to ORM calls; nothing is ever executed as a raw string.
  • Whitelist by construction — only the tables, columns, operators, functions and aggregates you declare in the schema are reachable. Anything else is rejected before it gets near the database.
  • Real QuerySets out — the result is a normal Django QuerySet, so it composes with everything else in your app (pagination, further .filter(), select_related, etc.).

Install

pip install django-safeql

Requires Django ≥ 4.2 and a PostgreSQL database (some supported functions — JSON operators, ARRAY_AGG, STRING_AGG — are Postgres-specific).

Quickstart

from django_safeql import SQLTranspilerSchema, TableSchema, JsonFieldSchema, SQLToQuerySetTranspiler
from myapp.models import Book, Author

schema = SQLTranspilerSchema(
    base_table="book",
    base_queryset=Book.objects.all(),
    tables={
        "book": TableSchema(
            queryset=Book.objects.all(),
            relation="",
            allowed_fields={"id", "title", "status", "author_id", "pages", "details"},
            json_fields={
                "details": JsonFieldSchema(schema={
                    "type": "object",
                    "properties": {
                        "language": {"type": "string"},
                        "edition": {"type": "integer"},
                    },
                }),
            },
        ),
        "author": TableSchema(
            queryset=Author.objects.all(),
            relation="author",
            allowed_fields={"id", "name"},
        ),
    },
    max_limit=1000,
)

transpiler = SQLToQuerySetTranspiler(schema)

queryset = transpiler.to_queryset("""
    SELECT book.title, author.name
      FROM book
      JOIN author ON book.author_id = author.id
     WHERE book.status = 'published'
       AND book.details->>'language' = 'en'
     ORDER BY book.pages DESC
     LIMIT 20
""")

list(queryset)  # a normal QuerySet — evaluate it however you like

Anything outside the declared schema is rejected before touching the database:

transpiler.to_queryset("SELECT * FROM pg_catalog.pg_user")
# django_safeql.ValidationError: Unknown table: pg_user

transpiler.to_queryset("DELETE FROM book WHERE id = 1")
# django_safeql.UnsupportedSQL: ...

How it works

SQLToQuerySetTranspiler runs SQL through four stages:

  1. Parse — sqlglot parses the SQL text (Postgres dialect) into an internal AST.
  2. Annotate — every column, join and function call is resolved against your SQLTranspilerSchema (including JSON Schema-typed JSON fields).
  3. Validate — anything not explicitly whitelisted (unknown table, disallowed function, unsupported syntax, LIMIT above your ceiling, …) raises ValidationError or UnsupportedSQL.
  4. Codegen — the validated AST is compiled into Django ORM constructs (Q, F, Case/When, Subquery, Exists, aggregates, JSON key transforms, casts, date truncation, …) and returned as a QuerySet.

What's supported

  • SELECT / WHERE / JOIN / GROUP BY / HAVING / ORDER BY / LIMIT
  • Comparison, boolean and arithmetic operators
  • Scalar aggregates (COUNT, SUM, AVG, MIN, MAX) and Postgres collection aggregates (ARRAY_AGG, STRING_AGG, JSON_AGG, …)
  • String functions (LOWER, UPPER, TRIM, SUBSTRING, CONCAT, …) and date functions (EXTRACT, DATE_TRUNC, …)
  • Read-only JSON/JSONB access and functions, validated against a JSON Schema you provide per field
  • LATERAL joins over jsonb_array_elements, EXISTS subqueries

Anything not explicitly listed — DDL, writes, arbitrary functions, joins to undeclared tables — is rejected.

Changelog

CHANGELOG.md

License

MIT — see LICENSE.

Metadata

Release files for django-safeql 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-safeql 1.1.0
File Size Uploaded
django_safeql-1.1.0.tar.gz 49.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-safeql 1.1.0
File Interpreter ABI Platform
django_safeql-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 85.3 kB

Release files / django_safeql-1.1.0.tar.gz

Download URL django_safeql-1.1.0.tar.gz
Size 49.2 kB
Tags Source
SHA-256 checksum
How to use checksums
48721c23abd639d8ac0ea2b857eac6b0f6803f7393a332580d98e0e8426674ae
BLAKE2b-256 checksum
How to use checksums
35d4504e4c4548676938354f3823e6b003bce90a91e85cdd01bb535b07e1d581
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release files / django_safeql-1.1.0-py3-none-any.whl

Download URL django_safeql-1.1.0-py3-none-any.whl
Size 36.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
94d935fcc57b448eb074833fd40f1957a4eae5a23fbcc10b8f8c72dfb0d97a2b
BLAKE2b-256 checksum
How to use checksums
3337c43f6fc8b8bbedb9b90f650eaa3f76e99eea7a1653f08a720ca90e44fbc2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release history Release notifications | RSS feed

2.0.0

2 release files

This release

1.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page