Completed Django support. Improved tests. Changed schema behavior

This commit is contained in:
Syrus Akbary 2015-10-01 01:54:52 -07:00
parent 72c88a19e5
commit c945df6064
24 changed files with 543 additions and 192 deletions

View File

@ -4,7 +4,8 @@ python:
- 2.7 - 2.7
install: install:
- pip install pytest pytest-cov coveralls flake8 six blinker - pip install pytest pytest-cov coveralls flake8 six blinker
- pip install -e .[django] # - pip install -e .[django] # TODO: Commented until graphqllib is in pypi
- pip install Django>=1.8.0 pytest-django singledispatch>=3.4.0.3
- pip install git+https://github.com/dittos/graphqllib.git # Last version of graphqllib - pip install git+https://github.com/dittos/graphqllib.git # Last version of graphqllib
- pip install graphql-relay - pip install graphql-relay
- python setup.py develop - python setup.py develop

View File

@ -35,4 +35,4 @@ from graphene.decorators import (
resolve_only_args resolve_only_args
) )
import graphene.relay # import graphene.relay

View File

@ -4,9 +4,11 @@ from graphene.core.fields import (
from graphene import relay from graphene import relay
from graphene.core.fields import Field, LazyField from graphene.core.fields import Field, LazyField
from graphene.utils import cached_property from graphene.utils import cached_property, memoize
from graphene.env import get_global_schema from graphene.env import get_global_schema
from graphene.relay.types import BaseNode
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.db.models.manager import Manager from django.db.models.manager import Manager
@ -29,11 +31,11 @@ class DjangoConnectionField(relay.ConnectionField):
class ConnectionOrListField(LazyField): class ConnectionOrListField(LazyField):
def get_field(self): @memoize
schema = self.schema def get_field(self, schema):
model_field = self.field_type model_field = self.field_type
field_object_type = model_field.get_object_type() field_object_type = model_field.get_object_type(schema)
if field_object_type and issubclass(field_object_type, schema.Node): if field_object_type and issubclass(field_object_type, BaseNode):
field = DjangoConnectionField(model_field) field = DjangoConnectionField(model_field)
else: else:
field = ListField(model_field) field = ListField(model_field)
@ -46,13 +48,13 @@ class DjangoModelField(Field):
super(DjangoModelField, self).__init__(None, *args, **kwargs) super(DjangoModelField, self).__init__(None, *args, **kwargs)
self.model = model self.model = model
@cached_property @memoize
def type(self): def internal_type(self, schema):
_type = self.get_object_type() _type = self.get_object_type(schema)
return _type and _type._meta.type return _type and _type.internal_type(schema)
def get_object_type(self): def get_object_type(self, schema):
_type = get_type_for_model(self.schema, self.model) _type = get_type_for_model(schema, self.model)
if not _type and self.object_type._meta.only_fields: if not _type and self.object_type._meta.only_fields:
# We will only raise the exception if the related field is specified in only_fields # We will only raise the exception if the related field is specified in only_fields
raise Exception("Field %s (%s) model not mapped in current schema" % (self, self.model._meta.object_name)) raise Exception("Field %s (%s) model not mapped in current schema" % (self, self.model._meta.object_name))

View File

@ -5,6 +5,7 @@ from graphene.core.options import Options
VALID_ATTRS = ('model', 'only_fields') VALID_ATTRS = ('model', 'only_fields')
from graphene.relay.types import Node, BaseNode
class DjangoOptions(Options): class DjangoOptions(Options):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -15,9 +16,9 @@ class DjangoOptions(Options):
def contribute_to_class(self, cls, name): def contribute_to_class(self, cls, name):
super(DjangoOptions, self).contribute_to_class(cls, name) super(DjangoOptions, self).contribute_to_class(cls, name)
if self.proxy: if cls.__name__ == 'DjangoNode':
return return
if not self.model: if not self.model:
raise Exception('Django ObjectType %s must have a model in the Meta attr' % cls) raise Exception('Django ObjectType %s must have a model in the Meta class attr' % cls)
elif not inspect.isclass(self.model) or not issubclass(self.model, models.Model): 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) raise Exception('Provided model in %s is not a Django model' % cls)

View File

