First working version with Django.

This commit is contained in:
Syrus Akbary 2015-09-28 00:34:25 -07:00
parent c79097879d
commit 2e8707aee6
14 changed files with 302 additions and 13 deletions

View File

View File

@ -0,0 +1,4 @@
from graphene.contrib.django.types import (
DjangoObjectType,
DjangoNode
)

View File

@ -0,0 +1,40 @@
from singledispatch import singledispatch
from django.db import models
from graphene.core.fields import (
StringField,
IDField,
IntField,
BooleanField,
FloatField,
)
@singledispatch
def convert_django_field(field):
raise Exception("Don't know how to convert the Django field %s"%field)
@convert_django_field.register(models.CharField)
def _(field):
return StringField(description=field.help_text)
@convert_django_field.register(models.AutoField)
def _(field):
return IDField(description=field.help_text)
@convert_django_field.register(models.BigIntegerField)
@convert_django_field.register(models.IntegerField)
def _(field):
return IntField(description=field.help_text)
@convert_django_field.register(models.BooleanField)
def _(field):
return BooleanField(description=field.help_text)
@convert_django_field.register(models.FloatField)
def _(field):
return FloatField(description=field.help_text)

View File

@ -0,0 +1,22 @@
import inspect
from django.db import models
from graphene.core.options import Options
VALID_ATTRS = ('model', 'only_fields')
class DjangoOptions(Options):
def __init__(self, *args, **kwargs):
self.model = None
super(DjangoOptions, self).__init__(*args, **kwargs)
self.valid_attrs += VALID_ATTRS
self.only_fields = None
def contribute_to_class(self, cls, name):
super(DjangoOptions, self).contribute_to_class(cls, name)
if self.proxy:
return
if not self.model:
raise Exception('Django ObjectType %s must have a model in the Meta attr' % cls)
elif not inspect.isclass(self.model) or not issubclass(self.model, models.Model):
raise Exception('Provided model in %s is not a Django model' % cls)

View File

@ -0,0 +1,31 @@
import six
from graphene.core.types import ObjectTypeMeta, ObjectType
from graphene.contrib.django.options import DjangoOptions
from graphene.contrib.django.converter import convert_django_field
from graphene.relay import Node
class DjangoObjectTypeMeta(ObjectTypeMeta):
options_cls = DjangoOptions
def add_extra_fields(cls):
if not cls._meta.model:
return
only_fields = cls._meta.only_fields
# print cls._meta.model._meta._get_fields(forward=False, reverse=True, include_hidden=True)
for field in cls._meta.model._meta.fields:
if only_fields and field.name not in only_fields:
continue
converted_field = convert_django_field(field)
cls.add_to_class(field.name, converted_field)
class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, ObjectType)):
class Meta:
proxy = True
class DjangoNode(six.with_metaclass(DjangoObjectTypeMeta, Node)):
class Meta:
proxy = True

View File

@ -8,8 +8,10 @@ from graphql.core.type import (
GraphQLBoolean, GraphQLBoolean,
GraphQLID, GraphQLID,
GraphQLArgument, GraphQLArgument,
GraphQLFloat,
) )
from graphene.utils import cached_property from graphene.utils import cached_property
from graphene.core.types import ObjectType
class Field(object): class Field(object):
def __init__(self, field_type, resolve=None, null=True, args=None, description='', **extra_args): def __init__(self, field_type, resolve=None, null=True, args=None, description='', **extra_args):
@ -44,7 +46,6 @@ class Field(object):
return resolve_fn(instance, args, info) return resolve_fn(instance, args, info)
def get_object_type(self): def get_object_type(self):
from graphene.core.types import ObjectType
field_type = self.field_type field_type = self.field_type
_is_class = inspect.isclass(field_type) _is_class = inspect.isclass(field_type)
if _is_class and issubclass(field_type, ObjectType): if _is_class and issubclass(field_type, ObjectType):
@ -143,6 +144,10 @@ class IDField(TypeField):
field_type = GraphQLID field_type = GraphQLID
class FloatField(TypeField):
field_type = GraphQLFloat
class ListField(Field): class ListField(Field):
def type_wrapper(self, field_type): def type_wrapper(self, field_type):
return GraphQLList(field_type) return GraphQLList(field_type)

View File

