Added List, NonNull types

This commit is contained in:
Syrus Akbary 2016-06-03 23:50:12 -07:00
parent 58dbfefc15
commit d58d1f8d89
10 changed files with 286 additions and 34 deletions

View File

@ -0,0 +1,64 @@
import copy
from collections import OrderedDict
import inspect
from itertools import chain
from graphql import GraphQLArgument
from graphql.utils.assert_valid_name import assert_valid_name
from ..utils.orderedtype import OrderedType
class Argument(GraphQLArgument, OrderedType):
__slots__ = ('name', 'type', 'default_value', 'description', 'creation_counter')
def __init__(self, type, default_value=None, description=None, name=None, _creation_counter=None):
self.name = name
self.type = type
self.default_value = default_value
self.description = description
OrderedType.__init__(self, _creation_counter)
@property
def name(self):
return self._name
@name.setter
def name(self, name):
if name is not None:
assert_valid_name(name)
self._name = name
@property
def type(self):
from ..utils.get_graphql_type import get_graphql_type
if inspect.isfunction(self._type):
return get_graphql_type(self._type())
return get_graphql_type(self._type)
@type.setter
def type(self, type):
self._type = type
def to_arguments(*args, **extra):
from .proxy import TypeProxy
args = list(filter(None, args))+[extra]
arguments = []
iter_arguments = chain(*[arg.items() for arg in args])
arguments_names = set()
for default_name, arg in iter_arguments:
if isinstance(arg, TypeProxy):
arg = arg.as_argument()
if not isinstance(arg, GraphQLArgument):
raise ValueError('Unknown argument "{}".'.format(default_name))
arg = copy.copy(arg)
arg.name = arg.name or default_name
assert arg.name, 'All arguments must have a name.'
assert arg.name not in arguments_names, 'More than one Argument have same name "{}".'.format(arg.name)
arguments.append(arg)
arguments_names.add(arg.name)
return OrderedDict([(a.name, a) for a in sorted(arguments)])

View File

