Package: graphene-sqlalchemy-filter
Graphene-SQLAlchemy-FilterFilters forGraphene SQLAlchemy integrationQuick startCreate a filter and add it to the graphene field.fromgraphene_sqlalchemy_filterimportFilterableConnectionField,FilterSetclassUserFilter(FilterSet):is_admin=graphene.Boolean()classMeta:model=Userfields={'username':['eq','ne','in','ilike'],'is_active':[...],# shortcut!}@staticmethoddefis_admin_filter(info,query,value):ifvalue:returnUser.username=='admin'else:returnUser.username!='admin'classQuery(ObjectType):all_users=FilterableConnectionField(UserConnection,filters=UserFilter())Now, we’re going to create query.{
  allUsers (
    filters: {
      isActive: true,
      or: [
        {isAdmin: true},
        {usernameIn: ["moderator", "cool guy"]}
      ]
    }
  ){
    edges {
      node {
        id
        username
      }
    }
  }
}FiltersFilterSet class must inheritgraphene_sqlalchemy_filter.FilterSetor your subclass of this class.There are three types of filters:automatically generated filterssimple filtersfilters that require joinAutomatically generated filtersclassUserFilter(FilterSet):classMeta:model=Userfields={'username':['eq','ne','in','ilike'],'is_active':[...],# shortcut!}Metaclass must contain the sqlalchemy model and fields.Automatically generated filters must be specified byfieldsvariable. Key - field name of sqlalchemy model, value - list of expressions (or shortcut).Shortcut (default:[...]) will add all the allowed filters for this type of sqlalchemy field (does not work with hybrid property).KeyDescriptionGraphQL postfixeqequalnenot equalNelikelikeLikeilikeinsensitive likeIlikeis_nullis nullIsNullininInnot_innot inNotInltless thanLtlteless than or equalLtegtgreater thanGtgtegreater than or equalGterangein rangeRangecontainscontains (PostgreSQL array)Containscontained_bycontained_by (PostgreSQL array)ContainedByoverlapoverlap (PostgreSQL array)OverlapSimple filtersclassUserFilter(FilterSet):is_admin=graphene.Boolean()@staticmethoddefis_admin_filter(info,query,value):ifvalue:returnUser.username=='admin'else:returnUser.username!='admin'Each simple filter has a class variable that passes to GraphQL schema as an input type and function<field_name>_filterthat makes filtration.The filtration function takes the following arguments: *info- ResolveInfo graphene object *query- sqlalchemy query (not used in that filters type) *value- the value of a filterThe return value can be any type of sqlalchemy clause. This means that you can returnnot_(and_(or_(...),...)).Metaclass is not required if you do not need automatically generated filters.Filters that require joinThis type of filter is the same assimple filtersbut has a different return type.The filtration function should return a new sqlalchemy query and clause (like simple filters).classUserFilter(FilterSet):is_moderator=graphene.Boolean()@classmethoddefis_admin_filter(cls,info,query,value):membership=cls.aliased(query,Membership,name='is_moderator')query=query.join(membership,and_(User.id==membership.user_id,membership.is_moderator.is_(True),),)ifvalue:filter_=membership.id.isnot(None)else:filter_=membership.id.is_(None)returnquery,filter_Model aliasesThe functioncls.aliased(query, model,name='...')returnssqlalchemy aliasfrom the query. It has one differing parameter -query(SQLAlchemy Query object). Other arguments are the same assqlalchemy.orm.aliased.Identical joins will be skipped by sqlalchemy.Changed in version 1.7: The first parameter is now a query.FeaturesFilter registration and nested fields filtersFilters can be registered for each SQLAlchemy model in a subclass ofFilterableConnectionField.Register your filters by inheritingFilterableConnectionFieldand settingfilters(key - SQLAlchemy model, value - FilterSet object).classCustomField(FilterableConnectionField):filters={User:UserFilter(),}OverridingSQLAlchemyObjectType.connection_field_factoryallows you to generate nested connections with filters.classUserNode(SQLAlchemyObjectType):classMeta:model=Userinterfaces=(Node,)connection_field_factory=CustomField.factoryImportant:pagination (first/after, last/before) are performed by python (keep this in mind when working with large amounts of data)nested filters work by dataloadersthis module optimizes one-to-many relationships, to optimize many-to-one relationships usesqlalchemy_bulk_lazy_loadernested filters requiregraphene_sqlalchemy>=2.1.2Example# FiltersclassUserFilter(FilterSet):classMeta:model=Userfields={'is_active':[...]}classCustomField(FilterableConnectionField):filters={User:UserFilter(),}# NodesclassUserNode(SQLAlchemyObjectType):classMeta:model=Userinterfaces=(Node,)connection_field_factory=CustomField.factoryclassGroupNode(SQLAlchemyObjectType):classMeta:model=Groupinterfaces=(Node,)connection_field_factory=CustomField.factory# ConnectionsclassUserConnection(Connection):classMeta:node=UserNodeclassGroupConnection(Connection):classMeta:node=GroupNode# QueryclassQuery(ObjectType):all_users=CustomField(UserConnection)all_groups=CustomField(GroupConnection){
  allUsers (filters: {isActive: true}){
    edges { node { id } }
  }
  allGroups {
    edges {
      node {
        users (filters: {isActive: true}) {
          edges { node { id } }
        }
      }
    }
  }
}Rename GraphQL filter fieldclassCustomField(FilterableConnectionField):filter_arg='where'classQuery(ObjectType):all_users=CustomField(UserConnection,where=UserFilter())all_groups=FilterableConnectionField(GroupConnection,filters=GroupFilter()){
  allUsers (where: {isActive: true}){
    edges { node { id } }
  }
  allGroups (filters: {nameIn: ["python", "development"]}){
    edges { node { id } }
  }
}Rename expressionclassBaseFilter(FilterSet):GRAPHQL_EXPRESSION_NAMES=dict(FilterSet.GRAPHQL_EXPRESSION_NAMES,**{'eq':'equal','not':'i_never_asked_for_this'})classMeta:abstract=TrueclassUserFilter(BaseFilter):classMeta:model=Userfields={'first_name':['eq'],'last_name':['eq']}{
  allUsers (filters: {iNeverAskedForThis: {firstNameEqual: "Adam", lastNameEqual: "Jensen"}}){
    edges { node { id } }
  }
}Custom shortcut valueclassBaseFilter(FilterSet):ALL='__all__'classMeta:abstract=TrueclassUserFilter(BaseFilter):classMeta:model=Userfields={'username':'__all__'}Localization of documentationclassBaseFilter(FilterSet):DESCRIPTIONS={'eq':'Полностью совпадает.','ne':'Не совпадает.','like':'Регистрозависимая проверка строки по шлабону.','ilike':'Регистронезависимая проверка строки по шлабону.','regexp':'Регистрозависимая проверка строки по регулярному выражению.','is_null':'Равно ли значение `null`. Принемает `true` или `false`.','in':'Проверка вхождения в список.','not_in':'Проверка не вхождения в список.','lt':'Меньше, чем указанное значение.','lte':'Меньше или равно указанному значению.','gt':'Больше, чем указанное значение.','gte':'Больше или равно указанному значению.','range':'Значение входит в диапазон значений.','and':'Объединение фильтров с помощью ``AND``.','or':'Объединение фильтров с помощью ``OR``.','not':'Отрицание указанных фильтров.',}classMeta:abstract=TrueCustom expressiondeftoday_filter(field,value:bool):today=func.date(field)==date.today()returntodayifvalueelsenot_(today)classBaseFilter(FilterSet):# Add expression.TODAY='today'EXTRA_EXPRESSIONS={'today':{# Add the name of the expression in GraphQL.'graphql_name':'today',# Update allowed filters (used by shortcut).'for_types':[types.Date,types.DateTime],# Add a filtering function (takes the sqlalchemy field and value).'filter':today_filter,# Add the GraphQL input type. Column type by default.'input_type':(lambdatype_,nullable,doc:graphene.Boolean(nullable=False)),# Description for the GraphQL schema.'description':'It is today.',}}classMeta:abstract=TrueclassPostFilter(BaseFilter):classMeta:model=Postfields={'created':['today'],'updated':[...]}{
  allPosts (filters: {createdToday: false, updatedToday: true}){
    edges { node { id } }
  }
}Custom column typesALLOWED_FILTERSandEXTRA_ALLOWED_FILTERSonly affect shortcut.If you do not use the shortcut, you can skip the next steps described in the section.classMyString(types.String):passclassBaseFilter(FilterSet):# You can override all allowed filters# ALLOWED_FILTERS = {types.Integer: ['eq']}# Or add new column typeEXTRA_ALLOWED_FILTERS={MyString:['eq']}classMeta:abstract=True