@ -1,11 +1,11 @@
import six import six
from django.db import models from django.db import models
from graphene.core.types import ObjectTypeMeta, ObjectType from graphene.core.types import ObjectTypeMeta, BaseObjectType
from graphene.contrib.django.options import DjangoOptions from graphene.contrib.django.options import DjangoOptions
from graphene.contrib.django.converter import convert_django_field from graphene.contrib.django.converter import convert_django_field
from graphene.relay import Node from graphene.relay.types import Node, BaseNode
def get_reverse_fields(model): def get_reverse_fields(model):
@ -18,6 +18,9 @@ def get_reverse_fields(model):
class DjangoObjectTypeMeta(ObjectTypeMeta): class DjangoObjectTypeMeta(ObjectTypeMeta):
options_cls = DjangoOptions options_cls = DjangoOptions
def is_interface(cls, parents):
return DjangoInterface in parents
def add_extra_fields(cls): def add_extra_fields(cls):
if not cls._meta.model: if not cls._meta.model:
return return
@ -30,11 +33,13 @@ class DjangoObjectTypeMeta(ObjectTypeMeta):
cls.add_to_class(field.name, converted_field) cls.add_to_class(field.name, converted_field)
class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, ObjectType)): class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, BaseObjectType)):
class Meta: pass
proxy = True
class DjangoNode(six.with_metaclass(DjangoObjectTypeMeta, Node)): class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, BaseObjectType)):
class Meta: pass
proxy = True
class DjangoNode(BaseNode, DjangoInterface):
pass

View File

