strawchemy


Namestrawchemy JSON
Version 0.19.0 PyPI version JSON
download
home_pageNone
SummaryGenerate GraphQL API from SQLAlchemy models
upload_time2025-09-06 21:04:40
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseMIT
keywords api sql graphql sqlalchemy strawberry
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Strawchemy

[![🔂 Tests and linting](https://github.com/gazorby/strawchemy/actions/workflows/ci.yaml/badge.svg)](https://github.com/gazorby/strawchemy/actions/workflows/ci.yaml) [![codecov](https://codecov.io/gh/gazorby/strawchemy/graph/badge.svg?token=BCU8SX1MJ7)](https://codecov.io/gh/gazorby/strawchemy) [![PyPI Downloads](https://static.pepy.tech/badge/strawchemy)](https://pepy.tech/projects/strawchemy)

Generates GraphQL types, inputs, queries and resolvers directly from SQLAlchemy models.

## Features

- 🔄 **Type Generation**: Generate strawberry types from SQLAlchemy models

- 🧠 **Smart Resolvers**: Automatically generates single, optimized database queries for a given GraphQL request

- 🔍 **Filtering**: Rich filtering capabilities on most data types, including PostGIS geo columns

- 📄 **Pagination**: Built-in offset-based pagination

- 📊 **Aggregation**: Support for aggregation functions like count, sum, avg, min, max, and statistical functions

- 🔀 **CRUD**: Full support for Create, Read, Update, Delete, and Upsert mutations with relationship handling

- 🪝 **Hooks**: Customize query behavior with query hooks: add filtering, load extra column etc.

- ⚡ **Sync/Async**: Works with both sync and async SQLAlchemy sessions

- 🛢 **Supported databases**:
  - PostgreSQL (using [asyncpg](https://github.com/MagicStack/asyncpg) or [psycopg3 sync/async](https://www.psycopg.org/psycopg3/))
  - MySQL (using [asyncmy](https://github.com/long2ice/asyncmy))
  - SQLite (using [aiosqlite](https://aiosqlite.omnilib.dev/en/stable/) or [sqlite](https://docs.python.org/3/library/sqlite3.html))

> [!Warning]
>
> Please note that strawchemy is currently in a pre-release stage of development. This means that the library is still under active development and the initial API is subject to change. We encourage you to experiment with strawchemy and provide feedback, but be sure to pin and update carefully until a stable release is available.

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Mapping SQLAlchemy Models](#mapping-sqlalchemy-models)
- [Resolver Generation](#resolver-generation)
- [Pagination](#pagination)
- [Filtering](#filtering)
- [Aggregations](#aggregations)
- [Mutations](#mutations)
- [Async Support](#async-support)
- [Configuration](#configuration)
- [Contributing](#contributing)
- [License](#license)

## Installation

Strawchemy is available on PyPi

```console
pip install strawchemy
```

Strawchemy has the following optional dependencies:

- `geo` : Enable Postgis support through [geoalchemy2](https://github.com/geoalchemy/geoalchemy2)

To install these dependencies along with strawchemy:

```console
pip install strawchemy[geo]
```

## Quick Start

```python
import strawberry
from strawchemy import Strawchemy
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship

# Initialize the strawchemy mapper
strawchemy = Strawchemy("postgresql")


# Define SQLAlchemy models
class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    posts: Mapped[list["Post"]] = relationship("Post", back_populates="author")


class Post(Base):
    __tablename__ = "post"

    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str]
    content: Mapped[str]
    author_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
    author: Mapped[User] = relationship("User", back_populates="posts")


# Map models to GraphQL types
@strawchemy.type(User, include="all")
class UserType:
    pass


@strawchemy.type(Post, include="all")
class PostType:
    pass


# Create filter inputs
@strawchemy.filter(User, include="all")
class UserFilter:
    pass


@strawchemy.filter(Post, include="all")
class PostFilter:
    pass


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


@strawchemy.order(Post, include="all")
class PostOrderBy:
    pass


# Define GraphQL query fields
@strawberry.type
class Query:
    users: list[UserType] = strawchemy.field(filter_input=UserFilter, order_by=UserOrderBy, pagination=True)
    posts: list[PostType] = strawchemy.field(filter_input=PostFilter, order_by=PostOrderBy, pagination=True)

# Create schema
schema = strawberry.Schema(query=Query)
```

```graphql
{
  # Users with pagination, filtering, and ordering
  users(
    offset: 0
    limit: 10
    filter: { name: { contains: "John" } }
    orderBy: { name: ASC }
  ) {
    id
    name
    posts {
      id
      title
      content
    }
  }

  # Posts with exact title match
  posts(filter: { title: { eq: "Introduction to GraphQL" } }) {
    id
    title
    content
    author {
      id
      name
    }
  }
}
```

## Mapping SQLAlchemy Models

Strawchemy provides an easy way to map SQLAlchemy models to GraphQL types using the `@strawchemy.type` decorator. You can include/exclude specific fields or have strawchemy map all columns/relationships of the model and it's children.

<details>
<summary>Mapping example</summary>

Include columns and relationships

```python
import strawberry
from strawchemy import Strawchemy

# Assuming these models are defined as in the Quick Start example
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy import ForeignKey

strawchemy = Strawchemy("postgresql")


class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    posts: Mapped[list["Post"]] = relationship("Post", back_populates="author")


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

Including/excluding specific fields

```python
class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    password: Mapped[str]


# Include specific fields
@strawchemy.type(User, include=["id", "name"])
class UserType:
    pass


# Exclude specific fields
@strawchemy.type(User, exclude=["password"])
class UserType:
    pass


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

Add a custom fields

```python
from strawchemy import ModelInstance

class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    first_name: Mapped[str]
    last_name: Mapped[str]


@strawchemy.type(User, include="all")
class UserType:
    instance: ModelInstance[User]

    @strawchemy.field
    def full_name(self) -> str:
        return f"{self.instance.first_name} {self.instance.last_name}"
```

See the [custom resolvers](#custom-resolvers) for more details

</details>

### Type Override

When generating types for relationships, Strawchemy creates default names (e.g., `<ModelName>Type`). If you have already defined a Python class with that same name, it will cause a name collision.

The `override=True` parameter tells Strawchemy that your definition should be used, resolving the conflict.

<details>
<summary>Using `override=True`</summary>

Consider these models:

```python
class Author(Base):
    __tablename__ = "author"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]

class Book(Base):
    __tablename__ = "book"
    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str]
    author_id: Mapped[int] = mapped_column(ForeignKey("author.id"))
    author: Mapped[Author] = relationship()
```

If you define a type for `Book`, Strawchemy will inspect the `author` relationship and attempt to auto-generate a type for the `Author` model, naming it `AuthorType` by default. If you have already defined a class with that name, it will cause a name collision.

```python
# Let's say you've already defined this class
@strawchemy.type(Book, include="all")
class BookType:
    pass

# This will cause an error because Strawchemy has already created `AuthorType` when generating `BookType`
@strawchemy.type(Book, include="all")
class AuthorType:
    ...
```

You would see an error like: `Type 'AuthorType' cannot be auto generated because it's already declared.`

To solve this, you can create a single, definitive `AuthorType` and mark it with `override=True`. This tells Strawchemy to use your version instead of generating a new one.

```python
@strawchemy.type(Author, include="all", override=True)
class AuthorType:
    pass

# Now this works, because Strawchemy knows to use your `AuthorType`
@strawchemy.type(Book, include="all")
class BookType:
    pass
```

</details>

### Reuse types in schema

While `override=True` solves name collisions, `scope="global"` is used to promote consistency and reuse.

By defining a type with `scope="global"`, you register it as the canonical type for a given SQLAlchemy model and purpose (e.g. a strawberry `type`, `filter`, or `input`). Strawchemy will then automatically use this globally-scoped type everywhere it's needed in your schema, rather than generating new ones.

<details>
<summary>Using `scope="global"`</summary>

Let's define a global type for the `Color` model. This type will now be the default for the `Color` model across the entire schema.

```python
# This becomes the canonical type for the `Color` model
@strawchemy.type(Color, include={"id", "name"}, scope="global")
class ColorType:
    pass

# Another type that references the Color model
@strawchemy.type(Fruit, include="all")
class FruitType:
    ...
    # Strawchemy automatically uses the globally-scoped `ColorType` here
    # without needing an explicit annotation.
```

This ensures that the `Color` model is represented consistently as `ColorType` in all parts of your GraphQL schema, such as in the `FruitType`'s `color` field, without needing to manually specify it every time.

</details>

## Resolver Generation

Strawchemy automatically generates resolvers for your GraphQL fields. You can use the `strawchemy.field()` function to generate fields that query your database

<details>
<summary>Resolvers example</summary>

```python
@strawberry.type
class Query:
    # Simple field that returns a list of users
    users: list[UserType] = strawchemy.field()
    # Field with filtering, ordering, and pagination
    filtered_users: list[UserType] = strawchemy.field(filter_input=UserFilter, order_by=UserOrderBy, pagination=True)
    # Field that returns a single user by ID
    user: UserType = strawchemy.field()
```

</details>

While Strawchemy automatically generates resolvers for most use cases, you can also create custom resolvers for more complex scenarios. There are two main approaches to creating custom resolvers:

### Using Repository Directly

When using `strawchemy.field()` as a function, strawchemy creates a resolver that delegates data fetching to the `StrawchemySyncRepository` or `StrawchemyAsyncRepository` classes depending on the SQLAlchemy session type.
You can create custom resolvers by using the `@strawchemy.field` as a decorator and working directly with the repository:

<details>
<summary>Custom resolvers using repository</summary>

```python
from sqlalchemy import select, true
from strawchemy import StrawchemySyncRepository

@strawberry.type
class Query:
    @strawchemy.field
    def red_color(self, info: strawberry.Info) -> ColorType:
        # Create a repository with a predefined filter
        repo = StrawchemySyncRepository(ColorType, info, filter_statement=select(Color).where(Color.name == "Red"))
        # Return a single result (will raise an exception if not found)
        return repo.get_one().graphql_type()

    @strawchemy.field
    def get_color_by_name(self, info: strawberry.Info, color: str) -> ColorType | None:
        # Create a repository with a custom filter statement
        repo = StrawchemySyncRepository(ColorType, info, filter_statement=select(Color).where(Color.name == color))
        # Return a single result or None if not found
        return repo.get_one_or_none().graphql_type_or_none()

    @strawchemy.field
    def get_color_by_id(self, info: strawberry.Info, id: str) -> ColorType | None:
        repo = StrawchemySyncRepository(ColorType, info)
        # Return a single result or None if not found
        return repo.get_by_id(id=id).graphql_type_or_none()

    @strawchemy.field
    def public_colors(self, info: strawberry.Info) -> ColorType:
        repo = StrawchemySyncRepository(ColorType, info, filter_statement=select(Color).where(Color.public.is_(true())))
        # Return a list of results
        return repo.list().graphql_list()
```

For async resolvers, use `StrawchemyAsyncRepository` which is the async variant of `StrawchemySyncRepository`:

```python
from strawchemy import StrawchemyAsyncRepository

@strawberry.type
class Query:
    @strawchemy.field
    async def get_color(self, info: strawberry.Info, color: str) -> ColorType | None:
        repo = StrawchemyAsyncRepository(ColorType, info, filter_statement=select(Color).where(Color.name == color))
        return (await repo.get_one_or_none()).graphql_type_or_none()
```

The repository provides several methods for fetching data:

- `get_one()`: Returns a single result, raises an exception if not found
- `get_one_or_none()`: Returns a single result or None if not found
- `get_by_id()`: Returns a single result filtered on primary key
- `list()`: Returns a list of results

</details>

### Query Hooks

Strawchemy provides query hooks that allow you to customize query behavior. Query hooks give you fine-grained control over how SQL queries are constructed and executed.

<details>
<summary>Using query hooks</summary>

The `QueryHook` base class provides several methods that you can override to customize query behavior:

#### Modifying the statement

You can subclass `QueryHook` and override the `apply_hook` method apply changes to the statement. By default, it returns it unchanged. This method is only for filtering or ordering customizations, if you want to explicitly load columns or relationships, use the `load` parameter instead.

```python
from strawchemy import ModelInstance, QueryHook
from sqlalchemy import Select, select
from sqlalchemy.orm.util import AliasedClass

# Define a model and type
class Fruit(Base):
    __tablename__ = "fruit"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    adjectives: Mapped[list[str]] = mapped_column(ARRAY(String))

# Apply the hook at the field level
@strawchemy.type(Fruit, exclude={"color"})
class FruitTypeWithDescription:
    instance: ModelInstance[Fruit]

    # Use QueryHook to ensure specific columns are loaded
    @strawchemy.field(query_hook=QueryHook(load=[Fruit.name, Fruit.adjectives]))
    def description(self) -> str:
        return f"The {self.instance.name} is {', '.join(self.instance.adjectives)}"

# Create a custom query hook for filtering
class FilterFruitHook(QueryHook[Fruit]):
    def apply_hook(self, statement: Select[tuple[Fruit]], alias: AliasedClass[Fruit]) -> Select[tuple[Fruit]]:
        # Add a custom WHERE clause
        return statement.where(alias.name == "Apple")

# Apply the hook at the type level
@strawchemy.type(Fruit, exclude={"color"}, query_hook=FilterFruitHook())
class FilteredFruitType:
    pass
```

Important notes when implementing `apply_hooks`:

- You must use the provided `alias` parameter to refer to columns of the model on which the hook is applied. Otherwise, the statement may fail.
- The GraphQL context is available through `self.info` within hook methods.
- You must set a `ModelInstance` typed attribute if you want to access the model instance values.
  The `instance` attribute is matched by the `ModelInstance[Fruit]` type hint, so you can give it any name you want.

#### Load specific columns/relationships

The `load` parameter specify columns and relationships that should always be loaded, even if not directly requested in the GraphQL query. This is useful for:

- Ensuring data needed for computed properties is available
- Loading columns or relationships required for custom resolvers

Examples of using the `load` parameter:

```python
# Load specific columns
@strawchemy.field(query_hook=QueryHook(load=[Fruit.name, Fruit.adjectives]))
def description(self) -> str:
    return f"The {self.instance.name} is {', '.join(self.instance.adjectives)}"

# Load a relationship without specifying columns
@strawchemy.field(query_hook=QueryHook(load=[Fruit.farms]))
def pretty_farms(self) -> str:
    return f"Farms are: {', '.join(farm.name for farm in self.instance.farms)}"

# Load a relationship with specific columns
@strawchemy.field(query_hook=QueryHook(load=[(Fruit.color, [Color.name, Color.created_at])]))
def pretty_color(self) -> str:
    return f"Color is {self.instance.color.name}" if self.instance.color else "No color!"

# Load nested relationships
@strawchemy.field(query_hook=QueryHook(load=[(Color.fruits, [(Fruit.farms, [FruitFarm.name])])]))
def farms(self) -> str:
    return f"Farms are: {', '.join(farm.name for fruit in self.instance.fruits for farm in fruit.farms)}"
```

</details>

## Pagination

Strawchemy supports offset-based pagination out of the box.

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

Enable pagination on fields:

```python
from strawchemy.types import DefaultOffsetPagination

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

In your GraphQL queries, you can use the `offset` and `limit` parameters:

```graphql
{
  users(offset: 0, limit: 10) {
    id
    name
  }
}
```

You can also enable pagination for nested relationships:

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

Then in your GraphQL queries:

```graphql
{
  users {
    id
    name
    posts(offset: 0, limit: 5) {
      id
      title
    }
  }
}
```

</details>

## Filtering

Strawchemy provides powerful filtering capabilities.

<details>
<summary>Filtering example</summary>

First, create a filter input type:

```python
@strawchemy.filter(User, include="all")
class UserFilter:
    pass
```

Then use it in your field:

```python
@strawberry.type
class Query:
    users: list[UserType] = strawchemy.field(filter_input=UserFilter)
```

Now you can use various filter operations in your GraphQL queries:

```graphql
{
  # Equality filter
  users(filter: { name: { eq: "John" } }) {
    id
    name
  }

  # Comparison filters
  users(filter: { age: { gt: 18, lte: 30 } }) {
    id
    name
    age
  }

  # String filters
  users(filter: { name: { contains: "oh", ilike: "%OHN%" } }) {
    id
    name
  }

  # Logical operators
  users(filter: { _or: [{ name: { eq: "John" } }, { name: { eq: "Jane" } }] }) {
    id
    name
  }
  # Nested filters
  users(filter: { posts: { title: { contains: "GraphQL" } } }) {
    id
    name
    posts {
      id
      title
    }
  }

  # Compare interval component
  tasks(filter: { duration: { days: { gt: 2 } } }) {
    id
    name
    duration
  }

  # Direct interval comparison
  tasks(filter: { duration: { gt: "P2DT5H" } }) {
    id
    name
    duration
  }
}
```

</details>

Strawchemy supports a wide range of filter operations:

| Data Type/Category                      | Filter Operations                                                                                                                                                                |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Common to most types**                | `eq`, `neq`, `isNull`, `in`, `nin`                                                                                                                                               |
| **Numeric types (Int, Float, Decimal)** | `gt`, `gte`, `lt`, `lte`                                                                                                                                                         |
| **String**                              | order filter, plus `like`, `nlike`, `ilike`, `nilike`, `regexp`, `iregexp`, `nregexp`, `inregexp`, `startswith`, `endswith`, `contains`, `istartswith`, `iendswith`, `icontains` |
| **JSON**                                | `contains`, `containedIn`, `hasKey`, `hasKeyAll`, `hasKeyAny`                                                                                                                    |
| **Array**                               | `contains`, `containedIn`, `overlap`                                                                                                                                             |
| **Date**                                | order filters on plain dates, plus `year`, `month`, `day`, `weekDay`, `week`, `quarter`, `isoYear` and `isoWeekDay` filters                                                      |
| **DateTime**                            | All Date filters plus `hour`, `minute`, `second`                                                                                                                                 |
| **Time**                                | order filters on plain times, plus `hour`, `minute` and `second` filters                                                                                                         |
| **Interval**                            | order filters on plain intervals, plus `days`, `hours`, `minutes` and `seconds` filters                                                                                          |
| **Logical**                             | `_and`, `_or`, `_not`                                                                                                                                                            |

### Geo Filters

Strawchemy supports spatial filtering capabilities for geometry fields using [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946). To use geo filters, you need to have PostGIS installed and enabled in your PostgreSQL database.

<details>
<summary>Geo filters example</summary>

Define models and types:

```python
class GeoModel(Base):
    __tablename__ = "geo"

    id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
    # Define geometry columns using GeoAlchemy2
    point: Mapped[WKBElement | None] = mapped_column(Geometry("POINT", srid=4326), nullable=True)
    polygon: Mapped[WKBElement | None] = mapped_column(Geometry("POLYGON", srid=4326), nullable=True)

@strawchemy.type(GeoModel, include="all")
class GeoType: ...

@strawchemy.filter(GeoModel, include="all")
class GeoFieldsFilter: ...

@strawberry.type
class Query:
geo: list[GeoType] = strawchemy.field(filter_input=GeoFieldsFilter)

```

Then you can use the following geo filter operations in your GraphQL queries:

```graphql
{
  # Find geometries that contain a point
  geo(
    filter: {
      polygon: { containsGeometry: { type: "Point", coordinates: [0.5, 0.5] } }
    }
  ) {
    id
    polygon
  }

  # Find geometries that are within a polygon
  geo(
    filter: {
      point: {
        withinGeometry: {
          type: "Polygon"
          coordinates: [[[0, 0], [0, 2], [2, 2], [2, 0], [0, 0]]]
        }
      }
    }
  ) {
    id
    point
  }

  # Find records with null geometry
  geo(filter: { point: { isNull: true } }) {
    id
  }
}
```

</details>

Strawchemy supports the following geo filter operations:

- **containsGeometry**: Filters for geometries that contain the specified GeoJSON geometry
- **withinGeometry**: Filters for geometries that are within the specified GeoJSON geometry
- **isNull**: Filters for null or non-null geometry values

These filters work with all geometry types supported by PostGIS, including:

- `Point`
- `LineString`
- `Polygon`
- `MultiPoint`
- `MultiLineString`
- `MultiPolygon`
- `Geometry` (generic geometry type)

## Aggregations

Strawchemy automatically exposes aggregation fields for list relationships.

When you define a model with a list relationship, the corresponding GraphQL type will include an aggregation field for that relationship, named `<field_name>Aggregate`.

<details>
<summary> Basic aggregation example:</summary>

With the folliing model definitions:

```python
class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    posts: Mapped[list["Post"]] = relationship("Post", back_populates="author")


class Post(Base):
    __tablename__ = "post"
    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str]
    content: Mapped[str]
    author_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
    author: Mapped[User] = relationship("User", back_populates="posts")
```

And the corresponding GraphQL types:

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


@strawchemy.type(Post, include="all")
class PostType:
    pass
```

You can query aggregations on the `posts` relationship:

```graphql
{
  users {
    id
    name
    postsAggregate {
      count
      min {
        title
      }
      max {
        title
      }
      # Other aggregation functions are also available
    }
  }
}
```

</details>

### Filtering by relationship aggregations

You can also filter entities based on aggregations of their related entities.

<details>
<summary>Aggregation filtering example</summary>

Define types with filters:

```python
@strawchemy.filter(User, include="all")
class UserFilter:
    pass


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

For example, to find users who have more than 5 posts:

```graphql
{
  users(
    filter: {
      postsAggregate: { count: { arguments: [id], predicate: { gt: 5 } } }
    }
  ) {
    id
    name
    postsAggregate {
      count
    }
  }
}
```

You can use various predicates for filtering:

```graphql
# Users with exactly 3 posts
users(filter: {
  postsAggregate: {
    count: {
      arguments: [id]
      predicate: { eq: 3 }
    }
  }
})

# Users with posts containing "GraphQL" in the title
users(filter: {
  postsAggregate: {
    maxString: {
      arguments: [title]
      predicate: { contains: "GraphQL" }
    }
  }
})

# Users with an average post length greater than 1000 characters
users(filter: {
  postsAggregate: {
    avg: {
      arguments: [contentLength]
      predicate: { gt: 1000 }
    }
  }
})
```

</details>

#### Distinct aggregations

<details>
<summary>Distinct aggregation filtering example</summary>

You can also use the `distinct` parameter to count only distinct values:

```graphql
{
  users(
    filter: {
      postsAggregate: {
        count: { arguments: [category], predicate: { gt: 2 }, distinct: true }
      }
    }
  ) {
    id
    name
  }
}
```

This would find users who have posts in more than 2 distinct categories.

</details>

### Root aggregations

Strawchemy supports query level aggregations.

<details>
<summary>Root aggregations example:</summary>

First, create an aggregation type:

```python
@strawchemy.aggregate(User, include="all")
class UserAggregationType:
    pass
```

Then set up the root aggregations on the field:

```python
@strawberry.type
class Query:
    users_aggregations: UserAggregationType = strawchemy.field(root_aggregations=True)
```

Now you can use aggregation functions on the result of your query:

```graphql
{
  usersAggregations {
    aggregations {
      # Basic aggregations
      count

      sum {
        age
      }

      avg {
        age
      }

      min {
        age
        createdAt
      }
      max {
        age
        createdAt
      }

      # Statistical aggregations
      stddev {
        age
      }
      variance {
        age
      }
    }
    # Access the actual data
    nodes {
      id
      name
      age
    }
  }
}
```

</details>

## Mutations

Strawchemy provides a powerful way to create GraphQL mutations for your SQLAlchemy models. These mutations allow you to create, update, and delete data through your GraphQL API.

<details>
<summary>Mutations example</summary>

```python
import strawberry
from strawchemy import Strawchemy, StrawchemySyncRepository, StrawchemyAsyncRepository

# Initialize the strawchemy mapper
strawchemy = Strawchemy("postgresql")

# Define input types for mutations
@strawchemy.input(User, include=["name", "email"])
class UserCreateInput:
    pass

@strawchemy.input(User, include=["id", "name", "email"])
class UserUpdateInput:
    pass

@strawchemy.filter(User, include="all")
class UserFilter:
    pass

# Define GraphQL mutation fields
@strawberry.type
class Mutation:
    # Create mutations
    create_user: UserType = strawchemy.create(UserCreateInput)
    create_users: list[UserType] = strawchemy.create(UserCreateInput)  # Batch creation

    # Update mutations
    update_user: UserType = strawchemy.update_by_ids(UserUpdateInput)
    update_users: list[UserType] = strawchemy.update_by_ids(UserUpdateInput)  # Batch update
    update_users_filter: list[UserType] = strawchemy.update(UserUpdateInput, UserFilter)  # Update with filter

    # Delete mutations
    delete_users: list[UserType] = strawchemy.delete()  # Delete all
    delete_users_filter: list[UserType] = strawchemy.delete(UserFilter)  # Delete with filter

# Create schema with mutations
schema = strawberry.Schema(query=Query, mutation=Mutation)
```

</details>

### Create Mutations

Create mutations allow you to insert new records into your database. Strawchemy provides two types of create mutations:

1. **Single entity creation**: Creates a single record
2. **Batch creation**: Creates multiple records in a single operation

<details>
<summary>Create mutation examples</summary>

#### Basic Create Mutation

```python
# Define input type for creation
@strawchemy.input(Color, include=["name"])
class ColorCreateInput:
    pass

@strawberry.type
class Mutation:
    # Single entity creation
    create_color: ColorType = strawchemy.create(ColorCreateInput)

    # Batch creation
    create_colors: list[ColorType] = strawchemy.create(ColorCreateInput)
```

GraphQL usage:

```graphql
# Create a single color
mutation {
  createColor(data: { name: "Purple" }) {
    id
    name
  }
}

# Create multiple colors in one operation
mutation {
  createColors(data: [{ name: "Teal" }, { name: "Magenta" }]) {
    id
    name
  }
}
```

</details>

### Working with Relationships in Create Mutations

Strawchemy supports creating entities with relationships. You can:

1. **Set existing relationships**: Link to existing records
2. **Create nested relationships**: Create related records in the same mutation
3. **Set to null**: Remove relationships

<details>
<summary>Create with relationships examples</summary>

#### To-One Relationships

```python
@strawchemy.input(Fruit, include=["name", "adjectives"])
class FruitCreateInput:
    # Define relationship inputs
    color: auto  # 'auto' will generate appropriate relationship inputs
```

GraphQL usage:

```graphql
# Set an existing relationship
mutation {
  createFruit(
    data: {
      name: "Apple"
      adjectives: ["sweet", "crunchy"]
      color: { set: { id: "123e4567-e89b-12d3-a456-426614174000" } }
    }
  ) {
    id
    name
    color {
      id
      name
    }
  }
}

# Create a new related entity
mutation {
  createFruit(
    data: {
      name: "Banana"
      adjectives: ["yellow", "soft"]
      color: { create: { name: "Yellow" } }
    }
  ) {
    id
    name
    color {
      id
      name
    }
  }
}

# Set relationship to null
mutation {
  createFruit(
    data: {
      name: "Strawberry"
      adjectives: ["red", "sweet"]
      color: { set: null }
    }
  ) {
    id
    name
    color {
      id
    }
  }
}
```

#### To-Many Relationships

```python
@strawchemy.input(Color, include=["name"])
class ColorCreateInput:
    # Define to-many relationship inputs
    fruits: auto  # 'auto' will generate appropriate relationship inputs
```

GraphQL usage:

```graphql
# Set existing to-many relationships
mutation {
  createColor(
    data: {
      name: "Red"
      fruits: { set: [{ id: "123e4567-e89b-12d3-a456-426614174000" }] }
    }
  ) {
    id
    name
    fruits {
      id
      name
    }
  }
}

# Add to existing to-many relationships
mutation {
  createColor(
    data: {
      name: "Green"
      fruits: { add: [{ id: "123e4567-e89b-12d3-a456-426614174000" }] }
    }
  ) {
    id
    name
    fruits {
      id
      name
    }
  }
}

# Create new related entities
mutation {
  createColor(
    data: {
      name: "Blue"
      fruits: {
        create: [
          { name: "Blueberry", adjectives: ["small", "blue"] }
          { name: "Plum", adjectives: ["juicy", "purple"] }
        ]
      }
    }
  ) {
    id
    name
    fruits {
      id
      name
    }
  }
}
```

#### Nested Relationships

You can create deeply nested relationships:

```graphql
mutation {
  createColor(
    data: {
      name: "White"
      fruits: {
        create: [
          {
            name: "Grape"
            adjectives: ["tangy", "juicy"]
            farms: { create: [{ name: "Bio farm" }] }
          }
        ]
      }
    }
  ) {
    name
    fruits {
      name
      farms {
        name
      }
    }
  }
}
```

</details>

### Update Mutations

Update mutations allow you to modify existing records. Strawchemy provides several types of update mutations:

1. **Update by primary key**: Update a specific record by its ID
2. **Batch update by primary keys**: Update multiple records by their IDs
3. **Update with filter**: Update records that match a filter condition

<details>
<summary>Update mutation examples</summary>

#### Basic Update Mutation

```python
# Define input type for updates
@strawchemy.input(Color, include=["id", "name"])
class ColorUpdateInput:
    pass

@strawchemy.filter(Color, include="all")
class ColorFilter:
    pass

@strawberry.type
class Mutation:
    # Update by ID
    update_color: ColorType = strawchemy.update_by_ids(ColorUpdateInput)

    # Batch update by IDs
    update_colors: list[ColorType] = strawchemy.update_by_ids(ColorUpdateInput)

    # Update with filter
    update_colors_filter: list[ColorType] = strawchemy.update(ColorUpdateInput, ColorFilter)
```

GraphQL usage:

```graphql
# Update by ID
mutation {
  updateColor(
    data: { id: "123e4567-e89b-12d3-a456-426614174000", name: "Crimson" }
  ) {
    id
    name
  }
}

# Batch update by IDs
mutation {
  updateColors(
    data: [
      { id: "123e4567-e89b-12d3-a456-426614174000", name: "Crimson" }
      { id: "223e4567-e89b-12d3-a456-426614174000", name: "Navy" }
    ]
  ) {
    id
    name
  }
}

# Update with filter
mutation {
  updateColorsFilter(
    data: { name: "Bright Red" }
    filter: { name: { eq: "Red" } }
  ) {
    id
    name
  }
}
```

</details>

### Working with Relationships in Update Mutations

Similar to create mutations, update mutations support modifying relationships:

<details>
<summary>Update with relationships examples</summary>

#### To-One Relationships

```python
@strawchemy.input(Fruit, include=["id", "name"])
class FruitUpdateInput:
    # Define relationship inputs
    color: auto
```

GraphQL usage:

```graphql
# Set an existing relationship
mutation {
  updateFruit(
    data: {
      id: "123e4567-e89b-12d3-a456-426614174000"
      name: "Red Apple"
      color: { set: { id: "223e4567-e89b-12d3-a456-426614174000" } }
    }
  ) {
    id
    name
    color {
      id
      name
    }
  }
}

# Create a new related entity
mutation {
  updateFruit(
    data: {
      id: "123e4567-e89b-12d3-a456-426614174000"
      name: "Green Apple"
      color: { create: { name: "Green" } }
    }
  ) {
    id
    name
    color {
      id
      name
    }
  }
}

# Set relationship to null
mutation {
  updateFruit(
    data: {
      id: "123e4567-e89b-12d3-a456-426614174000"
      name: "Plain Apple"
      color: { set: null }
    }
  ) {
    id
    name
    color {
      id
    }
  }
}
```

#### To-Many Relationships

```python
@strawchemy.input(Color, include=["id", "name"])
class ColorUpdateInput:
    # Define to-many relationship inputs
    fruits: auto
```

GraphQL usage:

```graphql
# Set (replace) to-many relationships
mutation {
  updateColor(
    data: {
      id: "123e4567-e89b-12d3-a456-426614174000"
      name: "Red"
      fruits: { set: [{ id: "223e4567-e89b-12d3-a456-426614174000" }] }
    }
  ) {
    id
    name
    fruits {
      id
      name
    }
  }
}

# Add to existing to-many relationships
mutation {
  updateColor(
    data: {
      id: "123e4567-e89b-12d3-a456-426614174000"
      name: "Red"
      fruits: { add: [{ id: "223e4567-e89b-12d3-a456-426614174000" }] }
    }
  ) {
    id
    name
    fruits {
      id
      name
    }
  }
}

# Remove from to-many relationships
mutation {
  updateColor(
    data: {
      id: "123e4567-e89b-12d3-a456-426614174000"
      name: "Red"
      fruits: { remove: [{ id: "223e4567-e89b-12d3-a456-426614174000" }] }
    }
  ) {
    id
    name
    fruits {
      id
      name
    }
  }
}

# Create new related entities
mutation {
  updateColor(
    data: {
      id: "123e4567-e89b-12d3-a456-426614174000"
      name: "Red"
      fruits: {
        create: [
          { name: "Cherry", adjectives: ["small", "red"] }
          { name: "Strawberry", adjectives: ["sweet", "red"] }
        ]
      }
    }
  ) {
    id
    name
    fruits {
      id
      name
    }
  }
}
```

#### Combining Operations

You can combine `add` and `create` operations in a single update:

```graphql
mutation {
  updateColor(
    data: {
      id: "123e4567-e89b-12d3-a456-426614174000"
      name: "Red"
      fruits: {
        add: [{ id: "223e4567-e89b-12d3-a456-426614174000" }]
        create: [{ name: "Raspberry", adjectives: ["tart", "red"] }]
      }
    }
  ) {
    id
    name
    fruits {
      id
      name
    }
  }
}
```

Note: You cannot use `set` with `add`, `remove`, or `create` in the same operation for to-many relationships.

</details>

### Delete Mutations

Delete mutations allow you to remove records from your database. Strawchemy provides two types of delete mutations:

1. **Delete all**: Removes all records of a specific type
2. **Delete with filter**: Removes records that match a filter condition

<details>
<summary>Delete mutation examples</summary>

```python
@strawchemy.filter(User, include="all")
class UserFilter:
    pass

@strawberry.type
class Mutation:
    # Delete all users
    delete_users: list[UserType] = strawchemy.delete()

    # Delete users that match a filter
    delete_users_filter: list[UserType] = strawchemy.delete(UserFilter)
```

GraphQL usage:

```graphql
# Delete all users
mutation {
  deleteUsers {
    id
    name
  }
}

# Delete users that match a filter
mutation {
  deleteUsersFilter(filter: { name: { eq: "Alice" } }) {
    id
    name
  }
}
```

The returned data contains the records that were deleted.

</details>

### Upsert Mutations

Upsert mutations provide "insert or update" functionality, allowing you to create new records or update existing ones based on conflict resolution. This is particularly useful when you want to ensure data exists without worrying about whether it's already in the database.

Strawchemy supports upsert operations for:

1. **Root-level upserts**: Direct upsert mutations on entities
2. **Relationship upserts**: Upsert operations within relationship mutations

<details>
<summary>Upsert mutation examples</summary>

#### Basic Upsert Mutation

First, define the necessary input types and enums:

```python
# Define input type for upsert
@strawchemy.input(Fruit, include=["name", "sweetness", "waterPercent"])
class FruitCreateInput:
    pass

# Define which fields can be updated during upsert
@strawchemy.upsert_update_fields(Fruit, include=["sweetness", "waterPercent"])
class FruitUpsertFields:
    pass

# Define which fields are used for conflict detection
@strawchemy.upsert_conflict_fields(Fruit)
class FruitUpsertConflictFields:
    pass

@strawberry.type
class Mutation:
    # Single entity upsert
    upsert_fruit: FruitType = strawchemy.upsert(
        FruitCreateInput,
        update_fields=FruitUpsertFields,
        conflict_fields=FruitUpsertConflictFields
    )

    # Batch upsert
    upsert_fruits: list[FruitType] = strawchemy.upsert(
        FruitCreateInput,
        update_fields=FruitUpsertFields,
        conflict_fields=FruitUpsertConflictFields
    )
```

#### GraphQL Usage

```graphql
# Upsert a single fruit (will create if name doesn't exist, update if it does)
mutation {
  upsertFruit(
    data: { name: "Apple", sweetness: 8, waterPercent: 0.85 }
    conflictFields: name
  ) {
    id
    name
    sweetness
    waterPercent
  }
}

# Batch upsert multiple fruits
mutation {
  upsertFruits(
    data: [
      { name: "Apple", sweetness: 8, waterPercent: 0.85 }
      { name: "Orange", sweetness: 6, waterPercent: 0.87 }
    ]
    conflictFields: name
  ) {
    id
    name
    sweetness
    waterPercent
  }
}
```

#### How Upsert Works

1. **Conflict Detection**: The `conflictFields` parameter specifies which field(s) to check for existing records
2. **Update Fields**: The `updateFields` parameter (optional) specifies which fields should be updated if a conflict is found
3. **Database Support**:
   - **PostgreSQL**: Uses `ON CONFLICT DO UPDATE`
   - **MySQL**: Uses `ON DUPLICATE KEY UPDATE`
   - **SQLite**: Uses `ON CONFLICT DO UPDATE`

#### Upsert in Relationships

You can also use upsert operations within relationship mutations:

```python
@strawchemy.input(Color, include=["id", "name"])
class ColorUpdateInput:
    fruits: auto  # This will include upsert options for fruits
```

```graphql
# Update a color and upsert related fruits
mutation {
  updateColor(
    data: {
      id: 1
      name: "Bright Red"
      fruits: {
        upsert: {
          create: [
            { name: "Cherry", sweetness: 7, waterPercent: 0.87 }
            { name: "Strawberry", sweetness: 8, waterPercent: 0.91 }
          ]
          conflictFields: name
        }
      }
    }
  ) {
    id
    name
    fruits {
      id
      name
      sweetness
    }
  }
}
```

#### Upsert Behavior

- **If no conflict**: Creates a new record with all provided data
- **If conflict found**: Updates the existing record with fields specified in `updateFields`
- **Conflict resolution**: Based on unique constraints, primary keys, or specified conflict fields
- **Return value**: Always returns the final state of the record (created or updated)

</details>

### Input Validation

Strawchemy supports input validation using Pydantic models. You can define validation schemas and apply them to mutations to ensure data meets specific requirements before being processed.

Create Pydantic models for the input type where you want the validation, and set the `validation` parameter on `strawchemy.field`:

<details>
<summary>Validation example</summary>

```python
from models import User, Group
from typing import Annotated
from pydantic import AfterValidator
from strawchemy import InputValidationError, ValidationErrorType
from strawchemy.validation.pydantic import PydanticValidation

def _check_lower_case(value: str) -> str:
    if not value.islower():
        raise ValueError("Name must be lower cased")
    return value


@strawchemy.pydantic.create(Group, include="all")
class GroupCreateValidation:
    name: Annotated[str, AfterValidator(_check_lower_case)]


@strawchemy.pydantic.create(User, include="all")
class UserCreateValidation:
    name: Annotated[str, AfterValidator(_check_lower_case)]
    group: GroupCreateValidation | None = strawberry.UNSET


@strawberry.type
class Mutation:
    create_user: UserType | ValidationErrorType = strawchemy.create(UserCreate, validation=PydanticValidation(UserCreateValidation))
```

> To get the validation errors exposed in the schema, you need to add `ValidationErrorType` in the field union type

When validation fails, the query will returns a `ValidationErrorType` with detailed error information from pydantic validation:

```graphql
mutation {
  createUser(data: { name: "Bob" }) {
    __typename
    ... on UserType {
      name
    }
    ... on ValidationErrorType {
      id
      errors {
        id
        loc
        message
        type
      }
    }
  }
}
```

```json
{
  "data": {
    "createUser": {
      "__typename": "ValidationErrorType",
      "id": "ERROR",
      "errors": [
        {
          "id": "ERROR",
          "loc": ["name"],
          "message": "Value error, Name must be lower cased",
          "type": "value_error"
        }
      ]
    }
  }
}
```

Validation also works with nested relationships:

```graphql
mutation {
  createUser(
    data: {
      name: "bob"
      group: {
        create: {
          name: "Group" # This will be validated
          tag: { set: { id: "..." } }
        }
      }
    }
  ) {
    __typename
    ... on ValidationErrorType {
      errors {
        loc
        message
      }
    }
  }
}
```

</details>

## Async Support

Strawchemy supports both synchronous and asynchronous operations. You can use either `StrawchemySyncRepository` or `StrawchemyAsyncRepository` depending on your needs:

```python
from strawchemy import StrawchemySyncRepository, StrawchemyAsyncRepository

# Synchronous resolver
@strawchemy.field
def get_color(self, info: strawberry.Info, color: str) -> ColorType | None:
    repo = StrawchemySyncRepository(ColorType, info, filter_statement=select(Color).where(Color.name == color))
    return repo.get_one_or_none().graphql_type_or_none()

# Asynchronous resolver
@strawchemy.field
async def get_color(self, info: strawberry.Info, color: str) -> ColorType | None:
    repo = StrawchemyAsyncRepository(ColorType, info, filter_statement=select(Color).where(Color.name == color))
    return await repo.get_one_or_none().graphql_type_or_none()

# Synchronous mutation
@strawberry.type
class Mutation:
    create_user: UserType = strawchemy.create(
        UserCreateInput,
        repository_type=StrawchemySyncRepository
    )

# Asynchronous mutation
@strawberry.type
class AsyncMutation:
    create_user: UserType = strawchemy.create(
        UserCreateInput,
        repository_type=StrawchemyAsyncRepository
    )
```

By default, Strawchemy uses the StrawchemySyncRepository as its repository type. You can override this behavior by specifying a different repository using the `repository_type` configuration option.

## Configuration

Configuration is made by passing a `StrawchemyConfig` to the `Strawchemy` instance.

### 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.                                                                                         |

### Example

```python
from strawchemy import Strawchemy, StrawchemyConfig

# Custom session getter function
def get_session_from_context(info):
    return info.context.db_session

# Initialize with custom configuration
strawchemy = Strawchemy(
    StrawchemyConfig(
      "postgresql",
      session_getter=get_session_from_context,
      auto_snake_case=True,
      pagination=True,
      pagination_default_limit=50,
      default_id_field_name="pk",
    )
)
```

## Contributing

Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to this project.

## License

This project is licensed under the terms of the license included in the [LICENCE](LICENCE) file.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "strawchemy",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "API, SQL, graphql, sqlalchemy, strawberry",
    "author": null,
    "author_email": "gazorby <gazorby@pm.me>",
    "download_url": "https://files.pythonhosted.org/packages/cc/f4/21fc0178371312508a4c21d9c7e39a46ba23ed946b6e7eb9b68a5b294527/strawchemy-0.19.0.tar.gz",
    "platform": null,
    "description": "# Strawchemy\n\n[![\ud83d\udd02 Tests and linting](https://github.com/gazorby/strawchemy/actions/workflows/ci.yaml/badge.svg)](https://github.com/gazorby/strawchemy/actions/workflows/ci.yaml) [![codecov](https://codecov.io/gh/gazorby/strawchemy/graph/badge.svg?token=BCU8SX1MJ7)](https://codecov.io/gh/gazorby/strawchemy) [![PyPI Downloads](https://static.pepy.tech/badge/strawchemy)](https://pepy.tech/projects/strawchemy)\n\nGenerates GraphQL types, inputs, queries and resolvers directly from SQLAlchemy models.\n\n## Features\n\n- \ud83d\udd04 **Type Generation**: Generate strawberry types from SQLAlchemy models\n\n- \ud83e\udde0 **Smart Resolvers**: Automatically generates single, optimized database queries for a given GraphQL request\n\n- \ud83d\udd0d **Filtering**: Rich filtering capabilities on most data types, including PostGIS geo columns\n\n- \ud83d\udcc4 **Pagination**: Built-in offset-based pagination\n\n- \ud83d\udcca **Aggregation**: Support for aggregation functions like count, sum, avg, min, max, and statistical functions\n\n- \ud83d\udd00 **CRUD**: Full support for Create, Read, Update, Delete, and Upsert mutations with relationship handling\n\n- \ud83e\ude9d **Hooks**: Customize query behavior with query hooks: add filtering, load extra column etc.\n\n- \u26a1 **Sync/Async**: Works with both sync and async SQLAlchemy sessions\n\n- \ud83d\udee2 **Supported databases**:\n  - PostgreSQL (using [asyncpg](https://github.com/MagicStack/asyncpg) or [psycopg3 sync/async](https://www.psycopg.org/psycopg3/))\n  - MySQL (using [asyncmy](https://github.com/long2ice/asyncmy))\n  - SQLite (using [aiosqlite](https://aiosqlite.omnilib.dev/en/stable/) or [sqlite](https://docs.python.org/3/library/sqlite3.html))\n\n> [!Warning]\n>\n> Please note that strawchemy is currently in a pre-release stage of development. This means that the library is still under active development and the initial API is subject to change. We encourage you to experiment with strawchemy and provide feedback, but be sure to pin and update carefully until a stable release is available.\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Mapping SQLAlchemy Models](#mapping-sqlalchemy-models)\n- [Resolver Generation](#resolver-generation)\n- [Pagination](#pagination)\n- [Filtering](#filtering)\n- [Aggregations](#aggregations)\n- [Mutations](#mutations)\n- [Async Support](#async-support)\n- [Configuration](#configuration)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Installation\n\nStrawchemy is available on PyPi\n\n```console\npip install strawchemy\n```\n\nStrawchemy has the following optional dependencies:\n\n- `geo` : Enable Postgis support through [geoalchemy2](https://github.com/geoalchemy/geoalchemy2)\n\nTo install these dependencies along with strawchemy:\n\n```console\npip install strawchemy[geo]\n```\n\n## Quick Start\n\n```python\nimport strawberry\nfrom strawchemy import Strawchemy\nfrom sqlalchemy import ForeignKey\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship\n\n# Initialize the strawchemy mapper\nstrawchemy = Strawchemy(\"postgresql\")\n\n\n# Define SQLAlchemy models\nclass Base(DeclarativeBase):\n    pass\n\n\nclass User(Base):\n    __tablename__ = \"user\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    name: Mapped[str]\n    posts: Mapped[list[\"Post\"]] = relationship(\"Post\", back_populates=\"author\")\n\n\nclass Post(Base):\n    __tablename__ = \"post\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    title: Mapped[str]\n    content: Mapped[str]\n    author_id: Mapped[int] = mapped_column(ForeignKey(\"user.id\"))\n    author: Mapped[User] = relationship(\"User\", back_populates=\"posts\")\n\n\n# Map models to GraphQL types\n@strawchemy.type(User, include=\"all\")\nclass UserType:\n    pass\n\n\n@strawchemy.type(Post, include=\"all\")\nclass PostType:\n    pass\n\n\n# Create filter inputs\n@strawchemy.filter(User, include=\"all\")\nclass UserFilter:\n    pass\n\n\n@strawchemy.filter(Post, include=\"all\")\nclass PostFilter:\n    pass\n\n\n# Create order by inputs\n@strawchemy.order(User, include=\"all\")\nclass UserOrderBy:\n    pass\n\n\n@strawchemy.order(Post, include=\"all\")\nclass PostOrderBy:\n    pass\n\n\n# Define GraphQL query fields\n@strawberry.type\nclass Query:\n    users: list[UserType] = strawchemy.field(filter_input=UserFilter, order_by=UserOrderBy, pagination=True)\n    posts: list[PostType] = strawchemy.field(filter_input=PostFilter, order_by=PostOrderBy, pagination=True)\n\n# Create schema\nschema = strawberry.Schema(query=Query)\n```\n\n```graphql\n{\n  # Users with pagination, filtering, and ordering\n  users(\n    offset: 0\n    limit: 10\n    filter: { name: { contains: \"John\" } }\n    orderBy: { name: ASC }\n  ) {\n    id\n    name\n    posts {\n      id\n      title\n      content\n    }\n  }\n\n  # Posts with exact title match\n  posts(filter: { title: { eq: \"Introduction to GraphQL\" } }) {\n    id\n    title\n    content\n    author {\n      id\n      name\n    }\n  }\n}\n```\n\n## Mapping SQLAlchemy Models\n\nStrawchemy provides an easy way to map SQLAlchemy models to GraphQL types using the `@strawchemy.type` decorator. You can include/exclude specific fields or have strawchemy map all columns/relationships of the model and it's children.\n\n<details>\n<summary>Mapping example</summary>\n\nInclude columns and relationships\n\n```python\nimport strawberry\nfrom strawchemy import Strawchemy\n\n# Assuming these models are defined as in the Quick Start example\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship\nfrom sqlalchemy import ForeignKey\n\nstrawchemy = Strawchemy(\"postgresql\")\n\n\nclass Base(DeclarativeBase):\n    pass\n\nclass User(Base):\n    __tablename__ = \"user\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    name: Mapped[str]\n    posts: Mapped[list[\"Post\"]] = relationship(\"Post\", back_populates=\"author\")\n\n\n@strawchemy.type(User, include=\"all\")\nclass UserType:\n    pass\n```\n\nIncluding/excluding specific fields\n\n```python\nclass User(Base):\n    __tablename__ = \"user\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    name: Mapped[str]\n    password: Mapped[str]\n\n\n# Include specific fields\n@strawchemy.type(User, include=[\"id\", \"name\"])\nclass UserType:\n    pass\n\n\n# Exclude specific fields\n@strawchemy.type(User, exclude=[\"password\"])\nclass UserType:\n    pass\n\n\n# Include all fields\n@strawchemy.type(User, include=\"all\")\nclass UserType:\n    pass\n```\n\nAdd a custom fields\n\n```python\nfrom strawchemy import ModelInstance\n\nclass User(Base):\n    __tablename__ = \"user\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    first_name: Mapped[str]\n    last_name: Mapped[str]\n\n\n@strawchemy.type(User, include=\"all\")\nclass UserType:\n    instance: ModelInstance[User]\n\n    @strawchemy.field\n    def full_name(self) -> str:\n        return f\"{self.instance.first_name} {self.instance.last_name}\"\n```\n\nSee the [custom resolvers](#custom-resolvers) for more details\n\n</details>\n\n### Type Override\n\nWhen generating types for relationships, Strawchemy creates default names (e.g., `<ModelName>Type`). If you have already defined a Python class with that same name, it will cause a name collision.\n\nThe `override=True` parameter tells Strawchemy that your definition should be used, resolving the conflict.\n\n<details>\n<summary>Using `override=True`</summary>\n\nConsider these models:\n\n```python\nclass Author(Base):\n    __tablename__ = \"author\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    name: Mapped[str]\n\nclass Book(Base):\n    __tablename__ = \"book\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    title: Mapped[str]\n    author_id: Mapped[int] = mapped_column(ForeignKey(\"author.id\"))\n    author: Mapped[Author] = relationship()\n```\n\nIf you define a type for `Book`, Strawchemy will inspect the `author` relationship and attempt to auto-generate a type for the `Author` model, naming it `AuthorType` by default. If you have already defined a class with that name, it will cause a name collision.\n\n```python\n# Let's say you've already defined this class\n@strawchemy.type(Book, include=\"all\")\nclass BookType:\n    pass\n\n# This will cause an error because Strawchemy has already created `AuthorType` when generating `BookType`\n@strawchemy.type(Book, include=\"all\")\nclass AuthorType:\n    ...\n```\n\nYou would see an error like: `Type 'AuthorType' cannot be auto generated because it's already declared.`\n\nTo solve this, you can create a single, definitive `AuthorType` and mark it with `override=True`. This tells Strawchemy to use your version instead of generating a new one.\n\n```python\n@strawchemy.type(Author, include=\"all\", override=True)\nclass AuthorType:\n    pass\n\n# Now this works, because Strawchemy knows to use your `AuthorType`\n@strawchemy.type(Book, include=\"all\")\nclass BookType:\n    pass\n```\n\n</details>\n\n### Reuse types in schema\n\nWhile `override=True` solves name collisions, `scope=\"global\"` is used to promote consistency and reuse.\n\nBy defining a type with `scope=\"global\"`, you register it as the canonical type for a given SQLAlchemy model and purpose (e.g. a strawberry `type`, `filter`, or `input`). Strawchemy will then automatically use this globally-scoped type everywhere it's needed in your schema, rather than generating new ones.\n\n<details>\n<summary>Using `scope=\"global\"`</summary>\n\nLet's define a global type for the `Color` model. This type will now be the default for the `Color` model across the entire schema.\n\n```python\n# This becomes the canonical type for the `Color` model\n@strawchemy.type(Color, include={\"id\", \"name\"}, scope=\"global\")\nclass ColorType:\n    pass\n\n# Another type that references the Color model\n@strawchemy.type(Fruit, include=\"all\")\nclass FruitType:\n    ...\n    # Strawchemy automatically uses the globally-scoped `ColorType` here\n    # without needing an explicit annotation.\n```\n\nThis ensures that the `Color` model is represented consistently as `ColorType` in all parts of your GraphQL schema, such as in the `FruitType`'s `color` field, without needing to manually specify it every time.\n\n</details>\n\n## Resolver Generation\n\nStrawchemy automatically generates resolvers for your GraphQL fields. You can use the `strawchemy.field()` function to generate fields that query your database\n\n<details>\n<summary>Resolvers example</summary>\n\n```python\n@strawberry.type\nclass Query:\n    # Simple field that returns a list of users\n    users: list[UserType] = strawchemy.field()\n    # Field with filtering, ordering, and pagination\n    filtered_users: list[UserType] = strawchemy.field(filter_input=UserFilter, order_by=UserOrderBy, pagination=True)\n    # Field that returns a single user by ID\n    user: UserType = strawchemy.field()\n```\n\n</details>\n\nWhile Strawchemy automatically generates resolvers for most use cases, you can also create custom resolvers for more complex scenarios. There are two main approaches to creating custom resolvers:\n\n### Using Repository Directly\n\nWhen using `strawchemy.field()` as a function, strawchemy creates a resolver that delegates data fetching to the `StrawchemySyncRepository` or `StrawchemyAsyncRepository` classes depending on the SQLAlchemy session type.\nYou can create custom resolvers by using the `@strawchemy.field` as a decorator and working directly with the repository:\n\n<details>\n<summary>Custom resolvers using repository</summary>\n\n```python\nfrom sqlalchemy import select, true\nfrom strawchemy import StrawchemySyncRepository\n\n@strawberry.type\nclass Query:\n    @strawchemy.field\n    def red_color(self, info: strawberry.Info) -> ColorType:\n        # Create a repository with a predefined filter\n        repo = StrawchemySyncRepository(ColorType, info, filter_statement=select(Color).where(Color.name == \"Red\"))\n        # Return a single result (will raise an exception if not found)\n        return repo.get_one().graphql_type()\n\n    @strawchemy.field\n    def get_color_by_name(self, info: strawberry.Info, color: str) -> ColorType | None:\n        # Create a repository with a custom filter statement\n        repo = StrawchemySyncRepository(ColorType, info, filter_statement=select(Color).where(Color.name == color))\n        # Return a single result or None if not found\n        return repo.get_one_or_none().graphql_type_or_none()\n\n    @strawchemy.field\n    def get_color_by_id(self, info: strawberry.Info, id: str) -> ColorType | None:\n        repo = StrawchemySyncRepository(ColorType, info)\n        # Return a single result or None if not found\n        return repo.get_by_id(id=id).graphql_type_or_none()\n\n    @strawchemy.field\n    def public_colors(self, info: strawberry.Info) -> ColorType:\n        repo = StrawchemySyncRepository(ColorType, info, filter_statement=select(Color).where(Color.public.is_(true())))\n        # Return a list of results\n        return repo.list().graphql_list()\n```\n\nFor async resolvers, use `StrawchemyAsyncRepository` which is the async variant of `StrawchemySyncRepository`:\n\n```python\nfrom strawchemy import StrawchemyAsyncRepository\n\n@strawberry.type\nclass Query:\n    @strawchemy.field\n    async def get_color(self, info: strawberry.Info, color: str) -> ColorType | None:\n        repo = StrawchemyAsyncRepository(ColorType, info, filter_statement=select(Color).where(Color.name == color))\n        return (await repo.get_one_or_none()).graphql_type_or_none()\n```\n\nThe repository provides several methods for fetching data:\n\n- `get_one()`: Returns a single result, raises an exception if not found\n- `get_one_or_none()`: Returns a single result or None if not found\n- `get_by_id()`: Returns a single result filtered on primary key\n- `list()`: Returns a list of results\n\n</details>\n\n### Query Hooks\n\nStrawchemy provides query hooks that allow you to customize query behavior. Query hooks give you fine-grained control over how SQL queries are constructed and executed.\n\n<details>\n<summary>Using query hooks</summary>\n\nThe `QueryHook` base class provides several methods that you can override to customize query behavior:\n\n#### Modifying the statement\n\nYou can subclass `QueryHook` and override the `apply_hook` method apply changes to the statement. By default, it returns it unchanged. This method is only for filtering or ordering customizations, if you want to explicitly load columns or relationships, use the `load` parameter instead.\n\n```python\nfrom strawchemy import ModelInstance, QueryHook\nfrom sqlalchemy import Select, select\nfrom sqlalchemy.orm.util import AliasedClass\n\n# Define a model and type\nclass Fruit(Base):\n    __tablename__ = \"fruit\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    name: Mapped[str]\n    adjectives: Mapped[list[str]] = mapped_column(ARRAY(String))\n\n# Apply the hook at the field level\n@strawchemy.type(Fruit, exclude={\"color\"})\nclass FruitTypeWithDescription:\n    instance: ModelInstance[Fruit]\n\n    # Use QueryHook to ensure specific columns are loaded\n    @strawchemy.field(query_hook=QueryHook(load=[Fruit.name, Fruit.adjectives]))\n    def description(self) -> str:\n        return f\"The {self.instance.name} is {', '.join(self.instance.adjectives)}\"\n\n# Create a custom query hook for filtering\nclass FilterFruitHook(QueryHook[Fruit]):\n    def apply_hook(self, statement: Select[tuple[Fruit]], alias: AliasedClass[Fruit]) -> Select[tuple[Fruit]]:\n        # Add a custom WHERE clause\n        return statement.where(alias.name == \"Apple\")\n\n# Apply the hook at the type level\n@strawchemy.type(Fruit, exclude={\"color\"}, query_hook=FilterFruitHook())\nclass FilteredFruitType:\n    pass\n```\n\nImportant notes when implementing `apply_hooks`:\n\n- You must use the provided `alias` parameter to refer to columns of the model on which the hook is applied. Otherwise, the statement may fail.\n- The GraphQL context is available through `self.info` within hook methods.\n- You must set a `ModelInstance` typed attribute if you want to access the model instance values.\n  The `instance` attribute is matched by the `ModelInstance[Fruit]` type hint, so you can give it any name you want.\n\n#### Load specific columns/relationships\n\nThe `load` parameter specify columns and relationships that should always be loaded, even if not directly requested in the GraphQL query. This is useful for:\n\n- Ensuring data needed for computed properties is available\n- Loading columns or relationships required for custom resolvers\n\nExamples of using the `load` parameter:\n\n```python\n# Load specific columns\n@strawchemy.field(query_hook=QueryHook(load=[Fruit.name, Fruit.adjectives]))\ndef description(self) -> str:\n    return f\"The {self.instance.name} is {', '.join(self.instance.adjectives)}\"\n\n# Load a relationship without specifying columns\n@strawchemy.field(query_hook=QueryHook(load=[Fruit.farms]))\ndef pretty_farms(self) -> str:\n    return f\"Farms are: {', '.join(farm.name for farm in self.instance.farms)}\"\n\n# Load a relationship with specific columns\n@strawchemy.field(query_hook=QueryHook(load=[(Fruit.color, [Color.name, Color.created_at])]))\ndef pretty_color(self) -> str:\n    return f\"Color is {self.instance.color.name}\" if self.instance.color else \"No color!\"\n\n# Load nested relationships\n@strawchemy.field(query_hook=QueryHook(load=[(Color.fruits, [(Fruit.farms, [FruitFarm.name])])]))\ndef farms(self) -> str:\n    return f\"Farms are: {', '.join(farm.name for fruit in self.instance.fruits for farm in fruit.farms)}\"\n```\n\n</details>\n\n## Pagination\n\nStrawchemy supports offset-based pagination out of the box.\n\n<details>\n<summary>Pagination example:</summary>\n\nEnable pagination on fields:\n\n```python\nfrom strawchemy.types import DefaultOffsetPagination\n\n@strawberry.type\nclass Query:\n    # Enable pagination with default settings\n    users: list[UserType] = strawchemy.field(pagination=True)\n    # Customize pagination defaults\n    users_custom_pagination: list[UserType] = strawchemy.field(pagination=DefaultOffsetPagination(limit=20))\n```\n\nIn your GraphQL queries, you can use the `offset` and `limit` parameters:\n\n```graphql\n{\n  users(offset: 0, limit: 10) {\n    id\n    name\n  }\n}\n```\n\nYou can also enable pagination for nested relationships:\n\n```python\n@strawchemy.type(User, include=\"all\", child_pagination=True)\nclass UserType:\n    pass\n```\n\nThen in your GraphQL queries:\n\n```graphql\n{\n  users {\n    id\n    name\n    posts(offset: 0, limit: 5) {\n      id\n      title\n    }\n  }\n}\n```\n\n</details>\n\n## Filtering\n\nStrawchemy provides powerful filtering capabilities.\n\n<details>\n<summary>Filtering example</summary>\n\nFirst, create a filter input type:\n\n```python\n@strawchemy.filter(User, include=\"all\")\nclass UserFilter:\n    pass\n```\n\nThen use it in your field:\n\n```python\n@strawberry.type\nclass Query:\n    users: list[UserType] = strawchemy.field(filter_input=UserFilter)\n```\n\nNow you can use various filter operations in your GraphQL queries:\n\n```graphql\n{\n  # Equality filter\n  users(filter: { name: { eq: \"John\" } }) {\n    id\n    name\n  }\n\n  # Comparison filters\n  users(filter: { age: { gt: 18, lte: 30 } }) {\n    id\n    name\n    age\n  }\n\n  # String filters\n  users(filter: { name: { contains: \"oh\", ilike: \"%OHN%\" } }) {\n    id\n    name\n  }\n\n  # Logical operators\n  users(filter: { _or: [{ name: { eq: \"John\" } }, { name: { eq: \"Jane\" } }] }) {\n    id\n    name\n  }\n  # Nested filters\n  users(filter: { posts: { title: { contains: \"GraphQL\" } } }) {\n    id\n    name\n    posts {\n      id\n      title\n    }\n  }\n\n  # Compare interval component\n  tasks(filter: { duration: { days: { gt: 2 } } }) {\n    id\n    name\n    duration\n  }\n\n  # Direct interval comparison\n  tasks(filter: { duration: { gt: \"P2DT5H\" } }) {\n    id\n    name\n    duration\n  }\n}\n```\n\n</details>\n\nStrawchemy supports a wide range of filter operations:\n\n| Data Type/Category                      | Filter Operations                                                                                                                                                                |\n| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Common to most types**                | `eq`, `neq`, `isNull`, `in`, `nin`                                                                                                                                               |\n| **Numeric types (Int, Float, Decimal)** | `gt`, `gte`, `lt`, `lte`                                                                                                                                                         |\n| **String**                              | order filter, plus `like`, `nlike`, `ilike`, `nilike`, `regexp`, `iregexp`, `nregexp`, `inregexp`, `startswith`, `endswith`, `contains`, `istartswith`, `iendswith`, `icontains` |\n| **JSON**                                | `contains`, `containedIn`, `hasKey`, `hasKeyAll`, `hasKeyAny`                                                                                                                    |\n| **Array**                               | `contains`, `containedIn`, `overlap`                                                                                                                                             |\n| **Date**                                | order filters on plain dates, plus `year`, `month`, `day`, `weekDay`, `week`, `quarter`, `isoYear` and `isoWeekDay` filters                                                      |\n| **DateTime**                            | All Date filters plus `hour`, `minute`, `second`                                                                                                                                 |\n| **Time**                                | order filters on plain times, plus `hour`, `minute` and `second` filters                                                                                                         |\n| **Interval**                            | order filters on plain intervals, plus `days`, `hours`, `minutes` and `seconds` filters                                                                                          |\n| **Logical**                             | `_and`, `_or`, `_not`                                                                                                                                                            |\n\n### Geo Filters\n\nStrawchemy supports spatial filtering capabilities for geometry fields using [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946). To use geo filters, you need to have PostGIS installed and enabled in your PostgreSQL database.\n\n<details>\n<summary>Geo filters example</summary>\n\nDefine models and types:\n\n```python\nclass GeoModel(Base):\n    __tablename__ = \"geo\"\n\n    id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)\n    # Define geometry columns using GeoAlchemy2\n    point: Mapped[WKBElement | None] = mapped_column(Geometry(\"POINT\", srid=4326), nullable=True)\n    polygon: Mapped[WKBElement | None] = mapped_column(Geometry(\"POLYGON\", srid=4326), nullable=True)\n\n@strawchemy.type(GeoModel, include=\"all\")\nclass GeoType: ...\n\n@strawchemy.filter(GeoModel, include=\"all\")\nclass GeoFieldsFilter: ...\n\n@strawberry.type\nclass Query:\ngeo: list[GeoType] = strawchemy.field(filter_input=GeoFieldsFilter)\n\n```\n\nThen you can use the following geo filter operations in your GraphQL queries:\n\n```graphql\n{\n  # Find geometries that contain a point\n  geo(\n    filter: {\n      polygon: { containsGeometry: { type: \"Point\", coordinates: [0.5, 0.5] } }\n    }\n  ) {\n    id\n    polygon\n  }\n\n  # Find geometries that are within a polygon\n  geo(\n    filter: {\n      point: {\n        withinGeometry: {\n          type: \"Polygon\"\n          coordinates: [[[0, 0], [0, 2], [2, 2], [2, 0], [0, 0]]]\n        }\n      }\n    }\n  ) {\n    id\n    point\n  }\n\n  # Find records with null geometry\n  geo(filter: { point: { isNull: true } }) {\n    id\n  }\n}\n```\n\n</details>\n\nStrawchemy supports the following geo filter operations:\n\n- **containsGeometry**: Filters for geometries that contain the specified GeoJSON geometry\n- **withinGeometry**: Filters for geometries that are within the specified GeoJSON geometry\n- **isNull**: Filters for null or non-null geometry values\n\nThese filters work with all geometry types supported by PostGIS, including:\n\n- `Point`\n- `LineString`\n- `Polygon`\n- `MultiPoint`\n- `MultiLineString`\n- `MultiPolygon`\n- `Geometry` (generic geometry type)\n\n## Aggregations\n\nStrawchemy automatically exposes aggregation fields for list relationships.\n\nWhen you define a model with a list relationship, the corresponding GraphQL type will include an aggregation field for that relationship, named `<field_name>Aggregate`.\n\n<details>\n<summary> Basic aggregation example:</summary>\n\nWith the folliing model definitions:\n\n```python\nclass User(Base):\n    __tablename__ = \"user\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    name: Mapped[str]\n    posts: Mapped[list[\"Post\"]] = relationship(\"Post\", back_populates=\"author\")\n\n\nclass Post(Base):\n    __tablename__ = \"post\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    title: Mapped[str]\n    content: Mapped[str]\n    author_id: Mapped[int] = mapped_column(ForeignKey(\"user.id\"))\n    author: Mapped[User] = relationship(\"User\", back_populates=\"posts\")\n```\n\nAnd the corresponding GraphQL types:\n\n```python\n@strawchemy.type(User, include=\"all\")\nclass UserType:\n    pass\n\n\n@strawchemy.type(Post, include=\"all\")\nclass PostType:\n    pass\n```\n\nYou can query aggregations on the `posts` relationship:\n\n```graphql\n{\n  users {\n    id\n    name\n    postsAggregate {\n      count\n      min {\n        title\n      }\n      max {\n        title\n      }\n      # Other aggregation functions are also available\n    }\n  }\n}\n```\n\n</details>\n\n### Filtering by relationship aggregations\n\nYou can also filter entities based on aggregations of their related entities.\n\n<details>\n<summary>Aggregation filtering example</summary>\n\nDefine types with filters:\n\n```python\n@strawchemy.filter(User, include=\"all\")\nclass UserFilter:\n    pass\n\n\n@strawberry.type\nclass Query:\n    users: list[UserType] = strawchemy.field(filter_input=UserFilter)\n```\n\nFor example, to find users who have more than 5 posts:\n\n```graphql\n{\n  users(\n    filter: {\n      postsAggregate: { count: { arguments: [id], predicate: { gt: 5 } } }\n    }\n  ) {\n    id\n    name\n    postsAggregate {\n      count\n    }\n  }\n}\n```\n\nYou can use various predicates for filtering:\n\n```graphql\n# Users with exactly 3 posts\nusers(filter: {\n  postsAggregate: {\n    count: {\n      arguments: [id]\n      predicate: { eq: 3 }\n    }\n  }\n})\n\n# Users with posts containing \"GraphQL\" in the title\nusers(filter: {\n  postsAggregate: {\n    maxString: {\n      arguments: [title]\n      predicate: { contains: \"GraphQL\" }\n    }\n  }\n})\n\n# Users with an average post length greater than 1000 characters\nusers(filter: {\n  postsAggregate: {\n    avg: {\n      arguments: [contentLength]\n      predicate: { gt: 1000 }\n    }\n  }\n})\n```\n\n</details>\n\n#### Distinct aggregations\n\n<details>\n<summary>Distinct aggregation filtering example</summary>\n\nYou can also use the `distinct` parameter to count only distinct values:\n\n```graphql\n{\n  users(\n    filter: {\n      postsAggregate: {\n        count: { arguments: [category], predicate: { gt: 2 }, distinct: true }\n      }\n    }\n  ) {\n    id\n    name\n  }\n}\n```\n\nThis would find users who have posts in more than 2 distinct categories.\n\n</details>\n\n### Root aggregations\n\nStrawchemy supports query level aggregations.\n\n<details>\n<summary>Root aggregations example:</summary>\n\nFirst, create an aggregation type:\n\n```python\n@strawchemy.aggregate(User, include=\"all\")\nclass UserAggregationType:\n    pass\n```\n\nThen set up the root aggregations on the field:\n\n```python\n@strawberry.type\nclass Query:\n    users_aggregations: UserAggregationType = strawchemy.field(root_aggregations=True)\n```\n\nNow you can use aggregation functions on the result of your query:\n\n```graphql\n{\n  usersAggregations {\n    aggregations {\n      # Basic aggregations\n      count\n\n      sum {\n        age\n      }\n\n      avg {\n        age\n      }\n\n      min {\n        age\n        createdAt\n      }\n      max {\n        age\n        createdAt\n      }\n\n      # Statistical aggregations\n      stddev {\n        age\n      }\n      variance {\n        age\n      }\n    }\n    # Access the actual data\n    nodes {\n      id\n      name\n      age\n    }\n  }\n}\n```\n\n</details>\n\n## Mutations\n\nStrawchemy provides a powerful way to create GraphQL mutations for your SQLAlchemy models. These mutations allow you to create, update, and delete data through your GraphQL API.\n\n<details>\n<summary>Mutations example</summary>\n\n```python\nimport strawberry\nfrom strawchemy import Strawchemy, StrawchemySyncRepository, StrawchemyAsyncRepository\n\n# Initialize the strawchemy mapper\nstrawchemy = Strawchemy(\"postgresql\")\n\n# Define input types for mutations\n@strawchemy.input(User, include=[\"name\", \"email\"])\nclass UserCreateInput:\n    pass\n\n@strawchemy.input(User, include=[\"id\", \"name\", \"email\"])\nclass UserUpdateInput:\n    pass\n\n@strawchemy.filter(User, include=\"all\")\nclass UserFilter:\n    pass\n\n# Define GraphQL mutation fields\n@strawberry.type\nclass Mutation:\n    # Create mutations\n    create_user: UserType = strawchemy.create(UserCreateInput)\n    create_users: list[UserType] = strawchemy.create(UserCreateInput)  # Batch creation\n\n    # Update mutations\n    update_user: UserType = strawchemy.update_by_ids(UserUpdateInput)\n    update_users: list[UserType] = strawchemy.update_by_ids(UserUpdateInput)  # Batch update\n    update_users_filter: list[UserType] = strawchemy.update(UserUpdateInput, UserFilter)  # Update with filter\n\n    # Delete mutations\n    delete_users: list[UserType] = strawchemy.delete()  # Delete all\n    delete_users_filter: list[UserType] = strawchemy.delete(UserFilter)  # Delete with filter\n\n# Create schema with mutations\nschema = strawberry.Schema(query=Query, mutation=Mutation)\n```\n\n</details>\n\n### Create Mutations\n\nCreate mutations allow you to insert new records into your database. Strawchemy provides two types of create mutations:\n\n1. **Single entity creation**: Creates a single record\n2. **Batch creation**: Creates multiple records in a single operation\n\n<details>\n<summary>Create mutation examples</summary>\n\n#### Basic Create Mutation\n\n```python\n# Define input type for creation\n@strawchemy.input(Color, include=[\"name\"])\nclass ColorCreateInput:\n    pass\n\n@strawberry.type\nclass Mutation:\n    # Single entity creation\n    create_color: ColorType = strawchemy.create(ColorCreateInput)\n\n    # Batch creation\n    create_colors: list[ColorType] = strawchemy.create(ColorCreateInput)\n```\n\nGraphQL usage:\n\n```graphql\n# Create a single color\nmutation {\n  createColor(data: { name: \"Purple\" }) {\n    id\n    name\n  }\n}\n\n# Create multiple colors in one operation\nmutation {\n  createColors(data: [{ name: \"Teal\" }, { name: \"Magenta\" }]) {\n    id\n    name\n  }\n}\n```\n\n</details>\n\n### Working with Relationships in Create Mutations\n\nStrawchemy supports creating entities with relationships. You can:\n\n1. **Set existing relationships**: Link to existing records\n2. **Create nested relationships**: Create related records in the same mutation\n3. **Set to null**: Remove relationships\n\n<details>\n<summary>Create with relationships examples</summary>\n\n#### To-One Relationships\n\n```python\n@strawchemy.input(Fruit, include=[\"name\", \"adjectives\"])\nclass FruitCreateInput:\n    # Define relationship inputs\n    color: auto  # 'auto' will generate appropriate relationship inputs\n```\n\nGraphQL usage:\n\n```graphql\n# Set an existing relationship\nmutation {\n  createFruit(\n    data: {\n      name: \"Apple\"\n      adjectives: [\"sweet\", \"crunchy\"]\n      color: { set: { id: \"123e4567-e89b-12d3-a456-426614174000\" } }\n    }\n  ) {\n    id\n    name\n    color {\n      id\n      name\n    }\n  }\n}\n\n# Create a new related entity\nmutation {\n  createFruit(\n    data: {\n      name: \"Banana\"\n      adjectives: [\"yellow\", \"soft\"]\n      color: { create: { name: \"Yellow\" } }\n    }\n  ) {\n    id\n    name\n    color {\n      id\n      name\n    }\n  }\n}\n\n# Set relationship to null\nmutation {\n  createFruit(\n    data: {\n      name: \"Strawberry\"\n      adjectives: [\"red\", \"sweet\"]\n      color: { set: null }\n    }\n  ) {\n    id\n    name\n    color {\n      id\n    }\n  }\n}\n```\n\n#### To-Many Relationships\n\n```python\n@strawchemy.input(Color, include=[\"name\"])\nclass ColorCreateInput:\n    # Define to-many relationship inputs\n    fruits: auto  # 'auto' will generate appropriate relationship inputs\n```\n\nGraphQL usage:\n\n```graphql\n# Set existing to-many relationships\nmutation {\n  createColor(\n    data: {\n      name: \"Red\"\n      fruits: { set: [{ id: \"123e4567-e89b-12d3-a456-426614174000\" }] }\n    }\n  ) {\n    id\n    name\n    fruits {\n      id\n      name\n    }\n  }\n}\n\n# Add to existing to-many relationships\nmutation {\n  createColor(\n    data: {\n      name: \"Green\"\n      fruits: { add: [{ id: \"123e4567-e89b-12d3-a456-426614174000\" }] }\n    }\n  ) {\n    id\n    name\n    fruits {\n      id\n      name\n    }\n  }\n}\n\n# Create new related entities\nmutation {\n  createColor(\n    data: {\n      name: \"Blue\"\n      fruits: {\n        create: [\n          { name: \"Blueberry\", adjectives: [\"small\", \"blue\"] }\n          { name: \"Plum\", adjectives: [\"juicy\", \"purple\"] }\n        ]\n      }\n    }\n  ) {\n    id\n    name\n    fruits {\n      id\n      name\n    }\n  }\n}\n```\n\n#### Nested Relationships\n\nYou can create deeply nested relationships:\n\n```graphql\nmutation {\n  createColor(\n    data: {\n      name: \"White\"\n      fruits: {\n        create: [\n          {\n            name: \"Grape\"\n            adjectives: [\"tangy\", \"juicy\"]\n            farms: { create: [{ name: \"Bio farm\" }] }\n          }\n        ]\n      }\n    }\n  ) {\n    name\n    fruits {\n      name\n      farms {\n        name\n      }\n    }\n  }\n}\n```\n\n</details>\n\n### Update Mutations\n\nUpdate mutations allow you to modify existing records. Strawchemy provides several types of update mutations:\n\n1. **Update by primary key**: Update a specific record by its ID\n2. **Batch update by primary keys**: Update multiple records by their IDs\n3. **Update with filter**: Update records that match a filter condition\n\n<details>\n<summary>Update mutation examples</summary>\n\n#### Basic Update Mutation\n\n```python\n# Define input type for updates\n@strawchemy.input(Color, include=[\"id\", \"name\"])\nclass ColorUpdateInput:\n    pass\n\n@strawchemy.filter(Color, include=\"all\")\nclass ColorFilter:\n    pass\n\n@strawberry.type\nclass Mutation:\n    # Update by ID\n    update_color: ColorType = strawchemy.update_by_ids(ColorUpdateInput)\n\n    # Batch update by IDs\n    update_colors: list[ColorType] = strawchemy.update_by_ids(ColorUpdateInput)\n\n    # Update with filter\n    update_colors_filter: list[ColorType] = strawchemy.update(ColorUpdateInput, ColorFilter)\n```\n\nGraphQL usage:\n\n```graphql\n# Update by ID\nmutation {\n  updateColor(\n    data: { id: \"123e4567-e89b-12d3-a456-426614174000\", name: \"Crimson\" }\n  ) {\n    id\n    name\n  }\n}\n\n# Batch update by IDs\nmutation {\n  updateColors(\n    data: [\n      { id: \"123e4567-e89b-12d3-a456-426614174000\", name: \"Crimson\" }\n      { id: \"223e4567-e89b-12d3-a456-426614174000\", name: \"Navy\" }\n    ]\n  ) {\n    id\n    name\n  }\n}\n\n# Update with filter\nmutation {\n  updateColorsFilter(\n    data: { name: \"Bright Red\" }\n    filter: { name: { eq: \"Red\" } }\n  ) {\n    id\n    name\n  }\n}\n```\n\n</details>\n\n### Working with Relationships in Update Mutations\n\nSimilar to create mutations, update mutations support modifying relationships:\n\n<details>\n<summary>Update with relationships examples</summary>\n\n#### To-One Relationships\n\n```python\n@strawchemy.input(Fruit, include=[\"id\", \"name\"])\nclass FruitUpdateInput:\n    # Define relationship inputs\n    color: auto\n```\n\nGraphQL usage:\n\n```graphql\n# Set an existing relationship\nmutation {\n  updateFruit(\n    data: {\n      id: \"123e4567-e89b-12d3-a456-426614174000\"\n      name: \"Red Apple\"\n      color: { set: { id: \"223e4567-e89b-12d3-a456-426614174000\" } }\n    }\n  ) {\n    id\n    name\n    color {\n      id\n      name\n    }\n  }\n}\n\n# Create a new related entity\nmutation {\n  updateFruit(\n    data: {\n      id: \"123e4567-e89b-12d3-a456-426614174000\"\n      name: \"Green Apple\"\n      color: { create: { name: \"Green\" } }\n    }\n  ) {\n    id\n    name\n    color {\n      id\n      name\n    }\n  }\n}\n\n# Set relationship to null\nmutation {\n  updateFruit(\n    data: {\n      id: \"123e4567-e89b-12d3-a456-426614174000\"\n      name: \"Plain Apple\"\n      color: { set: null }\n    }\n  ) {\n    id\n    name\n    color {\n      id\n    }\n  }\n}\n```\n\n#### To-Many Relationships\n\n```python\n@strawchemy.input(Color, include=[\"id\", \"name\"])\nclass ColorUpdateInput:\n    # Define to-many relationship inputs\n    fruits: auto\n```\n\nGraphQL usage:\n\n```graphql\n# Set (replace) to-many relationships\nmutation {\n  updateColor(\n    data: {\n      id: \"123e4567-e89b-12d3-a456-426614174000\"\n      name: \"Red\"\n      fruits: { set: [{ id: \"223e4567-e89b-12d3-a456-426614174000\" }] }\n    }\n  ) {\n    id\n    name\n    fruits {\n      id\n      name\n    }\n  }\n}\n\n# Add to existing to-many relationships\nmutation {\n  updateColor(\n    data: {\n      id: \"123e4567-e89b-12d3-a456-426614174000\"\n      name: \"Red\"\n      fruits: { add: [{ id: \"223e4567-e89b-12d3-a456-426614174000\" }] }\n    }\n  ) {\n    id\n    name\n    fruits {\n      id\n      name\n    }\n  }\n}\n\n# Remove from to-many relationships\nmutation {\n  updateColor(\n    data: {\n      id: \"123e4567-e89b-12d3-a456-426614174000\"\n      name: \"Red\"\n      fruits: { remove: [{ id: \"223e4567-e89b-12d3-a456-426614174000\" }] }\n    }\n  ) {\n    id\n    name\n    fruits {\n      id\n      name\n    }\n  }\n}\n\n# Create new related entities\nmutation {\n  updateColor(\n    data: {\n      id: \"123e4567-e89b-12d3-a456-426614174000\"\n      name: \"Red\"\n      fruits: {\n        create: [\n          { name: \"Cherry\", adjectives: [\"small\", \"red\"] }\n          { name: \"Strawberry\", adjectives: [\"sweet\", \"red\"] }\n        ]\n      }\n    }\n  ) {\n    id\n    name\n    fruits {\n      id\n      name\n    }\n  }\n}\n```\n\n#### Combining Operations\n\nYou can combine `add` and `create` operations in a single update:\n\n```graphql\nmutation {\n  updateColor(\n    data: {\n      id: \"123e4567-e89b-12d3-a456-426614174000\"\n      name: \"Red\"\n      fruits: {\n        add: [{ id: \"223e4567-e89b-12d3-a456-426614174000\" }]\n        create: [{ name: \"Raspberry\", adjectives: [\"tart\", \"red\"] }]\n      }\n    }\n  ) {\n    id\n    name\n    fruits {\n      id\n      name\n    }\n  }\n}\n```\n\nNote: You cannot use `set` with `add`, `remove`, or `create` in the same operation for to-many relationships.\n\n</details>\n\n### Delete Mutations\n\nDelete mutations allow you to remove records from your database. Strawchemy provides two types of delete mutations:\n\n1. **Delete all**: Removes all records of a specific type\n2. **Delete with filter**: Removes records that match a filter condition\n\n<details>\n<summary>Delete mutation examples</summary>\n\n```python\n@strawchemy.filter(User, include=\"all\")\nclass UserFilter:\n    pass\n\n@strawberry.type\nclass Mutation:\n    # Delete all users\n    delete_users: list[UserType] = strawchemy.delete()\n\n    # Delete users that match a filter\n    delete_users_filter: list[UserType] = strawchemy.delete(UserFilter)\n```\n\nGraphQL usage:\n\n```graphql\n# Delete all users\nmutation {\n  deleteUsers {\n    id\n    name\n  }\n}\n\n# Delete users that match a filter\nmutation {\n  deleteUsersFilter(filter: { name: { eq: \"Alice\" } }) {\n    id\n    name\n  }\n}\n```\n\nThe returned data contains the records that were deleted.\n\n</details>\n\n### Upsert Mutations\n\nUpsert mutations provide \"insert or update\" functionality, allowing you to create new records or update existing ones based on conflict resolution. This is particularly useful when you want to ensure data exists without worrying about whether it's already in the database.\n\nStrawchemy supports upsert operations for:\n\n1. **Root-level upserts**: Direct upsert mutations on entities\n2. **Relationship upserts**: Upsert operations within relationship mutations\n\n<details>\n<summary>Upsert mutation examples</summary>\n\n#### Basic Upsert Mutation\n\nFirst, define the necessary input types and enums:\n\n```python\n# Define input type for upsert\n@strawchemy.input(Fruit, include=[\"name\", \"sweetness\", \"waterPercent\"])\nclass FruitCreateInput:\n    pass\n\n# Define which fields can be updated during upsert\n@strawchemy.upsert_update_fields(Fruit, include=[\"sweetness\", \"waterPercent\"])\nclass FruitUpsertFields:\n    pass\n\n# Define which fields are used for conflict detection\n@strawchemy.upsert_conflict_fields(Fruit)\nclass FruitUpsertConflictFields:\n    pass\n\n@strawberry.type\nclass Mutation:\n    # Single entity upsert\n    upsert_fruit: FruitType = strawchemy.upsert(\n        FruitCreateInput,\n        update_fields=FruitUpsertFields,\n        conflict_fields=FruitUpsertConflictFields\n    )\n\n    # Batch upsert\n    upsert_fruits: list[FruitType] = strawchemy.upsert(\n        FruitCreateInput,\n        update_fields=FruitUpsertFields,\n        conflict_fields=FruitUpsertConflictFields\n    )\n```\n\n#### GraphQL Usage\n\n```graphql\n# Upsert a single fruit (will create if name doesn't exist, update if it does)\nmutation {\n  upsertFruit(\n    data: { name: \"Apple\", sweetness: 8, waterPercent: 0.85 }\n    conflictFields: name\n  ) {\n    id\n    name\n    sweetness\n    waterPercent\n  }\n}\n\n# Batch upsert multiple fruits\nmutation {\n  upsertFruits(\n    data: [\n      { name: \"Apple\", sweetness: 8, waterPercent: 0.85 }\n      { name: \"Orange\", sweetness: 6, waterPercent: 0.87 }\n    ]\n    conflictFields: name\n  ) {\n    id\n    name\n    sweetness\n    waterPercent\n  }\n}\n```\n\n#### How Upsert Works\n\n1. **Conflict Detection**: The `conflictFields` parameter specifies which field(s) to check for existing records\n2. **Update Fields**: The `updateFields` parameter (optional) specifies which fields should be updated if a conflict is found\n3. **Database Support**:\n   - **PostgreSQL**: Uses `ON CONFLICT DO UPDATE`\n   - **MySQL**: Uses `ON DUPLICATE KEY UPDATE`\n   - **SQLite**: Uses `ON CONFLICT DO UPDATE`\n\n#### Upsert in Relationships\n\nYou can also use upsert operations within relationship mutations:\n\n```python\n@strawchemy.input(Color, include=[\"id\", \"name\"])\nclass ColorUpdateInput:\n    fruits: auto  # This will include upsert options for fruits\n```\n\n```graphql\n# Update a color and upsert related fruits\nmutation {\n  updateColor(\n    data: {\n      id: 1\n      name: \"Bright Red\"\n      fruits: {\n        upsert: {\n          create: [\n            { name: \"Cherry\", sweetness: 7, waterPercent: 0.87 }\n            { name: \"Strawberry\", sweetness: 8, waterPercent: 0.91 }\n          ]\n          conflictFields: name\n        }\n      }\n    }\n  ) {\n    id\n    name\n    fruits {\n      id\n      name\n      sweetness\n    }\n  }\n}\n```\n\n#### Upsert Behavior\n\n- **If no conflict**: Creates a new record with all provided data\n- **If conflict found**: Updates the existing record with fields specified in `updateFields`\n- **Conflict resolution**: Based on unique constraints, primary keys, or specified conflict fields\n- **Return value**: Always returns the final state of the record (created or updated)\n\n</details>\n\n### Input Validation\n\nStrawchemy supports input validation using Pydantic models. You can define validation schemas and apply them to mutations to ensure data meets specific requirements before being processed.\n\nCreate Pydantic models for the input type where you want the validation, and set the `validation` parameter on `strawchemy.field`:\n\n<details>\n<summary>Validation example</summary>\n\n```python\nfrom models import User, Group\nfrom typing import Annotated\nfrom pydantic import AfterValidator\nfrom strawchemy import InputValidationError, ValidationErrorType\nfrom strawchemy.validation.pydantic import PydanticValidation\n\ndef _check_lower_case(value: str) -> str:\n    if not value.islower():\n        raise ValueError(\"Name must be lower cased\")\n    return value\n\n\n@strawchemy.pydantic.create(Group, include=\"all\")\nclass GroupCreateValidation:\n    name: Annotated[str, AfterValidator(_check_lower_case)]\n\n\n@strawchemy.pydantic.create(User, include=\"all\")\nclass UserCreateValidation:\n    name: Annotated[str, AfterValidator(_check_lower_case)]\n    group: GroupCreateValidation | None = strawberry.UNSET\n\n\n@strawberry.type\nclass Mutation:\n    create_user: UserType | ValidationErrorType = strawchemy.create(UserCreate, validation=PydanticValidation(UserCreateValidation))\n```\n\n> To get the validation errors exposed in the schema, you need to add `ValidationErrorType` in the field union type\n\nWhen validation fails, the query will returns a `ValidationErrorType` with detailed error information from pydantic validation:\n\n```graphql\nmutation {\n  createUser(data: { name: \"Bob\" }) {\n    __typename\n    ... on UserType {\n      name\n    }\n    ... on ValidationErrorType {\n      id\n      errors {\n        id\n        loc\n        message\n        type\n      }\n    }\n  }\n}\n```\n\n```json\n{\n  \"data\": {\n    \"createUser\": {\n      \"__typename\": \"ValidationErrorType\",\n      \"id\": \"ERROR\",\n      \"errors\": [\n        {\n          \"id\": \"ERROR\",\n          \"loc\": [\"name\"],\n          \"message\": \"Value error, Name must be lower cased\",\n          \"type\": \"value_error\"\n        }\n      ]\n    }\n  }\n}\n```\n\nValidation also works with nested relationships:\n\n```graphql\nmutation {\n  createUser(\n    data: {\n      name: \"bob\"\n      group: {\n        create: {\n          name: \"Group\" # This will be validated\n          tag: { set: { id: \"...\" } }\n        }\n      }\n    }\n  ) {\n    __typename\n    ... on ValidationErrorType {\n      errors {\n        loc\n        message\n      }\n    }\n  }\n}\n```\n\n</details>\n\n## Async Support\n\nStrawchemy supports both synchronous and asynchronous operations. You can use either `StrawchemySyncRepository` or `StrawchemyAsyncRepository` depending on your needs:\n\n```python\nfrom strawchemy import StrawchemySyncRepository, StrawchemyAsyncRepository\n\n# Synchronous resolver\n@strawchemy.field\ndef get_color(self, info: strawberry.Info, color: str) -> ColorType | None:\n    repo = StrawchemySyncRepository(ColorType, info, filter_statement=select(Color).where(Color.name == color))\n    return repo.get_one_or_none().graphql_type_or_none()\n\n# Asynchronous resolver\n@strawchemy.field\nasync def get_color(self, info: strawberry.Info, color: str) -> ColorType | None:\n    repo = StrawchemyAsyncRepository(ColorType, info, filter_statement=select(Color).where(Color.name == color))\n    return await repo.get_one_or_none().graphql_type_or_none()\n\n# Synchronous mutation\n@strawberry.type\nclass Mutation:\n    create_user: UserType = strawchemy.create(\n        UserCreateInput,\n        repository_type=StrawchemySyncRepository\n    )\n\n# Asynchronous mutation\n@strawberry.type\nclass AsyncMutation:\n    create_user: UserType = strawchemy.create(\n        UserCreateInput,\n        repository_type=StrawchemyAsyncRepository\n    )\n```\n\nBy default, Strawchemy uses the StrawchemySyncRepository as its repository type. You can override this behavior by specifying a different repository using the `repository_type` configuration option.\n\n## Configuration\n\nConfiguration is made by passing a `StrawchemyConfig` to the `Strawchemy` instance.\n\n### Configuration Options\n\n| Option                     | Type                                                        | Default                    | Description                                                                                                                              |\n| -------------------------- | ----------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |\n| `dialect`                  | `SupportedDialect`                                          |                            | Database dialect to use. Supported dialects are \"postgresql\", \"mysql\", \"sqlite\".                                                         |\n| `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`. |\n| `auto_snake_case`          | `bool`                                                      | `True`                     | Automatically convert snake cased names to camel case in GraphQL schema.                                                                 |\n| `repository_type`          | `type[Repository] \\| StrawchemySyncRepository`              | `StrawchemySyncRepository` | Repository class to use for auto resolvers.                                                                                              |\n| `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.        |\n| `execution_options`        | `dict[str, Any]`                                            | `None`                     | SQLAlchemy execution options for repository operations. These options are passed to the SQLAlchemy `execution_options()` method.         |\n| `pagination_default_limit` | `int`                                                       | `100`                      | Default pagination limit when `pagination=True`.                                                                                         |\n| `pagination`               | `bool`                                                      | `False`                    | Enable/disable pagination on list resolvers by default.                                                                                  |\n| `default_id_field_name`    | `str`                                                       | `\"id\"`                     | Name for primary key fields arguments on primary key resolvers.                                                                          |\n| `deterministic_ordering`   | `bool`                                                      | `True`                     | Force deterministic ordering for list resolvers.                                                                                         |\n\n### Example\n\n```python\nfrom strawchemy import Strawchemy, StrawchemyConfig\n\n# Custom session getter function\ndef get_session_from_context(info):\n    return info.context.db_session\n\n# Initialize with custom configuration\nstrawchemy = Strawchemy(\n    StrawchemyConfig(\n      \"postgresql\",\n      session_getter=get_session_from_context,\n      auto_snake_case=True,\n      pagination=True,\n      pagination_default_limit=50,\n      default_id_field_name=\"pk\",\n    )\n)\n```\n\n## Contributing\n\nContributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to this project.\n\n## License\n\nThis project is licensed under the terms of the license included in the [LICENCE](LICENCE) file.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Generate GraphQL API from SQLAlchemy models",
    "version": "0.19.0",
    "project_urls": {
        "Changelog": "https://github.com/gazorby/strawchemy/blob/main/CHANGELOG.md",
        "Documentation": "https://github.com/gazorby/strawchemy",
        "Homepage": "https://github.com/gazorby/strawchemy",
        "Issues": "https://github.com/gazorby/strawchemy/issues",
        "Repository": "https://github.com/gazorby/strawchemy",
        "Sponsor on GitHub": "https://github.com/sponsors/gazorby"
    },
    "split_keywords": [
        "api",
        " sql",
        " graphql",
        " sqlalchemy",
        " strawberry"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "b7e34aaf901474aa7e6c85991db31acc7d0cfca02ece7f164fcbd2f3fbd38c65",
                "md5": "89e5076ef7ff41247aaf7caf86f8ab38",
                "sha256": "f809925c338c9dc83979d89d3f45d0d8155230a47bb2b811c3449f8811322e4d"
            },
            "downloads": -1,
            "filename": "strawchemy-0.19.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "89e5076ef7ff41247aaf7caf86f8ab38",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 165692,
            "upload_time": "2025-09-06T21:04:39",
            "upload_time_iso_8601": "2025-09-06T21:04:39.010308Z",
            "url": "https://files.pythonhosted.org/packages/b7/e3/4aaf901474aa7e6c85991db31acc7d0cfca02ece7f164fcbd2f3fbd38c65/strawchemy-0.19.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "ccf421fc0178371312508a4c21d9c7e39a46ba23ed946b6e7eb9b68a5b294527",
                "md5": "a878e07694debb9dd8b8e772bc3a165b",
                "sha256": "7df9cd39730d26bf2543598e3ecebcdcc3aa9e0631dee2d5402b2c374425e561"
            },
            "downloads": -1,
            "filename": "strawchemy-0.19.0.tar.gz",
            "has_sig": false,
            "md5_digest": "a878e07694debb9dd8b8e772bc3a165b",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 720220,
            "upload_time": "2025-09-06T21:04:40",
            "upload_time_iso_8601": "2025-09-06T21:04:40.481153Z",
            "url": "https://files.pythonhosted.org/packages/cc/f4/21fc0178371312508a4c21d9c7e39a46ba23ed946b6e7eb9b68a5b294527/strawchemy-0.19.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-06 21:04:40",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "gazorby",
    "github_project": "strawchemy",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "strawchemy"
}
        
Elapsed time: 2.46716s