Skip to content

Support model instances in forward relation membership filters - #2323

Merged
waketzheng merged 6 commits into
tortoise:developfrom
IlyaKhalafi:feat/relation-instance-membership
Oct 15, 2026
Merged

waketzheng merged 6 commits into
tortoise:developfrom
IlyaKhalafi:feat/relation-instance-membership

Conversation

@IlyaKhalafi

@IlyaKhalafi IlyaKhalafi commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Description

This PR adds in and not_in filters on forward relations using model instances. With this, callers can filter related objects directly, including relations that reference a non-primary unique field.

Fixes #1819

Motivation and Context

Forward relation membership currently treats the lookup as a nested field name. The new lookup uses the existing source-field encoder and operators, preserving annotation filters and invalid lookup errors.

How Has This Been Tested?

Twenty regression cases fail on the original code. The full SQLite suite passes with 1997 passed, 149 skipped and two expected failures. make check passes, including formatting, lint, types, security scanning and package validation. The 23 focused tests also pass on PostgreSQL 18 using both asyncpg and psycopg. MySQL and MSSQL were not run locally.

Checklist

  • I have read the contribution guide
  • My code follows the project style
  • I have added regression tests
  • I have updated the documentation
  • I have added a changelog entry

@codspeed

codspeed Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ 21 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 24 untouched benchmarks


Comparing IlyaKhalafi:feat/relation-instance-membership (17418cb) with develop (ac38923)

Open in CodSpeed

Compare against the integer id column so PostgreSQL can infer the parameter type. Add a second row to verify the annotation selects only the intended relation.
Comment thread CHANGELOG.rst Outdated
Comment thread tortoise/expressions.py
@waketzheng

Copy link
Copy Markdown
Contributor

@IlyaKhalafi I tried the following code snippet:

from tortoise import Tortoise, fields, run_async
from tortoise.models import Model


class Event(Model):
    name = fields.TextField()


class Address(Model):
    city = fields.CharField(max_length=64)

    event: fields.OneToOneRelation[Event] = fields.OneToOneField(
        Event, related_name="address", primary_key=True
    )


async def run():
    await Tortoise.init(db_url="sqlite://:memory:", modules={"models": ["__main__"]})
    await Tortoise.generate_schemas()

    event = await Event.create(name="Test")
    address = await Address.create(city="Santa Monica", event=event)

    print(await Event.filter(address__city__in=[address.city]))
    print("-" * 20)

    try:
        print(await Event.filter(address__in=[address]))
    except Exception as e:
        print(e)


if __name__ == "__main__":
    run_async(run())

And got:

[<Event: 1>]
--------------------
no such column: address.event

Is this the expected result?

@IlyaKhalafi

Copy link
Copy Markdown
Contributor Author

No, that should return the event. I reproduced the same failure on develop at ac38923. The reverse filter uses address.event instead of the database column address.event_id, even when filtering with raw primary keys.

This PR covers the forward direction, where Address.filter(event__in=[event]) now works. The reverse lookup needs a separate column-mapping fix.

@waketzheng

Copy link
Copy Markdown
Contributor

No, that should return the event. I reproduced the same failure on develop at ac38923. The reverse filter uses address.event instead of the database column address.event_id, even when filtering with raw primary keys.

This PR covers the forward direction, where Address.filter(event__in=[event]) now works. The reverse lookup needs a separate column-mapping fix.

Agreed.

@IlyaKhalafi

Copy link
Copy Markdown
Contributor Author

Opened #2338 for the reverse lookup fix, with regression coverage for your example and renamed columns.

@waketzheng
waketzheng merged commit ef637d4 into tortoise:develop Oct 15, 2026
25 checks passed
@waketzheng

Copy link
Copy Markdown
Contributor

I updated examples/relations.py to add .filter(fk__in=...), added a new helper run_in_memory_sqlite to make it easier to run and debug, and updated your comment in tortoise/expression.py to include some examples.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support filtering with relation__in using model instances

2 participants