@ -10,8 +10,8 @@ from graphql.core.type import (
GraphQLArgument, GraphQLArgument,
GraphQLFloat, GraphQLFloat,
) )
from graphene.utils import cached_property from graphene.utils import cached_property, memoize
from graphene.core.types import ObjectType from graphene.core.types import BaseObjectType
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):
@ -27,7 +27,6 @@ class Field(object):
def contribute_to_class(self, cls, name): def contribute_to_class(self, cls, name):
self.field_name = name self.field_name = name
self.object_type = cls self.object_type = cls
self.schema = cls._meta.schema
if isinstance(self.field_type, Field) and not self.field_type.object_type: if isinstance(self.field_type, Field) and not self.field_type.object_type:
self.field_type.contribute_to_class(cls, name) self.field_type.contribute_to_class(cls, name)
cls._meta.add_field(self) cls._meta.add_field(self)
@ -45,42 +44,39 @@ class Field(object):
resolve_fn = lambda root, args, info: root.resolve(self.field_name, args, info) resolve_fn = lambda root, args, info: root.resolve(self.field_name, args, info)
return resolve_fn(instance, args, info) return resolve_fn(instance, args, info)
def get_object_type(self): def get_object_type(self, schema):
field_type = self.field_type field_type = self.field_type
_is_class = inspect.isclass(field_type) _is_class = inspect.isclass(field_type)
if isinstance(field_type, Field): if isinstance(field_type, Field):
return field_type.get_object_type() return field_type.get_object_type(schema)
if _is_class and issubclass(field_type, ObjectType): if _is_class and issubclass(field_type, BaseObjectType):
return field_type return field_type
elif isinstance(field_type, basestring): elif isinstance(field_type, basestring):
if field_type == 'self': if field_type == 'self':
return self.object_type return self.object_type
elif self.schema: else:
return self.schema.get_type(field_type) return schema.get_type(field_type)
@cached_property
def type(self):
field_type = self.field_type
if isinstance(field_type, Field):
field_type = self.field_type.type
else:
object_type = self.get_object_type()
if object_type:
field_type = object_type._meta.type
field_type = self.type_wrapper(field_type)
return field_type
def type_wrapper(self, field_type): def type_wrapper(self, field_type):
if not self.null: if not self.null:
field_type = GraphQLNonNull(field_type) field_type = GraphQLNonNull(field_type)
return field_type return field_type
@cached_property @memoize
def field(self): def internal_type(self, schema):
# if not self.field_type: field_type = self.field_type
# raise Exception('Must specify a field GraphQL type for the field %s'%self.field_name) if isinstance(field_type, Field):
field_type = self.field_type.internal_type(schema)
else:
object_type = self.get_object_type(schema)
if object_type:
field_type = object_type.internal_type(schema)
field_type = self.type_wrapper(field_type)
return field_type
@memoize
def internal_field(self, schema):
if not self.object_type: if not self.object_type:
raise Exception('Field could not be constructed in a non graphene.Type or graphene.Interface') raise Exception('Field could not be constructed in a non graphene.Type or graphene.Interface')
@ -97,8 +93,10 @@ class Field(object):
','.join(meta_attrs.keys()) ','.join(meta_attrs.keys())
)) ))
internal_type = self.internal_type(schema)
return GraphQLField( return GraphQLField(
self.type, internal_type,
description=self.description, description=self.description,
args=self.args, args=self.args,
resolver=self.resolver, resolver=self.resolver,
@ -122,30 +120,46 @@ class Field(object):
class NativeField(Field): class NativeField(Field):
def __init__(self, field=None): def __init__(self, field=None):
super(NativeField, self).__init__(None) super(NativeField, self).__init__(None)
self.field = field or getattr(self, 'field') self.field = field
def get_field(self, schema):
return self.field
@memoize
def internal_field(self, schema):
return self.get_field(schema)
@memoize
def internal_type(self, schema):
return self.internal_field(schema).type
class LazyField(Field): class LazyField(Field):
@cached_property @memoize
def inner_field(self): def inner_field(self, schema):
return self.get_field() return self.get_field(schema)
@cached_property def internal_type(self, schema):
def type(self): return self.inner_field(schema).internal_type(schema)
return self.inner_field.type
@cached_property def internal_field(self, schema):
def field(self): return self.inner_field(schema).internal_field(schema)
return self.inner_field.field
class LazyNativeField(LazyField): class LazyNativeField(NativeField):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(LazyNativeField, self).__init__(None, *args, **kwargs) super(LazyNativeField, self).__init__(None, *args, **kwargs)
@cached_property def get_field(self, schema):
def field(self): raise NotImplementedError("get_field function not implemented for %s LazyField" % self.__class__)
return self.inner_field
@memoize
def internal_field(self, schema):
return self.get_field(schema)
@memoize
def internal_type(self, schema):
return self.internal_field(schema).type
class TypeField(Field): class TypeField(Field):

View File

@ -1,17 +1,15 @@
from graphene.env import get_global_schema
from graphene.utils import cached_property from graphene.utils import cached_property
DEFAULT_NAMES = ('description', 'name', 'interface', 'schema', DEFAULT_NAMES = ('description', 'name', 'interface',
'type_name', 'interfaces', 'proxy') 'type_name', 'interfaces', 'proxy')
class Options(object): class Options(object):
def __init__(self, meta=None, schema=None): def __init__(self, meta=None):
self.meta = meta self.meta = meta
self.local_fields = [] self.local_fields = []
self.interface = False self.interface = False
self.proxy = False self.proxy = False
self.schema = schema or get_global_schema()
self.interfaces = [] self.interfaces = []
self.parents = [] self.parents = []
self.valid_attrs = DEFAULT_NAMES self.valid_attrs = DEFAULT_NAMES
@ -71,7 +69,3 @@ class Options(object):
@cached_property @cached_property
def fields_map(self): def fields_map(self):
return {f.field_name: f for f in self.fields} return {f.field_name: f for f in self.fields}
@cached_property
def type(self):
return self.parent.get_graphql_type()

View File

@ -1,3 +1,5 @@
from functools import wraps
from graphql.core import graphql from graphql.core import graphql
from graphql.core.type import ( from graphql.core.type import (
GraphQLSchema GraphQLSchema
@ -10,10 +12,10 @@ class Schema(object):
_query = None _query = None
def __init__(self, query=None, mutation=None, name='Schema'): def __init__(self, query=None, mutation=None, name='Schema'):
self._internal_types = {}
self.mutation = mutation self.mutation = mutation
self.query = query self.query = query
self.name = name self.name = name
self._types = {}
signals.init_schema.send(self) signals.init_schema.send(self)
def __repr__(self): def __repr__(self):
@ -25,34 +27,33 @@ class Schema(object):
@query.setter @query.setter
def query(self, query): def query(self, query):
if not query:
return
self._query = query self._query = query
self._query_type = query._meta.type self._query_type = query and query.internal_type(self)
self._schema = GraphQLSchema(query=self._query_type, mutation=self.mutation)
def register_type(self, type): @cached_property
type_name = type._meta.type_name def schema(self):
if type_name in self._types: if not self._query_type:
raise Exception('Type name %s already registered in %r' % (type_name, self)) raise Exception('You have to define a base query type')
self._types[type_name] = type return GraphQLSchema(query=self._query_type, mutation=self.mutation)
def associate_internal_type(self, internal_type, object_type):
self._internal_types[internal_type.name] = object_type
def get_type(self, type_name): def get_type(self, type_name):
if type_name not in self._types: # print 'get_type'
# _type = self.schema.get_type(type_name)
if type_name not in self._internal_types:
raise Exception('Type %s not found in %r' % (type_name, self)) raise Exception('Type %s not found in %r' % (type_name, self))
return self._types[type_name] return self._internal_types[type_name]
def __getattr__(self, name):
return self.get_type(name)
@property @property
def types(self): def types(self):
return self._types return self._internal_types
def execute(self, request='', root=None, vars=None, operation_name=None): def execute(self, request='', root=None, vars=None, operation_name=None):
root = root or object() root = root or object()
return graphql( return graphql(
self._schema, self.schema,
request=request, request=request,
root=self.query(root), root=self.query(root),
vars=vars, vars=vars,
@ -62,9 +63,12 @@ class Schema(object):
def introspect(self): def introspect(self):
return self._schema.get_type_map() return self._schema.get_type_map()
def register_internal_type(fun):
@wraps(fun)
def wrapper(cls, schema):
internal_type = fun(cls, schema)
if isinstance(schema, Schema):
schema.associate_internal_type(internal_type, cls)
return internal_type
@signals.class_prepared.connect return wrapper
def object_type_created(object_type):
schema = object_type._meta.schema
if schema:
schema.register_type(object_type)

View File

@ -8,11 +8,15 @@ from graphql.core.type import (
from graphene import signals from graphene import signals
from graphene.core.options import Options from graphene.core.options import Options
from graphene.utils import memoize
from graphene.core.schema import register_internal_type
class ObjectTypeMeta(type): class ObjectTypeMeta(type):
options_cls = Options options_cls = Options
def is_interface(cls, parents):
return Interface in parents
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, cls)] parents = [b for b in bases if isinstance(b, cls)]
@ -27,7 +31,6 @@ 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 = None meta = None
# meta = getattr(new_class, 'Meta', None) # meta = getattr(new_class, 'Meta', None)
@ -36,13 +39,9 @@ class ObjectTypeMeta(type):
base_meta = getattr(new_class, '_meta', None) base_meta = getattr(new_class, '_meta', None)
schema = (base_meta and base_meta.schema) new_class.add_to_class('_meta', new_class.options_cls(meta))
new_class.add_to_class('_meta', new_class.options_cls(meta, schema)) new_class._meta.interface = new_class.is_interface(parents)
if base_meta and base_meta.proxy:
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)
@ -93,13 +92,13 @@ class ObjectTypeMeta(type):
setattr(cls, name, value) setattr(cls, name, value)
class ObjectType(six.with_metaclass(ObjectTypeMeta)): class BaseObjectType(object):
def __new__(cls, instance=None, *args, **kwargs): def __new__(cls, instance=None, *args, **kwargs):
if cls._meta.interface: if cls._meta.interface:
raise Exception("An interface cannot be initialized") raise Exception("An interface cannot be initialized")
if instance == None: if instance == None:
return None return None
return super(ObjectType, cls).__new__(cls, instance, *args, **kwargs) return super(BaseObjectType, cls).__new__(cls, instance, *args, **kwargs)
def __init__(self, instance=None): def __init__(self, instance=None):
signals.pre_init.send(self.__class__, instance=instance) signals.pre_init.send(self.__class__, instance=instance)
@ -128,28 +127,35 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta)):
return True return True
@classmethod @classmethod
def resolve_type(cls, instance, *_): def resolve_type(cls, schema, instance, *_):
return instance._meta.type return instance.internal_type(schema)
@classmethod @classmethod
def get_graphql_type(cls): @memoize
fields = cls._meta.fields_map @register_internal_type
def internal_type(cls, schema):
fields_map = cls._meta.fields_map
fields = lambda: {
name: field.internal_field(schema)
for name, field in fields_map.items()
}
if cls._meta.interface: if cls._meta.interface:
return GraphQLInterfaceType( return GraphQLInterfaceType(
cls._meta.type_name, cls._meta.type_name,
description=cls._meta.description, description=cls._meta.description,
resolve_type=cls.resolve_type, resolve_type=lambda *args, **kwargs: cls.resolve_type(schema, *args, **kwargs),
fields=lambda: {name: field.field for name, field in fields.items()} fields=fields
) )
return GraphQLObjectType( return GraphQLObjectType(
cls._meta.type_name, cls._meta.type_name,
description=cls._meta.description, description=cls._meta.description,
interfaces=[i._meta.type for i in cls._meta.interfaces], interfaces=[i.internal_type(schema) for i in cls._meta.interfaces],
fields=lambda: {name: field.field for name, field in fields.items()} fields=fields
) )
class Interface(ObjectType): class ObjectType(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
class Meta: pass
interface = True
proxy = True class Interface(six.with_metaclass(ObjectTypeMeta, BaseObjectType)):
pass

View File

@ -5,6 +5,6 @@ from graphene.relay.fields import (
import graphene.relay.connections import graphene.relay.connections
from graphene.relay.nodes import ( from graphene.relay.types import (
Node Node
) )

View File

@ -1,24 +1,15 @@
from graphql_relay.node.node import ( from graphql_relay.node.node import (
globalIdField globalIdField
) )
from graphql_relay.connection.connection import (
connectionDefinitions
)
from graphene import signals from graphene import signals
from graphene.core.fields import NativeField from graphene.relay.fields import NodeIDField
from graphene.relay.types import BaseNode, Node
@signals.class_prepared.connect @signals.class_prepared.connect
def object_type_created(object_type): def object_type_created(object_type):
schema = object_type._meta.schema if issubclass(object_type, BaseNode) and BaseNode not in object_type.__bases__:
if hasattr(schema, 'Node') and issubclass(object_type, schema.Node) and object_type != schema.Node:
if object_type._meta.proxy:
return
type_name = object_type._meta.type_name type_name = object_type._meta.type_name
field = NativeField(globalIdField(type_name)) field = NodeIDField()
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
connection = connectionDefinitions(type_name, object_type._meta.type).connectionType
object_type.add_to_class('connection', connection)

View File

@ -6,8 +6,13 @@ from graphql_relay.connection.arrayconnection import (
from graphql_relay.connection.connection import ( from graphql_relay.connection.connection import (
connectionArgs connectionArgs
) )
from graphql_relay.node.node import (
globalIdField
)
from graphene.core.fields import Field, LazyNativeField from graphene.core.fields import Field, LazyNativeField
from graphene.utils import cached_property from graphene.utils import cached_property
from graphene.utils import memoize
class ConnectionField(Field): class ConnectionField(Field):
@ -25,13 +30,20 @@ class ConnectionField(Field):
assert isinstance(resolved, collections.Iterable), 'Resolved value from the connection field have to be iterable' assert isinstance(resolved, collections.Iterable), 'Resolved value from the connection field have to be iterable'
return connectionFromArray(resolved, args) return connectionFromArray(resolved, args)
@cached_property @memoize
def type(self): def internal_type(self, schema):
object_type = self.get_object_type() from graphene.relay.types import BaseNode
assert issubclass(object_type, self.schema.Node), 'Only nodes have connections.' object_type = self.get_object_type(schema)
return object_type.connection assert issubclass(object_type, BaseNode), 'Only nodes have connections.'
return object_type.get_connection(schema)
class NodeField(LazyNativeField): class NodeField(LazyNativeField):
def get_field(self): def get_field(self, schema):
return self.schema.Node._definitions.nodeField from graphene.relay.types import BaseNode
return BaseNode.get_definitions(schema).nodeField
class NodeIDField(LazyNativeField):
def get_field(self, schema):
return globalIdField(self.object_type._meta.type_name)

View File

@ -1,37 +0,0 @@
from graphql_relay.node.node import (
nodeDefinitions,
fromGlobalId
)
from graphene.env import get_global_schema
from graphene.core.types import Interface
from graphene.core.fields import LazyNativeField
def get_node_type(obj):
return obj._meta.type
def get_node(schema, globalId, *args):
resolvedGlobalId = fromGlobalId(globalId)
_type, _id = resolvedGlobalId.type, resolvedGlobalId.id
object_type = schema.get_type(_type)
return object_type.get_node(_id)
class Node(Interface):
_definitions = None
@classmethod
def contribute_to_schema(cls, schema):
if cls._definitions:
return
schema = cls._meta.schema
cls._definitions = nodeDefinitions(lambda *args: get_node(schema, *args), get_node_type)
@classmethod
def get_graphql_type(cls):
if cls is cls._meta.schema.Node:
# Return only nodeInterface when is the Node Inerface
cls.contribute_to_schema(cls._meta.schema)
return cls._definitions.nodeInterface
return super(Node, cls).get_graphql_type()

49
graphene/relay/types.py Normal file
View File

@ -0,0 +1,49 @@
from graphql_relay.node.node import (
nodeDefinitions,
fromGlobalId
)
from graphql_relay.connection.connection import (
connectionDefinitions
)
from graphene.env import get_global_schema
from graphene.core.types import Interface
from graphene.core.fields import LazyNativeField
from graphene.utils import memoize
def get_node_type(schema, obj):
return obj.internal_type(schema)
def get_node(schema, globalId, *args):
resolvedGlobalId = fromGlobalId(globalId)
_type, _id = resolvedGlobalId.type, resolvedGlobalId.id
object_type = schema.get_type(_type)
return object_type.get_node(_id)
class BaseNode(object):
@classmethod
@memoize
def get_definitions(cls, schema):
return nodeDefinitions(lambda *args: get_node(schema, *args), lambda *args: get_node_type(schema, *args))
@classmethod
@memoize
def get_connection(cls, schema):
_type = cls.internal_type(schema)
type_name = cls._meta.type_name
connection = connectionDefinitions(type_name, _type).connectionType
return connection
@classmethod
def internal_type(cls, schema):
if cls is Node or BaseNode in cls.__bases__:
# Return only nodeInterface when is the Node Inerface
return BaseNode.get_definitions(schema).nodeInterface
return super(BaseNode, cls).internal_type(schema)
class Node(BaseNode, Interface):
pass

View File

@ -1,3 +1,5 @@
from functools import wraps
class cached_property(object): class cached_property(object):
""" """
A property that is only computed once per instance and then replaces itself A property that is only computed once per instance and then replaces itself
@ -14,3 +16,17 @@ class cached_property(object):
return self return self
value = obj.__dict__[self.func.__name__] = self.func(obj) value = obj.__dict__[self.func.__name__] = self.func(obj)
return value return value
def memoize(fun):
"""A simple memoize decorator for functions supporting positional args."""
@wraps(fun)
def wrapper(*args, **kwargs):
key = (args, frozenset(sorted(kwargs.items())))
try:
return cache[key]
except KeyError:
ret = cache[key] = fun(*args, **kwargs)
return ret
cache = {}
return wrapper

View File

@ -114,6 +114,7 @@ def test_should_node():
class Query1(graphene.ObjectType): class Query1(graphene.ObjectType):
node = relay.NodeField() node = relay.NodeField()
reporter = graphene.Field(ReporterNodeType) reporter = graphene.Field(ReporterNodeType)
article = graphene.Field(ArticleNodeType)
def resolve_reporter(self, *args, **kwargs): def resolve_reporter(self, *args, **kwargs):
return ReporterNodeType(Reporter(id=1, first_name='ABA', last_name='X')) return ReporterNodeType(Reporter(id=1, first_name='ABA', last_name='X'))

View File

@ -0,0 +1,65 @@
from py.test import raises
from collections import namedtuple
from pytest import raises
from graphene.core.fields import (
Field,
StringField,
)
from graphql.core.type import (
GraphQLObjectType,
GraphQLInterfaceType
)
from graphene import Schema
from graphene.contrib.django.types import (
DjangoNode,
DjangoInterface
)
from .models import Reporter, Article
class Character(DjangoInterface):
'''Character description'''
class Meta:
model = Reporter
class Human(DjangoNode):
'''Human description'''
def get_node(self, id):
pass
class Meta:
model = Article
schema = Schema()
def test_django_interface():
assert DjangoNode._meta.interface == True
def test_pseudo_interface():
object_type = Character.internal_type(schema)
assert Character._meta.interface == True
assert isinstance(object_type, GraphQLInterfaceType)
assert Character._meta.model == Reporter
assert object_type.get_fields().keys() == ['articles', 'first_name', 'last_name', 'id', 'email']
def test_interface_resolve_type():
resolve_type = Character.resolve_type(schema, Human(object()))
assert isinstance(resolve_type, GraphQLObjectType)
def test_object_type():
object_type = Human.internal_type(schema)
assert Human._meta.interface == False
assert isinstance(object_type, GraphQLObjectType)
assert object_type.get_fields() == {
'headline': Human._meta.fields_map['headline'].internal_field(schema),
'id': Human._meta.fields_map['id'].internal_field(schema),
'reporter': Human._meta.fields_map['reporter'].internal_field(schema),
'pub_date': Human._meta.fields_map['pub_date'].internal_field(schema),
}
assert object_type.get_interfaces() == [DjangoNode.internal_type(schema)]

View File

@ -28,34 +28,65 @@ ot = ObjectType()
ObjectType._meta.contribute_to_class(ObjectType, '_meta') ObjectType._meta.contribute_to_class(ObjectType, '_meta')
class Schema(object):
pass
schema = Schema()
def test_field_no_contributed_raises_error(): def test_field_no_contributed_raises_error():
f = Field(GraphQLString) f = Field(GraphQLString)
with raises(Exception) as excinfo: with raises(Exception) as excinfo:
f.field f.internal_field(schema)
def test_field_type(): def test_field_type():
f = Field(GraphQLString) f = Field(GraphQLString)
f.contribute_to_class(ot, 'field_name') f.contribute_to_class(ot, 'field_name')
assert isinstance(f.field, GraphQLField) assert isinstance(f.internal_field(schema), GraphQLField)
assert f.type == GraphQLString assert f.internal_type(schema) == GraphQLString
def test_stringfield_type(): def test_stringfield_type():
f = StringField() f = StringField()
f.contribute_to_class(ot, 'field_name') f.contribute_to_class(ot, 'field_name')
assert f.type == GraphQLString assert f.internal_type(schema) == GraphQLString
def test_stringfield_type_null(): def test_stringfield_type_null():
f = StringField(null=False) f = StringField(null=False)
f.contribute_to_class(ot, 'field_name') f.contribute_to_class(ot, 'field_name')
assert isinstance(f.field, GraphQLField) assert isinstance(f.internal_field(schema), GraphQLField)
assert isinstance(f.type, GraphQLNonNull) assert isinstance(f.internal_type(schema), GraphQLNonNull)
def test_field_resolve(): def test_field_resolve():
f = StringField(null=False) f = StringField(null=False, resolve=lambda *args:'RESOLVED')
f.contribute_to_class(ot, 'field_name') f.contribute_to_class(ot, 'field_name')
field_type = f.field field_type = f.internal_field(schema)
field_type.resolver(ot,2,3) assert 'RESOLVED' == field_type.resolver(ot,2,3)
def test_field_resolve_type_custom():
class MyCustomType(object):
pass
class Schema(object):
def get_type(self, name):
if name == 'MyCustomType':
return MyCustomType
s = Schema()
f = Field('MyCustomType')
f.contribute_to_class(ot, 'field_name')
field_type = f.get_object_type(s)
assert field_type == MyCustomType
def test_field_resolve_type_custom():
s = Schema()
f = Field('self')
f.contribute_to_class(ot, 'field_name')
field_type = f.get_object_type(s)
assert field_type == ot

68
tests/core/test_query.py Normal file
View File

@ -0,0 +1,68 @@
from py.test import raises
from collections import namedtuple
from pytest import raises
from graphql.core import graphql
from graphene.core.fields import (
Field,
StringField,
ListField,
)
from graphql.core.type import (
GraphQLObjectType,
GraphQLSchema,
GraphQLInterfaceType
)
from graphene.core.types import (
Interface,
ObjectType
)
class Character(Interface):
name = StringField()
class Pet(ObjectType):
type = StringField(resolve=lambda *_:'Dog')
class Human(Character):
friends = ListField(Character)
pet = Field(Pet)
def resolve_name(self, *args):
return 'Peter'
def resolve_friend(self, *args):
return Human(object())
def resolve_pet(self, *args):
return Pet(object())
# def resolve_friends(self, *args, **kwargs):
# return 'HEY YOU!'
schema = object()
Human_type = Human.internal_type(schema)
def test_query():
schema = GraphQLSchema(query=Human_type)
query = '''
{
name
pet {
type
}
}
'''
expected = {
'name': 'Peter',
'pet': {
'type':'Dog'
}
}
result = graphql(schema, query, root=Human(object()))
assert not result.errors
assert result.data == expected

103
tests/core/test_schema.py Normal file
View File

@ -0,0 +1,103 @@
from py.test import raises
from collections import namedtuple
from pytest import raises
from graphql.core import graphql
from graphene.core.fields import (
Field,
StringField,
ListField,
)
from graphql.core.type import (
GraphQLObjectType,
GraphQLSchema,
GraphQLInterfaceType
)
from graphene import (
Interface,
ObjectType,
Schema
)
schema = Schema(name='My own schema')
class Character(Interface):
name = StringField()
class Pet(ObjectType):
type = StringField(resolve=lambda *_:'Dog')
class Human(Character):
friends = ListField(Character)
pet = Field(Pet)
def resolve_name(self, *args):
return 'Peter'
def resolve_friend(self, *args):
return Human(object())
def resolve_pet(self, *args):
return Pet(object())
schema.query = Human
def test_get_registered_type():
assert schema.get_type('Character') == Character
def test_get_unregistered_type():
with raises(Exception) as excinfo:
schema.get_type('NON_EXISTENT_MODEL')
assert 'not found' in str(excinfo.value)
def test_schema_query():
assert schema.query == Human
def test_query_schema_graphql():
a = object()
query = '''
{
name
pet {
type
}
}
'''
expected = {
'name': 'Peter',
'pet': {
'type':'Dog'
}
}
result = graphql(schema.schema, query, root=Human(object()))
assert not result.errors
assert result.data == expected
def test_query_schema_execute():
a = object()
query = '''
{
name
pet {
type
}
}
'''
expected = {
'name': 'Peter',
'pet': {
'type':'Dog'
}
}
result = schema.execute(query, root=object())
assert not result.errors
assert result.data == expected
def test_schema_get_type_map():
assert schema.schema.get_type_map().keys() == ['__Field', 'String', 'Pet', 'Character', '__InputValue', '__Directive', '__TypeKind', '__Schema', '__Type', 'Human', '__EnumValue', 'Boolean']

View File

@ -15,31 +15,43 @@ from graphene.core.types import (
ObjectType ObjectType
) )
class Character(Interface): class Character(Interface):
'''Character description''' '''Character description'''
name = StringField() name = StringField()
class Meta: class Meta:
type_name = 'core.Character' type_name = 'core.Character'
class Human(Character): class Human(Character):
'''Human description''' '''Human description'''
friends = StringField() friends = StringField()
class Meta: class Meta:
type_name = 'core.Human' type_name = 'core.Human'
schema = object()
def test_interface(): def test_interface():
object_type = Character._meta.type object_type = Character.internal_type(schema)
assert Character._meta.interface == True assert Character._meta.interface == True
assert Character._meta.type_name == 'core.Character'
assert isinstance(object_type, GraphQLInterfaceType) assert isinstance(object_type, GraphQLInterfaceType)
assert Character._meta.type_name == 'core.Character'
assert object_type.description == 'Character description' assert object_type.description == 'Character description'
assert object_type.get_fields() == {'name': Character._meta.fields_map['name'].field} assert object_type.get_fields() == {'name': Character._meta.fields_map['name'].internal_field(schema)}
def test_interface_resolve_type():
resolve_type = Character.resolve_type(schema, Human(object()))
assert isinstance(resolve_type, GraphQLObjectType)
def test_object_type(): def test_object_type():
object_type = Human._meta.type object_type = Human.internal_type(schema)
assert Human._meta.interface == False assert Human._meta.interface == False
assert Human._meta.type_name == 'core.Human' assert Human._meta.type_name == 'core.Human'
assert isinstance(object_type, GraphQLObjectType) assert isinstance(object_type, GraphQLObjectType)
assert object_type.description == 'Human description' assert object_type.description == 'Human description'
assert object_type.get_fields() == {'name': Character._meta.fields_map['name'].field, 'friends': Human._meta.fields_map['friends'].field} assert object_type.get_fields() == {'name': Character._meta.fields_map['name'].internal_field(schema), 'friends': Human._meta.fields_map['friends'].internal_field(schema)}
assert object_type.get_interfaces() == [Character._meta.type] assert object_type.get_interfaces() == [Character.internal_type(schema)]

View File

@ -21,8 +21,12 @@ def test_field_no_contributed_raises_error():
assert 'get_node' in str(excinfo.value) assert 'get_node' in str(excinfo.value)
def test_node_should_have_connection(): def test_node_should_have_same_connection_always():
assert OtherNode.connection s = object()
connection1 = OtherNode.get_connection(s)
connection2 = OtherNode.get_connection(s)
assert connection1 == connection2
def test_node_should_have_id_field(): def test_node_should_have_id_field():

View File

@ -88,6 +88,9 @@ def createShip(shipName, factionId):
def getShip(_id): def getShip(_id):
return Ship.objects.get(id=_id) return Ship.objects.get(id=_id)
def getShips():
return Ship.objects.all()
def getFaction(_id): def getFaction(_id):
return Faction.objects.get(id=_id) return Faction.objects.get(id=_id)

View File

@ -8,6 +8,7 @@ from .models import Ship as ShipModel, Faction as FactionModel
from .data import ( from .data import (
getFaction, getFaction,
getShip, getShip,
getShips,
getRebels, getRebels,
getEmpire, getEmpire,
) )
@ -35,6 +36,11 @@ class Query(graphene.ObjectType):
rebels = graphene.Field(Faction) rebels = graphene.Field(Faction)
empire = graphene.Field(Faction) empire = graphene.Field(Faction)
node = relay.NodeField() node = relay.NodeField()
ships = relay.ConnectionField(Ship, description='All the ships.')
@resolve_only_args
def resolve_ships(self):
return [Ship(s) for s in getShips()]
@resolve_only_args @resolve_only_args
def resolve_rebels(self): def resolve_rebels(self):