Improved tests and schema definition.

This commit is contained in:
Syrus Akbary 2015-09-26 03:36:00 -07:00
parent 750bbfbc2c
commit eafc9a102e
14 changed files with 255 additions and 187 deletions

View File

@ -6,6 +6,21 @@ from graphql.core.type import (
GraphQLID as ID GraphQLID as ID
) )
from graphene import signals
from graphene.core.schema import (
Schema
)
from graphene.env import (
get_global_schema
)
from graphene.core.types import (
ObjectType,
Interface
)
from graphene.core.fields import ( from graphene.core.fields import (
Field, Field,
StringField, StringField,
@ -16,12 +31,6 @@ from graphene.core.fields import (
NonNullField, NonNullField,
) )
from graphene.core.types import (
ObjectType,
Interface,
Schema
)
from graphene.decorators import ( from graphene.decorators import (
resolve_only_args resolve_only_args
) )

View File

@ -1,3 +1,4 @@
import inspect
from graphql.core.type import ( from graphql.core.type import (
GraphQLField, GraphQLField,
GraphQLList, GraphQLList,
@ -9,7 +10,7 @@ from graphql.core.type import (
GraphQLArgument, GraphQLArgument,
) )
from graphene.utils import cached_property from graphene.utils import cached_property
from graphene.core.utils import get_object_type 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):
@ -25,6 +26,7 @@ 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)
@ -42,12 +44,27 @@ 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):
field_type = self.field_type
_is_class = inspect.isclass(field_type)
if _is_class and issubclass(field_type, ObjectType):
return field_type
elif isinstance(field_type, basestring):
if field_type == 'self':
return self.object_type
elif self.schema:
return self.schema.get_type(field_type)
@cached_property @cached_property
def type(self): def type(self):
if isinstance(self.field_type, Field): field_type = self.field_type
if isinstance(field_type, Field):
field_type = self.field_type.type field_type = self.field_type.type
else: else:
field_type = get_object_type(self.field_type, self.object_type) object_type = self.get_object_type()
if object_type:
field_type = object_type._meta.type
field_type = self.type_wrapper(field_type) field_type = self.type_wrapper(field_type)
return field_type return field_type

View File

