Graphene: Circular dependencies

Created on 5 Feb 2016  Â·  17Comments  Â·  Source: graphql-python/graphene

Python is not able to resolve circular dependencies for schemas with recursive types.

e.g

  # category_type.py
  from .post_type import Post
  class Category(graphene.ObjectType):
    Post = graphene.List(Post)

  # post_type.py
  from .category_type import Category
  class Post(graphene.ObjectType):
    category = graphene.Field(Category)

At runtime, the above fails as the types haven't yet loaded before being used in field definitions. A better option would be to wrap the field definitions in a lambda to be called at resolve time and after the types have been declared.

e.g

  # category_type.py
-  from .post_type import Post
  class Category(graphene.ObjectType):
-   Post = graphene.List(Post)
+   Post = lambda: graphene.List(Post)

+ from .post_type import Post

  # post_type.py
- from .category_type import Category
  class Post(graphene.ObjectType):
-   category = graphene.Field(Category)
+   category = lambda: graphene.Field(Category)

+ from .category_type import Category

cc @Globegitter

Most helpful comment

Looking at some unrelated docs here it seems like you can do this:

graphene.List(lambda: MyCircularType)
graphene.Field(lambda: MyCircularType)

I just tested it and it works.

All 17 comments

Or looking at Graphql-core another syntax option could be

class Post(graphene.ObjectType):
    fields = lambda: dict(
        category = graphene.Field(Category)
    )

But of course that would mean that graphene would either have to break backwards-compatibility to a quite substantial degree, or support two different syntaxes.

Hi @imranolas,

You can achieve this by using String references (ala Django):

# category_type.py
@schema.register
class Category(graphene.ObjectType):
    Post = graphene.List('Post')

# post_type.py
@schema.register
class Post(graphene.ObjectType):
    category = graphene.Field('Category')

As posted in the pull request #111, there is already another "undocumented" way for doing solving this if you don't want to use string references.

class Category(graphene.ObjectType):
    Post = graphene.LazyType(lambda _: Post)

from .post_type import Post

# post_type.py
class Post(graphene.ObjectType):
    category = graphene.LazyType(lambda _: Category)

Please feel free to reopen if you have any other suggestion of how to achieve it! :)

Hey guys– I've seen a lot of similar issues and pull requests, all of which are closed as being fixed; is any of this documented anywhere? I can't find any reference to it in the main page documentation.

I agree, what's the way to do this in graphene 2.0? Neither the string or the LazyType seem to work for me...

I think, based on reading through the source code, that graphene.Dynamic is intended to be the solution. It looks like this:

graph.List(graph.Dynamic(lambda: CircularType))

But there's sadly no documentation for this class at all. For instance, would this be more appropriate?

graph.Dynamic(lambda: graph.List(CircularType))

Looking at some unrelated docs here it seems like you can do this:

graphene.List(lambda: MyCircularType)
graphene.Field(lambda: MyCircularType)

I just tested it and it works.

Confirmed that the lambda thing works pretty much everywhere. Seems like that's the thing to do. Should definitely be documented explicitly somewhere.

This whole project is woefully under-documented. But great finds!

@syrusakbary
anyway to do this just using graphql-core? thanks

@sanfilippopablo How did you actually import the types? I'm still struggling to get this working without an error

the lambda option doesn't solve the problem of circular imports, just circular dependencies. Is there a supported solution for the problem of circular imports? specifying it as a dotted package string 'mymodule.gqlobjects.Foobar' seems to work, I think.

Hi @imranolas,

You can achieve this by using String references (ala Django):

# category_type.py
@schema.register
class Category(graphene.ObjectType):
    Post = graphene.List('Post')

# post_type.py
@schema.register
class Post(graphene.ObjectType):
    category = graphene.Field('Category')

This does not work anymore. Just tested like this:

class Webhook(ModelObjectType):
  triggered_by = graphene.Field('WebhookEventType')

class WebhookEventType(ModelObjectType):
  ...

This raises ImportError from here: https://github.com/graphql-python/graphene/blame/master/graphene/utils/module_loading.py#L39

If I change string to mymodule.WebhookEventType then it works but this is not ideal.

Using string references does not appear to be support in Connection.Meta.Node

class A(graphene.Connection):
    class Meta:
        node = 'B'

Any solutions. Ive also tried lazy_import and import_string, but it doesnt seem to be finding the class in the module.

Using string references does not appear to be support in Connection.Meta.Node

class A(graphene.Connection):
    class Meta:
        node = 'B'

Any solutions. Ive also tried lazy_import and import_string, but it doesnt seem to be finding the class in the module.

I faced the same problem. Did you find any solutions?

@McPo @amaksymov did you manage to solve this?

Was this page helpful?
0 / 5 - 0 ratings