Skip to main content

Bridge Django ORM models to Tortoise ORM for truly async database access

Project description

django-tortoise-objects

Bridge Django ORM models to Tortoise ORM for truly async database access. Define your models once in Django, query them asynchronously via Tortoise — no duplicate schema, no manual sync.

Why?

Django's async ORM wraps synchronous calls in sync_to_async threads. Tortoise ORM is natively async but requires its own model definitions. This library eliminates that trade-off:

  • Single source of truth — Django models define the schema, migrations, and admin
  • Truly async queries — Tortoise ORM handles the actual database I/O with native async drivers
  • Zero boilerplate — Add to INSTALLED_APPS, and every model gets a tortoise_objects manager

Installation

pip install django-tortoise-objects

With database drivers:

# PostgreSQL
pip install django-tortoise-objects[pg]

# SQLite
pip install django-tortoise-objects[sqlite]

# MySQL
pip install django-tortoise-objects[mysql]

# All drivers
pip install django-tortoise-objects[all]

Quick Start

1. Add to INSTALLED_APPS

INSTALLED_APPS = [
    # ...
    "django_tortoise",
    # ... your apps
]

2. Query your existing Django models asynchronously

from myapp.models import Article

# All standard query operations — fully async, no sync_to_async wrapper
articles = await Article.tortoise_objects.filter(published=True)
article = await Article.tortoise_objects.get(id=42)
count = await Article.tortoise_objects.count()
exists = await Article.tortoise_objects.filter(title="foo").exists()

# Create, update, delete
article = await Article.tortoise_objects.create(title="Hello", body="World")
await Article.tortoise_objects.filter(published=False).update(draft=True)
await Article.tortoise_objects.filter(id=42).delete()

# Chaining
recent = await Article.tortoise_objects.filter(published=True).order_by("-created_at").limit(10)

# Prefetch relations (via underlying Tortoise model)
employees = await Employee.tortoise_objects.model.all().prefetch_related("team")

3. Use in async Django views

from django.http import JsonResponse
from myapp.models import Tag

async def tag_list(request):
    tags = await Tag.tortoise_objects.all()
    return JsonResponse({"tags": [{"id": t.id, "name": t.name} for t in tags]})

That's it. No Tortoise model definitions, no configuration files, no manual initialization.

Configuration

Optional settings via TORTOISE_OBJECTS in your Django settings:

TORTOISE_OBJECTS = {
    # Include only specific app models (fnmatch patterns)
    "INCLUDE_MODELS": ["myapp.*", "blog.*"],

    # Exclude specific models
    "EXCLUDE_MODELS": ["auth.*", "admin.*"],

    # Override database backend mapping
    "DB_ENGINE_MAP": {
        "django.db.backends.postgresql": "tortoise.backends.psycopg",
    },

    # Connection pool settings per database alias
    "CONNECTION_POOL": {
        "default": {"minsize": 5, "maxsize": 20},
    },

    # Logging level
    "LOG_LEVEL": "WARNING",
}

All settings are optional. With no configuration, all models are included and database backends are auto-detected from Django's DATABASES setting.

Supported Field Types

Django Field Tortoise Field
CharField, SlugField, EmailField, URLField CharField
TextField TextField
IntegerField, BigIntegerField, SmallIntegerField IntField, BigIntField, SmallIntField
BooleanField BooleanField
FloatField FloatField
DecimalField DecimalField
DateField, DateTimeField, TimeField DateField, DatetimeField, TimeField
DurationField TimeDeltaField
UUIDField UUIDField
JSONField JSONField
BinaryField BinaryField
FileField, ImageField CharField (stores path)
ForeignKey ForeignKeyField
OneToOneField OneToOneField
ManyToManyField ManyToManyField
AutoField, BigAutoField, SmallAutoField IntField/BigIntField/SmallIntField (pk=True)

ASGI Lifespan

For production ASGI deployments, explicitly manage Tortoise connections:

# asgi.py
from django_tortoise import init, close

async def lifespan(scope, receive, send):
    if scope["type"] == "lifespan":
        while True:
            message = await receive()
            if message["type"] == "lifespan.startup":
                await init()
                await send({"type": "lifespan.startup.complete"})
            elif message["type"] == "lifespan.shutdown":
                await close()
                await send({"type": "lifespan.shutdown.complete"})
                return

If you don't set up lifespan, connections are initialized lazily on first query.

Generating Static Tortoise Models

By default, Tortoise models are generated dynamically at runtime. If you want static files you can inspect, customize, or version-control, use the management command:

# Generate for all apps
python manage.py generate_tortoise_models --output-dir ./tortoise_models

# Generate for a specific app
python manage.py generate_tortoise_models --app-label demo --output-dir .

# Custom Tortoise app name
python manage.py generate_tortoise_models --tortoise-app-name myapp

