mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-14 21:56:54 +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 .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
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
|
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
|
||||||
|
|
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 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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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):
|
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))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user