mirror of
				https://github.com/graphql-python/graphene.git
				synced 2025-10-30 23:47:55 +03:00 
			
		
		
		
	
						commit
						0037d8aa2e
					
				|  | @ -3,7 +3,7 @@ sudo: false | ||||||
| python: | python: | ||||||
| - 2.7 | - 2.7 | ||||||
| install: | install: | ||||||
| - pip install pytest pytest-cov coveralls flake8 six | - pip install pytest pytest-cov coveralls flake8 six blinker | ||||||
| - 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 | ||||||
|  |  | ||||||
							
								
								
									
										56
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								README.md
									
									
									
									
									
								
							|  | @ -77,6 +77,62 @@ query = ''' | ||||||
| result = Schema.execute(query) | result = Schema.execute(query) | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | ### Relay Schema | ||||||
|  | 
 | ||||||
|  | Graphene also supports Relay, check the (Starwars Relay example)[/tests/starwars_relay]! | ||||||
|  | 
 | ||||||
|  | ```python | ||||||
|  | import graphene | ||||||
|  | from graphene import relay | ||||||
|  | 
 | ||||||
|  | class Ship(relay.Node): | ||||||
|  |     '''A ship in the Star Wars saga''' | ||||||
|  |     name = graphene.StringField(description='The name of the ship.') | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def get_node(cls, id): | ||||||
|  |         ship = getShip(id) | ||||||
|  |         if ship: | ||||||
|  |             return Ship(ship) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Faction(relay.Node): | ||||||
|  |     '''A faction in the Star Wars saga''' | ||||||
|  |     name = graphene.StringField(description='The name of the faction.') | ||||||
|  |     ships = relay.ConnectionField(Ship, description='The ships used by the faction.') | ||||||
|  | 
 | ||||||
|  |     @resolve_only_args | ||||||
|  |     def resolve_ships(self, **kwargs): | ||||||
|  |         return [Ship(getShip(ship)) for ship in self.instance.ships] | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def get_node(cls, id): | ||||||
|  |         faction = getFaction(id) | ||||||
|  |         if faction: | ||||||
|  |             return Faction(faction) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Query(graphene.ObjectType): | ||||||
|  |     rebels = graphene.Field(Faction) | ||||||
|  |     empire = graphene.Field(Faction) | ||||||
|  |     node = relay.NodeField() | ||||||
|  | 
 | ||||||
|  |     @resolve_only_args | ||||||
|  |     def resolve_rebels(self): | ||||||
|  |         return Faction(getRebels()) | ||||||
|  | 
 | ||||||
|  |     @resolve_only_args | ||||||
|  |     def resolve_empire(self): | ||||||
|  |         return Faction(getEmpire()) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Schema = graphene.Schema(query=Query) | ||||||
|  | 
 | ||||||
|  | # Later on, for querying | ||||||
|  | Schema.execute('''rebels { name }''') | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ## Contributing | ## Contributing | ||||||
| 
 | 
 | ||||||
| After cloning this repo, ensure dependencies are installed by running: | After cloning this repo, ensure dependencies are installed by running: | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| from graphql.core.type import ( | from graphql.core.type import ( | ||||||
|     GraphQLEnumType as Enum, |     GraphQLEnumType as Enum, | ||||||
|     GraphQLArgument as Argument, |     GraphQLArgument as Argument, | ||||||
|     # GraphQLSchema as Schema, |  | ||||||
|     GraphQLString as String, |     GraphQLString as String, | ||||||
|     GraphQLInt as Int, |     GraphQLInt as Int, | ||||||
|     GraphQLID as ID |     GraphQLID as ID | ||||||
|  |  | ||||||
|  | @ -1,4 +1,3 @@ | ||||||
| import inspect |  | ||||||
| from graphql.core.type import ( | from graphql.core.type import ( | ||||||
|     GraphQLField, |     GraphQLField, | ||||||
|     GraphQLList, |     GraphQLList, | ||||||
|  | @ -9,8 +8,8 @@ from graphql.core.type import ( | ||||||
|     GraphQLID, |     GraphQLID, | ||||||
|     GraphQLArgument, |     GraphQLArgument, | ||||||
| ) | ) | ||||||
| from graphene.core.types import ObjectType, Interface |  | ||||||
| from graphene.utils import cached_property | from graphene.utils import cached_property | ||||||
|  | from graphene.core.utils import get_object_type | ||||||
| 
 | 
 | ||||||
| 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): | ||||||
|  | @ -45,16 +44,11 @@ class Field(object): | ||||||
| 
 | 
 | ||||||
|     @cached_property |     @cached_property | ||||||
|     def type(self): |     def type(self): | ||||||
|         field_type = self.field_type |         if isinstance(self.field_type, Field): | ||||||
|         _is_class = inspect.isclass(field_type) |             field_type = self.field_type.type | ||||||
|         if _is_class and issubclass(field_type, ObjectType): |         else: | ||||||
|             field_type = field_type._meta.type |             field_type = get_object_type(self.field_type, self.object_type) | ||||||
|         elif isinstance(field_type, Field): |  | ||||||
|             field_type = field_type.type |  | ||||||
|         elif field_type == 'self': |  | ||||||
|             field_type = self.object_type._meta.type |  | ||||||
|         field_type = self.type_wrapper(field_type) |         field_type = self.type_wrapper(field_type) | ||||||
| 
 |  | ||||||
|         return field_type |         return field_type | ||||||
| 
 | 
 | ||||||
|     def type_wrapper(self, field_type): |     def type_wrapper(self, field_type): | ||||||
|  | @ -105,6 +99,12 @@ class Field(object): | ||||||
|         return '<%s>' % path |         return '<%s>' % path | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class NativeField(Field): | ||||||
|  |     def __init__(self, field=None): | ||||||
|  |         super(NativeField, self).__init__(None) | ||||||
|  |         self.field = field or getattr(self, 'field') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class TypeField(Field): | class TypeField(Field): | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         super(TypeField, self).__init__(self.field_type, *args, **kwargs) |         super(TypeField, self).__init__(self.field_type, *args, **kwargs) | ||||||
|  |  | ||||||
|  | @ -1,15 +1,18 @@ | ||||||
| from graphene.utils import cached_property | from graphene.utils import cached_property | ||||||
| 
 | 
 | ||||||
| DEFAULT_NAMES = ('description', 'name', 'interface', 'type_name', 'interfaces',  'proxy') | DEFAULT_NAMES = ('app_label', 'description', 'name', 'interface', | ||||||
|  |                  'type_name', 'interfaces',  'proxy') | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class Options(object): | class Options(object): | ||||||
|     def __init__(self, meta=None): |     def __init__(self, meta=None, app_label=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.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 | ||||||
|  | @ -51,7 +54,6 @@ class Options(object): | ||||||
| 
 | 
 | ||||||
|     def add_field(self, field): |     def add_field(self, field): | ||||||
|         self.local_fields.append(field) |         self.local_fields.append(field) | ||||||
|         setattr(self.parent, field.field_name, field) |  | ||||||
| 
 | 
 | ||||||
|     @cached_property |     @cached_property | ||||||
|     def fields(self): |     def fields(self): | ||||||
|  | @ -62,7 +64,7 @@ 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 |     @cached_property | ||||||
|     def type(self): |     def type(self): | ||||||
|  |  | ||||||
|  | @ -8,8 +8,10 @@ from graphql.core.type import ( | ||||||
| ) | ) | ||||||
| from graphql.core import graphql | from graphql.core import graphql | ||||||
| 
 | 
 | ||||||
|  | from graphene import signals | ||||||
| from graphene.core.options import Options | from graphene.core.options import Options | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class ObjectTypeMeta(type): | class ObjectTypeMeta(type): | ||||||
|     def __new__(cls, name, bases, attrs): |     def __new__(cls, name, bases, attrs): | ||||||
|         super_new = super(ObjectTypeMeta, cls).__new__ |         super_new = super(ObjectTypeMeta, cls).__new__ | ||||||
|  | @ -20,7 +22,10 @@ class ObjectTypeMeta(type): | ||||||
| 
 | 
 | ||||||
|         module = attrs.pop('__module__') |         module = attrs.pop('__module__') | ||||||
|         doc = attrs.pop('__doc__', None) |         doc = attrs.pop('__doc__', None) | ||||||
|         new_class = super_new(cls, name, bases, {'__module__': module, '__doc__': doc}) |         new_class = super_new(cls, name, bases, { | ||||||
|  |             '__module__': module, | ||||||
|  |             '__doc__': doc | ||||||
|  |         }) | ||||||
|         attr_meta = attrs.pop('Meta', None) |         attr_meta = attrs.pop('Meta', None) | ||||||
|         if not attr_meta: |         if not attr_meta: | ||||||
|             meta = getattr(new_class, 'Meta', None) |             meta = getattr(new_class, 'Meta', None) | ||||||
|  | @ -28,7 +33,12 @@ class ObjectTypeMeta(type): | ||||||
|             meta = attr_meta |             meta = attr_meta | ||||||
|         base_meta = getattr(new_class, '_meta', None) |         base_meta = getattr(new_class, '_meta', None) | ||||||
| 
 | 
 | ||||||
|         new_class.add_to_class('_meta', Options(meta)) |         if '.' in module: | ||||||
|  |             app_label, _ = module.rsplit('.', 1) | ||||||
|  |         else: | ||||||
|  |             app_label = module | ||||||
|  | 
 | ||||||
|  |         new_class.add_to_class('_meta', Options(meta, app_label)) | ||||||
|         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. | ||||||
|  | @ -51,7 +61,7 @@ class ObjectTypeMeta(type): | ||||||
|             # moment). |             # moment). | ||||||
|             for field in parent_fields: |             for field in parent_fields: | ||||||
|                 if field.field_name in field_names: |                 if field.field_name in field_names: | ||||||
|                     raise FieldError( |                     raise Exception( | ||||||
|                         'Local field %r in class %r clashes ' |                         'Local field %r in class %r clashes ' | ||||||
|                         'with field of similar name from ' |                         'with field of similar name from ' | ||||||
|                         'base class %r' % (field.field_name, name, base.__name__) |                         'base class %r' % (field.field_name, name, base.__name__) | ||||||
|  | @ -61,8 +71,12 @@ class ObjectTypeMeta(type): | ||||||
|                 new_class._meta.interfaces.append(base) |                 new_class._meta.interfaces.append(base) | ||||||
|             # new_class._meta.parents.extend(base._meta.parents) |             # new_class._meta.parents.extend(base._meta.parents) | ||||||
| 
 | 
 | ||||||
|  |         new_class._prepare() | ||||||
|         return new_class |         return new_class | ||||||
| 
 | 
 | ||||||
|  |     def _prepare(cls): | ||||||
|  |         signals.class_prepared.send(cls) | ||||||
|  | 
 | ||||||
|     def add_to_class(cls, name, value): |     def add_to_class(cls, name, value): | ||||||
|         # We should call the contribute_to_class method only if it's bound |         # We should call the contribute_to_class method only if it's bound | ||||||
|         if not inspect.isclass(value) and hasattr(value, 'contribute_to_class'): |         if not inspect.isclass(value) and hasattr(value, 'contribute_to_class'): | ||||||
|  | @ -73,15 +87,21 @@ class ObjectTypeMeta(type): | ||||||
| 
 | 
 | ||||||
| class ObjectType(six.with_metaclass(ObjectTypeMeta)): | class ObjectType(six.with_metaclass(ObjectTypeMeta)): | ||||||
|     def __init__(self, instance=None): |     def __init__(self, instance=None): | ||||||
|  |         signals.pre_init.send(self.__class__, instance=instance) | ||||||
|         self.instance = instance |         self.instance = instance | ||||||
|  |         signals.post_init.send(self.__class__, instance=self) | ||||||
|  | 
 | ||||||
|  |     def __getattr__(self, name): | ||||||
|  |         if self.instance: | ||||||
|  |             return getattr(self.instance, name) | ||||||
| 
 | 
 | ||||||
|     def get_field(self, field): |     def get_field(self, field): | ||||||
|         return getattr(self.instance, field, None) |         return getattr(self.instance, field, None) | ||||||
| 
 | 
 | ||||||
|     def resolve(self, field_name, args, info): |     def resolve(self, field_name, args, info): | ||||||
|         if field_name not in self._meta.fields_map.keys(): |         if field_name not in self._meta.fields_map.keys(): | ||||||
|             raise Exception('Field %s not found in model'%field_name) |             raise Exception('Field %s not found in model' % field_name) | ||||||
|         custom_resolve_fn = 'resolve_%s'%field_name |         custom_resolve_fn = 'resolve_%s' % field_name | ||||||
|         if hasattr(self, custom_resolve_fn): |         if hasattr(self, custom_resolve_fn): | ||||||
|             resolve_fn = getattr(self, custom_resolve_fn) |             resolve_fn = getattr(self, custom_resolve_fn) | ||||||
|             return resolve_fn(args, info) |             return resolve_fn(args, info) | ||||||
|  | @ -104,13 +124,13 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta)): | ||||||
|                 cls._meta.type_name, |                 cls._meta.type_name, | ||||||
|                 description=cls._meta.description, |                 description=cls._meta.description, | ||||||
|                 resolve_type=cls.resolve_type, |                 resolve_type=cls.resolve_type, | ||||||
|                 fields=lambda: {name:field.field for name, field in fields.items()} |                 fields=lambda: {name: field.field for name, field in fields.items()} | ||||||
|             ) |             ) | ||||||
|         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._meta.type for i in cls._meta.interfaces], | ||||||
|             fields=lambda: {name:field.field for name, field in fields.items()} |             fields=lambda: {name: field.field for name, field in fields.items()} | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -125,7 +145,7 @@ class Schema(object): | ||||||
|         self.query = query |         self.query = query | ||||||
|         self.query_type = query._meta.type |         self.query_type = query._meta.type | ||||||
|         self._schema = GraphQLSchema(query=self.query_type, mutation=mutation) |         self._schema = GraphQLSchema(query=self.query_type, mutation=mutation) | ||||||
|      | 
 | ||||||
|     def execute(self, request='', root=None, vars=None, operation_name=None): |     def execute(self, request='', root=None, vars=None, operation_name=None): | ||||||
|         return graphql( |         return graphql( | ||||||
|             self._schema, |             self._schema, | ||||||
|  |  | ||||||
							
								
								
									
										54
									
								
								graphene/core/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								graphene/core/utils.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,54 @@ | ||||||
|  | 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) | ||||||
|  | @ -0,0 +1,85 @@ | ||||||
|  | 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 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 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) | ||||||
							
								
								
									
										5
									
								
								graphene/signals.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								graphene/signals.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | from blinker import Signal | ||||||
|  | 
 | ||||||
|  | class_prepared = Signal() | ||||||
|  | pre_init = Signal() | ||||||
|  | post_init = Signal() | ||||||
							
								
								
									
										1
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								setup.py
									
									
									
									
									
								
							|  | @ -48,6 +48,7 @@ setup( | ||||||
| 
 | 
 | ||||||
|     install_requires=[ |     install_requires=[ | ||||||
|         'six', |         'six', | ||||||
|  |         'blinker', | ||||||
|         'graphqllib', |         'graphqllib', | ||||||
|         'graphql-relay' |         'graphql-relay' | ||||||
|     ], |     ], | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ def test_interface(): | ||||||
|     assert Character._meta.type_name == 'Character' |     assert Character._meta.type_name == '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.name.field} |     assert object_type.get_fields() == {'name': Character._meta.fields_map['name'].field} | ||||||
| 
 | 
 | ||||||
| def test_object_type(): | def test_object_type(): | ||||||
|     object_type = Human._meta.type |     object_type = Human._meta.type | ||||||
|  | @ -37,5 +37,5 @@ def test_object_type(): | ||||||
|     assert Human._meta.type_name == 'Human' |     assert Human._meta.type_name == '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.name.field, 'friends': Human.friends.field} |     assert object_type.get_fields() == {'name': Character._meta.fields_map['name'].field, 'friends': Human._meta.fields_map['friends'].field} | ||||||
|     assert object_type.get_interfaces() == [Character._meta.type] |     assert object_type.get_interfaces() == [Character._meta.type] | ||||||
|  |  | ||||||
							
								
								
									
										41
									
								
								tests/relay/test_relay.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								tests/relay/test_relay.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | ||||||
|  | from pytest import raises | ||||||
|  | 
 | ||||||
|  | import graphene | ||||||
|  | from graphene import relay | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class OtherNode(relay.Node): | ||||||
|  |     name = graphene.StringField() | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def get_node(cls, id): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_field_no_contributed_raises_error(): | ||||||
|  |     with raises(Exception) as excinfo: | ||||||
|  |         class Part(relay.Node): | ||||||
|  |             x = graphene.StringField() | ||||||
|  | 
 | ||||||
|  |     assert 'get_node' in str(excinfo.value) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_node_should_have_connection(): | ||||||
|  |     assert OtherNode.connection | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 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() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         class Faction(relay.Node): | ||||||
|  |             name = graphene.StringField() | ||||||
|  |             ships = relay.ConnectionField(Ship) | ||||||
|  | 
 | ||||||
|  |     assert 'same type_name' in str(excinfo.value) | ||||||
|  | @ -9,12 +9,14 @@ Episode = graphene.Enum('Episode', dict( | ||||||
|     JEDI = 6 |     JEDI = 6 | ||||||
| )) | )) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def wrap_character(character): | def wrap_character(character): | ||||||
|     if isinstance(character, _Human):  |     if isinstance(character, _Human):  | ||||||
|         return Human(character) |         return Human(character) | ||||||
|     elif isinstance(character, _Droid): |     elif isinstance(character, _Droid): | ||||||
|         return Droid(character) |         return Droid(character) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class Character(graphene.Interface): | class Character(graphene.Interface): | ||||||
|     id = graphene.IDField() |     id = graphene.IDField() | ||||||
|     name = graphene.StringField() |     name = graphene.StringField() | ||||||
|  | @ -24,6 +26,7 @@ class Character(graphene.Interface): | ||||||
|     def resolve_friends(self, args, *_): |     def resolve_friends(self, args, *_): | ||||||
|         return [wrap_character(getCharacter(f)) for f in self.instance.friends] |         return [wrap_character(getCharacter(f)) for f in self.instance.friends] | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class Human(Character): | class Human(Character): | ||||||
|     homePlanet = graphene.StringField() |     homePlanet = graphene.StringField() | ||||||
| 
 | 
 | ||||||
|  | @ -50,8 +53,6 @@ class Query(graphene.ObjectType): | ||||||
|     @resolve_only_args |     @resolve_only_args | ||||||
|     def resolve_human(self, id): |     def resolve_human(self, id): | ||||||
|         return wrap_character(getHuman(id)) |         return wrap_character(getHuman(id)) | ||||||
|         if human: |  | ||||||
|             return Human(human) |  | ||||||
| 
 | 
 | ||||||
|     @resolve_only_args |     @resolve_only_args | ||||||
|     def resolve_droid(self, id): |     def resolve_droid(self, id): | ||||||
|  |  | ||||||
							
								
								
									
										0
									
								
								tests/starwars_relay/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/starwars_relay/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										98
									
								
								tests/starwars_relay/data.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								tests/starwars_relay/data.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,98 @@ | ||||||
|  | from collections import namedtuple | ||||||
|  | 
 | ||||||
|  | Ship = namedtuple('Ship',['id', 'name']) | ||||||
|  | Faction = namedtuple('Faction',['id', 'name', 'ships']) | ||||||
|  | 
 | ||||||
|  | xwing = Ship( | ||||||
|  |     id='1', | ||||||
|  |     name='X-Wing', | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | ywing = Ship( | ||||||
|  |     id='2', | ||||||
|  |     name='Y-Wing', | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | awing = Ship( | ||||||
|  |     id='3', | ||||||
|  |     name='A-Wing', | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | # Yeah, technically it's Corellian. But it flew in the service of the rebels, | ||||||
|  | # so for the purposes of this demo it's a rebel ship. | ||||||
|  | falcon = Ship( | ||||||
|  |     id='4', | ||||||
|  |     name='Millenium Falcon', | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | homeOne = Ship( | ||||||
|  |     id='5', | ||||||
|  |     name='Home One', | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | tieFighter = Ship( | ||||||
|  |     id='6', | ||||||
|  |     name='TIE Fighter', | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | tieInterceptor = Ship( | ||||||
|  |     id='7', | ||||||
|  |     name='TIE Interceptor', | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | executor = Ship( | ||||||
|  |     id='8', | ||||||
|  |     name='Executor', | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | rebels = Faction( | ||||||
|  |     id='1', | ||||||
|  |     name='Alliance to Restore the Republic', | ||||||
|  |     ships=['1', '2', '3', '4', '5'] | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | empire = Faction( | ||||||
|  |     id='2', | ||||||
|  |     name='Galactic Empire', | ||||||
|  |     ships= ['6', '7', '8'] | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | data = { | ||||||
|  |     'Faction': { | ||||||
|  |         '1': rebels, | ||||||
|  |         '2': empire | ||||||
|  |     }, | ||||||
|  |     'Ship': { | ||||||
|  |         '1': xwing, | ||||||
|  |         '2': ywing, | ||||||
|  |         '3': awing, | ||||||
|  |         '4': falcon, | ||||||
|  |         '5': homeOne, | ||||||
|  |         '6': tieFighter, | ||||||
|  |         '7': tieInterceptor, | ||||||
|  |         '8': executor | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | def createShip(shipName, factionId): | ||||||
|  |     nextShip = len(data['Ship'].keys())+1 | ||||||
|  |     newShip = Ship( | ||||||
|  |         id=str(nextShip), | ||||||
|  |         name=shipName | ||||||
|  |     ) | ||||||
|  |     data['Ship'][newShip.id] = newShip | ||||||
|  |     data['Faction'][factionId].ships.append(newShip.id) | ||||||
|  |     return newShip | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def getShip(_id): | ||||||
|  |     return data['Ship'][_id] | ||||||
|  | 
 | ||||||
|  | def getFaction(_id): | ||||||
|  |     return data['Faction'][_id] | ||||||
|  | 
 | ||||||
|  | def getRebels(): | ||||||
|  |     return rebels | ||||||
|  | 
 | ||||||
|  | def getEmpire(): | ||||||
|  |     return empire | ||||||
							
								
								
									
										53
									
								
								tests/starwars_relay/schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								tests/starwars_relay/schema.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | ||||||
|  | import graphene | ||||||
|  | from graphene import resolve_only_args, relay | ||||||
|  | 
 | ||||||
|  | from .data import ( | ||||||
|  |     getFaction, | ||||||
|  |     getShip, | ||||||
|  |     getRebels, | ||||||
|  |     getEmpire, | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Ship(relay.Node): | ||||||
|  |     '''A ship in the Star Wars saga''' | ||||||
|  |     name = graphene.StringField(description='The name of the ship.') | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def get_node(cls, id): | ||||||
|  |         ship = getShip(id) | ||||||
|  |         if ship: | ||||||
|  |             return Ship(ship) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Faction(relay.Node): | ||||||
|  |     '''A faction in the Star Wars saga''' | ||||||
|  |     name = graphene.StringField(description='The name of the faction.') | ||||||
|  |     ships = relay.ConnectionField(Ship, description='The ships used by the faction.') | ||||||
|  | 
 | ||||||
|  |     @resolve_only_args | ||||||
|  |     def resolve_ships(self, **kwargs): | ||||||
|  |         return [Ship(getShip(ship)) for ship in self.instance.ships] | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def get_node(cls, id): | ||||||
|  |         faction = getFaction(id) | ||||||
|  |         if faction: | ||||||
|  |             return Faction(faction) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Query(graphene.ObjectType): | ||||||
|  |     rebels = graphene.Field(Faction) | ||||||
|  |     empire = graphene.Field(Faction) | ||||||
|  |     node = relay.NodeField() | ||||||
|  | 
 | ||||||
|  |     @resolve_only_args | ||||||
|  |     def resolve_rebels(self): | ||||||
|  |         return Faction(getRebels()) | ||||||
|  | 
 | ||||||
|  |     @resolve_only_args | ||||||
|  |     def resolve_empire(self): | ||||||
|  |         return Faction(getEmpire()) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Schema = graphene.Schema(query=Query) | ||||||
							
								
								
									
										60
									
								
								tests/starwars_relay/schema_other.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								tests/starwars_relay/schema_other.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | ||||||
|  | import graphene | ||||||
|  | from graphene import resolve_only_args, relay | ||||||
|  | 
 | ||||||
|  | from .data import ( | ||||||
|  |     getHero, getHuman, getCharacter, getDroid, | ||||||
|  |     Human as _Human, Droid as _Droid) | ||||||
|  | 
 | ||||||
|  | Episode = graphene.Enum('Episode', dict( | ||||||
|  |     NEWHOPE=4, | ||||||
|  |     EMPIRE=5, | ||||||
|  |     JEDI=6 | ||||||
|  | )) | ||||||
|  | 
 | ||||||
|  | def wrap_character(character): | ||||||
|  |     if isinstance(character, _Human): | ||||||
|  |         return Human(character) | ||||||
|  |     elif isinstance(character, _Droid): | ||||||
|  |         return Droid(character) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Character(graphene.Interface): | ||||||
|  |     name = graphene.StringField() | ||||||
|  |     friends = relay.Connection('Character') | ||||||
|  |     appearsIn = graphene.ListField(Episode) | ||||||
|  | 
 | ||||||
|  |     def resolve_friends(self, args, *_): | ||||||
|  |         return [wrap_character(getCharacter(f)) for f in self.instance.friends] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Human(relay.Node, Character): | ||||||
|  |     homePlanet = graphene.StringField() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Droid(relay.Node, Character): | ||||||
|  |     primaryFunction = graphene.StringField() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Query(graphene.ObjectType): | ||||||
|  |     hero = graphene.Field(Character, | ||||||
|  |                           episode=graphene.Argument(Episode)) | ||||||
|  |     human = graphene.Field(Human, | ||||||
|  |                            id=graphene.Argument(graphene.String)) | ||||||
|  |     droid = graphene.Field(Droid, | ||||||
|  |                            id=graphene.Argument(graphene.String)) | ||||||
|  |     node = relay.NodeField() | ||||||
|  | 
 | ||||||
|  |     @resolve_only_args | ||||||
|  |     def resolve_hero(self, episode): | ||||||
|  |         return wrap_character(getHero(episode)) | ||||||
|  | 
 | ||||||
|  |     @resolve_only_args | ||||||
|  |     def resolve_human(self, id): | ||||||
|  |         return wrap_character(getHuman(id)) | ||||||
|  | 
 | ||||||
|  |     @resolve_only_args | ||||||
|  |     def resolve_droid(self, id): | ||||||
|  |         return wrap_character(getDroid(id)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Schema = graphene.Schema(query=Query) | ||||||
							
								
								
									
										37
									
								
								tests/starwars_relay/test_connections.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								tests/starwars_relay/test_connections.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | ||||||
|  | from pytest import raises | ||||||
|  | from graphql.core import graphql | ||||||
|  | 
 | ||||||
|  | from .schema import Schema | ||||||
|  | 
 | ||||||
|  | def test_correct_fetch_first_ship_rebels(): | ||||||
|  |     query = ''' | ||||||
|  |     query RebelsShipsQuery { | ||||||
|  |       rebels { | ||||||
|  |         name, | ||||||
|  |         ships(first: 1) { | ||||||
|  |           edges { | ||||||
|  |             node { | ||||||
|  |               name | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     ''' | ||||||
|  |     expected = { | ||||||
|  |       'rebels': { | ||||||
|  |         'name': 'Alliance to Restore the Republic', | ||||||
|  |         'ships': { | ||||||
|  |           'edges': [ | ||||||
|  |             { | ||||||
|  |               'node': { | ||||||
|  |                 'name': 'X-Wing' | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           ] | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     result = Schema.execute(query) | ||||||
|  |     assert result.errors == None | ||||||
|  |     assert result.data == expected | ||||||
							
								
								
									
										105
									
								
								tests/starwars_relay/test_objectidentification.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								tests/starwars_relay/test_objectidentification.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,105 @@ | ||||||
|  | from pytest import raises | ||||||
|  | from graphql.core import graphql | ||||||
|  | 
 | ||||||
|  | from .schema import Schema | ||||||
|  | 
 | ||||||
|  | def test_correctly_fetches_id_name_rebels(): | ||||||
|  |     query = ''' | ||||||
|  |       query RebelsQuery { | ||||||
|  |         rebels { | ||||||
|  |           id | ||||||
|  |           name | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     ''' | ||||||
|  |     expected = { | ||||||
|  |       'rebels': { | ||||||
|  |         'id': 'RmFjdGlvbjox', | ||||||
|  |         'name': 'Alliance to Restore the Republic' | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     result = Schema.execute(query) | ||||||
|  |     assert result.errors == None | ||||||
|  |     assert result.data == expected | ||||||
|  | 
 | ||||||
|  | def test_correctly_refetches_rebels(): | ||||||
|  |     query = ''' | ||||||
|  |       query RebelsRefetchQuery { | ||||||
|  |         node(id: "RmFjdGlvbjox") { | ||||||
|  |           id | ||||||
|  |           ... on Faction { | ||||||
|  |             name | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     ''' | ||||||
|  |     expected = { | ||||||
|  |       'node': { | ||||||
|  |         'id': 'RmFjdGlvbjox', | ||||||
|  |         'name': 'Alliance to Restore the Republic' | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     result = Schema.execute(query) | ||||||
|  |     assert result.errors == None | ||||||
|  |     assert result.data == expected | ||||||
|  | 
 | ||||||
|  | def test_correctly_fetches_id_name_empire(): | ||||||
|  |     query = ''' | ||||||
|  |       query EmpireQuery { | ||||||
|  |         empire { | ||||||
|  |           id | ||||||
|  |           name | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     ''' | ||||||
|  |     expected = { | ||||||
|  |       'empire': { | ||||||
|  |         'id': 'RmFjdGlvbjoy', | ||||||
|  |         'name': 'Galactic Empire' | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     result = Schema.execute(query) | ||||||
|  |     assert result.errors == None | ||||||
|  |     assert result.data == expected | ||||||
|  | 
 | ||||||
|  | def test_correctly_refetches_empire(): | ||||||
|  |     query = ''' | ||||||
|  |       query EmpireRefetchQuery { | ||||||
|  |         node(id: "RmFjdGlvbjoy") { | ||||||
|  |           id | ||||||
|  |           ... on Faction { | ||||||
|  |             name | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     ''' | ||||||
|  |     expected = { | ||||||
|  |       'node': { | ||||||
|  |         'id': 'RmFjdGlvbjoy', | ||||||
|  |         'name': 'Galactic Empire' | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     result = Schema.execute(query) | ||||||
|  |     assert result.errors == None | ||||||
|  |     assert result.data == expected | ||||||
|  | 
 | ||||||
|  | def test_correctly_refetches_xwing(): | ||||||
|  |     query = ''' | ||||||
|  |       query XWingRefetchQuery { | ||||||
|  |         node(id: "U2hpcDox") { | ||||||
|  |           id | ||||||
|  |           ... on Ship { | ||||||
|  |             name | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     ''' | ||||||
|  |     expected = { | ||||||
|  |       'node': { | ||||||
|  |         'id': 'U2hpcDox', | ||||||
|  |         'name': 'X-Wing' | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     result = Schema.execute(query) | ||||||
|  |     assert result.errors == None | ||||||
|  |     assert result.data == expected | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user