Tortoise ORM relations and filters on sqlite under pytest-asyncio: working versions (tested 2026-10-15)
Model foreign keys, reverse and many-to-many relations in Tortoise ORM, then exercise select_related, prefetch_related, dotted filters, distinct and annotate counts offline on in-memory sqlite.
passed6 passed · 0 failed2026-10-15 21:56 UTC- Status
- passed
- Last passed
- 2026-10-15 21:56 UTC
- Tests
- 6 / 6
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38813530570
The code
Exactly the files that passed. Your agent can pull them with get_recipe("tortoise-orm-sqlite-relations") (install), or see them on GitHub.
tortoise-orm==1.1.9 pytest-asyncio==1.4.0 pytest==9.1.1
from tortoise import fields
from tortoise.models import Model
class Tag(Model):
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=30, unique=True)
class Meta:
table = "tag"
class Author(Model):
id = fields.IntField(primary_key=True)
name = fields.CharField(max_length=50, unique=True)
books: fields.ReverseRelation["Book"]
class Meta:
table = "author"
class Book(Model):
id = fields.IntField(primary_key=True)
title = fields.CharField(max_length=100)
rating = fields.IntField(default=0)
author = fields.ForeignKeyField("models.Author", related_name="books")
tags = fields.ManyToManyField("models.Tag", related_name="books")
class Meta:
table = "book"
import pytest
import pytest_asyncio
from tortoise import Tortoise
from tortoise.exceptions import IntegrityError
from tortoise.functions import Count
from models import Author, Book, Tag
@pytest_asyncio.fixture(autouse=True)
async def db():
await Tortoise.init(db_url="sqlite://:memory:", modules={"models": ["models"]})
await Tortoise.generate_schemas()
yield
await Tortoise.close_connections()
async def make_author(name, *books):
author = await Author.create(name=name)
for title, rating in books:
await Book.create(title=title, rating=rating, author=author)
return author
async def test_select_related_loads_fk():
await make_author("ada", ("A", 5))
book = await Book.get(title="A").select_related("author")
assert book.author.name == "ada"
async def test_prefetch_reverse_relation():
await make_author("ada", ("A", 5), ("B", 3))
author = await Author.get(name="ada").prefetch_related("books")
assert sorted(b.title for b in author.books) == ["A", "B"]
async def test_reverse_filter_returns_duplicate_rows():
await make_author("ada", ("A", 9), ("B", 8))
dupes = await Author.filter(books__rating__gte=8)
assert len(dupes) == 2
unique = await Author.filter(books__rating__gte=8).distinct()
assert [a.name for a in unique] == ["ada"]
async def test_m2m_add_and_filter():
await make_author("ada", ("A", 5))
book = await Book.get(title="A")
tag = await Tag.create(name="python")
await book.tags.add(tag)
tagged = await Tag.get(name="python").prefetch_related("books")
assert [b.title for b in tagged.books] == ["A"]
assert await Book.filter(tags__name="python").count() == 1
async def test_annotate_count_books():
await make_author("ada", ("A", 5), ("B", 3))
await make_author("bob", ("C", 4))
rows = await Author.annotate(n=Count("books")).order_by("-n", "name").values("name", "n")
assert rows == [{"name": "ada", "n": 2}, {"name": "bob", "n": 1}]
async def test_unique_name_raises_integrity_error():
await Author.create(name="ada")
with pytest.raises(IntegrityError):
await Author.create(name="ada")
[pytest] pythonpath = . asyncio_mode = auto
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Alembic autogenerate compare_metadata drift check against SQLite
Declare SQLAlchemy 2 models with a naming convention, then detect schema drift offline by diffing the live SQLite schema against the metadata with Alembic compare_metadata.
FastAPI + SQLAlchemy 2 CRUD with dependency-injected sessions
FastAPI routes backed by SQLAlchemy 2.x typed models, a per-request session dependency, and TestClient tests on SQLite.
SQLAlchemy 2 + Alembic programmatic migrations
Alembic migrations driven from Python (upgrade, downgrade, autogenerate check) against SQLite, with SQLAlchemy 2 models.
Drizzle ORM + better-sqlite3 CRUD
Typed schema, migrations-free table creation and CRUD with Drizzle on an in-memory SQLite database.