I would like to separate my Graphene schema definition from its implementation.
resolve_* functions and mutate functions.(Why do I want to do this? Lots of reasons. First, it's super nice to be able to reference all the types and documentation in one place -- putting code in there too makes it far less readable. Second, the implementation requires fancy imports, like accessing my database, that the definition doesn't -- it's nice to be able to import the schema without being forced to import all the database code.)
Currently Graphene makes this quite a challenge.
Example of what I want to do --
(in graphene_models.py:)
class User(ObjectType):
name = String()
class Signup(Mutation):
class Arguments:
name = String()
id = ID()
(in graphene_impl.py:)
@resolver_for(User, 'name')
def resolve_user_name(self, info):
return 'Cam Newton'
@mutator_for(Signup)
def mutate_signup(self, info, name):
# do something fancy with the db
return Signup(id=...)
As things stand today, I can do this, but it's terribly awkward:
resolver_for decorator to monkeypatch the function into, e.g., User.resolve_name. This only works if I delay the Schema construction till after the implementation module is imported, because it's the TypeMap's job to figure out the resolve functions for the fields and that happens at schema construction time. I'm mainly curious about whether people agree that having the ability to separate declaration and implementation is desirable, and if so, whether there are any better ways to do it than this, or if there are any plans to improve the situation.
Thanks for your consideration and thanks for writing a very nice piece of software!
Hi,
personally I don't see the big win in separating these things, when it comes to readability I'd almost argue the opposite is true. But anyway if you want to separate type declarations from resolve logic I guess you could do like this:
class UserResolver(object):
def resolve_full_name(self, info):
return 'John Doe'
class UserObject(UserResolver, ObjectType):
name = graphene.String()
(or the other way around: UserResolver is ObjectType and inherits from UserObject)
A better way to decouple your data fetching logic I think would be to make separate functions that you use inside the resolvers. (See starwars example https://github.com/graphql-python/graphene/tree/master/examples/starwars)
One potential win from separating the two is being able to define types with the GraphQL Schema Definition Language for easier interop with clientside code, while defining resolvers in Python-land.
@lincolnq all fields take a resolver parameter so your example can become (without the need for decorators):
# graphene_models.py
from graphene_impl import resolve_user_name, mutate_signup
class User(ObjectType):
name = String(resolver=resolve_user_name)
class Signup(Mutation):
class Arguments:
name = String()
id = ID()
class Mutations(ObjectType):
signup = Signup.Field(resolver=mutate_signup)
Otherwise @HeyHugo 's suggestion would work. Also https://github.com/graphql-python/graphene/issues/448#issuecomment-356777997 has some code that would allow you define your server using the GraphQL Schema Definition Language like graphql-tools.
If any of these solutions don't work for you do say and I'll open this issue again.
Most helpful comment
One potential win from separating the two is being able to define types with the GraphQL Schema Definition Language for easier interop with clientside code, while defining resolvers in Python-land.