@ -14,6 +14,7 @@ class Options(object):
self.schema = schema self.schema = schema
self.interfaces = [] self.interfaces = []
self.parents = [] self.parents = []
self.valid_attrs = DEFAULT_NAMES
def contribute_to_class(self, cls, name): def contribute_to_class(self, cls, name):
cls._meta = self cls._meta = self
@ -36,7 +37,7 @@ class Options(object):
# over it, so we loop over the *original* dictionary instead. # over it, so we loop over the *original* dictionary instead.
if name.startswith('_'): if name.startswith('_'):
del meta_attrs[name] del meta_attrs[name]
for attr_name in DEFAULT_NAMES: for attr_name in self.valid_attrs:
if attr_name in meta_attrs: if attr_name in meta_attrs:
setattr(self, attr_name, meta_attrs.pop(attr_name)) setattr(self, attr_name, meta_attrs.pop(attr_name))
self.original_attrs[attr_name] = getattr(self, attr_name) self.original_attrs[attr_name] = getattr(self, attr_name)
@ -44,9 +45,13 @@ class Options(object):
setattr(self, attr_name, getattr(self.meta, attr_name)) setattr(self, attr_name, getattr(self.meta, attr_name))
self.original_attrs[attr_name] = getattr(self, attr_name) self.original_attrs[attr_name] = getattr(self, attr_name)
del self.valid_attrs
# Any leftover attributes must be invalid. # Any leftover attributes must be invalid.
if meta_attrs != {}: if meta_attrs != {}:
raise TypeError("'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())) raise TypeError("'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys()))
else:
self.proxy = False
if self.interfaces != [] and self.interface: if self.interfaces != [] and self.interface:
raise Exception("A interface cannot inherit from interfaces") raise Exception("A interface cannot inherit from interfaces")

View File

@ -11,9 +11,11 @@ from graphene.core.options import Options
class ObjectTypeMeta(type): class ObjectTypeMeta(type):
options_cls = Options
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
super_new = super(ObjectTypeMeta, cls).__new__ super_new = super(ObjectTypeMeta, cls).__new__
parents = [b for b in bases if isinstance(b, ObjectTypeMeta)] parents = [b for b in bases if isinstance(b, cls)]
if not parents: if not parents:
# If this isn't a subclass of Model, don't do anything special. # If this isn't a subclass of Model, don't do anything special.
return super_new(cls, name, bases, attrs) return super_new(cls, name, bases, attrs)
@ -25,20 +27,26 @@ class ObjectTypeMeta(type):
'__doc__': doc '__doc__': doc
}) })
attr_meta = attrs.pop('Meta', None) attr_meta = attrs.pop('Meta', None)
proxy = None
if not attr_meta: if not attr_meta:
meta = getattr(new_class, 'Meta', None) meta = None
# meta = getattr(new_class, 'Meta', None)
else: else:
meta = attr_meta meta = attr_meta
base_meta = getattr(new_class, '_meta', None) base_meta = getattr(new_class, '_meta', None)
schema = (base_meta and base_meta.schema) schema = (base_meta and base_meta.schema)
new_class.add_to_class('_meta', Options(meta, schema)) new_class.add_to_class('_meta', new_class.options_cls(meta, schema))
if base_meta and base_meta.proxy: if base_meta and base_meta.proxy:
new_class._meta.interface = base_meta.interface new_class._meta.interface = base_meta.interface
# Add all attributes to the class. # Add all attributes to the class.
for obj_name, obj in attrs.items(): for obj_name, obj in attrs.items():
new_class.add_to_class(obj_name, obj) new_class.add_to_class(obj_name, obj)
new_class.add_extra_fields()
new_fields = new_class._meta.local_fields new_fields = new_class._meta.local_fields
field_names = {f.field_name for f in new_fields} field_names = {f.field_name for f in new_fields}
@ -71,6 +79,9 @@ class ObjectTypeMeta(type):
new_class._prepare() new_class._prepare()
return new_class return new_class
def add_extra_fields(cls):
pass
def _prepare(cls): def _prepare(cls):
signals.class_prepared.send(cls) signals.class_prepared.send(cls)

View File

@ -1,5 +1,3 @@
import collections
from graphql_relay.node.node import ( from graphql_relay.node.node import (
globalIdField globalIdField
) )
@ -8,7 +6,6 @@ from graphql_relay.connection.connection import (
) )
from graphene import signals from graphene import signals
from graphene.core.fields import NativeField from graphene.core.fields import NativeField
from graphene.relay.utils import get_relay from graphene.relay.utils import get_relay
from graphene.relay.relay import Relay from graphene.relay.relay import Relay
@ -18,10 +15,9 @@ from graphene.relay.relay import Relay
def object_type_created(object_type): def object_type_created(object_type):
relay = get_relay(object_type._meta.schema) relay = get_relay(object_type._meta.schema)
if relay and issubclass(object_type, relay.Node): if relay and issubclass(object_type, relay.Node):
if object_type._meta.proxy:
return
type_name = object_type._meta.type_name type_name = object_type._meta.type_name
# def getId(*args, **kwargs):
# print '**GET ID', args, kwargs
# return 2
field = NativeField(globalIdField(type_name)) field = NativeField(globalIdField(type_name))
object_type.add_to_class('id', field) object_type.add_to_class('id', field)
assert hasattr(object_type, 'get_node'), 'get_node classmethod not found in %s Node' % type_name assert hasattr(object_type, 'get_node'), 'get_node classmethod not found in %s Node' % type_name

