diff --git a/graphene/__init__.py b/graphene/__init__.py index c84eeb8c..4fd823ce 100644 --- a/graphene/__init__.py +++ b/graphene/__init__.py @@ -6,6 +6,21 @@ from graphql.core.type import ( 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 ( Field, StringField, @@ -16,12 +31,6 @@ from graphene.core.fields import ( NonNullField, ) -from graphene.core.types import ( - ObjectType, - Interface, - Schema -) - from graphene.decorators import ( resolve_only_args ) diff --git a/graphene/core/fields.py b/graphene/core/fields.py index 478e3630..430ec9f9 100644 --- a/graphene/core/fields.py +++ b/graphene/core/fields.py @@ -1,3 +1,4 @@ +import inspect from graphql.core.type import ( GraphQLField, GraphQLList, @@ -9,7 +10,7 @@ from graphql.core.type import ( GraphQLArgument, ) from graphene.utils import cached_property -from graphene.core.utils import get_object_type +from graphene.core.types import ObjectType class Field(object): 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): self.field_name = name self.object_type = cls + self.schema = cls._meta.schema if isinstance(self.field_type, Field) and not self.field_type.object_type: self.field_type.contribute_to_class(cls, name) 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) 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 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 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) return field_type diff --git a/graphene/core/options.py b/graphene/core/options.py index c3dc13af..e5eac894 100644 --- a/graphene/core/options.py +++ b/graphene/core/options.py @@ -1,18 +1,19 @@ +from graphene.env import get_global_schema from graphene.utils import cached_property -DEFAULT_NAMES = ('app_label', 'description', 'name', 'interface', +DEFAULT_NAMES = ('description', 'name', 'interface', 'schema', 'type_name', 'interfaces', 'proxy') class Options(object): - def __init__(self, meta=None, app_label=None): + def __init__(self, meta=None, schema=None): self.meta = meta self.local_fields = [] self.interface = False self.proxy = False + self.schema = schema or get_global_schema() self.interfaces = [] self.parents = [] - self.app_label = app_label def contribute_to_class(self, cls, name): cls._meta = self diff --git a/graphene/core/schema.py b/graphene/core/schema.py new file mode 100644 index 00000000..4a5519af --- /dev/null +++ b/graphene/core/schema.py @@ -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 '' % 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 diff --git a/graphene/core/types.py b/graphene/core/types.py index 3ffb466d..f020a72a 100644 --- a/graphene/core/types.py +++ b/graphene/core/types.py @@ -3,10 +3,8 @@ import six from graphql.core.type import ( GraphQLObjectType, - GraphQLInterfaceType, - GraphQLSchema + GraphQLInterfaceType ) -from graphql.core import graphql from graphene import signals from graphene.core.options import Options @@ -33,12 +31,9 @@ class ObjectTypeMeta(type): meta = attr_meta base_meta = getattr(new_class, '_meta', None) - if '.' in module: - app_label, _ = module.rsplit('.', 1) - else: - app_label = module + schema = (base_meta and base_meta.schema) - 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: new_class._meta.interface = base_meta.interface # Add all attributes to the class. @@ -54,6 +49,8 @@ class ObjectTypeMeta(type): # Things without _meta aren't functional models, so they're # uninteresting parents. continue + if base._meta.schema != new_class._meta.schema: + raise Exception('The parent schema is not the same') parent_fields = base._meta.local_fields # Check for clashes between locally declared fields and those @@ -138,19 +135,3 @@ class Interface(ObjectType): class Meta: interface = 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 - ) diff --git a/graphene/core/utils.py b/graphene/core/utils.py deleted file mode 100644 index b50a7664..00000000 --- a/graphene/core/utils.py +++ /dev/null @@ -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) diff --git a/graphene/env.py b/graphene/env.py new file mode 100644 index 00000000..604dbc2c --- /dev/null +++ b/graphene/env.py @@ -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 diff --git a/graphene/relay/__init__.py b/graphene/relay/__init__.py index d93aa7ac..b7a71ce5 100644 --- a/graphene/relay/__init__.py +++ b/graphene/relay/__init__.py @@ -1,85 +1,5 @@ -import collections - -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 +from graphene.relay.nodes import ( + create_node_definitions ) -registered_nodes = {} - - -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) +from graphene.relay.relay import * diff --git a/graphene/relay/nodes.py b/graphene/relay/nodes.py new file mode 100644 index 00000000..81223a52 --- /dev/null +++ b/graphene/relay/nodes.py @@ -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 diff --git a/graphene/relay/relay.py b/graphene/relay/relay.py new file mode 100644 index 00000000..2df13e81 --- /dev/null +++ b/graphene/relay/relay.py @@ -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) diff --git a/tests/core/test_types.py b/tests/core/test_types.py index b17cae48..1408caa2 100644 --- a/tests/core/test_types.py +++ b/tests/core/test_types.py @@ -18,15 +18,18 @@ from graphene.core.types import ( class Character(Interface): '''Character description''' name = StringField() - + class Meta: + type_name = 'core.Character' class Human(Character): '''Human description''' friends = StringField() + class Meta: + type_name = 'core.Human' def test_interface(): object_type = Character._meta.type assert Character._meta.interface == True - assert Character._meta.type_name == 'Character' + assert Character._meta.type_name == 'core.Character' assert isinstance(object_type, GraphQLInterfaceType) assert object_type.description == 'Character description' assert object_type.get_fields() == {'name': Character._meta.fields_map['name'].field} @@ -34,7 +37,7 @@ def test_interface(): def test_object_type(): object_type = Human._meta.type assert Human._meta.interface == False - assert Human._meta.type_name == 'Human' + assert Human._meta.type_name == 'core.Human' assert isinstance(object_type, GraphQLObjectType) 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} diff --git a/tests/relay/test_relay.py b/tests/relay/test_relay.py index c532949f..2bcdc836 100644 --- a/tests/relay/test_relay.py +++ b/tests/relay/test_relay.py @@ -3,6 +3,7 @@ from pytest import raises import graphene from graphene import relay +schema = graphene.Schema() class OtherNode(relay.Node): name = graphene.StringField() @@ -28,14 +29,19 @@ def test_node_should_have_id_field(): assert 'id' in OtherNode._meta.fields_map -def test_field_no_contributed_raises_error(): - with raises(Exception) as excinfo: - class Ship(graphene.ObjectType): - name = graphene.StringField() +# def test_field_no_contributed_raises_error(): +# with raises(Exception) as excinfo: +# class Ship(graphene.ObjectType): +# name = graphene.StringField() +# class Meta: +# schema = schema - - class Faction(relay.Node): - name = graphene.StringField() - ships = relay.ConnectionField(Ship) - - assert 'same type_name' in str(excinfo.value) +# class Faction(relay.Node): +# name = graphene.StringField() +# ships = relay.ConnectionField(Ship) +# @classmethod +# def get_node(cls): +# pass +# class Meta: +# schema = schema +# assert 'same type_name' in str(excinfo.value) diff --git a/tests/starwars/schema.py b/tests/starwars/schema.py index 9fbee302..d3527a83 100644 --- a/tests/starwars/schema.py +++ b/tests/starwars/schema.py @@ -46,6 +46,9 @@ class Query(graphene.ObjectType): id = graphene.Argument(graphene.String) ) + class Meta: + type_name = 'core.Query' + @resolve_only_args def resolve_hero(self, episode): return wrap_character(getHero(episode)) diff --git a/tests/starwars_relay/schema.py b/tests/starwars_relay/schema.py index 55e03e6c..f1a12050 100644 --- a/tests/starwars_relay/schema.py +++ b/tests/starwars_relay/schema.py @@ -8,6 +8,7 @@ from .data import ( getEmpire, ) +schema = graphene.Schema() class Ship(relay.Node): '''A ship in the Star Wars saga''' @@ -19,6 +20,8 @@ class Ship(relay.Node): if ship: return Ship(ship) + # class Meta: + # schema = schema class Faction(relay.Node): '''A faction in the Star Wars saga''' @@ -35,6 +38,9 @@ class Faction(relay.Node): if faction: return Faction(faction) + # class Meta: + # schema = schema + class Query(graphene.ObjectType): rebels = graphene.Field(Faction) @@ -50,4 +56,10 @@ class Query(graphene.ObjectType): return Faction(getEmpire()) -Schema = graphene.Schema(query=Query) + # class Meta: + # schema = schema + +print '*CACA', schema._types + +schema.query = Query +Schema = schema