mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-02 12:44:15 +03:00
First relay version
This commit is contained in:
parent
f2bc5eddd5
commit
9a84d595a1
|
@ -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
|
||||||
|
|
|
@ -26,3 +26,7 @@ from graphene.core.types import (
|
||||||
from graphene.decorators import (
|
from graphene.decorators import (
|
||||||
resolve_only_args
|
resolve_only_args
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from graphene.relay import (
|
||||||
|
Relay
|
||||||
|
)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from graphene.utils import cached_property
|
from graphene.utils import cached_property
|
||||||
|
|
||||||
DEFAULT_NAMES = ('description', 'name', 'interface', 'type_name', 'interfaces', 'proxy')
|
DEFAULT_NAMES = ('description', 'name', 'interface',
|
||||||
|
'type_name', 'interfaces', 'proxy')
|
||||||
|
|
||||||
|
|
||||||
class Options(object):
|
class Options(object):
|
||||||
def __init__(self, meta=None):
|
def __init__(self, meta=None):
|
||||||
|
@ -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)
|
||||||
|
@ -51,7 +56,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 +66,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 +82,17 @@ 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 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 +115,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 +136,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,
|
||||||
|
|
30
graphene/core/utils.py
Normal file
30
graphene/core/utils.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
from graphene.core.types import ObjectType
|
||||||
|
from graphene import signals
|
||||||
|
|
||||||
|
registered_object_types = []
|
||||||
|
|
||||||
|
|
||||||
|
def get_object_type(field_type, object_type=None):
|
||||||
|
_is_class = inspect.isclass(field_type)
|
||||||
|
if _is_class and issubclass(field_type, ObjectType):
|
||||||
|
field_type = field_type._meta.type
|
||||||
|
elif isinstance(field_type, basestring):
|
||||||
|
if field_type == 'self':
|
||||||
|
field_type = object_type._meta.type
|
||||||
|
else:
|
||||||
|
object_type = get_registered_object_type(field_type)
|
||||||
|
field_type = object_type._meta.type
|
||||||
|
return field_type
|
||||||
|
|
||||||
|
|
||||||
|
def get_registered_object_type(name):
|
||||||
|
for object_type in registered_object_types:
|
||||||
|
if object_type._meta.type_name == name:
|
||||||
|
return object_type
|
||||||
|
return None
|
||||||
|
|
||||||
|
@signals.class_prepared.connect
|
||||||
|
def object_type_created(sender):
|
||||||
|
registered_object_types.append(sender)
|
|
@ -0,0 +1,2 @@
|
||||||
|
class Relay(object):
|
||||||
|
pass
|
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'
|
||||||
],
|
],
|
||||||
|
|
|
@ -18,7 +18,7 @@ def wrap_character(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('Character')
|
||||||
appearsIn = graphene.ListField(Episode)
|
appearsIn = graphene.ListField(Episode)
|
||||||
|
|
||||||
def resolve_friends(self, args, *_):
|
def resolve_friends(self, args, *_):
|
||||||
|
|
61
tests/starwars_relay/schema.py
Normal file
61
tests/starwars_relay/schema.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import graphene
|
||||||
|
from graphene import resolve_only_args
|
||||||
|
|
||||||
|
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):
|
||||||
|
id = graphene.IDField()
|
||||||
|
name = graphene.StringField()
|
||||||
|
friends = graphene.ListField('self')
|
||||||
|
appearsIn = graphene.ListField(Episode)
|
||||||
|
|
||||||
|
def resolve_friends(self, args, *_):
|
||||||
|
return [wrap_character(getCharacter(f)) for f in self.instance.friends]
|
||||||
|
|
||||||
|
class Human(Character):
|
||||||
|
homePlanet = graphene.StringField()
|
||||||
|
|
||||||
|
|
||||||
|
class Droid(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)
|
||||||
|
)
|
||||||
|
|
||||||
|
@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))
|
||||||
|
if human:
|
||||||
|
return Human(human)
|
||||||
|
|
||||||
|
@resolve_only_args
|
||||||
|
def resolve_droid(self, id):
|
||||||
|
return wrap_character(getDroid(id))
|
||||||
|
|
||||||
|
|
||||||
|
Schema = graphene.Schema(query=Query)
|
Loading…
Reference in New Issue
Block a user