This produces one file per Django app (e.g., tortoise_models_demo.py):

# Auto-generated by django-tortoise-objects. Do not edit manually.

import uuid

from tortoise import fields
from tortoise.fields.relational import OnDelete
from tortoise.models import Model


class DepartmentTortoise(Model):
    id = fields.BigIntField(primary_key=True, generated=True)
    name = fields.CharField(max_length=200)
    code = fields.CharField(unique=True, max_length=20)
    budget = fields.DecimalField(default=0, max_digits=14, decimal_places=2)
    is_active = fields.BooleanField(default=True)

    class Meta:
        table = "demo_department"
        app = "django_tortoise"


class TeamTortoise(Model):
    id = fields.BigIntField(primary_key=True, generated=True)
    name = fields.CharField(max_length=200)
    department = fields.ForeignKeyField(
        "django_tortoise.DepartmentTortoise",
        related_name='teams',
        on_delete=OnDelete.CASCADE,
        source_field='department_id',
    )

    class Meta:
        table = "demo_team"
        app = "django_tortoise"

The command respects INCLUDE_MODELS and EXCLUDE_MODELS from your TORTOISE_OBJECTS settings.

Limitations & Non-Goals

Query results are Tortoise model instances, not Django models. Methods like tortoise_objects.get() and tortoise_objects.filter() return Tortoise ORM objects. They cannot be passed directly to Django forms, serializers, admin, or template tags that expect Django model instances. Use tortoise_objects for async read/write paths (APIs, WebSockets, background tasks) and the regular Django ORM for everything else.

This library is not intended to:

  • Replace Django ORM — it is a complementary tool for async-critical paths, not a full substitute. Django ORM remains the right choice for admin, forms, management commands, and sync views.
  • Manage schema or migrations — all schema management is delegated to Django. Tortoise never writes to your database schema.
  • Support cross-ORM transactions — you cannot mix Django and Tortoise queries in a single database transaction.
  • Provide Django admin integration — Tortoise query results don't work with Django's admin site. Use Django's ORM for admin.
  • Expose Tortoise-specific features — Tortoise signals, custom managers, and validators are not bridged.
  • Generate Django models from Tortoise — the bridge is one-way only (Django → Tortoise).

Other things to keep in mind:

  • Tortoise maintains its own connection pool, separate from Django's. Configure pool sizes via the CONNECTION_POOL setting to avoid excess connections.
  • Unsupported or custom Django field types are silently skipped during model generation. Check logs at DEBUG level if a field is missing.
  • ManyToManyField relations require that the related model is also included in the Tortoise bridge (not excluded via EXCLUDE_MODELS).

Performance

Benchmarks comparing tortoise_objects vs Django's native async ORM (aget, acreate, etc.) on the same models and data. Measured on PostgreSQL (both using psycopg).

PostgreSQL

Benchmark results — PostgreSQL

  • tortoise_objects wins on single-record ops: get 1.3-2.0x, count 1.3-1.5x, exists 1.5x
  • tortoise_objects wins on writes: create+delete 1.4-2.6x
  • Bulk fetches nearly tied

See example_project/README.md for full benchmark details, methodology, and raw data.

Requirements

  • Python >= 3.10
  • Django >= 4.2
  • Tortoise ORM >= 1.1.2

License

Apache 2.0 — see LICENSE for details.

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

django_tortoise_objects-0.1.2.tar.gz (674.2 kB view details)

Uploaded Source

Built Distribution

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

django_tortoise_objects-0.1.2-py3-none-any.whl (33.9 kB view details)

Uploaded Python 3

File details

Details for the file django_tortoise_objects-0.1.2.tar.gz.

File metadata

  • Download URL: django_tortoise_objects-0.1.2.tar.gz
  • Upload date:
  • Size: 674.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for django_tortoise_objects-0.1.2.tar.gz
Algorithm Hash digest
SHA256 db7a0b3a220d01a367d0197a5a8b0e8f17f60eee9185a12a89960ab3c7234361
MD5 8a4b66458fdceba0855980c10de8d292
BLAKE2b-256 018a3fdb5486832514de9d9c86da24ab8f1756c8abccf1da54b1366feb7127f5

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_tortoise_objects-0.1.2.tar.gz:

Publisher: publish.yml on tortoise/django-tortoise-objects

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_tortoise_objects-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for django_tortoise_objects-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 28befa5e00b4954091294ca12ae1813adb75668b51795e765db6d565cb6f5a38
MD5 7ca87a03141ff34375ecb88cda1f6b3d
BLAKE2b-256 6ca5d1c3903a40e097b9dd1d39aec1ae07afa5dacca775e8e6923941572d6105

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_tortoise_objects-0.1.2-py3-none-any.whl:

Publisher: publish.yml on tortoise/django-tortoise-objects

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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