Graphene: Get requested fields in resolve function

Created on 30 Nov 2015  Â·  14Comments  Â·  Source: graphql-python/graphene

I have seen that the info parameter in the resolve function provides a info.field_asts, which provides information about the selected fields, but when fragments are provided you get something like:

[Field(alias=None, name=Name(value=u'customer'), arguments=[], directives=[], selection_set=SelectionSet(selections=[Field(alias=None, name=Name(value=u'id'), arguments=[], directives=[], selection_set=None), FragmentSpread(name=Name(value=u'__RelayQueryFragment0wau8gf'), directives=[])]))]

which means for fragments we can't really figure out which fields are selected at runtime.

Our use-case for knowing the fields in the resolve functions is,that we only want to calculate the fields that are actually requested because some of the fields are expensive to calculate.

Edit: Or are the resolve methods for specific fields meant to be used for that? E.g. resolve_full_name on the Customer node?

Also happy to provide an example of that would make it easier.

question

Most helpful comment

After much work, here's a much nicer code snippet to get requested fields:

https://gist.github.com/mixxorz/dc36e180d1888629cf33

All 14 comments


from graphql.core.execution.base import collect_fields
fields = collect_fields(info.context, info.parent_type, info.field_asts[0], {}, set())

ahhh great, thanks @jhgg, will give that a try!

@jhgg if I change your code example to:

fields = collections.defaultdict(list) #can not just be dict because of 'id' from relay
fields = collect_fields(info.context, info.parent_type, info.field_asts[0].selection_set, fields, set())

I am getting to print all the field names in the 'collect_fields' function, but it just returns before it has traversed the whole ast. So the actual retyurn of fields is just defaultdict(<type 'list'>, {u'id': [Field(alias=None, name=Name(value=u'id'), arguments=[], directives=[], selection_set=None)]})

Weird. Can you post you code somewhere. I'll mess with it when I've got some time!

Sent from my iPhone

On Nov 30, 2015, at 9:50 AM, Markus Padourek [email protected] wrote:

@jhgg if I change your code example to:

fields = collections.defaultdict(list) #can not just be dict because of 'id' from relay
fields = collect_fields(info.context, info.parent_type, info.field_asts[0].selection_set, fields, set())
I am getting to print all the field names in the 'collect_fields' function, but it just returns before it has traversed the whole ast. So the actual retyurn of fields is just defaultdict(, {u'id': [Field(alias=None, name=Name(value=u'id'), arguments=[], directives=[], selection_set=None)]})

—
Reply to this email directly or view it on GitHub.

Yep will do as soon as possible.

Here we go @jhgg http://graphene-python.org/playground/?schema=import%2520collections%250A%250Aimport%2520graphene%250Afrom%2520graphene%2520import%2520relay%250Afrom%2520graphql.core.execution.base%2520import%2520collect_fields%250A%250Aclass%2520Customer(relay.Node)%253A%250A%2520%2520%2520%2520first_name%2520%253D%2520graphene.String()%250A%2520%2520%2520%2520middle_name%2520%253D%2520graphene.String()%250A%2520%2520%2520%2520last_name%2520%253D%2520graphene.String()%250A%2520%2520%2520%2520expensive_field%2520%253D%2520graphene.String()%250A%2520%2520%2520%2520%250A%2520%2520%2520%2520%2540classmethod%250A%2520%2520%2520%2520def%2520get_node(cls%252C%2520customer_id%252C%2520info)%253A%250A%2520%2520%2520%2520%2520%2520%2520%2520return%2520Customer()%250A%2520%2520%2520%2520%2520%2520%2520%2520%250Aclass%2520Query(graphene.ObjectType)%253A%250A%2520%2520%2520%2520hello%2520%253D%2520graphene.String()%250A%2520%2520%2520%2520ping%2520%253D%2520graphene.String(to%253Dgraphene.String())%250A%2520%2520%2520%2520customer%2520%253D%2520graphene.Field(Customer)%250A%250A%2520%2520%2520%2520def%2520resolve_hello(self%252C%2520args%252C%2520info)%253A%250A%2520%2520%2520%2520%2520%2520%2520%2520return%2520%27World%27%250A%250A%2520%2520%2520%2520def%2520resolve_ping(self%252C%2520args%252C%2520info)%253A%250A%2520%2520%2520%2520%2520%2520%2520%2520return%2520%27Pinging%2520%257B%257D%27.format(args.get(%27to%27))%250A%2520%2520%2520%2520%250A%2520%2520%2520%2520def%2520resolve_customer(self%252C%2520_%252C%2520info)%253A%250A%2520%2520%2520%2520%2520%2520%2520%2520fields%2520%253D%2520collections.defaultdict(list)%250A%2520%2520%2520%2520%2520%2520%2520%2520fields%2520%253D%2520collect_fields(info.context%252C%2520info.parent_type%252C%2520info.field_asts%255B0%255D.selection_set%252C%2520fields%252C%2520set())%250A%2520%2520%2520%2520%2520%2520%2520%2520print(fields)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2523%2520only%2520calculate%2520expensive_field%2520here%2520if%2520it%2520is%2520in%2520fields%250A%2520%2520%2520%2520%2520%2520%2520%2520return%2520Customer(id%253D%271%27%252C%2520first_name%253D%27Test%27%252C%2520last_name%253D%27customer%27)%250A%250Aschema%2520%253D%2520graphene.Schema(query%253DQuery)%250A&query=query%2520GetCustomer%257B%250A%2520%2520customer%257B%250A%2520%2520%2520%2520id%252C...__RelayQueryFragment0wau8gf%250A%2520%2520%257D%250A%257D%2520%250Afragment%2520__RelayQueryFragment0wau8gf%2520on%2520Customer%257B%250A%2520%2520firstName%250A%2520%2520lastName%250A%257D

That should be pretty similar to what I am currently working with

@jhgg for seeing the print output just open the browser console.
@Globegitter I'm happy to see you started using the playground, let me know if any suggestions there!

@syrusakbary yeah thanks for the docs, they are great! And the playground also works well, especially knowing the prints are showing in the console.

I think this thing will be improved in the next version of Relay, so the might send the flattened query
https://github.com/facebook/relay/commit/ca0afe134d29fc8c2dc046be5f91253aebdf8565

However, no idea why graphql-core (and probably graphql-js) doesn't get the fields from fragments... any suggestion @jhgg?

This code snippet successfully extracts all fields from info:

def get_fields(info):
    prev_fragment_names = set()
    params = collections.defaultdict(list)
    params = collect_fields(info.context,
                            info.parent_type,
                            info.field_asts[0].selection_set,
                            params,
                            prev_fragment_names)

    for fragment_name in prev_fragment_names:
        params = collect_fields(info.context,
                                info.parent_type,
                                info.fragments[fragment_name].selection_set,
                                params,
                                prev_fragment_names)

    return set(params.keys())

This hasn't been tested for all edge cases though.

After much work, here's a much nicer code snippet to get requested fields:

https://gist.github.com/mixxorz/dc36e180d1888629cf33

Thanks for that work @mixxorz Also it seems that graphql itself might get that feature anyway. See: https://github.com/graphql/graphql-js/pull/304

Looks pretty great :)

This appears resolved, per gist above / also from 2016 :p

I would like to reopen this, as all proposed solutions do not work in graphene 3 anymore. Namely, the context does not provide field_asts property anymore.

Was this page helpful?
0 / 5 - 0 ratings