Name | strawchemy JSON |
Version |
0.9.0
JSON |
| download |
home_page | None |
Summary | Generate GraphQL API from SQLAlchemy models |
upload_time | 2025-03-21 21:32:26 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.9 |
license | MIT |
keywords |
api
sql
graphql
sqlalchemy
strawberry
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# Strawchemy
[](https://github.com/gazorby/strawchemy/actions/workflows/ci.yaml) [](https://codecov.io/gh/gazorby/strawchemy) [](https://pepy.tech/projects/strawchemy)
Generates GraphQL types, inputs, queries and resolvers directly from SQLAlchemy models.
## Features
- 🔄 **Automatic Type Generation**: Generate strawberry types from SQLAlchemy models
- 🧠 **Smart Resolvers**: Automatically generates single, optimized database queries for a given GraphQL request
- 🔍 **Comprehensive Filtering**: Rich filtering capabilities on most data types, including PostGIS geo columns
- 📄 **Pagination Support**: Built-in offset-based pagination
- 📊 **Aggregation Queries**: Support for aggregation functions like count, sum, avg, min, max, and statistical functions
- ⚡ **Sync/Async Support**: Works with both synchronous and asynchronous SQLAlchemy sessions
- 🛢 **Database Support**: Currently only PostgreSQL is officially supported and tested (using [asyncpg](https://github.com/MagicStack/asyncpg) or [psycopg3 sync/async](https://www.psycopg.org/psycopg3/))
## 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)
- [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()
# 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_input(User, include="all")
class UserFilter:
pass
@strawchemy.filter_input(Post, include="all")
class PostFilter:
pass
# Create order by inputs
@strawchemy.order_by_input(User, include="all")
class UserOrderBy:
pass
@strawchemy.order_by_input(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()
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
By default, strawchemy generates strawberry types when visiting the model and the following relationships, but only if you have not already defined a type with the same name using the @strawchemy.type decorator, otherwise you will see an error.
To explicitly tell strawchemy to use your type, you need to define it with `@strawchemy.type(override=True)`.
<details>
<summary>Using the Override Parameter</summary>
```python
from strawchemy import Strawchemy
strawchemy = Strawchemy()
# Define models
class Color(Base):
__tablename__ = "color"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
fruits: Mapped[list["Fruit"]] = relationship("Fruit", back_populates="color")
class Fruit(Base):
__tablename__ = "fruit"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
color_id: Mapped[int] = mapped_column(ForeignKey("color.id"))
color: Mapped[Color] = relationship("Color", back_populates="fruits")
# Define a type with override=True
@strawchemy.type(Color, include="all", override=True)
class ColorType:
fruits: auto
name: int
# Define another type that uses the same model
@strawchemy.type(Fruit, include="all", override=True)
class FruitType:
name: int
color: auto # This will use the ColorType defined above
# Define a query that uses these types
@strawberry.type
class Query:
fruit: FruitType = strawchemy.field()
```
The `override` parameter is useful in the following scenarios:
1. **Type Reuse**: When you need to use the same type in multiple places where the same model is referenced.
2. **Auto-generated Type Override**: When you want to override the default auto-generated type for a model.
3. **Custom Type Names**: When you want to use a custom name for your type but still have it recognized as the type for a specific model.
Without setting `override=True`, you would get an error like:
```
Type `FruitType` cannot be auto generated because it's already declared.
You may want to set `override=True` on the existing type to use it everywhere.
```
This happens when Strawchemy tries to auto-generate a type for a model that already has a type defined for it.
You can also use `override=True` with input types:
```python
@strawchemy.order_by_input(Fruit, include="all", override=True)
class FruitOrderBy:
# Custom order by fields
override: bool = True
```
</details>
</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>
### Custom Resolvers
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:
#### 1. 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()
@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()
@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)
@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()
```
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()
```
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>
#### 2. Using Query Hooks
Strawchemy provides query hooks that allow you to customize query behavior. These hooks can be applied at both the field level and the type level:
<details>
<summary>Using query hooks</summary>
```python
from strawchemy import ModelInstance, QueryHook
from strawchemy.sqlalchemy.hook import QueryHookResult
from sqlalchemy import Select
from sqlalchemy.orm.util import AliasedClass
from strawberry import Info
@strawchemy.type(Fruit, exclude={"color"})
class FruitTypeWithDescription:
instance: ModelInstance[Fruit]
# Use QueryHook with always_load parameter to ensure columns are loaded
@strawchemy.field(query_hook=QueryHook(always_load=[Fruit.name, Fruit.adjectives]))
def description(self) -> str:
return f"The {self.instance.name} is {', '.join(self.instance.adjectives)}"
# Custom query hook function
def user_fruit_filter(
statement: Select[tuple[Fruit]], alias: AliasedClass[Fruit], info: Info
) -> QueryHookResult[Fruit]:
# Add a custom filter based on context
if info.context.role == "user":
return QueryHookResult(statement=statement.where(alias.name == "Apple"))
return QueryHookResult(statement=statement)
# Type-level query hook
@strawchemy.type(Fruit, exclude={"color"}, query_hook=user_fruit_filter)
class FilteredFruitType:
pass
```
> 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.
Query hooks provide powerful ways to:
- Ensure specific columns are always loaded, even if not directly requested in the GraphQL query. (useful to expose hybrid properties in the schema)
- Apply custom filters based on context (e.g., user role)
- Modify the underlying SQLAlchemy query for optimization or security
</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_input(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_input(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_input(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.aggregation_type(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>
## Configuration
Strawchemy can be configured when initializing the mapper.
### Configuration Options
| Option | Type | Default | Description |
| -------------------------- | ----------------------------------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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] \| "auto"` | `"auto"` | Repository class to use for auto resolvers. When set to `"auto"`, Strawchemy will automatically choose between sync and async repositories based on the session type. |
| `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. |
| `dialect` | `Literal["postgresql"]` | `"postgresql"` | Database dialect to use. Currently, only PostgreSQL is supported. |
### Example
```python
from strawchemy import Strawchemy
# Custom session getter function
def get_session_from_context(info):
return info.context.db_session
# Initialize with custom configuration
strawchemy = Strawchemy(
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/4b/94/aa357d352742f9a9776300bbefb5ccff56a82f2cbb1b8410046db6a16e38/strawchemy-0.9.0.tar.gz",
"platform": null,
"description": "# Strawchemy\n\n[](https://github.com/gazorby/strawchemy/actions/workflows/ci.yaml) [](https://codecov.io/gh/gazorby/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 **Automatic 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 **Comprehensive Filtering**: Rich filtering capabilities on most data types, including PostGIS geo columns\n\n- \ud83d\udcc4 **Pagination Support**: Built-in offset-based pagination\n\n- \ud83d\udcca **Aggregation Queries**: Support for aggregation functions like count, sum, avg, min, max, and statistical functions\n\n- \u26a1 **Sync/Async Support**: Works with both synchronous and asynchronous SQLAlchemy sessions\n\n- \ud83d\udee2 **Database Support**: Currently only PostgreSQL is officially supported and tested (using [asyncpg](https://github.com/MagicStack/asyncpg) or [psycopg3 sync/async](https://www.psycopg.org/psycopg3/))\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- [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()\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_input(User, include=\"all\")\nclass UserFilter:\n pass\n\n\n@strawchemy.filter_input(Post, include=\"all\")\nclass PostFilter:\n pass\n\n\n# Create order by inputs\n@strawchemy.order_by_input(User, include=\"all\")\nclass UserOrderBy:\n pass\n\n\n@strawchemy.order_by_input(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()\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\nBy default, strawchemy generates strawberry types when visiting the model and the following relationships, but only if you have not already defined a type with the same name using the @strawchemy.type decorator, otherwise you will see an error.\n\nTo explicitly tell strawchemy to use your type, you need to define it with `@strawchemy.type(override=True)`.\n\n<details>\n<summary>Using the Override Parameter</summary>\n\n```python\nfrom strawchemy import Strawchemy\n\nstrawchemy = Strawchemy()\n\n# Define models\nclass Color(Base):\n __tablename__ = \"color\"\n id: Mapped[int] = mapped_column(primary_key=True)\n name: Mapped[str]\n fruits: Mapped[list[\"Fruit\"]] = relationship(\"Fruit\", back_populates=\"color\")\n\nclass Fruit(Base):\n __tablename__ = \"fruit\"\n id: Mapped[int] = mapped_column(primary_key=True)\n name: Mapped[str]\n color_id: Mapped[int] = mapped_column(ForeignKey(\"color.id\"))\n color: Mapped[Color] = relationship(\"Color\", back_populates=\"fruits\")\n\n# Define a type with override=True\n@strawchemy.type(Color, include=\"all\", override=True)\nclass ColorType:\n fruits: auto\n name: int\n\n# Define another type that uses the same model\n@strawchemy.type(Fruit, include=\"all\", override=True)\nclass FruitType:\n name: int\n color: auto # This will use the ColorType defined above\n\n# Define a query that uses these types\n@strawberry.type\nclass Query:\n fruit: FruitType = strawchemy.field()\n```\n\nThe `override` parameter is useful in the following scenarios:\n\n1. **Type Reuse**: When you need to use the same type in multiple places where the same model is referenced.\n2. **Auto-generated Type Override**: When you want to override the default auto-generated type for a model.\n3. **Custom Type Names**: When you want to use a custom name for your type but still have it recognized as the type for a specific model.\n\nWithout setting `override=True`, you would get an error like:\n\n```\nType `FruitType` cannot be auto generated because it's already declared.\nYou may want to set `override=True` on the existing type to use it everywhere.\n```\n\nThis happens when Strawchemy tries to auto-generate a type for a model that already has a type defined for it.\n\nYou can also use `override=True` with input types:\n\n```python\n@strawchemy.order_by_input(Fruit, include=\"all\", override=True)\nclass FruitOrderBy:\n # Custom order by fields\n override: bool = True\n```\n\n</details>\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\n### Custom Resolvers\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#### 1. 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()\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()\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)\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()\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()\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#### 2. Using Query Hooks\n\nStrawchemy provides query hooks that allow you to customize query behavior. These hooks can be applied at both the field level and the type level:\n\n<details>\n<summary>Using query hooks</summary>\n\n```python\nfrom strawchemy import ModelInstance, QueryHook\nfrom strawchemy.sqlalchemy.hook import QueryHookResult\nfrom sqlalchemy import Select\nfrom sqlalchemy.orm.util import AliasedClass\nfrom strawberry import Info\n\n@strawchemy.type(Fruit, exclude={\"color\"})\nclass FruitTypeWithDescription:\n instance: ModelInstance[Fruit]\n\n # Use QueryHook with always_load parameter to ensure columns are loaded\n @strawchemy.field(query_hook=QueryHook(always_load=[Fruit.name, Fruit.adjectives]))\n def description(self) -> str:\n return f\"The {self.instance.name} is {', '.join(self.instance.adjectives)}\"\n\n# Custom query hook function\ndef user_fruit_filter(\n statement: Select[tuple[Fruit]], alias: AliasedClass[Fruit], info: Info\n) -> QueryHookResult[Fruit]:\n # Add a custom filter based on context\n if info.context.role == \"user\":\n return QueryHookResult(statement=statement.where(alias.name == \"Apple\"))\n return QueryHookResult(statement=statement)\n\n# Type-level query hook\n@strawchemy.type(Fruit, exclude={\"color\"}, query_hook=user_fruit_filter)\nclass FilteredFruitType:\n pass\n```\n\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\nQuery hooks provide powerful ways to:\n\n- Ensure specific columns are always loaded, even if not directly requested in the GraphQL query. (useful to expose hybrid properties in the schema)\n- Apply custom filters based on context (e.g., user role)\n- Modify the underlying SQLAlchemy query for optimization or security\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_input(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_input(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_input(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.aggregation_type(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## Configuration\n\nStrawchemy can be configured when initializing the mapper.\n\n### Configuration Options\n\n| Option | Type | Default | Description |\n| -------------------------- | ----------------------------------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\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] \\| \"auto\"` | `\"auto\"` | Repository class to use for auto resolvers. When set to `\"auto\"`, Strawchemy will automatically choose between sync and async repositories based on the session type. |\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| `dialect` | `Literal[\"postgresql\"]` | `\"postgresql\"` | Database dialect to use. Currently, only PostgreSQL is supported. |\n\n### Example\n\n```python\nfrom strawchemy import Strawchemy\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 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## 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.9.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"
},
"split_keywords": [
"api",
" sql",
" graphql",
" sqlalchemy",
" strawberry"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "5e64a9f26bc428f018bbca1ec0b920523331004f40592b4d55c9cc79a6b609aa",
"md5": "e1c322e78af1e96c9e01bbaa53d532dd",
"sha256": "1a08a62cfe4909c2181aaebbfbd3aa0e03c447ce0fffa759d5e71101b9075d44"
},
"downloads": -1,
"filename": "strawchemy-0.9.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "e1c322e78af1e96c9e01bbaa53d532dd",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 115125,
"upload_time": "2025-03-21T21:32:24",
"upload_time_iso_8601": "2025-03-21T21:32:24.736491Z",
"url": "https://files.pythonhosted.org/packages/5e/64/a9f26bc428f018bbca1ec0b920523331004f40592b4d55c9cc79a6b609aa/strawchemy-0.9.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "4b94aa357d352742f9a9776300bbefb5ccff56a82f2cbb1b8410046db6a16e38",
"md5": "63a690203f06b8ad6c59c3502a85fa8a",
"sha256": "f241711745acddc59a268623e7c16a661e620dfac9b3846b12483c6c51124186"
},
"downloads": -1,
"filename": "strawchemy-0.9.0.tar.gz",
"has_sig": false,
"md5_digest": "63a690203f06b8ad6c59c3502a85fa8a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 414348,
"upload_time": "2025-03-21T21:32:26",
"upload_time_iso_8601": "2025-03-21T21:32:26.525649Z",
"url": "https://files.pythonhosted.org/packages/4b/94/aa357d352742f9a9776300bbefb5ccff56a82f2cbb1b8410046db6a16e38/strawchemy-0.9.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-03-21 21:32:26",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "gazorby",
"github_project": "strawchemy",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "strawchemy"
}