Graphene SQLAlchemy Filter
graphene-sqlalchemy-filter provides a flexible way to generate GraphQL filter arguments for SQLAlchemy models, enabling complex queries and sorting directly through your GraphQL API. As of version 1.14.0, it offers robust filtering capabilities compatible with SQLAlchemy 1.4+ and Graphene SQLAlchemy integrations. The library maintains an active development pace, regularly releasing updates with bug fixes and feature enhancements.
Common errors
-
TypeError: 'NoneType' object is not subscriptable
cause Often related to issues processing enum types or hybrid properties in versions prior to 1.12.x, especially when the underlying SQLAlchemy model data could be `None` or malformed in a specific context.fixEnsure you are on `graphene-sqlalchemy-filter>=1.12.2`. Additionally, verify your SQLAlchemy enum definitions or hybrid property implementations handle potential `None` values gracefully. -
AttributeError: 'SQLAlchemyConnectionField' object has no attribute 'aliased'
cause This error typically occurs when attempting to use filter or sorting logic directly with `graphene_sqlalchemy.SQLAlchemyConnectionField`, which lacks the necessary filter integration.fixReplace `graphene_sqlalchemy.SQLAlchemyConnectionField` with `graphene_sqlalchemy_filter.FilterableConnectionField` in your Graphene schema definitions to enable filtering capabilities. -
ValueError: Unknown filter argument 'some_field_unknown_filter'
cause This means you've attempted to use a filter operator (e.g., `_gt`, `_like`, `_in`) that was not explicitly defined or allowed for `some_field` in your `FilterSet.Meta.fields` configuration.fixReview your `FilterSet.Meta.fields` configuration. For each field, ensure the list of allowed operators includes the one you are trying to use in your GraphQL query. For example: `'my_field': ['eq', 'like']`. -
sqlalchemy.exc.CompileError: (in _create_join_clause) Can't find column...
cause This can happen in complex queries with multiple relationships, especially when filters are applied to related models, and SQLAlchemy struggles to infer the correct join conditions.fixFor ambiguous relationships, explicitly define `primaryjoin` and `foreign_keys` arguments in your SQLAlchemy `relationship` definitions to guide the ORM in constructing joins. Simplify your GraphQL filter query if possible.
Warnings
- breaking Version 1.13.0 introduced changes for compatibility with SQLAlchemy 1.4+. If you are upgrading SQLAlchemy to 1.4+ and encounter issues, ensure `graphene-sqlalchemy-filter` is also updated.
- gotcha Versions prior to 1.14.0 might produce `RemovedIn20Warning` when used with newer SQLAlchemy versions, indicating potential future incompatibility with SQLAlchemy 2.0.
- gotcha Incorrect filtering or errors might occur with `Enum` types or `hybrid_property` attributes on versions older than 1.12.x.
- gotcha When constructing complex queries involving multiple join filters on the same table, older versions might yield incorrect results or unexpected behavior.
Install
-
pip install graphene-sqlalchemy-filter
Imports
- FilterSet
from graphene_sqlalchemy_filter import FilterSet
- FilterableConnectionField
from graphene_sqlalchemy import SQLAlchemyConnectionField
from graphene_sqlalchemy_filter import FilterableConnectionField
- ModelLoader
from graphene_sqlalchemy_filter import ModelLoader
Quickstart
import graphene
import sqlalchemy as sa
from sqlalchemy.orm import declarative_base, sessionmaker, relationship
from graphene_sqlalchemy import SQLAlchemyObjectType
from graphene_sqlalchemy_filter import FilterableConnectionField, FilterSet
# 1. Setup SQLAlchemy (using in-memory SQLite for example)
Base = declarative_base()
class UserModel(Base):
__tablename__ = 'users'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String, nullable=False)
email = sa.Column(sa.String, unique=True, nullable=False)
posts = relationship("PostModel", back_populates="author")
class PostModel(Base):
__tablename__ = 'posts'
id = sa.Column(sa.Integer, primary_key=True)
title = sa.Column(sa.String, nullable=False)
content = sa.Column(sa.String)
author_id = sa.Column(sa.Integer, sa.ForeignKey('users.id'))
author = relationship("UserModel", back_populates="posts")
engine = sa.create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
# Add some initial data
user1 = UserModel(name="Alice", email="alice@example.com")
user2 = UserModel(name="Bob", email="bob@example.com")
session.add_all([user1, user2])
session.commit()
session.refresh(user1)
session.refresh(user2)
post1 = PostModel(title="First Post", content="Hello world", author=user1)
post2 = PostModel(title="Second Post", content="Another one", author=user2)
session.add_all([post1, post2])
session.commit()
# 2. Define Graphene ObjectTypes
class Post(SQLAlchemyObjectType):
class Meta:
model = PostModel
interfaces = (graphene.relay.Node,)
class User(SQLAlchemyObjectType):
class Meta:
model = UserModel
interfaces = (graphene.relay.Node,)
# 3. Define FilterSet for your models
class UserFilter(FilterSet):
class Meta:
model = UserModel
fields = {
'name': ['eq', 'like'],
'email': ['eq'],
'id': ['eq', 'in']
}
class PostFilter(FilterSet):
class Meta:
model = PostModel
fields = {
'title': ['eq', 'like'],
'content': ['like'],
'author_id': ['eq'],
}
# 4. Define your Graphene Query with FilterableConnectionField
class Query(graphene.ObjectType):
node = graphene.relay.Node.Field()
all_users = FilterableConnectionField(User.connection, filters=UserFilter())
all_posts = FilterableConnectionField(Post.connection, filters=PostFilter())
def resolve_all_users(root, info, **kwargs):
return User.get_query(info).session(session)
def resolve_all_posts(root, info, **kwargs):
return Post.get_query(info).session(session)
schema = graphene.Schema(query=Query)
# Example GraphQL query (for demonstration, not part of quickstart output)
# query = """
# query {
# allUsers(filters: {name_like: "Ali"}) {
# edges {
# node {
# id
# name
# email
# posts {
# edges {
# node {
# title
# }
# }
# }
# }
# }
# }
# allPosts(filters: {title_eq: "First Post"}) {
# edges {
# node {
# title
# author {
# name
# }
# }
# }
# }
# }
# """
# result = schema.execute(query)
# if result.errors: print(result.errors)
# else: print(result.data)