mirror of
https://github.com/graphql-python/graphene.git
synced 2024-12-01 14:03:58 +03:00
Improved types as containers
This commit is contained in:
parent
2958cc18af
commit
129999d41a
72
README.md
72
README.md
|
@ -1,10 +1,10 @@
|
||||||
# ![Graphene Logo](http://graphene-python.org/favicon.png) [Graphene](http://graphene-python.org) [![Build Status](https://travis-ci.org/graphql-python/graphene.svg?branch=master)](https://travis-ci.org/graphql-python/graphene) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene?branch=master)
|
# ![Graphene Logo](http://graphene-python.org/favicon.png) [Graphene](http://graphene-python.org) [![Build Status](https://travis-ci.org/graphql-python/graphene.svg?branch=master)](https://travis-ci.org/graphql-python/graphene) [![PyPI version](https://badge.fury.io/py/graphene.svg)](https://badge.fury.io/py/graphene) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene?branch=master)
|
||||||
|
|
||||||
|
|
||||||
Graphene is a Python library for building GraphQL schemas/types fast and easily.
|
Graphene is a Python library for building GraphQL schemas/types fast and easily.
|
||||||
* **Easy to use:** It maps the models/fields to internal GraphQL objects without effort.
|
* **Easy to use:** It maps the models/fields to internal GraphQL objects without effort.
|
||||||
* **Relay:** Graphene has builtin support for Relay
|
* **Relay:** Graphene has builtin support for Relay
|
||||||
* **Django:** Automatic [Django models](#djangorelay-schema) conversion. *See an [example Django](http://github.com/graphql-python/swapi-graphene) implementation*
|
* **Django:** Automatic *Django model* mapping to Graphene Types. *See an [example Django](http://github.com/graphql-python/swapi-graphene) implementation*
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
@ -16,26 +16,21 @@ pip install graphene
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Examples
|
||||||
|
|
||||||
Example code of a GraphQL schema using Graphene:
|
Here is one example for get you started:
|
||||||
|
|
||||||
### Schema definition
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class Character(graphene.Interface):
|
|
||||||
id = graphene.IDField()
|
|
||||||
name = graphene.StringField()
|
|
||||||
friends = graphene.ListField('self')
|
|
||||||
|
|
||||||
def resolve_friends(self, args, *_):
|
|
||||||
return [Human(f) for f in self.instance.friends]
|
|
||||||
|
|
||||||
class Human(Character):
|
|
||||||
homePlanet = graphene.StringField()
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
human = graphene.Field(Human)
|
hello = graphene.StringField(description='A typical hello world')
|
||||||
|
ping = graphene.StringField(description='Ping someone',
|
||||||
|
to=graphene.Argument(graphene.String))
|
||||||
|
|
||||||
|
def resolve_hello(self, args, info):
|
||||||
|
return 'World'
|
||||||
|
|
||||||
|
def resolve_ping(self, args, info):
|
||||||
|
return 'Pinging {}'.format(args.get('to'))
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query)
|
schema = graphene.Schema(query=Query)
|
||||||
```
|
```
|
||||||
|
@ -44,48 +39,19 @@ Then Querying `graphene.Schema` is as simple as:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
query = '''
|
query = '''
|
||||||
query HeroNameQuery {
|
query SayHello {
|
||||||
hero {
|
hello
|
||||||
name
|
ping(to:'peter')
|
||||||
}
|
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
result = schema.execute(query)
|
result = schema.execute(query)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Relay Schema
|
If you want to learn even more, you can also check the following examples:
|
||||||
|
|
||||||
Graphene also supports Relay, check the [Starwars Relay example](tests/starwars_relay)!
|
* Relay Schema: [Starwars Relay example](tests/starwars_relay)
|
||||||
|
* Django: [Starwars Django example](tests/starwars_django)
|
||||||
|
|
||||||
```python
|
|
||||||
class Ship(relay.Node):
|
|
||||||
name = graphene.StringField()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_node(cls, id):
|
|
||||||
return Ship(your_ship_instance)
|
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
ships = relay.ConnectionField(Ship)
|
|
||||||
node = relay.NodeField()
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### Django+Relay Schema
|
|
||||||
|
|
||||||
If you want to use graphene with your Django Models check the [Starwars Django example](tests/starwars_django)!
|
|
||||||
|
|
||||||
```python
|
|
||||||
class Ship(DjangoNode):
|
|
||||||
class Meta:
|
|
||||||
model = YourDjangoModelHere
|
|
||||||
# only_fields = ('id', 'name') # Only map this fields from the model
|
|
||||||
# exclude_fields ('field_to_exclude', ) # Exclude mapping this fields from the model
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
|
||||||
node = relay.NodeField()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -37,11 +37,20 @@ class DjangoObjectTypeMeta(ObjectTypeMeta):
|
||||||
cls.add_to_class(field.name, converted_field)
|
cls.add_to_class(field.name, converted_field)
|
||||||
|
|
||||||
|
|
||||||
class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, BaseObjectType)):
|
class InstanceObjectType(BaseObjectType):
|
||||||
|
def __init__(self, instance=None):
|
||||||
|
self.instance = instance
|
||||||
|
super(InstanceObjectType, self).__init__()
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.instance, attr)
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, BaseObjectType)):
|
class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class Field(object):
|
||||||
creation_counter = 0
|
creation_counter = 0
|
||||||
required = False
|
required = False
|
||||||
|
|
||||||
def __init__(self, field_type, name=None, resolve=None, required=False, args=None, description='', **extra_args):
|
def __init__(self, field_type, name=None, resolve=None, required=False, args=None, description='', default=None, **extra_args):
|
||||||
self.field_type = field_type
|
self.field_type = field_type
|
||||||
self.resolve_fn = resolve
|
self.resolve_fn = resolve
|
||||||
self.required = self.required or required
|
self.required = self.required or required
|
||||||
|
@ -38,9 +38,13 @@ class Field(object):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.description = description or self.__doc__
|
self.description = description or self.__doc__
|
||||||
self.object_type = None
|
self.object_type = None
|
||||||
|
self.default = default
|
||||||
self.creation_counter = Field.creation_counter
|
self.creation_counter = Field.creation_counter
|
||||||
Field.creation_counter += 1
|
Field.creation_counter += 1
|
||||||
|
|
||||||
|
def get_default(self):
|
||||||
|
return self.default
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name, add=True):
|
def contribute_to_class(self, cls, name, add=True):
|
||||||
if not self.name:
|
if not self.name:
|
||||||
self.name = to_camel_case(name)
|
self.name = to_camel_case(name)
|
||||||
|
@ -57,7 +61,7 @@ class Field(object):
|
||||||
if resolve_fn:
|
if resolve_fn:
|
||||||
return resolve_fn(instance, args, info)
|
return resolve_fn(instance, args, info)
|
||||||
else:
|
else:
|
||||||
return getattr(instance, self.field_name, None)
|
return getattr(instance, self.field_name, self.get_default())
|
||||||
|
|
||||||
def get_resolve_fn(self, schema):
|
def get_resolve_fn(self, schema):
|
||||||
object_type = self.get_object_type(schema)
|
object_type = self.get_object_type(schema)
|
||||||
|
|
|
@ -129,29 +129,48 @@ class ObjectTypeMeta(type):
|
||||||
|
|
||||||
class BaseObjectType(object):
|
class BaseObjectType(object):
|
||||||
|
|
||||||
def __new__(cls, instance=None, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if cls._meta.is_interface:
|
if cls._meta.is_interface:
|
||||||
raise Exception("An interface cannot be initialized")
|
raise Exception("An interface cannot be initialized")
|
||||||
if instance is None:
|
if not args and not kwargs:
|
||||||
if not kwargs:
|
return None
|
||||||
return None
|
|
||||||
elif type(instance) is cls:
|
|
||||||
return instance
|
|
||||||
|
|
||||||
return super(BaseObjectType, cls).__new__(cls)
|
return super(BaseObjectType, cls).__new__(cls)
|
||||||
|
|
||||||
def __init__(self, instance=None, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
signals.pre_init.send(self.__class__, instance=instance)
|
signals.pre_init.send(self.__class__, args=args, kwargs=kwargs)
|
||||||
assert instance or kwargs
|
args_len = len(args)
|
||||||
if not instance:
|
fields = self._meta.fields
|
||||||
init_kwargs = dict({k: None for k in self._meta.fields_map.keys()}, **kwargs)
|
if args_len > len(fields):
|
||||||
instance = self._meta.object(**init_kwargs)
|
# Daft, but matches old exception sans the err msg.
|
||||||
self.instance = instance
|
raise IndexError("Number of args exceeds number of fields")
|
||||||
signals.post_init.send(self.__class__, instance=self)
|
fields_iter = iter(fields)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
if not kwargs:
|
||||||
if self.instance:
|
for val, field in zip(args, fields_iter):
|
||||||
return getattr(self.instance, name)
|
setattr(self, field.field_name, val)
|
||||||
|
else:
|
||||||
|
for val, field in zip(args, fields_iter):
|
||||||
|
setattr(self, field.field_name, val)
|
||||||
|
kwargs.pop(field.field_name, None)
|
||||||
|
|
||||||
|
for field in fields_iter:
|
||||||
|
try:
|
||||||
|
val = kwargs.pop(field.field_name)
|
||||||
|
setattr(self, field.field_name, val)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if kwargs:
|
||||||
|
for prop in list(kwargs):
|
||||||
|
try:
|
||||||
|
if isinstance(getattr(self.__class__, prop), property):
|
||||||
|
setattr(self, prop, kwargs.pop(prop))
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
if kwargs:
|
||||||
|
raise TypeError("'%s' is an invalid keyword argument for this function" % list(kwargs)[0])
|
||||||
|
|
||||||
|
signals.post_init.send(self.__class__, instance=self)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fields_as_arguments(cls, schema):
|
def fields_as_arguments(cls, schema):
|
||||||
|
|
|
@ -32,21 +32,3 @@ def test_node_should_have_same_connection_always():
|
||||||
|
|
||||||
def test_node_should_have_id_field():
|
def test_node_should_have_id_field():
|
||||||
assert 'id' in OtherNode._meta.fields_map
|
assert 'id' in OtherNode._meta.fields_map
|
||||||
|
|
||||||
|
|
||||||
# def test_field_no_contributed_raises_error():
|
|
||||||
# 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)
|
|
||||||
# @classmethod
|
|
||||||
# def get_node(cls):
|
|
||||||
# pass
|
|
||||||
# class Meta:
|
|
||||||
# schema = schema
|
|
||||||
# assert 'same type_name' in str(excinfo.value)
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
from pytest import raises
|
|
||||||
from graphql.core.type import (
|
from graphql.core.type import (
|
||||||
GraphQLNonNull,
|
GraphQLNonNull,
|
||||||
GraphQLID
|
GraphQLID
|
||||||
|
@ -10,11 +9,6 @@ from graphene import relay
|
||||||
schema = graphene.Schema()
|
schema = graphene.Schema()
|
||||||
|
|
||||||
|
|
||||||
class MyType(object):
|
|
||||||
name = 'my'
|
|
||||||
arg = None
|
|
||||||
|
|
||||||
|
|
||||||
class MyConnection(relay.Connection):
|
class MyConnection(relay.Connection):
|
||||||
my_custom_field = graphene.StringField(resolve=lambda instance, *_: 'Custom')
|
my_custom_field = graphene.StringField(resolve=lambda instance, *_: 'Custom')
|
||||||
|
|
||||||
|
@ -24,7 +18,7 @@ class MyNode(relay.Node):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node(cls, id):
|
def get_node(cls, id):
|
||||||
return MyNode(MyType())
|
return MyNode(name='mo')
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
|
@ -32,10 +26,9 @@ class Query(graphene.ObjectType):
|
||||||
all_my_nodes = relay.ConnectionField(MyNode, connection_type=MyConnection, customArg=graphene.Argument(graphene.String))
|
all_my_nodes = relay.ConnectionField(MyNode, connection_type=MyConnection, customArg=graphene.Argument(graphene.String))
|
||||||
|
|
||||||
def resolve_all_my_nodes(self, args, info):
|
def resolve_all_my_nodes(self, args, info):
|
||||||
t = MyType()
|
|
||||||
custom_arg = args.get('customArg')
|
custom_arg = args.get('customArg')
|
||||||
assert custom_arg == "1"
|
assert custom_arg == "1"
|
||||||
return [MyNode(t)]
|
return [MyNode(name='my')]
|
||||||
|
|
||||||
schema.query = Query
|
schema.query = Query
|
||||||
|
|
||||||
|
@ -61,7 +54,7 @@ def test_nodefield_query():
|
||||||
'''
|
'''
|
||||||
expected = {
|
expected = {
|
||||||
'myNode': {
|
'myNode': {
|
||||||
'name': 'my'
|
'name': 'mo'
|
||||||
},
|
},
|
||||||
'allMyNodes': {
|
'allMyNodes': {
|
||||||
'edges': [{
|
'edges': [{
|
||||||
|
|
|
@ -1,77 +1,78 @@
|
||||||
from collections import namedtuple
|
humanData = {}
|
||||||
|
droidData = {}
|
||||||
|
|
||||||
Human = namedtuple('Human', 'id name friends appearsIn homePlanet')
|
|
||||||
|
|
||||||
luke = Human(
|
def setup():
|
||||||
id='1000',
|
from .schema import Human, Droid
|
||||||
name='Luke Skywalker',
|
global humanData, droidData
|
||||||
friends=['1002', '1003', '2000', '2001'],
|
luke = Human(
|
||||||
appearsIn=[4, 5, 6],
|
id='1000',
|
||||||
homePlanet='Tatooine',
|
name='Luke Skywalker',
|
||||||
)
|
friends=['1002', '1003', '2000', '2001'],
|
||||||
|
appears_in=[4, 5, 6],
|
||||||
|
home_planet='Tatooine',
|
||||||
|
)
|
||||||
|
|
||||||
vader = Human(
|
vader = Human(
|
||||||
id='1001',
|
id='1001',
|
||||||
name='Darth Vader',
|
name='Darth Vader',
|
||||||
friends=['1004'],
|
friends=['1004'],
|
||||||
appearsIn=[4, 5, 6],
|
appears_in=[4, 5, 6],
|
||||||
homePlanet='Tatooine',
|
home_planet='Tatooine',
|
||||||
)
|
)
|
||||||
|
|
||||||
han = Human(
|
han = Human(
|
||||||
id='1002',
|
id='1002',
|
||||||
name='Han Solo',
|
name='Han Solo',
|
||||||
friends=['1000', '1003', '2001'],
|
friends=['1000', '1003', '2001'],
|
||||||
appearsIn=[4, 5, 6],
|
appears_in=[4, 5, 6],
|
||||||
homePlanet=None,
|
home_planet=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
leia = Human(
|
leia = Human(
|
||||||
id='1003',
|
id='1003',
|
||||||
name='Leia Organa',
|
name='Leia Organa',
|
||||||
friends=['1000', '1002', '2000', '2001'],
|
friends=['1000', '1002', '2000', '2001'],
|
||||||
appearsIn=[4, 5, 6],
|
appears_in=[4, 5, 6],
|
||||||
homePlanet='Alderaan',
|
home_planet='Alderaan',
|
||||||
)
|
)
|
||||||
|
|
||||||
tarkin = Human(
|
tarkin = Human(
|
||||||
id='1004',
|
id='1004',
|
||||||
name='Wilhuff Tarkin',
|
name='Wilhuff Tarkin',
|
||||||
friends=['1001'],
|
friends=['1001'],
|
||||||
appearsIn=[4],
|
appears_in=[4],
|
||||||
homePlanet=None,
|
home_planet=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
humanData = {
|
humanData = {
|
||||||
'1000': luke,
|
'1000': luke,
|
||||||
'1001': vader,
|
'1001': vader,
|
||||||
'1002': han,
|
'1002': han,
|
||||||
'1003': leia,
|
'1003': leia,
|
||||||
'1004': tarkin,
|
'1004': tarkin,
|
||||||
}
|
}
|
||||||
|
|
||||||
Droid = namedtuple('Droid', 'id name friends appearsIn primaryFunction')
|
threepio = Droid(
|
||||||
|
id='2000',
|
||||||
|
name='C-3PO',
|
||||||
|
friends=['1000', '1002', '1003', '2001'],
|
||||||
|
appears_in=[4, 5, 6],
|
||||||
|
primary_function='Protocol',
|
||||||
|
)
|
||||||
|
|
||||||
threepio = Droid(
|
artoo = Droid(
|
||||||
id='2000',
|
id='2001',
|
||||||
name='C-3PO',
|
name='R2-D2',
|
||||||
friends=['1000', '1002', '1003', '2001'],
|
friends=['1000', '1002', '1003'],
|
||||||
appearsIn=[4, 5, 6],
|
appears_in=[4, 5, 6],
|
||||||
primaryFunction='Protocol',
|
primary_function='Astromech',
|
||||||
)
|
)
|
||||||
|
|
||||||
artoo = Droid(
|
droidData = {
|
||||||
id='2001',
|
'2000': threepio,
|
||||||
name='R2-D2',
|
'2001': artoo,
|
||||||
friends=['1000', '1002', '1003'],
|
}
|
||||||
appearsIn=[4, 5, 6],
|
|
||||||
primaryFunction='Astromech',
|
|
||||||
)
|
|
||||||
|
|
||||||
droidData = {
|
|
||||||
'2000': threepio,
|
|
||||||
'2001': artoo,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def getCharacter(id):
|
def getCharacter(id):
|
||||||
|
@ -84,8 +85,8 @@ def getFriends(character):
|
||||||
|
|
||||||
def getHero(episode):
|
def getHero(episode):
|
||||||
if episode == 5:
|
if episode == 5:
|
||||||
return luke
|
return humanData['1000']
|
||||||
return artoo
|
return droidData['2001']
|
||||||
|
|
||||||
|
|
||||||
def getHuman(id):
|
def getHuman(id):
|
||||||
|
|
|
@ -2,7 +2,7 @@ from graphql.core.type import GraphQLEnumValue
|
||||||
import graphene
|
import graphene
|
||||||
from graphene import resolve_only_args
|
from graphene import resolve_only_args
|
||||||
|
|
||||||
from .data import getHero, getHuman, getCharacter, getDroid, Human as _Human, Droid as _Droid
|
from .data import getHero, getHuman, getCharacter, getDroid
|
||||||
|
|
||||||
Episode = graphene.Enum('Episode', dict(
|
Episode = graphene.Enum('Episode', dict(
|
||||||
NEWHOPE=GraphQLEnumValue(4),
|
NEWHOPE=GraphQLEnumValue(4),
|
||||||
|
@ -11,29 +11,23 @@ Episode = graphene.Enum('Episode', dict(
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
def wrap_character(character):
|
|
||||||
if isinstance(character, _Human):
|
|
||||||
return Human(character)
|
|
||||||
elif isinstance(character, _Droid):
|
|
||||||
return Droid(character)
|
|
||||||
|
|
||||||
|
|
||||||
class Character(graphene.Interface):
|
class Character(graphene.Interface):
|
||||||
id = graphene.IDField()
|
id = graphene.IDField()
|
||||||
name = graphene.StringField()
|
name = graphene.StringField()
|
||||||
friends = graphene.ListField('self')
|
friends = graphene.ListField('self')
|
||||||
appearsIn = graphene.ListField(Episode)
|
appears_in = graphene.ListField(Episode)
|
||||||
|
|
||||||
def resolve_friends(self, args, *_):
|
def resolve_friends(self, args, *_):
|
||||||
return [wrap_character(getCharacter(f)) for f in self.instance.friends]
|
# The character friends is a list of strings
|
||||||
|
return [getCharacter(f) for f in self.friends]
|
||||||
|
|
||||||
|
|
||||||
class Human(Character):
|
class Human(Character):
|
||||||
homePlanet = graphene.StringField()
|
home_planet = graphene.StringField()
|
||||||
|
|
||||||
|
|
||||||
class Droid(Character):
|
class Droid(Character):
|
||||||
primaryFunction = graphene.StringField()
|
primary_function = graphene.StringField()
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
|
@ -52,15 +46,15 @@ class Query(graphene.ObjectType):
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_hero(self, episode=None):
|
def resolve_hero(self, episode=None):
|
||||||
return wrap_character(getHero(episode))
|
return getHero(episode)
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_human(self, id):
|
def resolve_human(self, id):
|
||||||
return wrap_character(getHuman(id))
|
return getHuman(id)
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_droid(self, id):
|
def resolve_droid(self, id):
|
||||||
return wrap_character(getDroid(id))
|
return getDroid(id)
|
||||||
|
|
||||||
|
|
||||||
Schema = graphene.Schema(query=Query)
|
Schema = graphene.Schema(query=Query)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from .schema import Schema, Query
|
from .schema import Schema, Query
|
||||||
from graphql.core import graphql
|
from graphql.core import graphql
|
||||||
|
from .data import setup
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
||||||
def test_hero_name_query():
|
def test_hero_name_query():
|
||||||
query = '''
|
query = '''
|
||||||
|
|
|
@ -27,7 +27,7 @@ class Ship(DjangoNode):
|
||||||
|
|
||||||
|
|
||||||
@schema.register
|
@schema.register
|
||||||
class CharacterModel(DjangoObjectType):
|
class Character(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CharacterModel
|
model = CharacterModel
|
||||||
|
|
||||||
|
|
|
@ -1,78 +1,80 @@
|
||||||
from collections import namedtuple
|
data = {}
|
||||||
|
|
||||||
Ship = namedtuple('Ship', ['id', 'name'])
|
|
||||||
Faction = namedtuple('Faction', ['id', 'name', 'ships'])
|
|
||||||
|
|
||||||
xwing = Ship(
|
def setup():
|
||||||
id='1',
|
global data
|
||||||
name='X-Wing',
|
|
||||||
)
|
|
||||||
|
|
||||||
ywing = Ship(
|
from .schema import Ship, Faction
|
||||||
id='2',
|
xwing = Ship(
|
||||||
name='Y-Wing',
|
id='1',
|
||||||
)
|
name='X-Wing',
|
||||||
|
)
|
||||||
|
|
||||||
awing = Ship(
|
ywing = Ship(
|
||||||
id='3',
|
id='2',
|
||||||
name='A-Wing',
|
name='Y-Wing',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Yeah, technically it's Corellian. But it flew in the service of the rebels,
|
awing = Ship(
|
||||||
# so for the purposes of this demo it's a rebel ship.
|
id='3',
|
||||||
falcon = Ship(
|
name='A-Wing',
|
||||||
id='4',
|
)
|
||||||
name='Millenium Falcon',
|
|
||||||
)
|
|
||||||
|
|
||||||
homeOne = Ship(
|
# Yeah, technically it's Corellian. But it flew in the service of the rebels,
|
||||||
id='5',
|
# so for the purposes of this demo it's a rebel ship.
|
||||||
name='Home One',
|
falcon = Ship(
|
||||||
)
|
id='4',
|
||||||
|
name='Millenium Falcon',
|
||||||
|
)
|
||||||
|
|
||||||
tieFighter = Ship(
|
homeOne = Ship(
|
||||||
id='6',
|
id='5',
|
||||||
name='TIE Fighter',
|
name='Home One',
|
||||||
)
|
)
|
||||||
|
|
||||||
tieInterceptor = Ship(
|
tieFighter = Ship(
|
||||||
id='7',
|
id='6',
|
||||||
name='TIE Interceptor',
|
name='TIE Fighter',
|
||||||
)
|
)
|
||||||
|
|
||||||
executor = Ship(
|
tieInterceptor = Ship(
|
||||||
id='8',
|
id='7',
|
||||||
name='Executor',
|
name='TIE Interceptor',
|
||||||
)
|
)
|
||||||
|
|
||||||
rebels = Faction(
|
executor = Ship(
|
||||||
id='1',
|
id='8',
|
||||||
name='Alliance to Restore the Republic',
|
name='Executor',
|
||||||
ships=['1', '2', '3', '4', '5']
|
)
|
||||||
)
|
|
||||||
|
|
||||||
empire = Faction(
|
rebels = Faction(
|
||||||
id='2',
|
id='1',
|
||||||
name='Galactic Empire',
|
name='Alliance to Restore the Republic',
|
||||||
ships=['6', '7', '8']
|
ships=['1', '2', '3', '4', '5']
|
||||||
)
|
)
|
||||||
|
|
||||||
data = {
|
empire = Faction(
|
||||||
'Faction': {
|
id='2',
|
||||||
'1': rebels,
|
name='Galactic Empire',
|
||||||
'2': empire
|
ships=['6', '7', '8']
|
||||||
},
|
)
|
||||||
'Ship': {
|
|
||||||
'1': xwing,
|
data = {
|
||||||
'2': ywing,
|
'Faction': {
|
||||||
'3': awing,
|
'1': rebels,
|
||||||
'4': falcon,
|
'2': empire
|
||||||
'5': homeOne,
|
},
|
||||||
'6': tieFighter,
|
'Ship': {
|
||||||
'7': tieInterceptor,
|
'1': xwing,
|
||||||
'8': executor
|
'2': ywing,
|
||||||
|
'3': awing,
|
||||||
|
'4': falcon,
|
||||||
|
'5': homeOne,
|
||||||
|
'6': tieFighter,
|
||||||
|
'7': tieInterceptor,
|
||||||
|
'8': executor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def createShip(shipName, factionId):
|
def createShip(shipName, factionId):
|
||||||
|
@ -95,8 +97,8 @@ def getFaction(_id):
|
||||||
|
|
||||||
|
|
||||||
def getRebels():
|
def getRebels():
|
||||||
return rebels
|
return getFaction('1')
|
||||||
|
|
||||||
|
|
||||||
def getEmpire():
|
def getEmpire():
|
||||||
return empire
|
return getFaction('2')
|
||||||
|
|
|
@ -12,29 +12,28 @@ schema = graphene.Schema(name='Starwars Relay Schema')
|
||||||
|
|
||||||
|
|
||||||
class Ship(relay.Node):
|
class Ship(relay.Node):
|
||||||
|
|
||||||
'''A ship in the Star Wars saga'''
|
'''A ship in the Star Wars saga'''
|
||||||
name = graphene.StringField(description='The name of the ship.')
|
name = graphene.StringField(description='The name of the ship.')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node(cls, id):
|
def get_node(cls, id):
|
||||||
return Ship(getShip(id))
|
return getShip(id)
|
||||||
|
|
||||||
|
|
||||||
class Faction(relay.Node):
|
class Faction(relay.Node):
|
||||||
|
|
||||||
'''A faction in the Star Wars saga'''
|
'''A faction in the Star Wars saga'''
|
||||||
name = graphene.StringField(description='The name of the faction.')
|
name = graphene.StringField(description='The name of the faction.')
|
||||||
ships = relay.ConnectionField(
|
ships = relay.ConnectionField(
|
||||||
Ship, description='The ships used by the faction.')
|
Ship, description='The ships used by the faction.')
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_ships(self, **kwargs):
|
def resolve_ships(self, **args):
|
||||||
return [Ship(getShip(ship)) for ship in self.instance.ships]
|
# Transform the instance ship_ids into real instances
|
||||||
|
return [getShip(ship_id) for ship_id in self.ships]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node(cls, id):
|
def get_node(cls, id):
|
||||||
return Faction(getFaction(id))
|
return getFaction(id)
|
||||||
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
|
@ -44,11 +43,11 @@ class Query(graphene.ObjectType):
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_rebels(self):
|
def resolve_rebels(self):
|
||||||
return Faction(getRebels())
|
return getRebels()
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_empire(self):
|
def resolve_empire(self):
|
||||||
return Faction(getEmpire())
|
return getEmpire()
|
||||||
|
|
||||||
|
|
||||||
schema.query = Query
|
schema.query = Query
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
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)
|
|
|
@ -1,7 +1,7 @@
|
||||||
from pytest import raises
|
|
||||||
from graphql.core import graphql
|
|
||||||
|
|
||||||
from .schema import schema
|
from .schema import schema
|
||||||
|
from .data import setup
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
||||||
|
|
||||||
def test_correct_fetch_first_ship_rebels():
|
def test_correct_fetch_first_ship_rebels():
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from pytest import raises
|
|
||||||
from graphql.core import graphql
|
|
||||||
|
|
||||||
from .schema import schema
|
from .schema import schema
|
||||||
|
from .data import setup
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
||||||
|
|
||||||
def test_correctly_fetches_id_name_rebels():
|
def test_correctly_fetches_id_name_rebels():
|
||||||
|
|
Loading…
Reference in New Issue
Block a user