| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
`filter(**lookups)` is a shorthand for `objects().where(...)` built from keyword arguments, for when the criteria arrive as data (query params, config) rather than being written by hand. `criteria(**lookups)` returns the same lookups as a where clause, so they compose with `|` and `&`. `where` is unchanged, and stays the recommended style. Closes piccolo-orm#1413 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| Back | FazBrowse Home | New Git URL |
Follows on from #1413. Draft — the shape is up for discussion.
Why filter and not where
You asked whether the lookup syntax should go into Table.where too. I don't think it should:
kwargs can only ever mean AND, and where can do much more than that.
Django hit this exact wall, which is why it has Q objects. Teaching where the kwargs syntax
would import that problem into the part of Piccolo that doesn't have it. A separate filter is
honest about being the limited one:
What it does
It returns Objects, so it chains as usual:
The payoff is that a lookup from a payload is the lookup you pass to the query:
criteria for OR
This is Django's Q, but it doesn't need to be a lazy expression tree the way Django's is —
Piccolo's expressions already compose, so criteria is just a constructor for clauses you
already have. Which means it drops into where untouched:
That felt like a better answer to your question than putting lookups in where: you get the
data-driven case and keep one expression language.
On Table rather than a compat module
I tried it as a compat.django mixin first, per your suggestion, and moved it back for three
reasons:
anything from table_reflection — you can't add a mixin to those, and "filter this from query
params" lands on them often.
*prefetch, called with arguments in piccolo/query/methods/objects.py:107 and on user table
classes in piccolo/columns/m2m.py:366, plus six places in piccolo_api. And with no abstract=
flag on __init_subclass__, Model itself becomes a table called model that table_finder
picks up.
cls: type[TableInstance] on one ("the erased type of self is not a supertype of its class"),
so it needs a Protocol scaffold that disappears entirely inside Table.
filter is the 25th classmethod on Table, and shadows a same-named column exactly the way
select/objects/delete already do — I checked, a column called select works fine today.
There's an assert_type in tests/type_checking.py confirming await Band.filter(...) still
infers list[Band].
Happy to move it back to compat.django if you'd rather — it's the same code either way.
Lookups
field[__related_field...][__transform][__op]
A column always beats a suffix sharing its name, so both of these work:
Two things worth knowing:
popularity__gte="1000" works on SQLite and fails on Postgres. That's true of
where(Band.popularity >= "1000") too; filter doesn't change it. Should it convert against
column.value_type? Django does. I left it out as it's a bigger decision.
foreign keys into other tables. Documented with a warning.
Left out on purpose, happy to add: exclude(), ~criteria (needs __invert__ on
CombinableMixin), __isnull, __contains.
Notes
criteria normalises them to WhereRaw the way WhereDelegate.where does. There's a test.
select. WhereRaw doesn't, so a transform over a foreign key needed a small JoinedWhereRaw
in lookups.py that repeats the rewrite. If you'd rather that lived on WhereRaw itself,
say so — it'd fix the same case for anyone passing a raw QueryString to where.
applied around :790-825 — and its version can't traverse joins, because line 792 is a flat
getattr(self.table, field_name).
Questions
in Piccolo, and clause is the word the docs use most.
expressions? Dropping criteria is a clean retreat.
Tests pass on SQLite and Postgres; lint.sh and pyright are clean.