@ -6,17 +6,18 @@ from graphql.utils.assert_valid_name import assert_valid_name
from .objecttype import ObjectType from .objecttype import ObjectType
from .interface import Interface from .interface import Interface
from ..utils.orderedtype import OrderedType from ..utils.orderedtype import OrderedType
from .argument import to_arguments
class Field(GraphQLField, OrderedType): class Field(GraphQLField, OrderedType):
__slots__ = ('_name', '_type', '_args', '_resolver', 'deprecation_reason', 'description', 'source', '_extra_args', 'attname', 'parent', 'creation_counter') __slots__ = ('_name', '_type', '_args', '_resolver', 'deprecation_reason', 'description', 'source', 'attname', 'parent', 'creation_counter')
def __init__(self, type, args=None, resolver=None, source=None, deprecation_reason=None, name=None, description=None, **extra_args): def __init__(self, type, args=None, resolver=None, source=None, deprecation_reason=None, name=None, description=None, _creation_counter=None, **extra_args):
self.name = name self.name = name
self.attname = None self.attname = None
self.parent = None self.parent = None
self.type = type self.type = type
self.args = args self.args = to_arguments(args, extra_args)
assert not (source and resolver), ('You cannot have a source ' assert not (source and resolver), ('You cannot have a source '
'and a resolver at the same time') 'and a resolver at the same time')
@ -24,8 +25,7 @@ class Field(GraphQLField, OrderedType):
self.source = source self.source = source
self.deprecation_reason = deprecation_reason self.deprecation_reason = deprecation_reason
self.description = description self.description = description
self._extra_args = extra_args OrderedType.__init__(self, _creation_counter=_creation_counter)
OrderedType.__init__(self)
def contribute_to_class(self, cls, attname): def contribute_to_class(self, cls, attname):
assert issubclass(cls, (ObjectType, Interface)), 'Field {} cannot be mounted in {}'.format( assert issubclass(cls, (ObjectType, Interface)), 'Field {} cannot be mounted in {}'.format(

34
graphene/types/proxy.py Normal file
View File

@ -0,0 +1,34 @@
from .field import Field
from .argument import Argument
from ..utils.orderedtype import OrderedType
class TypeProxy(OrderedType):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
OrderedType.__init__(self)
def get_type(self):
return self._meta.graphql_type
def as_field(self):
return Field(
self.get_type(),
*self.args,
_creation_counter=self.creation_counter,
**self.kwargs
)
def as_argument(self):
return Argument(
self.get_type(),
*self.args,
_creation_counter=self.creation_counter,
**self.kwargs
)
def contribute_to_class(self, cls, attname):
field = self.as_field()
return field.contribute_to_class(cls, attname)

View File

@ -1,8 +1,8 @@
import six import six
from graphql import GraphQLScalarType, GraphQLString, GraphQLInt, GraphQLFloat, GraphQLBoolean from graphql import GraphQLString, GraphQLInt, GraphQLFloat, GraphQLBoolean
from .definitions import ClassTypeMeta, GrapheneScalarType from .definitions import ClassTypeMeta, GrapheneScalarType
from .field import Field from .proxy import TypeProxy
class ScalarTypeMeta(ClassTypeMeta): class ScalarTypeMeta(ClassTypeMeta):
@ -34,25 +34,10 @@ class ScalarTypeMeta(ClassTypeMeta):
return constructed return constructed
class Scalar(six.with_metaclass(ScalarTypeMeta)): class Scalar(six.with_metaclass(ScalarTypeMeta, TypeProxy)):
class Meta: class Meta:
abstract = True abstract = True
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
def as_field(self):
return Field(
lambda: self._meta.graphql_type,
*self.args,
**self.kwargs
)
def contribute_to_class(self, cls, attname):
field = self.as_field()
return field.contribute_to_class(cls, attname)
def construct_scalar_class(graphql_type): def construct_scalar_class(graphql_type):
# This is equivalent to # This is equivalent to

View File

@ -0,0 +1,33 @@
import inspect
from graphql import GraphQLList, GraphQLNonNull
from .proxy import TypeProxy
class Structure(TypeProxy):
def __init__(self, of_type, *args, **kwargs):
super(Structure, self).__init__(*args, **kwargs)
self.of_type = of_type
def get_type(self):
return self
@property
def of_type(self):
from ..utils.get_graphql_type import get_graphql_type
if inspect.isfunction(self._of_type):
return get_graphql_type(self._of_type())
return get_graphql_type(self._of_type)
@of_type.setter
def of_type(self, value):
self._of_type = value
class List(Structure, GraphQLList):
pass
class NonNull(Structure, GraphQLNonNull):
pass

View File

@ -0,0 +1,63 @@
import pytest
import copy
from graphql import GraphQLString, GraphQLArgument
from ..argument import Argument, to_arguments
from ..scalars import String
def test_argument():
argument = Argument(GraphQLString, name="name", description="description")
assert isinstance(argument, GraphQLArgument)
assert argument.name == "name"
assert argument.description == "description"
assert argument.type == GraphQLString
def test_field_wrong_name():
with pytest.raises(AssertionError) as excinfo:
Argument(GraphQLString, name="a field")
assert """Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "a field" does not.""" == str(excinfo.value)
def test_argument_type():
argument = Argument(lambda: GraphQLString)
assert argument.type == GraphQLString
def test_argument_graphene_type():
argument = Argument(String())
assert argument.type == GraphQLString
def test_argument_proxy_graphene_type():
proxy = String()
argument = proxy.as_argument()
assert argument.type == GraphQLString
def test_copy_argument_works():
argument = Argument(GraphQLString)
copy.copy(argument)
def test_to_arguments():
arguments = to_arguments(a=String(), b=Argument(GraphQLString), c=Argument(String()))
assert list(arguments.keys()) == ['a', 'b', 'c']
assert [a.type for a in arguments.values()] == [GraphQLString] * 3
def test_to_arguments_incorrect():
with pytest.raises(ValueError) as excinfo:
to_arguments(incorrect=object())
assert """Unknown argument "incorrect".""" == str(excinfo.value)
def test_to_arguments_no_name():
with pytest.raises(AssertionError) as excinfo:
to_arguments(dict(a=String()), dict(a=String()))
assert """More than one Argument have same name "a".""" == str(excinfo.value)

View File

@ -1,10 +1,12 @@
import pytest import pytest
import copy import copy
from graphql import GraphQLString, GraphQLField from graphql import GraphQLString, GraphQLField, GraphQLInt
from ..field import Field from ..field import Field
from ..argument import Argument
from ..objecttype import ObjectType from ..objecttype import ObjectType
from ..scalars import String, Int
def test_field(): def test_field():
@ -55,3 +57,22 @@ def test_copy_field_works():
def test_field_callable_type(): def test_field_callable_type():
field = Field(lambda: GraphQLString) field = Field(lambda: GraphQLString)
assert field.type == GraphQLString assert field.type == GraphQLString
def test_field_with_arguments():
field = Field(GraphQLString, name="name", description="description", input=Argument(GraphQLString))
assert isinstance(field, GraphQLField)
assert field.name == "name"
assert field.description == "description"
assert 'input' in field.args
assert field.args['input'].type == GraphQLString
def test_field_with_argument_proxies():
field = Field(GraphQLString, name="name", description="description", int=Int(), string=String())
assert isinstance(field, GraphQLField)
assert field.name == "name"
assert field.description == "description"
assert list(field.args.keys()) == ['int', 'string']
assert field.args['string'].type == GraphQLString
assert field.args['int'].type == GraphQLInt

View File

@ -1,13 +1,14 @@
import pytest from ..scalars import String
from ..scalars import Scalar, String, Int, Float, Boolean
from ..field import Field from ..field import Field
from ..objecttype import ObjectType, implements from ..objecttype import ObjectType, implements
from ..interface import Interface from ..interface import Interface
from ..structures import List
from ..schema import Schema from ..schema import Schema
class Character(Interface): class Character(Interface):
name = String() name = String()
friends = List(lambda: Character)
class Pet(ObjectType): class Pet(ObjectType):
@ -21,20 +22,23 @@ class Human(ObjectType):
def resolve_pet(self, *args): def resolve_pet(self, *args):
return Pet(type='Dog') return Pet(type='Dog')
def resolve_friends(self, *args):
return [Human(name='Peter')]
class RootQuery(ObjectType): class RootQuery(ObjectType):
character = Field(Character) character = Field(Character)
def resolve_character(self, *_): def resolve_character(self, *_):
return Human(name='Hey!') return Human(name='Harry')
schema = Schema(query=RootQuery, types=[Human]) schema = Schema(query=RootQuery, types=[Human])
def test_schema(): def test_schema():
executed = schema.execute('{ character {name, ...on Human {pet { type } } } }') executed = schema.execute('{ character {name, friends { name}, ...on Human {pet { type } } } }')
assert executed.data == {'character': {'name': 'Hey!', 'pet': {'type': 'Dog'}}} assert executed.data == {'character': {'name': 'Harry', 'friends': [{'name': 'Peter'}], 'pet': {'type': 'Dog'}}}
def test_schema_introspect(): def test_schema_introspect():
@ -50,10 +54,12 @@ schema {
interface Character { interface Character {
name: String name: String
friends: [Character]
} }
type Human implements Character { type Human implements Character {
name: String name: String
friends: [Character]
pet: Pet pet: Pet
} }

View File

@ -0,0 +1,44 @@
import pytest
from graphql import GraphQLString, GraphQLList, GraphQLNonNull
from ..structures import List, NonNull
from ..scalars import String
def test_list():
list_instance = List(String())
assert isinstance(list_instance, GraphQLList)
assert list_instance.of_type == GraphQLString
def test_list_lambda():
list_instance = List(lambda: String())
assert isinstance(list_instance, GraphQLList)
assert list_instance.of_type == GraphQLString
def test_list_list():
list_instance = List(List(String()))
assert isinstance(list_instance, List)
assert isinstance(list_instance.of_type, GraphQLList)
assert list_instance.of_type.of_type == GraphQLString
def test_nonnull():
list_instance = NonNull(String())
assert isinstance(list_instance, GraphQLNonNull)
assert list_instance.of_type == GraphQLString
def test_nonnull_lambda():
list_instance = NonNull(lambda: String())
assert isinstance(list_instance, GraphQLNonNull)
assert list_instance.of_type == GraphQLString
def test_nonnull_list():
list_instance = NonNull(List(String()))
assert isinstance(list_instance, GraphQLNonNull)
assert isinstance(list_instance.of_type, GraphQLList)
assert list_instance.of_type.of_type == GraphQLString

View File

@ -5,8 +5,10 @@ from ..types.scalars import Scalar
def is_graphene_type(_type): def is_graphene_type(_type):
return inspect.isclass(_type) and issubclass(_type, ( if inspect.isclass(_type):
return issubclass(_type, (
Interface, Interface,
ObjectType, ObjectType,
Scalar Scalar
)) ))
return is_graphene_type(type(_type))