Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
16d303b
refactor: new paginate/order api
gazorby Dec 28, 2025
abf6ba0
test: add tests
gazorby Dec 29, 2025
18ba6e5
fix: is_field_included logic
gazorby Dec 29, 2025
5afbf12
test: add test for offset default config
gazorby Dec 31, 2025
912c82e
refactor(config): make pagination setting global; add analogous order…
gazorby Jan 1, 2026
f972983
docs(readme): update pagination/ordering sections
gazorby Jan 1, 2026
9fcd3ee
refactor: minor fixes
gazorby Jan 1, 2026
01276e4
refactor: allow include field iterables for order and distinct_on
gazorby Jan 8, 2026
dff45dc
refactor: allow IncludeFields on order_by, distinct_on and pagination…
gazorby Jan 9, 2026
701b55c
refactor: update
gazorby Mar 8, 2026
c7fe563
ref: next iteration
gazorby May 7, 2026
e5f7709
ref: encapsulate strawchemy object attributes
gazorby May 14, 2026
771b8e4
ref: centralize field_map populate
gazorby May 15, 2026
2dd8ccd
test(nox): reuse venv
gazorby May 15, 2026
ac4a4cc
buld(mise): remove python tool
gazorby May 15, 2026
2d37d34
ci(test): use setup-uv action
gazorby May 15, 2026
d377419
ci: add setup-uv to other job needing it
gazorby May 15, 2026
07a433b
test: adjust nox dependencies
gazorby May 15, 2026
6122783
test: remove -t flag from nox command
gazorby May 16, 2026
46d37a2
test: drop UV_PYTHON_PREFERENCE
gazorby May 16, 2026
641305d
test(nox): only include test group in nox sessions
gazorby May 16, 2026
d5fbd1e
ci(test): do not install python when installing now
gazorby May 16, 2026
184db04
ci(test): constraint dependencies in uv run phase
gazorby May 16, 2026
b035407
ci(test): restore setup-uv cache-python
gazorby May 16, 2026
ca619d7
build: update default groups
gazorby May 16, 2026
d5d145b
ci(nox): drop venv reuse
gazorby May 16, 2026
a8e87cf
Revert "ci(nox): drop venv reuse"
gazorby May 16, 2026
5e30467
ci(nox): try setting uv link mode to copy
gazorby May 16, 2026
baff8c2
ci(nox): set UV_PYTHON_INSTALL_DIR and add python install dir to cach…
gazorby May 16, 2026
b3dc416
ci: fix lint job
gazorby May 16, 2026
0010b8b
ci(lint): ignore .uv-python for pyright
gazorby May 16, 2026
640756e
ci(test): remove python version from nox cache key
gazorby May 16, 2026
e66c5f0
ci(test): bring back UV_PYTHON_PREFERENCE=only-managed
gazorby May 16, 2026
62338ee
ci(test): use --frozen when installing nox
gazorby May 16, 2026
c2c3c71
build(mise): arg format migration
gazorby May 16, 2026
79a03cd
ref(factories): wrap kwargs in typed dicts to collapse long signatures
gazorby May 22, 2026
01b2260
ref(factories): renaming
gazorby May 22, 2026
227913a
ref: move some logic to StrawchemyDefinition
gazorby May 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: 🔂 Tests and linting
env:
COLUMNS: 120 # Makes the error summary table printed by pytest-pretty much easier to read
PROJECT_NAME: "strawchemy"
UV_PYTHON_INSTALL_DIR: ${{ github.workspace }}/.uv-python

on:
push:
Expand Down Expand Up @@ -49,6 +50,11 @@ jobs:
cache: true
log_level: debug

- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
enable-cache: true

- name: Generate test matrix
id: set-matrix
shell: bash
Expand All @@ -74,19 +80,24 @@ jobs:
id: docker-buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3

