Skip to content

feat: add per-field default order by#196

Merged
gazorby merged 3 commits into
mainfrom
feat/default-order-by
Jun 13, 2026
Merged

feat: add per-field default order by#196
gazorby merged 3 commits into
mainfrom
feat/default-order-by

Conversation

@gazorby

@gazorby gazorby commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Add configurable default ordering for GraphQL list fields and accept ordering inputs via a renamed parameter (order_by_input).
    • Default ordering is applied when clients omit orderBy and integrates with deterministic tiebreaking and distinct-on handling.
  • Tests

    • New unit and integration tests cover default ordering, validation errors, multi-column defaults, pagination interaction, and distinct-on behavior.
  • Documentation

    • README examples updated to use order_by_input= in schema examples.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 94ee5401-e636-4ebb-a95c-fdd734ccae60

📥 Commits

Reviewing files that changed from the base of the PR and between e07677d and bd3b092.

📒 Files selected for processing (1)
  • src/strawchemy/dto/strawberry.py

📝 Walkthrough

Walkthrough

This PR adds default_order_by configuration throughout Strawchemy: a new OrderByExpr type and decompose_order_by utility support order-by expression introspection; the Strawchemy.field() API changes from order_by to order_by_input while introducing default_order_by; and the full stack (field, repository, transpiler) applies client-omitted defaults with deterministic ordering semantics.

Changes

Default Order By Feature

