mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-11 12:16:58 +03:00
Added List, NonNull types
This commit is contained in:
parent
58dbfefc15
commit
d58d1f8d89
64
graphene/types/argument.py
Normal file
64
graphene/types/argument.py
Normal 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)])
|
|
@ -6,17 +6,18 @@ from graphql.utils.assert_valid_name import assert_valid_name
|
|||
from .objecttype import ObjectType
|
||||
from .interface import Interface
|
||||
from ..utils.orderedtype import OrderedType
|
||||
from .argument import to_arguments
|
||||
|
||||
|
||||
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.attname = None
|
||||
self.parent = None
|
||||
self.type = type
|
||||
self.args = args
|
||||
self.args = to_arguments(args, extra_args)
|
||||
assert not (source and resolver), ('You cannot have a source '
|
||||
'and a resolver at the same time')
|
||||
|
||||
|
@ -24,8 +25,7 @@ class Field(GraphQLField, OrderedType):
|
|||
self.source = source
|
||||
self.deprecation_reason = deprecation_reason
|
||||
self.description = description
|
||||
self._extra_args = extra_args
|
||||
OrderedType.__init__(self)
|
||||
OrderedType.__init__(self, _creation_counter=_creation_counter)
|
||||
|
||||
def contribute_to_class(self, cls, attname):
|
||||
assert issubclass(cls, (ObjectType, Interface)), 'Field {} cannot be mounted in {}'.format(
|
||||
|
|
34
graphene/types/proxy.py
Normal file
34
graphene/types/proxy.py
Normal 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)
|
|
@ -1,8 +1,8 @@
|
|||
import six
|
||||
from graphql import GraphQLScalarType, GraphQLString, GraphQLInt, GraphQLFloat, GraphQLBoolean
|
||||
from graphql import GraphQLString, GraphQLInt, GraphQLFloat, GraphQLBoolean
|
||||
|
||||
from .definitions import ClassTypeMeta, GrapheneScalarType
|
||||
from .field import Field
|
||||
from .proxy import TypeProxy
|
||||
|
||||
|
||||
class ScalarTypeMeta(ClassTypeMeta):
|
||||
|
@ -34,25 +34,10 @@ class ScalarTypeMeta(ClassTypeMeta):
|
|||
return constructed
|
||||
|
||||
|
||||
class Scalar(six.with_metaclass(ScalarTypeMeta)):
|
||||
class Scalar(six.with_metaclass(ScalarTypeMeta, TypeProxy)):
|
||||
class Meta:
|
||||
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):
|
||||
# This is equivalent to
|
||||
|
|
33
graphene/types/structures.py
Normal file
33
graphene/types/structures.py
Normal 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
|
63
graphene/types/tests/test_argument.py
Normal file
63
graphene/types/tests/test_argument.py
Normal 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)
|
|
@ -1,10 +1,12 @@
|
|||
import pytest
|
||||
import copy
|
||||
|
||||
from graphql import GraphQLString, GraphQLField
|
||||
from graphql import GraphQLString, GraphQLField, GraphQLInt
|
||||
|
||||
from ..field import Field
|
||||
from ..argument import Argument
|
||||
from ..objecttype import ObjectType
|
||||
from ..scalars import String, Int
|
||||
|
||||
|
||||
def test_field():
|
||||
|
@ -55,3 +57,22 @@ def test_copy_field_works():
|
|||
def test_field_callable_type():
|
||||
field = Field(lambda: 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
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import pytest
|
||||
from ..scalars import Scalar, String, Int, Float, Boolean
|
||||
from ..scalars import String
|
||||
from ..field import Field
|
||||
from ..objecttype import ObjectType, implements
|
||||
from ..interface import Interface
|
||||
from ..structures import List
|
||||
from ..schema import Schema
|
||||
|
||||
|
||||
class Character(Interface):
|
||||
name = String()
|
||||
friends = List(lambda: Character)
|
||||
|
||||
|
||||
class Pet(ObjectType):
|
||||
|
@ -21,20 +22,23 @@ class Human(ObjectType):
|
|||
def resolve_pet(self, *args):
|
||||
return Pet(type='Dog')
|
||||
|
||||
def resolve_friends(self, *args):
|
||||
return [Human(name='Peter')]
|
||||
|
||||
|
||||
class RootQuery(ObjectType):
|
||||
character = Field(Character)
|
||||
|
||||
def resolve_character(self, *_):
|
||||
return Human(name='Hey!')
|
||||
return Human(name='Harry')
|
||||
|
||||
|
||||
schema = Schema(query=RootQuery, types=[Human])
|
||||
|
||||
|
||||
def test_schema():
|
||||
executed = schema.execute('{ character {name, ...on Human {pet { type } } } }')
|
||||
assert executed.data == {'character': {'name': 'Hey!', 'pet': {'type': 'Dog'}}}
|
||||
executed = schema.execute('{ character {name, friends { name}, ...on Human {pet { type } } } }')
|
||||
assert executed.data == {'character': {'name': 'Harry', 'friends': [{'name': 'Peter'}], 'pet': {'type': 'Dog'}}}
|
||||
|
||||
|
||||
def test_schema_introspect():
|
||||
|
@ -50,10 +54,12 @@ schema {
|
|||
|
||||
interface Character {
|
||||
name: String
|
||||
friends: [Character]
|
||||
}
|
||||
|
||||
type Human implements Character {
|
||||
name: String
|
||||
friends: [Character]
|
||||
pet: Pet
|
||||
}
|
||||
|
||||
|
|
44
graphene/types/tests/test_structures.py
Normal file
44
graphene/types/tests/test_structures.py
Normal 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
|
|
@ -5,8 +5,10 @@ from ..types.scalars import Scalar
|
|||
|
||||
|
||||
def is_graphene_type(_type):
|
||||
return inspect.isclass(_type) and issubclass(_type, (
|
||||
Interface,
|
||||
ObjectType,
|
||||
Scalar
|
||||
))
|
||||
if inspect.isclass(_type):
|
||||
return issubclass(_type, (
|
||||
Interface,
|
||||
ObjectType,
|
||||
Scalar
|
||||
))
|
||||
return is_graphene_type(type(_type))
|
||||
|
|
Loading…
Reference in New Issue
Block a user