Skip to content

Instantly share code, notes, and snippets.

@adamcharnock
Last active February 9, 2017 21:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adamcharnock/78dbc5d8e448b4e5b1d9 to your computer and use it in GitHub Desktop.
Save adamcharnock/78dbc5d8e448b4e5b1d9 to your computer and use it in GitHub Desktop.
import graphene
import six
from graphene import relay
from graphene import ObjectType
from graphene.core.types import ObjectTypeMeta
from graphene.core.types.scalars import String
from graphene.utils.str_converters import to_snake_case
schema = graphene.Schema(name='My Schema')
class DjangoQueryMeta(ObjectTypeMeta):
def __new__(cls, name, bases, attrs):
super_new = super(DjangoQueryMeta, cls).__new__
attr_meta = attrs.get('DjangoMeta')
if not attr_meta:
return super_new(cls, name, bases, attrs)
nodes = getattr(attr_meta, 'nodes', [])
for node in nodes:
# Make the attributes for this node
for k, v in cls.make_node_attrs(node).items():
# Set the attribute as the default value
attrs.setdefault(k, v)
return super_new(cls, name, bases, attrs)
@classmethod
def make_node_attrs(cls, node):
"""Create the attributes related to a given node
This will create the following (and return in the form of a dict):
- all_{field_name}s -> ConnectionField
- {field_name} -> NodeField
- resolve_{field_name} -> Callable
TODO: Do not mandate field naming in the form 'all_{field_name}s'.
The developer should have control of this.
"""
name = to_snake_case(node.__name__)
# Create connection field (with naive pluralisation)
connection_field_name = 'all_{}s'.format(name)
connection_field = relay.ConnectionField(
node,
description='Get all {} nodes'.format(node.__name__),
args=cls.get_node_args(node)
)
# Create relay field
relay_field_name = name
relay_field = relay.NodeField(node)
# Create resolve method
resolve_method_name = 'resolve_{}'.format(connection_field_name)
resolve_method = cls.make_resolve_method(node)
return {
connection_field_name: connection_field,
relay_field_name: relay_field,
resolve_method_name: resolve_method,
}
@classmethod
def get_node_args(cls, node):
"""Get the args which will become attributes on the new Query class"""
args = {}
for field in node._meta.fields:
# ID field is included automatically, do not include here
if field.name == 'id':
continue
# TODO: Determine correct field type!
args[field.attname] = String()
return args
@classmethod
def make_resolve_method(cls, node):
"""Factory for creating resolve_{field_name}() methods."""
def resolver(self, args, info):
# Crude generalised filtering
ignore = ['first', 'last', 'before', 'after']
filter_kwargs = {k: v for k, v in args.items() if k not in ignore}
model = node._meta.model
return model.objects.filter(**filter_kwargs)
return resolver
class DjangoQuery(six.with_metaclass(DjangoQueryMeta, ObjectType)):
pass
class Query(DjangoQuery):
class DjangoMeta:
# I called this DjangoMeta rather than Meta to avoid
# thowing up 'invalid attribute' errors. This may not
# even be the right place for this (i.e. could auto-load
# nodes from apps, or could be list in settings.py)
nodes = [
... your DjangoNodes here...
]
schema.query = Query
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment