Description
Hi there,
The following pertains to graphene v2.1 combined with graphene-sqlalchemy v2.0.0
Consider the following SQLAlchemy ORM class:
class Book(Base, OrmBaseMixin):
__tablename__ = "books"
book_id = sqlalchemy.Column(
sqlalchemy.types.Integer(),
primary_key=True,
)
title = sqlalchemy.Column(
sqlalchemy.types.Unicode(length=80),
nullable=False,
)
year = sqlalchemy.Column(
sqlalchemy.types.Integer(),
nullable=False,
)
cover_artist = sqlalchemy.Column(
sqlalchemy.types.Unicode(length=80),
nullable=True,
)
exposed to GraphQL through this graphene-sqlalchemy class:
class TypeBook(SQLAlchemyObjectType):
class Meta:
model = Book
Now consider the following query meant to filter books by different fields:
books = graphene.List(
of_type=TypeBook,
title=graphene.Argument(type=graphene.String, required=False),
year=graphene.Argument(type=graphene.Int, required=False),
)
resolved through:
@staticmethod
def resolve_books(
args: Dict,
info: graphql.execution.base.ResolveInfo,
title: Union[str, None] = None,
year: Union[int, None] = None,
):
query = TypeBook.get_query(info=info)
if title:
query = query.filter(Book.title == title)
if year:
query = query.filter(Book.year == year)
# Limit query to the requested fields only.
query = apply_requested_fields(info=info, query=query, orm_class=Book)
books = query.all()
return books
As you can see the above if <name_of_field> then filter on said column
approach would become ugly and cumbersome when dealing with tables/classes with dozens of fields one might want to filter by.
What I'd like to do is implement a generic filter_books
query that will allow for a filters
argument that can take a mix of different filter-classes each explicitly typed to the kind of field it pertains to such as:
class TypeFilterBookTitle(graphene.InputObjectType):
_field = "title"
operator = graphene.Field(FilterOperatorType)
value = graphene.String()
class TypeFilterBookYear(graphene.InputObjectType):
_field = "year"
operator = graphene.Field(FilterOperatorType)
value = graphene.Int()
class TypeFiltersBook(graphene.Union):
class Meta:
types = (TypeFilterBookTitle, TypeFilterBookYear)
filter_books = graphene.List(
of_type=TypeBook,
filters=graphene.Argument(
type=graphene.List(of_type=TypeFiltersBook),
required=False,
)
)
Now the above obviously fails with an:
AssertionError: TypeFiltersBook may only contain Object types, it cannot contain: TypeFilterBookTitle.
which is known behaviour (as seen in #628, graphql/graphql-js#207, etc) as Unions do not support Input types in GraphQL.
My question is: Is there any (roundabout) way to implement this behaviour? The only alternative I can think is a generic filter class that would accept values as strings and which would casted at runtime (based on some mapping somewhere) which, however, does away with the type-system.
Any suggestions are welcome, thanks!