- name: Pip and nox cache
- name: nox cache
id: cache
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
with:
path: |
~/.cache
~/.nox
.nox
${{ github.workspace }}/.uv-python
key:
${{ runner.os }}-nox-${{ matrix.session.session }}-${{ env.pythonLocation }}-${{
${{ runner.os }}-nox-${{ matrix.session.session }}-${{
hashFiles('**/uv.lock') }}-${{ hashFiles('**/noxfile.py') }}
restore-keys: |
${{ runner.os }}-nox-${{ matrix.session.session }}-${{ env.pythonLocation }}
${{ runner.os }}-nox-${{ matrix.session.session }}

- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
enable-cache: true

- name: Setup mise
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
Expand Down Expand Up @@ -196,6 +207,11 @@ jobs:
cache: true
log_level: debug

- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
enable-cache: true

- name: Install dependencies
run: mise run uv:install

Expand Down
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@ mise.local.toml
*.sqlite

.windsurf

CLAUDE.md

.serena
docs/superpowers
192 changes: 172 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Generates GraphQL types, inputs, queries and resolvers directly from SQLAlchemy
- [Mapping SQLAlchemy Models](#mapping-sqlalchemy-models)
- [Resolver Generation](#resolver-generation)
- [Pagination](#pagination)
- [Ordering](#ordering)
- [Filtering](#filtering)
- [Aggregations](#aggregations)
- [Mutations](#mutations)
Expand Down Expand Up @@ -567,20 +568,25 @@ def farms(self) -> str:
Strawchemy supports offset-based pagination out of the box.

<details>
<summary>Pagination example:</summary>
<summary>Pagination examples</summary>

Enable pagination on fields:
### Field-Level Pagination

Enable pagination on specific fields:

```python
from strawchemy.schema.pagination import DefaultOffsetPagination


@strawberry.type
class Query:
# Enable pagination with default settings
# Enable pagination with default settings (limit=100, offset=0)
users: list[UserType] = strawchemy.field(pagination=True)
# Customize pagination defaults
users_custom_pagination: list[UserType] = strawchemy.field(pagination=DefaultOffsetPagination(limit=20))

# Customize pagination defaults for this specific field
users_custom: list[UserType] = strawchemy.field(
pagination=DefaultOffsetPagination(limit=20, offset=10)
)
```

In your GraphQL queries, you can use the `offset` and `limit` parameters:
Expand All @@ -594,10 +600,40 @@ In your GraphQL queries, you can use the `offset` and `limit` parameters:
}
```

You can also enable pagination for nested relationships:
### Config-Level Pagination

Enable pagination globally for all list fields:

```python
@strawchemy.type(User, include="all", child_pagination=True)
from strawchemy import Strawchemy, StrawchemyConfig

strawchemy = Strawchemy(
StrawchemyConfig(
"postgresql",
pagination="all", # Enable on all list fields
pagination_default_limit=100, # Default limit
pagination_default_offset=0, # Default offset
)
)


@strawchemy.type(User, include="all")
class UserType:
pass


@strawberry.type
class Query:
# This field automatically has pagination enabled
users: list[UserType] = strawchemy.field()
```

### Type-level pagination

Enable pagination for nested relationships from a specific type:

```python
@strawchemy.type(User, include="all", paginate="all")
class UserType:
pass
```
Expand All @@ -619,6 +655,118 @@ Then in your GraphQL queries:

</details>

## Ordering

Strawchemy provides flexible ordering capabilities for query results.

<details>
<summary>Ordering examples</summary>

### Field-Level Ordering

Define ordering inputs and use them on specific fields:

```python
# Create order by input
@strawchemy.order(User, include="all")
class UserOrderBy:
pass


@strawberry.type
class Query:
users: list[UserType] = strawchemy.field(order_by=UserOrderBy)
```

Query with ordering:

```graphql
{
users(orderBy: [{ name: ASC }, { createdAt: DESC }]) {
id
name
createdAt
}
}
```

Available ordering options:

- `ASC` - Ascending order
- `DESC` - Descending order
- `ASC_NULLS_FIRST` - Ascending with nulls first
- `ASC_NULLS_LAST` - Ascending with nulls last
- `DESC_NULLS_FIRST` - Descending with nulls first
- `DESC_NULLS_LAST` - Descending with nulls last

### Type-Level Ordering

Enable ordering automatically on a type:

```python
@strawchemy.type(User, include="all", order="all")
class UserType:
pass
```

This automatically generates and applies an order by input for all fields using this type.

### Config-Level Ordering

Enable ordering globally for all list fields:

```python
from strawchemy import Strawchemy, StrawchemyConfig

strawchemy = Strawchemy(
StrawchemyConfig(
"postgresql",
order_by="all", # Enable ordering on all list fields
)
)


@strawchemy.type(User, include="all")
class UserType:
pass


@strawberry.type
class Query:
# This field automatically has ordering enabled
users: list[UserType] = strawchemy.field()
```

With this configuration, all list fields will automatically have an `orderBy` argument without needing to specify it per
field.

### Nested Relationship Ordering

Order nested relationships:

```python
@strawchemy.type(User, include="all", order="all")
class UserType:
pass
```

Query with nested ordering:

```graphql
{
users(orderBy: [{ name: ASC }]) {
id
name
posts(orderBy: [{ title: ASC }]) {
id
title
}
}
}
```

</details>

## Filtering

Strawchemy provides powerful filtering capabilities.
Expand Down Expand Up @@ -1950,18 +2098,20 @@ Configuration is made by passing a `StrawchemyConfig` to the `Strawchemy` instan

### Configuration Options

| Option | Type | Default | Description |
|----------------------------|-------------------------------------------------------------|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------|
| `dialect` | `SupportedDialect` | | Database dialect to use. Supported dialects are "postgresql", "mysql", "sqlite". |
| `session_getter` | `Callable[[Info], Session]` | `default_session_getter` | Function to retrieve SQLAlchemy session from strawberry `Info` object. By default, it retrieves the session from `info.context.session`. |
| `auto_snake_case` | `bool` | `True` | Automatically convert snake cased names to camel case in GraphQL schema. |
| `repository_type` | `type[Repository] \| StrawchemySyncRepository` | `StrawchemySyncRepository` | Repository class to use for auto resolvers. |
| `filter_overrides` | `OrderedDict[tuple[type, ...], type[SQLAlchemyFilterBase]]` | `None` | Override default filters with custom filters. This allows you to provide custom filter implementations for specific column types. |
| `execution_options` | `dict[str, Any]` | `None` | SQLAlchemy execution options for repository operations. These options are passed to the SQLAlchemy `execution_options()` method. |
| `pagination_default_limit` | `int` | `100` | Default pagination limit when `pagination=True`. |
| `pagination` | `bool` | `False` | Enable/disable pagination on list resolvers by default. |
| `default_id_field_name` | `str` | `"id"` | Name for primary key fields arguments on primary key resolvers. |
| `deterministic_ordering` | `bool` | `True` | Force deterministic ordering for list resolvers. |
| Option | Type | Default | Description |
|-----------------------------|-------------------------------------------------------------|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------|
| `dialect` | `SupportedDialect` | | Database dialect to use. Supported dialects are "postgresql", "mysql", "sqlite". |
| `session_getter` | `Callable[[Info], Session]` | `default_session_getter` | Function to retrieve SQLAlchemy session from strawberry `Info` object. By default, it retrieves the session from `info.context.session`. |
| `auto_snake_case` | `bool` | `True` | Automatically convert snake cased names to camel case in GraphQL schema. |
Comment thread
gazorby marked this conversation as resolved.
| `repository_type` | `type[Repository] \| StrawchemySyncRepository` | `StrawchemySyncRepository` | Repository class to use for auto resolvers. |
| `filter_overrides` | `OrderedDict[tuple[type, ...], type[SQLAlchemyFilterBase]]` | `None` | Override default filters with custom filters. This allows you to provide custom filter implementations for specific column types. |
| `execution_options` | `dict[str, Any]` | `None` | SQLAlchemy execution options for repository operations. These options are passed to the SQLAlchemy `execution_options()` method. |
| `default_id_field_name` | `str` | `"id"` | Name for primary key fields arguments on primary key resolvers. |
| `deterministic_ordering` | `bool` | `True` | Force deterministic ordering for list resolvers. |
| `pagination` | `Literal["all"] \| None` | `None` | Enable/disable pagination on list resolvers by default. Set to `"all"` to enable pagination on all list fields. |
| `order_by` | `Literal["all"] \| None` | `None` | Enable/disable order by on list resolvers by default. Set to `"all"` to enable ordering on all list fields. |
| `pagination_default_limit` | `int` | `100` | Default pagination limit when `pagination=True`. |
| `pagination_default_offset` | `int` | `0` | Default pagination offset when `pagination=True`. |

### Example

Expand All @@ -1980,8 +2130,10 @@ strawchemy = Strawchemy(
"postgresql",
session_getter=get_session_from_context,
auto_snake_case=True,
pagination=True,
pagination="all",
pagination_default_limit=50,
pagination_default_offset=0,
order_by="all",
default_id_field_name="pk",
)
)
Expand Down
10 changes: 5 additions & 5 deletions examples/testapp/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ description = "Basic test app"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"aiosqlite",
"litestar[sqlalchemy,standard]",
"pydantic",
"sqlalchemy",
"strawberry-graphql",
"aiosqlite",
"litestar[sqlalchemy,standard]",
"pydantic",
"sqlalchemy",
"strawberry-graphql",
]

[build-system]
Expand Down
8 changes: 4 additions & 4 deletions examples/testapp/testapp/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from strawchemy import Strawchemy, StrawchemyAsyncRepository, StrawchemyConfig
from testapp.models import Customer, Milestone, Project, Ticket

strawchemy = Strawchemy(StrawchemyConfig("sqlite", repository_type=StrawchemyAsyncRepository))
strawchemy = Strawchemy(StrawchemyConfig("sqlite", repository_type=StrawchemyAsyncRepository, include=["name"]))

# Ticket

Expand All @@ -20,7 +20,7 @@ class TicketOrder: ...
class TicketFilter: ...


@strawchemy.type(Ticket, include="all", filter_input=TicketFilter, order_by=TicketOrder, override=True)
@strawchemy.type(Ticket, include="all", filter_input=TicketFilter, order=TicketOrder, override=True)
Comment thread
gazorby marked this conversation as resolved.
class TicketType: ...


Expand Down Expand Up @@ -55,7 +55,7 @@ class ProjectOrder: ...
class ProjectFilter: ...


@strawchemy.type(Project, include="all", filter_input=ProjectFilter, order_by=ProjectOrder, override=True)
@strawchemy.type(Project, include="all", filter_input=ProjectFilter, order=ProjectOrder, override=True)
class ProjectType: ...


Expand All @@ -66,7 +66,7 @@ class ProjectCreate: ...
# Milestone


@strawchemy.type(Milestone, include="all", override=True)
@strawchemy.type(Milestone, include={"name"}, override=True, distinct_on=["age"], paginate=["projects"])
class MilestoneType: ...
Comment thread
gazorby marked this conversation as resolved.


Expand Down
Loading
Loading