Skip to main content

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.

Metadata

Release files for django-tortoise-objects 0.1.2

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-tortoise-objects 0.1.2
File Size Uploaded Provenance Actions
django_tortoise_objects-0.1.2.tar.gz 674.2 kB With provenance Details for django_tortoise_objects-0.1.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-tortoise-objects 0.1.2
File Interpreter ABI Platform Actions
django_tortoise_objects-0.1.2-py3-none-any.whl Python 3 none any Details for django_tortoise_objects-0.1.2-py3-none-any.whl

Total release size: 708.0 kB

Release files / django_tortoise_objects-0.1.2.tar.gz

Download URL django_tortoise_objects-0.1.2.tar.gz
Size 674.2 kB
Tags Source
SHA-256 checksum
How to use checksums
db7a0b3a220d01a367d0197a5a8b0e8f17f60eee9185a12a89960ab3c7234361
BLAKE2b-256 checksum
How to use checksums
018a3fdb5486832514de9d9c86da24ab8f1756c8abccf1da54b1366feb7127f5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Feb 13, 2026.

Transparency log

Release files / django_tortoise_objects-0.1.2-py3-none-any.whl

Download URL django_tortoise_objects-0.1.2-py3-none-any.whl
Size 33.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
28befa5e00b4954091294ca12ae1813adb75668b51795e765db6d565cb6f5a38
BLAKE2b-256 checksum
How to use checksums
6ca5d1c3903a40e097b9dd1d39aec1ae07afa5dacca775e8e6923941572d6105
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Feb 13, 2026.

Transparency log

Security for django-tortoise-objects 0.1.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_tortoise_objects-0.1.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Feb 13, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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