Search code examples
pythongraphqlgraphene-python

return user-defined data structure from graphene (graphql) mutation


I have a mutation where I would like to return arbitrarily shaped data, but I'm having a hard time envisioning how to do this via a graphene mutation.

I would like to return data like this:

{
    "foo": {
        "success": [
            {
                "id": "abc123",
                "goober": {
                    "success": [
                        {
                            "id": "goober-type-1",
                            "baz": "blahblahblah"
                        },
                        {
                            "id": "goober-type-2",
                            "baz": "blahblahblah"
                        }
                    ],
                    "failed": [],
                    "cancelled": []
                },
                "tronic": {
                    "success": [
                        {
                            "id": "tronic-type-1",
                            "baz": "blahblahblah"
                        },
                        {
                            "id": "tronic-type-2",
                            "baz": "blahblahblah"
                        }
                    ],
                    "failed": [],
                    "cancelled": []
                }
            }
        ],
        "failed": [
            {
                "id": "abc123",
                "goober": {
                    "success": [],
                    "failed": [
                        {
                            "id": "goober-type-3",
                            "baz": "blahblahblah"
                        }
                    ],
                    "cancelled": [
                        {
                            "id": "goober-type-4",
                            "baz": "blahblahblah"
                        }
                    ]
                },
                "tronic": {
                    "success": [],
                    "failed": [
                        {
                            "id": "tronic-type-4",
                            "baz": "blahblahblah"
                        }
                    ],
                    "cancelled": [
                        {
                            "id": "tronic-type-3",
                            "baz": "blahblahblah"
                        }
                    ]
                }
            }
        ]
    }
}

My instinct was wanting to define graphene.ObjectType's in a fashion like this:

class Goober(graphene.ObjectType):
    id = graphene.String()
    baz = graphene.String()

class Tronic(graphene.ObjectType):
    id = graphene.String()
    baz = graphene.String()

class Foo(graphene.ObjectType):

    id = graphene.String()
    goober = graphene.ObjectType(
        success = graphene.List(Goober),
        failed = graphene.List(Goober),
        cancelled = graphene.List(Goober)
    )
    tronic = graphene.ObjectType(
        success = graphene.List(Tronic),
        failed = graphene.List(Tronic),
        cancelled = graphene.List(Tronic)
    )

class Results(graphene.ObjectType):
    foo = graphene.ObjectType(
        success=graphene.List(Foo),
        failed=graphene.List(Foo)
    )

But I'm really not getting anywhere with this approach, and feeling like I'm fundamentally misunderstanding something.

I've had success in other areas, returning SQL objects and some custom ones, but not with generating and returning relatively complex, nested data like this. Any advice would be much appreciated.


Solution

  • The truth is that Graphene documentation lacks a lot. Take a look at this rather lengthy but complete example replicating your data:

    import json
    import graphene
    
    # let's just prepare some data
    SUCCESS = [{
        "id": "abc123",
        "goober": {
            "success": [
                {
                    "id": "goober-type-1",
                    "baz": "blahblahblah"
                },
                {
                    "id": "goober-type-2",
                    "baz": "blahblahblah"
                }
            ],
            "failed": [],
            "cancelled": []
        },
        "tronic": {
            "success": [
                {
                    "id": "tronic-type-1",
                    "baz": "blahblahblah"
                },
                {
                    "id": "tronic-type-2",
                    "baz": "blahblahblah"
                }
            ],
            "failed": [],
            "cancelled": []
        }
    }]
    
    FAILED = [{
        "id": "abc123",
        "goober": {
            "success": [],
            "failed": [
                {
                    "id": "goober-type-3",
                    "baz": "blahblahblah"
                }
            ],
            "cancelled": [
                {
                    "id": "goober-type-4",
                    "baz": "blahblahblah"
                }
            ]
        },
        "tronic": {
            "success": [],
            "failed": [
                {
                    "id": "tronic-type-4",
                    "baz": "blahblahblah"
                }
            ],
            "cancelled": [
                {
                    "id": "tronic-type-3",
                    "baz": "blahblahblah"
                }
            ]
        }
    }]
    
    class Goober(graphene.ObjectType):
        id = graphene.String()
        baz = graphene.String()
    
    class Tronic(graphene.ObjectType):
        id = graphene.String()
        baz = graphene.String()
    
    class GooberResult(graphene.ObjectType):
        success = graphene.List(Goober)
        failed = graphene.List(Goober)
        cancelled = graphene.List(Goober)
    
    class TronicResult(graphene.ObjectType):
        success = graphene.List(Tronic)
        failed = graphene.List(Tronic)
        cancelled = graphene.List(Tronic)
    
    class FooResult(graphene.ObjectType):
        id = graphene.String()
        goober = graphene.Field(GooberResult)
        tronic = graphene.Field(TronicResult)
    
    class Foo(graphene.Mutation):
        success = graphene.List(FooResult)
        failed = graphene.List(FooResult)
    
        def mutate(self, info):
            # provide some data as a mutation result
            success = SUCCESS
            failed = FAILED
            return Foo(success=success, failed=failed)
    
    class Mutation(graphene.ObjectType):
        foo = Foo.Field()
    
    schema = graphene.Schema(mutation=Mutation)
    result = schema.execute("""
    mutation Foo {
        foo {
            success {
                id
                goober {
                    success {
                        id
                        baz
                    }
                }
            }
            failed {
                id
                tronic {
                    cancelled {
                        id
                        baz
                    }
                }
            }
        }
    }
    """)
    
    print(json.dumps(result.data, indent=2))
    

    When you run this script, you'll get the expected result:

    {
      "foo": {
        "success": [
          {
            "id": "abc123",
            "goober": {
              "success": [
                {
                  "id": "goober-type-1",
                  "baz": "blahblahblah"
                },
                {
                  "id": "goober-type-2",
                  "baz": "blahblahblah"
                }
              ]
            }
          }
        ],
        "failed": [
          {
            "id": "abc123",
            "tronic": {
              "cancelled": [
                {
                  "id": "tronic-type-3",
                  "baz": "blahblahblah"
                }
              ]
            }
          }
        ]
      }
    }
    

    The place to start in the documentation is probably at the relationship between ObjectTypes and Fields. Scalars act as Fields, however your defined ObjectTypes do not, that's why you need to wrap them in graphene.Field. Then just take a look at Mutations to use it all together.