View File

@ -25,7 +25,7 @@ def create_node_definitions(getNode=None, getNodeType=getNodeType, schema=None):
getNode = getNode or getSchemaNode(schema) getNode = getNode or getSchemaNode(schema)
_nodeDefinitions = nodeDefinitions(getNode, getNodeType) _nodeDefinitions = nodeDefinitions(getNode, getNodeType)
_Interface = getattr(schema,'Interface', Interface) _Interface = getattr(schema, 'Interface', Interface)
class Node(_Interface): class Node(_Interface):
@classmethod @classmethod

View File

View File

@ -0,0 +1,17 @@
from datetime import date
from .models import Reporter, Article
r = Reporter(first_name='John', last_name='Smith', email='john@example.com')
r.save()
r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com')
r2.save()
a = Article(id=None, headline="This is a test", pub_date=date(2005, 7, 27), reporter=r)
a.save()
new_article = r.articles.create(headline="John's second story", pub_date=date(2005, 7, 29))
new_article2 = Article(headline="Paul's story", pub_date=date(2006, 1, 17))
r.articles.add(new_article2)

View File

@ -0,0 +1,43 @@
from __future__ import absolute_import
import django
from django.conf import settings
settings.configure(
DATABASES={
'INSTALLED_APPS': [
'graphql.contrib.django',
],
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'db_test.sqlite',
}
}
)
from django.db import models
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
def __str__(self): # __unicode__ on Python 2
return "%s %s" % (self.first_name, self.last_name)
class Meta:
app_label = 'graphql'
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, related_name='articles')
def __str__(self): # __unicode__ on Python 2
return self.headline
class Meta:
ordering = ('headline',)
app_label = 'graphql'
django.setup()

View File

@ -0,0 +1,115 @@
from py.test import raises
from collections import namedtuple
from pytest import raises
import graphene
from graphene import relay
from graphene.contrib.django import (
DjangoObjectType,
DjangoNode
)
from .models import Reporter, Article
def test_should_raise_if_no_model():
with raises(Exception) as excinfo:
class Character1(DjangoObjectType):
pass
assert 'model in the Meta' in str(excinfo.value)
def test_should_raise_if_model_is_invalid():
with raises(Exception) as excinfo:
class Character2(DjangoObjectType):
class Meta:
model = 1
assert 'not a Django model' in str(excinfo.value)
def test_should_map_fields():
class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
class Query(graphene.ObjectType):
reporter = graphene.Field(ReporterType)
def resolve_reporter(self, *args, **kwargs):
return ReporterType(Reporter(first_name='ABA', last_name='X'))
query = '''
query ReporterQuery {
reporter {
first_name,
last_name,
email
}
}
'''
expected = {
'reporter': {
'first_name': 'ABA',
'last_name': 'X',
'email': ''
}
}
Schema = graphene.Schema(query=Query)
result = Schema.execute(query)
assert not result.errors
assert result.data == expected
def test_should_map_only_few_fields():
class Reporter2(DjangoObjectType):
class Meta:
model = Reporter
only_fields = ('id', 'email')
assert Reporter2._meta.fields_map.keys() == ['id', 'email']
def test_should_node():
class ReporterNodeType(DjangoNode):
class Meta:
model = Reporter
@classmethod
def get_node(cls, id):
return ReporterNodeType(Reporter(id=2, first_name='Cookie Monster'))
class Query(graphene.ObjectType):
node = relay.NodeField()
reporter = graphene.Field(ReporterNodeType)
def resolve_reporter(self, *args, **kwargs):
return ReporterNodeType(Reporter(id=1, first_name='ABA', last_name='X'))
query = '''
query ReporterQuery {
reporter {
id,
first_name,
last_name,
email
}
aCustomNode: node(id:"UmVwb3J0ZXJOb2RlVHlwZToy") {
id
... on ReporterNodeType {
first_name
}
}
}
'''
expected = {
'reporter': {
'id': 'UmVwb3J0ZXJOb2RlVHlwZTox',
'first_name': 'ABA',
'last_name': 'X',
'email': ''
},
'aCustomNode': {
'id': 'UmVwb3J0ZXJOb2RlVHlwZToy',
'first_name': 'Cookie Monster'
}
}
Schema = graphene.Schema(query=Query)
result = Schema.execute(query)
assert not result.errors
assert result.data == expected