question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Flexible arguments in SQLAlchemyConnectionField and custom query to filter against arguments

See original GitHub issue

Hi maintainers,

I have this situation that I need more args to filter the SQLAlchemyConnectionField. Currently from graphene, it gives us (before, after, first, last). My objective is to add additional args for each Model included.

Here be dragons.

Models and theirs attributes: Job: name, blocks Block: name, job Note: 1 Job has 0 or more Blocks

Arguments: jobs: name, before, after, first, last blocks: before, after,first,last

{
  jobs(name: "test") {
    edges {
      node {
        id
        name
        blocks {
          edges {
            node {
              id
            }
          }
        }
      }
    }
  }
}

Notes:

  • NOTE1: this is very important to get this working, maybe need some relationship checking in the code. Without the dynamic relationship, the relationship is not configurable as Query object.
  • NOTE2: this is the base model for the GQL schema, I am thinking to have two filter functions to help on the NOTE3 and NOTE4
  • NOTE3: the filter/hook to add more arguments for example shown in NOTE5
  • NOTE4: the filter/hook to process the arguments for example shown in NOTE6
  • NOTE5: adding name as additional args
  • NOTE6: process the name to the existing query in relationship
  • NOTE7: I subclassing your existing class, I hope this proposal is accepted and incorporated into the master.
  • NOTE8: so glad you did this maintainer, this is the way for me to intercept the class instantiation
  • NOTE9: the way to set the field connection now
class Job(Model):
    name = sa.Column(sa.String)
    # NOTE1
    blocks = sa.relationship('block', back_populates='job', lazy='dynamic')

class Block(Model):
    name = sa.Column(sa.String)
    # NOTE1
    job = sa.relationship('job', back_populates='blocks', lazy='dynamic')

# NOTE2
class BaseObjectType(SQLAlchemyObjectType):
    class Meta:
        abstract = True

    # NOTE3
    @classmethod
    def update_connection_args(cls, **kwargs):
        return kwargs

    # NOTE4
    @classmethod
    def process_args(cls, query, root, connection, args: dict, **kwargs):
        return query

class JobObjectType(BaseObjectType):
    class Meta:
        model = Job
        interfaces = (graphene.relay.Node,)

    @classmethod
    def update_connection_args(cls, **kwargs):
        kwargs = super(JobObjectType, cls).update_connection_args(**kwargs)
        # NOTE5
        kwargs.setdefault('name', String(required=False))
        return kwargs

    @classmethod
    def process_args(cls, query, root, connection, args: dict, **kwargs):
        # NOTE6
        name = args.get('name')
        if name:
            query = query.filter(Job.name == name)
        return query

class BlockObjectType(BaseObjectType):
    class Meta:
        model = Block
        interfaces = (graphene.relay.Node,)

class JobConnection(relay.Connection):
    class Meta:
        node = JobObjectType

class BlockConnection(relay.Connection):
    class Meta:
        node = BlockObjectType

# NOTE7
class FlexibleSQLAlchemyConnectionField(SQLAlchemyConnectionField):
    def __init__(self, model_type, *args, **kwargs):
        update_connection_args = getattr(model_type._meta.node, 'update_connection_args', None)
        if update_connection_args:
            kwargs = update_connection_args(**kwargs)
        super(FlexibleSQLAlchemyConnectionField, self).__init__(model_type, *args, **kwargs)

    @classmethod
    def connection_resolver(cls, resolver, connection, model, root, info, **args):
        iterable = resolver(root, info, **args)
        if iterable is None:
            iterable = cls.get_query(model, info, **args)

        process_args = getattr(connection._meta.node, 'process_args', None)
        if process_args:
            iterable = process_args(query=iterable, root=root, connection=connection, args=args)

        if isinstance(iterable, orm.Query):
            _len = iterable.count()
        else:
            _len = len(iterable)

        connection = connection_from_list_slice(
            iterable,
            args,
            slice_start=0,
            list_length=_len,
            list_slice_length=_len,
            connection_type=connection,
            pageinfo_type=PageInfo,
            edge_type=connection.Edge,
        )

        connection.iterable = iterable
        connection.length = _len
        return connection


# NOTE8
def register_connection_field(_type):
    return FlexibleSQLAlchemyConnectionField(_type)

registerConnectionFieldFactory(register_connection_field)


class Query(graphene.ObjectType):
    node = graphene.relay.Node.Field()
    # NOTE9
    blocks = FlexibleSQLAlchemyConnectionField(BlockConnection)
    jobs = FlexibleSQLAlchemyConnectionField(JobConnection)

schema = graphene.Schema(
    query=Query,
    types=[JobObjectType, BlockObjectType]
)

Apologies for long explanation, I really hope this illustrates the situation I am facing and the proposal from me to add two additional filters/hooks into the SQLAlchemyConnectionField. I am sure this proposal will be greatly accepted by community, since from my perspective this is immediate update that I require to do after installing your module.

I am happy to make the PR changes including the test to the master, just let me know whether the hook/filter naming convention is good, the arguments in the def is good and also the way that I approach to my problem is acceptable. I am open to any discussion.

Thanks!!

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:3
  • Comments:9 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
matuttercommented, Feb 6, 2020

A builtin way to support declaring extra inputs for the SQLAlchemyConnectionField would be a huge improvement. This is the only part of graphene-sqlalchemy where I actually have to extend something to fit my use-case.

This is a much less complete solution, but just for an idea about what people are doing to work around it. But does what I need it to 99% of the time.

class RoleInput(graphene.InputObjectType):
  role = String()

class FilteredConnectionField(SQLAlchemyConnectionField):

  def __init__(self, type, input_type, *args, **kwargs):
    fields = {name: field.type() for name, field in input_type._meta.fields.items()}
    kwargs.update(fields)
    super().__init__(type, *args, **kwargs)

  @classmethod
  def get_query(cls, model, info, sort=None, **args):
    query = super().get_query(model, info, sort=sort, **args)
    omitted = ('first', 'last', 'hasPreviousPage', 'hasNextPage', 'startCursor', 'endCursor')
    for name, val in args.items():
      if name in omitted: continue
      col = getattr(model, name, None)
      if col:
        query = query.filter(col == val)
    return query

class Query(graphene.ObjectType):
  users = FilteredConnectionField(Users, RoleInput)
0reactions
erikwredecommented, May 13, 2022

I am closing all old issues&PRs related to filtering. The discussion will continue in https://github.com/graphql-python/graphene-sqlalchemy/discussions/347 (WIP). A proposal for the implementation of filters is currently being worked on and will be posted there once it is ready.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Flexible arguments in SQLAlchemyConnectionField and ...
Flexible arguments in SQLAlchemyConnectionField and custom query to filter against arguments.
Read more >
How to add filtering by id to my user resolver? - Stack Overflow
I found it, i was overriding graphql reserved variable called id that is used for caching and pagination. In models.py, i renamed column...
Read more >
GraphQL in Python with Graphene - Jefferson Heard
Filtering results · Handle arguments on a Connection by Connection basis, leading each query to be custom, and you have to enforce consistency ......
Read more >
Filter Data Based on Query Arguments with GraphQL
With GraphQL, every field and nested object can have a set of arguments which can be used to request very specific data from...
Read more >
GraphQL Authorization with Graphene, SQLAlchemy and oso
There are many more combinations of fields in a GraphQL query than ... Each parameter starts with _ which indicates an anonymous variable...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found