Home > Software engineering >  Resolve external API response json with Flask and Graphene
Resolve external API response json with Flask and Graphene

Time:06-30

Last week I posted a kind of vague question as I was trying to join data from an external Rest API with a local SQLAlchemy schema. Unsurprisingly, I didn't get a lot of responses and after some experimentation with parsing the response json into a temporary table I've decided to move away from this approach and add a new resolver for the external API that can be called separately from the resolver for SQLAlchemy. This is what I have so far for the querying the external API:

class Red(graphene.ObjectType):
    redName     = graphene.String()
    accessLevel = graphene.String()
    numBytes    = graphene.Int()


class Query(graphene.ObjectType):
    
    red = graphene.Field(Red, redName = graphene.String(required=True))


    def resolve_red(self, info, **kwargs):
        dataset_Red  = <custom library for querying API>.dataset(kwargs['redName'])
        dataset_Red.get()
        resp = dataset_Red.properties
        return json.dumps(resp)

GraphiQL recognizes my new resolver and will run the query with no errors but it returns no data. For example, if this is my query:

query{red(redName:"<dataset_name>") {
    accessLevel
    numBytes
    }
}

I get the following response:

{
  "data": {
    "red": {
       "accessLevel": null,
       "numBytes": null
  }
 }
}

What did I miss? I'm thinking there's an issue with class definition. Can someone show me what I did wrong?

CodePudding user response:

I think you should return Red type from resolver resolve_red instead of json dumped string:

class Red(graphene.ObjectType):
    redName     = graphene.String()
    accessLevel = graphene.String()
    numBytes    = graphene.Int()


class Query(graphene.ObjectType):
    
    red = graphene.Field(Red, redName = graphene.String(required=True))

    def resolve_red(self, info, **kwargs):
        dataset_Red  = <custom library for querying API>.dataset(kwargs['redName'])
        dataset_Red.get()
        resp = dataset_Red.properties
        return Red(
            redName=resp.get('redName'),
            accessLevel=resp.get('accessLevel'),
            NumBytes=resp.get('numBytes')
        )

Or assuming fields in response are the same as Red attributes:

class Red(graphene.ObjectType):
    redName     = graphene.String()
    accessLevel = graphene.String()
    numBytes    = graphene.Int()


class Query(graphene.ObjectType):
    
    red = graphene.Field(Red, redName = graphene.String(required=True))

    def resolve_red(self, info, **kwargs):
        dataset_Red  = <custom library for querying API>.dataset(kwargs['redName'])
        dataset_Red.get()
        resp = dataset_Red.properties
        return Red(**resp)

CodePudding user response:

Sound I found a solution that seems to work. The issue was the response type, I had to serialize and then deserialize it for flask (obvious right?).

I added this two functions:

def _json_object_hook(d):
    return namedtuple('X', d.keys())(*d.values())

def json2obj(data):
    return json.loads(data, object_hook=_json_object_hook)

And then changed my resolver to call json2obj on the response:

 def resolve_red(self, info, **kwargs):
    dataset_Red  = <custom library for querying API>.dataset(kwargs['redName'])
    dataset_Red.get()
    resp = dataset_Red.properties
    return json2obj(json.dumps(resp))

I found this solution in another stackoverflow post but now I can't find the original. If anyone else finds it, please add a link in the comments and I'll add it to this answer.

  • Related