Layer / File(s) Summary
Type definitions and order decomposition utilities
src/strawchemy/typing.py, src/strawchemy/dto/strawberry.py, tests/unit/test_order_by_expr.py
New OrderByExpr type alias representing SQLAlchemy UnaryExpression or InstrumentedAttribute; _DecomposedOrderBy dataclass and decompose_order_by() function unwrap order expressions into column keys and OrderByEnum directions, raising StrawchemyFieldError for invalid modifiers or unresolvable columns.
Public API contract: mapper and field signatures
src/strawchemy/mapper.py
Strawchemy.field() overloads and implementation signature updated: order_by=order_by_input= (client-provided ordering input) and new default_order_by= parameter (server default). Docstring describes interaction with deterministic ordering and primary-key tiebreaker.
Field-level configuration and validation
src/strawchemy/schema/field.py, src/strawchemy/schema/factories/types.py
StrawchemyField constructor accepts default_order_by, normalizes to list, validates expressions target root-model columns on list fields only, and forwards to repository. Factory's _relation_field wiring updated from order_by= to order_by_input=.
Repository layer configuration wiring
src/strawchemy/repository/sqlalchemy/_base.py, src/strawchemy/repository/strawberry/_async.py, src/strawchemy/repository/strawberry/_sync.py
SQLAlchemy and Strawberry repositories accept default_order_by parameter, store as instance attribute, and forward to QueryTranspiler during executor construction.
Query transpiler: default ordering logic
src/strawchemy/transpiler/_transpiler.py
Transpiler constructor accepts default_order_by, new _default_order_columns() helper decomposes expressions and validates columns, _order_by() appends defaults when user omits orderBy, and _build_query() generates ordering when defaults are present.
Integration test schema updates
tests/integration/types/mysql.py, tests/integration/types/postgres.py, tests/integration/types/sqlite.py
Test schemas update existing query fields from order_by= to order_by_input= and add new fields demonstrating default_order_by across pagination and distinct-on scenarios with corresponding FruitDistinctOn helper types.
Unit tests: validation and error cases
tests/unit/test_order_by_expr.py, tests/unit/mapping/test_schemas.py, tests/unit/schemas/default_order_by_*.py, tests/unit/schemas/*/field_order_by*.py
decompose_order_by behavior validated with parametrized tests; error tests confirm StrawchemyFieldError raised when default order applied to non-list fields or cross-model columns; schema files updated from order_by= to order_by_input=.
Integration tests: default ordering behavior
tests/integration/test_default_order_by.py
Comprehensive test coverage: default ordering applies when client omits orderBy, client-provided orderBy overrides defaults, SQL snapshots verify deterministic+PK tiebreaker output, pagination adapts defaults via subquery aliasing, and distinct-on uses RANK window with defaults.
Documentation: README examples
README.md
Quick Start, Resolver Generation, and Ordering examples updated to use order_by_input= parameter replacing order_by=.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A rabbit's note on ordering defaults:
When queries come without their sort, I hop and tuck a steady sort.
Columns decomposed, directions clear, defaults apply when clients veer.
Tests confirm the SQL sings, with PK ties and ordering things.
🥕🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a per-field default order by feature across the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/default-order-by

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.26%. Comparing base (a1b6052) to head (bd3b092).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #196      +/-   ##
==========================================
+ Coverage   93.06%   93.26%   +0.19%     
==========================================
  Files          69       69              
  Lines        6215     6338     +123     
  Branches      821      853      +32     
==========================================
+ Hits         5784     5911     +127     
+ Misses        288      285       -3     
+ Partials      143      142       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/strawchemy/schema/field.py`:
- Around line 261-264: Replace the incorrect type-based check using
is_list(type_) with the instance-level list determination self.is_list when
validating self._default_order_by: in the method where the block raises
StrawchemyFieldError for `default_order_by`, change the condition to `if not
self.is_list:` (or equivalent) so fields flagged as list via `self.is_list`
(e.g., root_aggregations=True) are treated as list fields; keep the same error
message and raise StrawchemyFieldError when the instance is not a list.
- Around line 265-269: The code dereferences
decompose_order_by(expr).element.entity_namespace without checking existence;
update the loop that processes self._default_order_by to first retrieve element
= decompose_order_by(expr).element and guard access (e.g., use
hasattr/hasattr-like check or getattr with a sentinel) before comparing to
model, and if the attribute is missing raise StrawchemyFieldError with the same
message; ensure you reference
dto_model_from_type(strawberry_contained_user_type(type_))/decompose_order_by
and the StrawchemyFieldError to locate the fix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9b8c679a-c6bb-439d-a0ca-ed9e4ef731f8

📥 Commits

Reviewing files that changed from the base of the PR and between e8ef6a3 and e07677d.

⛔ Files ignored due to path filters (1)
  • tests/integration/__snapshots__/test_default_order_by.ambr is excluded by !**/__snapshots__/**
📒 Files selected for processing (25)
  • README.md
  • src/strawchemy/dto/strawberry.py
  • src/strawchemy/mapper.py
  • src/strawchemy/repository/sqlalchemy/_base.py
  • src/strawchemy/repository/strawberry/_async.py
  • src/strawchemy/repository/strawberry/_sync.py
  • src/strawchemy/schema/factories/types.py
  • src/strawchemy/schema/field.py
  • src/strawchemy/transpiler/_transpiler.py
  • src/strawchemy/typing.py
  • tests/integration/test_default_order_by.py
  • tests/integration/types/mysql.py
  • tests/integration/types/postgres.py
  • tests/integration/types/sqlite.py
  • tests/unit/mapping/test_schemas.py
  • tests/unit/schemas/default_order_by_invalid.py
  • tests/unit/schemas/default_order_by_non_list.py
  • tests/unit/schemas/include/all_order_by.py
  • tests/unit/schemas/order/field_order_by.py
  • tests/unit/schemas/order/field_order_by_all.py
  • tests/unit/schemas/order/field_order_by_specific_fields.py
  • tests/unit/schemas/order/order_config_all_with_field_override.py
  • tests/unit/schemas/order/order_config_with_field_override.py
  • tests/unit/schemas/override/override_argument.py
  • tests/unit/test_order_by_expr.py

Comment thread src/strawchemy/schema/field.py
Comment on lines +265 to +269
model = dto_model_from_type(strawberry_contained_user_type(type_))
for expr in self._default_order_by:
if decompose_order_by(expr).element.entity_namespace is not model:
msg = f"`default_order_by` expression is not a column of {model.__name__}"
raise StrawchemyFieldError(msg)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard entity_namespace access to avoid uncaught runtime errors.

Line 267 dereferences .entity_namespace unconditionally. If the decomposed element doesn’t expose that attribute, this raises AttributeError instead of the expected StrawchemyFieldError.

Suggested fix
             model = dto_model_from_type(strawberry_contained_user_type(type_))
             for expr in self._default_order_by:
-                if decompose_order_by(expr).element.entity_namespace is not model:
+                decomposed = decompose_order_by(expr)
+                entity_namespace = getattr(decomposed.element, "entity_namespace", None)
+                if entity_namespace is not model:
                     msg = f"`default_order_by` expression is not a column of {model.__name__}"
                     raise StrawchemyFieldError(msg)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
model = dto_model_from_type(strawberry_contained_user_type(type_))
for expr in self._default_order_by:
if decompose_order_by(expr).element.entity_namespace is not model:
msg = f"`default_order_by` expression is not a column of {model.__name__}"
raise StrawchemyFieldError(msg)
model = dto_model_from_type(strawberry_contained_user_type(type_))
for expr in self._default_order_by:
decomposed = decompose_order_by(expr)
entity_namespace = getattr(decomposed.element, "entity_namespace", None)
if entity_namespace is not model:
msg = f"`default_order_by` expression is not a column of {model.__name__}"
raise StrawchemyFieldError(msg)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/strawchemy/schema/field.py` around lines 265 - 269, The code dereferences
decompose_order_by(expr).element.entity_namespace without checking existence;
update the loop that processes self._default_order_by to first retrieve element
= decompose_order_by(expr).element and guard access (e.g., use
hasattr/hasattr-like check or getattr with a sentinel) before comparing to
model, and if the attribute is missing raise StrawchemyFieldError with the same
message; ensure you reference
dto_model_from_type(strawberry_contained_user_type(type_))/decompose_order_by
and the StrawchemyFieldError to locate the fix.

@gazorby gazorby merged commit 227bc21 into main Jun 13, 2026
96 checks passed
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.

1 participant