Skip to content

Commit 9494b65

Browse files
Let a schema allow every orderable relation at once (v0.19.2).
Ordering through a row-scoped relation is refused so a sort cannot rank rows the caller may not read, and the override names the relations individually. That reads well by hand and not at all for a schema that generates its order inputs in a loop: the traversable names only exist once the type is built, so there is nothing to name at the call site. allow_scoped_ordering=True opts the whole order input in. Naming relations stays the better habit, since it keeps the decision visible one relation at a time, but a codebase that has decided every readable parent implies a readable child can now say so once. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 0c17b35 commit 9494b65

6 files changed

Lines changed: 30 additions & 5 deletions

File tree

API_REFERENCE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ A field you resolve yourself: your callable receives `self` and runs once per pa
150150

151151
```python
152152
orm.filter(model_or_type, *, name=None, include=None, exclude=None)
153-
orm.order(model_or_type, *, name=None, include=None, exclude=None, allow_scoped_ordering=None)
153+
orm.order(model_or_type, *, name=None, include=None, exclude=None, allow_scoped_ordering=None # names, or True for all)
154154
orm.group(model_or_type, *, name=None, include=None, exclude=None)
155155
orm.aggregate(model_or_type, *, name=None, include=None, exclude=None)
156156
```

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,8 @@ class PostOrder:
648648

649649
`allow_scoped_ordering` only lifts the ordering restriction for the relations you name, on that one order input. `scope_rows` keeps scoping reads and filter traversal exactly as before, and another order input over the same model is unaffected. Naming a relation the order input cannot sort through is an error, so typos fail loudly.
650650

651+
Pass `allow_scoped_ordering=True` to allow every relation that order input can sort through. Naming them one by one is the better habit — it keeps the decision visible per relation — but the names only exist once the type is built, so a schema generating its order inputs in a loop cannot list them. Reach for it when you have decided the rule holds across the model: every readable parent implies a readable child.
652+
651653
If you build with `strawberry.Schema` directly instead of `orm.schema()`, the build-time check does not run; the offending query is rejected at execution instead.
652654

653655
### Common mistakes

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "strawberry-orm"
3-
version = "0.19.1"
3+
version = "0.19.2"
44
description = "Unified, backend-agnostic ORM abstraction for Strawberry GraphQL"
55
readme = "README.md"
66
license = "MIT"

src/strawberry_orm/backends/_base.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,20 @@ def _set_scoped_ordering_allowance(order_type: type, model: type, allowed: Any)
6666
6767
Kept on the order type rather than keyed by model, so one order input
6868
opting in cannot widen another order input over the same model.
69+
70+
``True`` allows every relation this order type can traverse. Naming them
71+
individually is the safer habit, but it cannot be done without first
72+
knowing the set, which only exists once the type is built - so a schema
73+
that has decided its parents always imply readable children would
74+
otherwise have to list them by hand.
6975
"""
76+
traversable = set(getattr(order_type, "_relation_models", {}))
77+
if allowed is True:
78+
order_type._scoped_ordering_allowed = frozenset(traversable) # type: ignore[attr-defined]
79+
return
7080
names = frozenset(allowed or ())
7181
order_type._scoped_ordering_allowed = names # type: ignore[attr-defined]
72-
unknown = names - set(getattr(order_type, "_relation_models", {}))
82+
unknown = names - traversable
7383
if unknown:
7484
raise ValueError(
7585
f"allow_scoped_ordering names {sorted(unknown)} on "
@@ -878,7 +888,11 @@ def order_type(
878888
*,
879889
include: list[str] | tuple[str, ...] | set[str] | None = None,
880890
exclude: list[str] | tuple[str, ...] | set[str] | None = None,
881-
allow_scoped_ordering: list[str] | tuple[str, ...] | set[str] | None = None,
891+
allow_scoped_ordering: list[str]
892+
| tuple[str, ...]
893+
| set[str]
894+
| bool
895+
| None = None,
882896
) -> Callable[[type], type]:
883897
"""Decorator that builds a ``@oneOf`` order input from a user class.
884898

tests/abstract/filter_traversal_scoping.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,15 @@ def test_override_keeps_filter_traversal_scoped(self):
189189
schema = self.build(allow=["author"])
190190
assert titles(self.execute(schema, NESTED_HIDDEN)) == []
191191

192+
def test_true_allows_every_relation_the_type_can_order_through(self):
193+
"""The names only exist once the type is built, so a schema that has
194+
decided its parents imply readable children cannot list them itself."""
195+
assert self.build(allow=True) is not None
196+
197+
def test_true_still_keeps_reads_scoped(self):
198+
schema = self.build(allow=True)
199+
assert titles(self.execute(schema, NESTED_HIDDEN)) == []
200+
192201
def test_override_must_name_a_relation_that_can_be_ordered_through(self):
193202
with pytest.raises(ValueError, match="allow_scoped_ordering"):
194203
self.build(allow=["not_a_relation"])

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)