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: | ||||
| - 2.7 | ||||
| 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 graphql-relay | ||||
| - python setup.py develop | ||||
|  |  | |||
							
								
								
									
										56
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								README.md
									
									
									
									
									
								
							|  | @ -77,6 +77,62 @@ 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 | ||||
| 
 | ||||
| After cloning this repo, ensure dependencies are installed by running: | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| from graphql.core.type import ( | ||||
|     GraphQLEnumType as Enum, | ||||
|     GraphQLArgument as Argument, | ||||
|     # GraphQLSchema as Schema, | ||||
|     GraphQLString as String, | ||||
|     GraphQLInt as Int, | ||||
|     GraphQLID as ID | ||||
|  |  | |||
|  | @ -1,4 +1,3 @@ | |||
| import inspect | ||||
| from graphql.core.type import ( | ||||
|     GraphQLField, | ||||
|     GraphQLList, | ||||
|  | @ -9,8 +8,8 @@ from graphql.core.type import ( | |||
|     GraphQLID, | ||||
|     GraphQLArgument, | ||||
| ) | ||||
| from graphene.core.types import ObjectType, Interface | ||||
| from graphene.utils import cached_property | ||||
| from graphene.core.utils import get_object_type | ||||
| 
 | ||||
| class Field(object): | ||||
|     def __init__(self, field_type, resolve=None, null=True, args=None, description='', **extra_args): | ||||
|  | @ -45,16 +44,11 @@ class Field(object): | |||
| 
 | ||||
|     @cached_property | ||||
|     def type(self): | ||||
|         field_type = self.field_type | ||||
|         _is_class = inspect.isclass(field_type) | ||||
|         if _is_class and issubclass(field_type, ObjectType): | ||||
|             field_type = field_type._meta.type | ||||
|         elif isinstance(field_type, Field): | ||||
|             field_type = field_type.type | ||||
|         elif field_type == 'self': | ||||
|             field_type = self.object_type._meta.type | ||||
|         if isinstance(self.field_type, Field): | ||||
|             field_type = self.field_type.type | ||||
|         else: | ||||
|             field_type = get_object_type(self.field_type, self.object_type) | ||||
|         field_type = self.type_wrapper(field_type) | ||||
| 
 | ||||
|         return field_type | ||||
| 
 | ||||
|     def type_wrapper(self, field_type): | ||||
|  | @ -105,6 +99,12 @@ class Field(object): | |||
|         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): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(TypeField, self).__init__(self.field_type, *args, **kwargs) | ||||
|  |  | |||
|  | @ -1,15 +1,18 @@ | |||
| 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): | ||||
|     def __init__(self, meta=None): | ||||
|     def __init__(self, meta=None, app_label=None): | ||||
|         self.meta = meta | ||||
|         self.local_fields = [] | ||||
|         self.interface = False | ||||
|         self.proxy = False | ||||
|         self.interfaces = [] | ||||
|         self.parents = [] | ||||
|         self.app_label = app_label | ||||
| 
 | ||||
|     def contribute_to_class(self, cls, name): | ||||
|         cls._meta = self | ||||
|  | @ -51,7 +54,6 @@ class Options(object): | |||
| 
 | ||||
|     def add_field(self, field): | ||||
|         self.local_fields.append(field) | ||||
|         setattr(self.parent, field.field_name, field) | ||||
| 
 | ||||
|     @cached_property | ||||
|     def fields(self): | ||||
|  |  | |||
|  | @ -8,8 +8,10 @@ from graphql.core.type import ( | |||
| ) | ||||
| from graphql.core import graphql | ||||
| 
 | ||||
| from graphene import signals | ||||
| from graphene.core.options import Options | ||||
| 
 | ||||
| 
 | ||||
| class ObjectTypeMeta(type): | ||||
|     def __new__(cls, name, bases, attrs): | ||||
|         super_new = super(ObjectTypeMeta, cls).__new__ | ||||
|  | @ -20,7 +22,10 @@ class ObjectTypeMeta(type): | |||
| 
 | ||||
|         module = attrs.pop('__module__') | ||||
|         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) | ||||
|         if not attr_meta: | ||||
|             meta = getattr(new_class, 'Meta', None) | ||||
|  | @ -28,7 +33,12 @@ class ObjectTypeMeta(type): | |||
|             meta = attr_meta | ||||
|         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: | ||||
|             new_class._meta.interface = base_meta.interface | ||||
|         # Add all attributes to the class. | ||||
|  | @ -51,7 +61,7 @@ class ObjectTypeMeta(type): | |||
|             # moment). | ||||
|             for field in parent_fields: | ||||
|                 if field.field_name in field_names: | ||||
|                     raise FieldError( | ||||
|                     raise Exception( | ||||
|                         'Local field %r in class %r clashes ' | ||||
|                         'with field of similar name from ' | ||||
|                         '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.parents.extend(base._meta.parents) | ||||
| 
 | ||||
|         new_class._prepare() | ||||
|         return new_class | ||||
| 
 | ||||
|     def _prepare(cls): | ||||
|         signals.class_prepared.send(cls) | ||||
| 
 | ||||
|     def add_to_class(cls, name, value): | ||||
|         # We should call the contribute_to_class method only if it's bound | ||||
|         if not inspect.isclass(value) and hasattr(value, 'contribute_to_class'): | ||||
|  | @ -73,7 +87,13 @@ class ObjectTypeMeta(type): | |||
| 
 | ||||
| class ObjectType(six.with_metaclass(ObjectTypeMeta)): | ||||
|     def __init__(self, instance=None): | ||||
|         signals.pre_init.send(self.__class__, 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): | ||||
|         return getattr(self.instance, field, None) | ||||
|  |  | |||
							
								
								
									
										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=[ | ||||
|         'six', | ||||
|         'blinker', | ||||
|         'graphqllib', | ||||
|         'graphql-relay' | ||||
|     ], | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ def test_interface(): | |||
|     assert Character._meta.type_name == 'Character' | ||||
|     assert isinstance(object_type, GraphQLInterfaceType) | ||||
|     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(): | ||||
|     object_type = Human._meta.type | ||||
|  | @ -37,5 +37,5 @@ def test_object_type(): | |||
|     assert Human._meta.type_name == 'Human' | ||||
|     assert isinstance(object_type, GraphQLObjectType) | ||||
|     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] | ||||
|  |  | |||
							
								
								
									
										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 | ||||
| )) | ||||
| 
 | ||||
| 
 | ||||
| def wrap_character(character): | ||||
|     if isinstance(character, _Human):  | ||||
|         return Human(character) | ||||
|     elif isinstance(character, _Droid): | ||||
|         return Droid(character) | ||||
| 
 | ||||
| 
 | ||||
| class Character(graphene.Interface): | ||||
|     id = graphene.IDField() | ||||
|     name = graphene.StringField() | ||||
|  | @ -24,6 +26,7 @@ class Character(graphene.Interface): | |||
|     def resolve_friends(self, args, *_): | ||||
|         return [wrap_character(getCharacter(f)) for f in self.instance.friends] | ||||
| 
 | ||||
| 
 | ||||
| class Human(Character): | ||||
|     homePlanet = graphene.StringField() | ||||
| 
 | ||||
|  | @ -50,8 +53,6 @@ class Query(graphene.ObjectType): | |||
|     @resolve_only_args | ||||
|     def resolve_human(self, id): | ||||
|         return wrap_character(getHuman(id)) | ||||
|         if human: | ||||
|             return Human(human) | ||||
| 
 | ||||
|     @resolve_only_args | ||||
|     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