@ -1,18 +1,19 @@
from graphene.env import get_global_schema
from graphene.utils import cached_property from graphene.utils import cached_property
DEFAULT_NAMES = ('app_label', 'description', 'name', 'interface', DEFAULT_NAMES = ('description', 'name', 'interface', 'schema',
'type_name', 'interfaces', 'proxy') 'type_name', 'interfaces', 'proxy')
class Options(object): class Options(object):
def __init__(self, meta=None, app_label=None): def __init__(self, meta=None, schema=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.app_label = app_label
def contribute_to_class(self, cls, name): def contribute_to_class(self, cls, name):
cls._meta = self cls._meta = self

73
graphene/core/schema.py Normal file
View File

@ -0,0 +1,73 @@
from graphql.core import graphql
from graphql.core.type import (
GraphQLSchema
)
from graphene import signals
from graphene.utils import cached_property
# from graphene.relay.nodes import create_node_definitions
class Schema(object):
_query = None
def __init__(self, query=None, mutation=None, name='Schema'):
self.mutation = mutation
self.query = query
self.name = name
self._types = {}
def __repr__(self):
return '<Schema: %s>' % str(self.name)
# @cachedproperty
# def node_definitions(self):
# return [object, object]
# # from graphene.relay import create_node_definitions
# # return create_node_definitions(schema=self)
# @property
# def Node(self):
# return self.node_definitions[0]
# @property
# def NodeField(self):
# return self.node_definitions[1]
@property
def query(self):
return self._query
@query.setter
def query(self, query):
if not query:
return
self._query = query
self._query_type = query._meta.type
self._schema = GraphQLSchema(query=self._query_type, mutation=self.mutation)
def register_type(self, type):
type_name = type._meta.type_name
if type_name in self._types:
raise Exception('Type name %s already registered in %r' % (type_name, self))
self._types[type_name] = type
def get_type(self, type_name):
if type_name not in self._types:
raise Exception('Type %s not found in %r' % (type_name, self))
return self._types[type_name]
def execute(self, request='', root=None, vars=None, operation_name=None):
return graphql(
self._schema,
request=request,
root=root or self.query(),
vars=vars,
operation_name=operation_name
)
@signals.class_prepared.connect
def object_type_created(object_type):
schema = object_type._meta.schema
if schema:
schema.register_type(object_type)
from graphene.env import get_global_schema

View File

@ -3,10 +3,8 @@ import six
from graphql.core.type import ( from graphql.core.type import (
GraphQLObjectType, GraphQLObjectType,
GraphQLInterfaceType, GraphQLInterfaceType
GraphQLSchema
) )
from graphql.core import graphql
from graphene import signals from graphene import signals
from graphene.core.options import Options from graphene.core.options import Options
@ -33,12 +31,9 @@ class ObjectTypeMeta(type):
meta = attr_meta meta = attr_meta
base_meta = getattr(new_class, '_meta', None) base_meta = getattr(new_class, '_meta', None)
if '.' in module: schema = (base_meta and base_meta.schema)
app_label, _ = module.rsplit('.', 1)
else:
app_label = module
new_class.add_to_class('_meta', Options(meta, app_label)) new_class.add_to_class('_meta', Options(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.
@ -54,6 +49,8 @@ class ObjectTypeMeta(type):
# Things without _meta aren't functional models, so they're # Things without _meta aren't functional models, so they're
# uninteresting parents. # uninteresting parents.
continue continue
if base._meta.schema != new_class._meta.schema:
raise Exception('The parent schema is not the same')
parent_fields = base._meta.local_fields parent_fields = base._meta.local_fields
# Check for clashes between locally declared fields and those # Check for clashes between locally declared fields and those
@ -138,19 +135,3 @@ class Interface(ObjectType):
class Meta: class Meta:
interface = True interface = True
proxy = True proxy = True
class Schema(object):
def __init__(self, query, mutation=None):
self.query = query
self.query_type = query._meta.type
self._schema = GraphQLSchema(query=self.query_type, mutation=mutation)
def execute(self, request='', root=None, vars=None, operation_name=None):
return graphql(
self._schema,
request=request,
root=root or self.query(),
vars=vars,
operation_name=operation_name
)

View File

@ -1,54 +0,0 @@
import inspect
from graphene.core.types import ObjectType
from graphene import signals
registered_object_types = []
def get_object_type(field_type, object_type=None):
native_type = get_type(field_type, object_type)
if native_type:
field_type = native_type._meta.type
return field_type
def get_type(field_type, object_type=None):
_is_class = inspect.isclass(field_type)
if _is_class and issubclass(field_type, ObjectType):
return field_type
elif isinstance(field_type, basestring):
if field_type == 'self':
return object_type
else:
object_type = get_registered_object_type(field_type, object_type)
return object_type
return None
def get_registered_object_type(name, object_type=None):
app_label = None
object_type_name = name
if '.' in name:
app_label, object_type_name = name.rsplit('.', 1)
elif object_type:
app_label = object_type._meta.app_label
# Filter all registered object types which have the same name
ots = [ot for ot in registered_object_types if ot._meta.type_name == object_type_name]
# If the list have more than one object type with the name, filter by
# the app_label
if len(ots)>1 and app_label:
ots = [ot for ot in ots if ot._meta.app_label == app_label]
if len(ots)>1:
raise Exception('Multiple ObjectTypes returned with the name %s' % name)
if not ots:
raise Exception('No ObjectType found with name %s' % name)
return ots[0]
@signals.class_prepared.connect
def object_type_created(sender):
registered_object_types.append(sender)

9
graphene/env.py Normal file
View File

@ -0,0 +1,9 @@
from graphene.core.schema import Schema
_global_schema = None
def get_global_schema():
global _global_schema
if not _global_schema:
_global_schema = Schema(name='Global Schema')
return _global_schema

View File

@ -1,85 +1,5 @@
import collections from graphene.relay.nodes import (
create_node_definitions
from graphene import signals
from graphene.core.fields import Field, NativeField
from graphene.core.types import Interface
from graphene.core.utils import get_type
from graphene.utils import cached_property
from graphql_relay.node.node import (
nodeDefinitions,
globalIdField,
fromGlobalId
)
from graphql_relay.connection.arrayconnection import (
connectionFromArray
)
from graphql_relay.connection.connection import (
connectionArgs,
connectionDefinitions
) )
registered_nodes = {} from graphene.relay.relay import *
def getNode(globalId, *args):
resolvedGlobalId = fromGlobalId(globalId)
_type, _id = resolvedGlobalId.type, resolvedGlobalId.id
if _type in registered_nodes:
object_type = registered_nodes[_type]
return object_type.get_node(_id)
def getNodeType(obj):
return obj._meta.type
_nodeDefinitions = nodeDefinitions(getNode, getNodeType)
class Node(Interface):
@classmethod
def get_graphql_type(cls):
if cls is Node:
# Return only nodeInterface when is the Node Inerface
return _nodeDefinitions.nodeInterface
return super(Node, cls).get_graphql_type()
class NodeField(NativeField):
field = _nodeDefinitions.nodeField
class ConnectionField(Field):
def __init__(self, field_type, resolve=None, description=''):
super(ConnectionField, self).__init__(field_type, resolve=resolve,
args=connectionArgs, description=description)
def resolve(self, instance, args, info):
resolved = super(ConnectionField, self).resolve(instance, args, info)
if resolved:
assert isinstance(resolved, collections.Iterable), 'Resolved value from the connection field have to be iterable'
return connectionFromArray(resolved, args)
@cached_property
def type(self):
object_type = get_type(self.field_type, self.object_type)
assert issubclass(object_type, Node), 'Only nodes have connections.'
return object_type.connection
@signals.class_prepared.connect
def object_type_created(object_type):
if issubclass(object_type, Node):
type_name = object_type._meta.type_name
assert type_name not in registered_nodes, 'Two nodes with the same type_name: %s' % type_name
registered_nodes[type_name] = object_type
# def getId(*args, **kwargs):
# print '**GET ID', args, kwargs
# return 2
field = NativeField(globalIdField(type_name))
object_type.add_to_class('id', field)
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)

37
graphene/relay/nodes.py Normal file
View File

@ -0,0 +1,37 @@
from graphql_relay.node.node import (
nodeDefinitions,
fromGlobalId
)
def create_node_definitions(getNode=None, getNodeType=None, schema=None):
from graphene.core.types import Interface
from graphene.core.fields import Field, NativeField
if not getNode:
def getNode(globalId, *args):
from graphene.env import get_global_schema
_schema = schema or get_global_schema()
resolvedGlobalId = fromGlobalId(globalId)
_type, _id = resolvedGlobalId.type, resolvedGlobalId.id
object_type = _schema.get_type(_type)
return object_type.get_node(_id)
if not getNodeType:
def getNodeType(obj):
return obj._meta.type
_nodeDefinitions = nodeDefinitions(getNode, getNodeType)
class Node(Interface):
@classmethod
def get_graphql_type(cls):
if cls is Node:
# Return only nodeInterface when is the Node Inerface
return _nodeDefinitions.nodeInterface
return super(Node, cls).get_graphql_type()
class NodeField(NativeField):
field = _nodeDefinitions.nodeField
return Node, NodeField

51
graphene/relay/relay.py Normal file
View File

@ -0,0 +1,51 @@
import collections
from graphene import signals
from graphene.utils import cached_property
from graphql_relay.node.node import (
globalIdField
)
from graphql_relay.connection.arrayconnection import (
connectionFromArray
)
from graphql_relay.connection.connection import (
connectionArgs,
connectionDefinitions
)
from graphene.relay.nodes import create_node_definitions
from graphene.core.fields import Field, NativeField
Node, NodeField = create_node_definitions()
class ConnectionField(Field):
def __init__(self, field_type, resolve=None, description=''):
super(ConnectionField, self).__init__(field_type, resolve=resolve,
args=connectionArgs, description=description)
def resolve(self, instance, args, info):
resolved = super(ConnectionField, self).resolve(instance, args, info)
if resolved:
assert isinstance(resolved, collections.Iterable), 'Resolved value from the connection field have to be iterable'
return connectionFromArray(resolved, args)
@cached_property
def type(self):
object_type = self.get_object_type()
assert issubclass(object_type, Node), 'Only nodes have connections.'
return object_type.connection
@signals.class_prepared.connect
def object_type_created(object_type):
if issubclass(object_type, Node):
type_name = object_type._meta.type_name
# def getId(*args, **kwargs):
# print '**GET ID', args, kwargs
# return 2
field = NativeField(globalIdField(type_name))
object_type.add_to_class('id', field)
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

@ -18,15 +18,18 @@ from graphene.core.types import (
class Character(Interface): class Character(Interface):
'''Character description''' '''Character description'''
name = StringField() name = StringField()
class Meta:
type_name = 'core.Character'
class Human(Character): class Human(Character):
'''Human description''' '''Human description'''
friends = StringField() friends = StringField()
class Meta:
type_name = 'core.Human'
def test_interface(): def test_interface():
object_type = Character._meta.type object_type = Character._meta.type
assert Character._meta.interface == True assert Character._meta.interface == True
assert Character._meta.type_name == 'Character' assert Character._meta.type_name == 'core.Character'
assert isinstance(object_type, GraphQLInterfaceType) assert isinstance(object_type, GraphQLInterfaceType)
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'].field}
@ -34,7 +37,7 @@ def test_interface():
def test_object_type(): def test_object_type():
object_type = Human._meta.type object_type = Human._meta.type
assert Human._meta.interface == False assert Human._meta.interface == False
assert Human._meta.type_name == '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'].field, 'friends': Human._meta.fields_map['friends'].field}

View File

@ -3,6 +3,7 @@ from pytest import raises
import graphene import graphene
from graphene import relay from graphene import relay
schema = graphene.Schema()
class OtherNode(relay.Node): class OtherNode(relay.Node):
name = graphene.StringField() name = graphene.StringField()
@ -28,14 +29,19 @@ def test_node_should_have_id_field():
assert 'id' in OtherNode._meta.fields_map assert 'id' in OtherNode._meta.fields_map
def test_field_no_contributed_raises_error(): # def test_field_no_contributed_raises_error():
with raises(Exception) as excinfo: # with raises(Exception) as excinfo:
class Ship(graphene.ObjectType): # class Ship(graphene.ObjectType):
name = graphene.StringField() # name = graphene.StringField()
# class Meta:
# schema = schema
# class Faction(relay.Node):
class Faction(relay.Node): # name = graphene.StringField()
name = graphene.StringField() # ships = relay.ConnectionField(Ship)
ships = relay.ConnectionField(Ship) # @classmethod
# def get_node(cls):
assert 'same type_name' in str(excinfo.value) # pass
# class Meta:
# schema = schema
# assert 'same type_name' in str(excinfo.value)

View File

@ -46,6 +46,9 @@ class Query(graphene.ObjectType):
id = graphene.Argument(graphene.String) id = graphene.Argument(graphene.String)
) )
class Meta:
type_name = 'core.Query'
@resolve_only_args @resolve_only_args
def resolve_hero(self, episode): def resolve_hero(self, episode):
return wrap_character(getHero(episode)) return wrap_character(getHero(episode))

View File

@ -8,6 +8,7 @@ from .data import (
getEmpire, getEmpire,
) )
schema = graphene.Schema()
class Ship(relay.Node): class Ship(relay.Node):
'''A ship in the Star Wars saga''' '''A ship in the Star Wars saga'''
@ -19,6 +20,8 @@ class Ship(relay.Node):
if ship: if ship:
return Ship(ship) return Ship(ship)
# class Meta:
# schema = schema
class Faction(relay.Node): class Faction(relay.Node):
'''A faction in the Star Wars saga''' '''A faction in the Star Wars saga'''
@ -35,6 +38,9 @@ class Faction(relay.Node):
if faction: if faction:
return Faction(faction) return Faction(faction)
# class Meta:
# schema = schema
class Query(graphene.ObjectType): class Query(graphene.ObjectType):
rebels = graphene.Field(Faction) rebels = graphene.Field(Faction)
@ -50,4 +56,10 @@ class Query(graphene.ObjectType):
return Faction(getEmpire()) return Faction(getEmpire())
Schema = graphene.Schema(query=Query) # class Meta:
# schema = schema
print '*CACA', schema._types
schema.query = Query
Schema = schema