Django database backend for libSQL/Turso — SQLite-compatible remote databases over HTTP
Project description
django-libsql-backend
Django database backend for libSQL and Turso — supports both remote Turso databases over HTTP and local SQLite files.
Drop-in replacement for Django's built-in SQLite backend. Use a local .sqlite3 file during development, then switch to a production Turso URL with zero code changes — same ENGINE, same ORM, same migrations.
Features
- Dual-mode — auto-detects local file paths vs remote URLs from the
NAMEsetting - 100% remote when you need it — no local SQLite files, no file locks, no persistent connections
- Full local when you need it — real transactions, WAL mode, FK enforcement
- SQLite-compatible — delegates to Django's built-in SQLite schema editor and introspection
- Migrations —
makemigrations,migrate,sqlmigratework as expected - ORM — full queryset API, aggregations, annotations,
ON CONFLICT(upsert) - Admin — Django admin works out of the box
- Authentication — password hashing, sessions, permissions
- Type mapping — Django model fields map to correct SQLite column types
- Batch API —
executemany()uses Turso's/v1/batchendpoint for efficient bulk inserts
Requirements
| Dependency | Minimum Version |
|---|---|
| Python | 3.10+ |
| Django | 4.2+ (tested on 5.0, 5.1, 6.0) |
| Turso / libSQL database | SQLite 3.31+ (Turso currently ships 3.45) |
The backend uses only Python's standard library (urllib, json) — no additional Python packages are required.
Installation
pip install django-libsql-backend
Quick Start
1. Get a Turso database
Create a database and grab the HTTP URL and an auth token:
turso db create my-django-app
turso db tokens create my-django-app
2. Configure Django
In settings.py:
DATABASES = {
"default": {
"ENGINE": "django_libsql",
"NAME": "https://my-django-app-org.turso.io",
"AUTH_TOKEN": "your-jwt-auth-token-here",
"OPTIONS": {
"timeout": 30,
},
}
}
NAME accepts any of these forms:
| Form | Example | Connection type |
|---|---|---|
| Full HTTPS URL | https://my-db-org.turso.io |
Remote (Turso HTTP) |
| Bare hostname | my-db-org.turso.io |
Remote (Turso HTTP) |
libsql:// URL |
libsql://my-db-org.turso.io |
Remote (converted to HTTPS) |
| Absolute file path | /var/data/db.sqlite3 |
Local (sqlite3) |
| Relative file path | ./local_dev.db |
Local (sqlite3) |
| Bare filename | db.sqlite3 |
Local (sqlite3) |
| In-memory | :memory: |
Local (sqlite3) |
3. Run migrations
python manage.py migrate
That's it. Django is now running against your remote Turso database.
Local development workflow
During local development, just point NAME at a file path — no Turso account needed:
DATABASES = {
"default": {
"ENGINE": "django_libsql",
"NAME": "dev.sqlite3",
}
}
Run python manage.py migrate and you're developing locally with full SQLite transaction support, WAL mode, and FK enforcement. When you're ready to deploy, change NAME to your Turso URL and add the AUTH_TOKEN — that's the only change needed.
Configuration Reference
| Setting | Required | Default | Description |
|---|---|---|---|
ENGINE |
Yes | — | "django_libsql" |
NAME |
Yes | — | Database URL, hostname, or local file path |
AUTH_TOKEN |
Remote only | "" |
Turso platform auth token (JWT) |
OPTIONS.timeout |
No | 30 |
HTTP request timeout in seconds (remote only) |
Environment variables
For production, store the auth token in an environment variable rather than hard-coding it:
import os
DATABASES = {
"default": {
"ENGINE": "django_libsql",
"NAME": os.environ["TURSO_DB_URL"],
"AUTH_TOKEN": os.environ["TURSO_AUTH_TOKEN"],
}
}
Architecture
┌──────────┐ HTTP POST /v1/execute ┌──────────────┐
│ Django │ ───────────────────────────────► │ Turso / │
│ ORM / │ HTTP POST /v1/batch │ libSQL │
│ Migrations│ ◄─────────────────────────────── │ Server │
└──────────┘ JSON (typed-value) └──────────────┘
How it differs from Django's built-in SQLite backend
| | Django SQLite | django-libsql-backend (local) | django-libsql-backend (remote) |
|---|---|---|---|---|
| Transport | Local file I/O | Local file I/O | HTTP REST API |
| Connection | Persistent sqlite3.Connection | Persistent sqlite3.Connection | Stateless — each request = new connection |
| Transactions | Real SQLite transactions | Real SQLite transactions | Buffered writes flushed as batch on commit; rollback may not recover flushed writes |
| Savepoints | Supported | Supported | Client-side buffer snapshots only (no server-side SAVEPOINT) |
| DDL rollback | Supported | Supported | Not supported — each DDL auto-commits |
| FK deferral | Supported | Supported | Not supported — needs persistent PRAGMA state |
| INSERT...RETURNING | Supported (3.35+) | Supported (3.35+) | Not used — would bypass write buffering; lastrowid used instead |
| PRAGMAs | Persistent per connection | Persistent per connection | Reset every HTTP request |
| File locking | Yes | Yes | None — Turso handles concurrency |
| Schema changes | _remake_table | _remake_table | Same, each DDL is its own HTTP call |
Internal module layout
django_libsql/
├── __init__.py # Exports DatabaseWrapper, __version__
├── base.py # DatabaseWrapper, TursoCursor, TursoHTTPConnection
├── features.py # SQLite-compatible feature flags (39 flags)
├── operations.py # SQL generation (date/time, upsert, operators)
├── schema.py # Proxy to Django's SQLite schema editor
├── introspection.py # Proxy to Django's SQLite introspection
├── creation.py # Test database create/destroy
└── client.py # CLI: python manage.py dbshell → turso db shell
Supported Django Field Types
All standard Django model fields are supported with correct SQLite column types:
| Django Field | SQLite Column Type |
|---|---|
AutoField / BigAutoField / SmallAutoField |
integer AUTOINCREMENT |
CharField / SlugField / FileField / FilePathField |
varchar(N) |
TextField |
text |
IntegerField |
integer |
BigIntegerField |
bigint |
BooleanField |
bool |
FloatField |
real |
DecimalField |
decimal |
DateField |
date |
DateTimeField |
datetime |
TimeField |
time |
DurationField |
bigint |
BinaryField |
BLOB |
JSONField |
text (with JSON_VALID check constraint) |
UUIDField |
char(32) |
IPAddressField |
char(15) |
GenericIPAddressField |
char(39) |
PositiveIntegerField |
integer unsigned |
Known Limitations
These apply only to remote (Turso HTTP) mode. Local mode has full SQLite transaction support.
Stateless HTTP connection
Each Turso HTTP request creates a new SQLite connection. This means:
PRAGMAsettings do not persist between requests.- DDL cannot be rolled back — each schema change auto-commits immediately.
- FK constraint checks cannot be deferred — needs persistent connection state.
Transactions
Remote mode uses client-side write buffering for best-effort atomicity:
- Writes inside
atomic()blocks are buffered in memory - On commit (outermost
atomicexit), all buffered writes are flushed as a single batch request - Reading inside a transaction auto-flushes buffered writes first (read-your-writes)
- Accessing
cursor.lastrowidalso triggers a flush - Once flushed, writes cannot be rolled back — rollback raises
DatabaseError
Savepoints are client-side buffer snapshots — they track buffer length but don't send SAVEPOINT SQL to the server. Rolling back to a savepoint truncates the buffer; releasing is a no-op.
SQL function availability
These Django ORM functions use Python-registered SQL functions that are not available on remote servers:
- Hash:
MD5,SHA1,SHA224,SHA256,SHA384,SHA512 - String:
LPad,RPad,Repeat,Reverse - Math:
Cot,Sign,BitXor(raisesNotSupportedError) - Aggregate:
StdDev,Variance(raiseNotSupportedError)
Timezone handling
SQLite has no native timezone support. In remote mode, all date/time SQL is generated using strftime/date/time without timezone conversion. Results are correct when the database stores UTC (Django's default). Queries involving non-UTC timezone conversion may produce incorrect results.
Schema migrations
_remake_table (used by ALTER TABLE operations) may fail for tables referenced by foreign keys due to the stateless PRAGMA issue. Most makemigrations/migrate operations work correctly for the common path of creating new tables and adding columns.
Development & Testing
# Clone the repo
git clone https://github.com/CyberCalculus/django-libsql-backend.git
cd django-libsql-backend
# Install in development mode
pip install -e .
# Run Django checks
python manage.py check
# Run migrations
python manage.py migrate
Troubleshooting
"Turso HTTP 401"
Your auth token is invalid or expired. Generate a new one:
turso db tokens create <db-name>
"Turso connection error: timed out"
Check network connectivity to your Turso database URL. Increase the timeout in OPTIONS if needed:
"OPTIONS": {"timeout": 60}
"RuntimeError: No connection established"
The HTTP connection hasn't been initialized. This usually means the database settings are misconfigured — verify NAME and AUTH_TOKEN.
Migrations fail with "no such table: django_migrations"
Run python manage.py migrate — this table is created by Django's first migration.
License
MIT. See LICENSE for details.
Related Projects
- Turso — Managed libSQL platform
- libSQL — Open-source SQLite fork
- Django SQLite backend — Built-in backend this project is modeled on
Contributing
Issues and pull requests are welcome. Before opening a PR, please:
- Run
python manage.py checkagainst a Turso database - Verify
python manage.py migrateruns cleanly - Test basic ORM operations (create, read, update, delete)
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_libsql_backend-0.1.1.tar.gz.
File metadata
- Download URL: django_libsql_backend-0.1.1.tar.gz
- Upload date:
- Size: 28.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
80164f9abba5392474e055564f3150ea3afc3912dd0735d7131053341e87d3a9
|
|
| MD5 |
1a1bedaad51305f5af6d23c1a40e27ec
|
|
| BLAKE2b-256 |
e2ae5e430eed3d8eb7c6109a61f59abcb873347f1b303299fa0344fc985ec460
|
Provenance
The following attestation bundles were made for django_libsql_backend-0.1.1.tar.gz:
Publisher:
publish.yml on CyberCalculus/django-libsql-backend
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_libsql_backend-0.1.1.tar.gz -
Subject digest:
80164f9abba5392474e055564f3150ea3afc3912dd0735d7131053341e87d3a9 - Sigstore transparency entry: 1523371576
- Sigstore integration time:
-
Permalink:
CyberCalculus/django-libsql-backend@6355c019b3515ad27001bdb7b10e59898df0b096 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/CyberCalculus
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6355c019b3515ad27001bdb7b10e59898df0b096 -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_libsql_backend-0.1.1-py3-none-any.whl.
File metadata
- Download URL: django_libsql_backend-0.1.1-py3-none-any.whl
- Upload date:
- Size: 27.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
49311dda6264b4e9dfa547955e2bdf9203eed7b9b838f99b99c11a25cb84fdd0
|
|
| MD5 |
86eb5596eebbb1cdccda90ecbc257807
|
|
| BLAKE2b-256 |
28202d2531b63e43d8de528ee5eedef0b1c143b32c15472b106949d17a22e338
|
Provenance
The following attestation bundles were made for django_libsql_backend-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on CyberCalculus/django-libsql-backend
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_libsql_backend-0.1.1-py3-none-any.whl -
Subject digest:
49311dda6264b4e9dfa547955e2bdf9203eed7b9b838f99b99c11a25cb84fdd0 - Sigstore transparency entry: 1523371584
- Sigstore integration time:
-
Permalink:
CyberCalculus/django-libsql-backend@6355c019b3515ad27001bdb7b10e59898df0b096 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/CyberCalculus
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6355c019b3515ad27001bdb7b10e59898df0b096 -
Trigger Event:
push
-
Statement type: