First types implementation

This commit is contained in:
Syrus Akbary 2015-11-06 00:07:44 -08:00
parent 2f0bd7cf7c
commit afe8614753
15 changed files with 492 additions and 1 deletions

View File

View File

@ -0,0 +1,42 @@
from itertools import chain
from graphql.core.type import GraphQLArgument
from .base import OrderedType, ArgumentType
from ...utils import to_camel_case
class Argument(OrderedType):
def __init__(self, type, description=None, default=None, name=None, _creation_counter=None):
super(Argument, self).__init__(_creation_counter=_creation_counter)
self.name = name
self.type = type
self.description = description
self.default = default
def internal_type(self, schema):
return GraphQLArgument(schema.T(self.type), self.default, self.description)
def __repr__(self):
return self.name
def to_arguments(*args, **kwargs):
arguments = {}
iter_arguments = chain(kwargs.items(), [(None, a) for a in args])
for name, arg in iter_arguments:
if isinstance(arg, Argument):
argument = arg
elif isinstance(arg, ArgumentType):
argument = arg.as_argument()
else:
raise ValueError('Unknown argument value type %r' % arg)
if name:
argument.name = to_camel_case(name)
assert argument.name, 'Argument in field must have a name'
assert argument.name not in arguments, 'Found more than one Argument with same name {}'.format(argument.name)
arguments[argument.name] = argument
return sorted(arguments.values())

View File

@ -0,0 +1,71 @@
from functools import total_ordering
from ..types import BaseObjectType, InputObjectType
@total_ordering
class OrderedType(object):
creation_counter = 0
def __init__(self, _creation_counter=None):
self.creation_counter = _creation_counter or self.gen_counter()
@staticmethod
def gen_counter():
counter = OrderedType.creation_counter
OrderedType.creation_counter += 1
return counter
def __eq__(self, other):
# Needed for @total_ordering
if type(self) == type(other):
return self.creation_counter == other.creation_counter
return NotImplemented
def __lt__(self, other):
# This is needed because bisect does not take a comparison function.
if type(self) == type(other):
return self.creation_counter < other.creation_counter
return NotImplemented
def __hash__(self):
return hash((self.creation_counter))
class MirroredType(OrderedType):
def __init__(self, *args, **kwargs):
_creation_counter = kwargs.pop('_creation_counter', None)
super(MirroredType, self).__init__(_creation_counter=_creation_counter)
self.args = args
self.kwargs = kwargs
@classmethod
def internal_type(cls, schema):
return getattr(cls, 'T', None)
class ArgumentType(MirroredType):
def as_argument(self):
from .argument import Argument
return Argument(self.__class__, _creation_counter=self.creation_counter, *self.args, **self.kwargs)
class FieldType(MirroredType):
def contribute_to_class(self, cls, name):
if issubclass(cls, InputObjectType):
inputfield = self.as_inputfield()
return inputfield.contribute_to_class(cls, name)
elif issubclass(cls, BaseObjectType):
field = self.as_field()
return field.contribute_to_class(cls, name)
def as_field(self):
from .field import Field
return Field(self.__class__, _creation_counter=self.creation_counter, *self.args, **self.kwargs)
def as_inputfield(self):
from .field import InputField
return InputField(self.__class__, _creation_counter=self.creation_counter, *self.args, **self.kwargs)
class MountedType(FieldType, ArgumentType):
pass

View File

@ -0,0 +1,20 @@
from graphql.core.type import (GraphQLList, GraphQLNonNull)
from .base import MountedType
class OfType(MountedType):
def __init__(self, of_type, *args, **kwargs):
self.of_type = of_type
super(OfType, self).__init__(*args, **kwargs)
def internal_type(self, schema):
return self.T(schema.T(self.of_type))
class List(OfType):
T = GraphQLList
class NonNull(OfType):
T = GraphQLNonNull

View File

@ -0,0 +1,61 @@
from collections import OrderedDict
from graphql.core.type import GraphQLField, GraphQLInputObjectField
from .base import OrderedType
from .argument import to_arguments
from ...utils import to_camel_case
from ..types import BaseObjectType, InputObjectType
class Field(OrderedType):
def __init__(self, type, description=None, args=None, name=None, resolver=None, *args_list, **kwargs):
_creation_counter = kwargs.pop('_creation_counter', None)
super(Field, self).__init__(_creation_counter=_creation_counter)
self.name = name
self.type = type
self.description = description
args = OrderedDict(args or {}, **kwargs)
self.arguments = to_arguments(*args_list, **args)
self.resolver = resolver
def contribute_to_class(self, cls, attname):
assert issubclass(cls, BaseObjectType), 'Field {} cannot be mounted in {}'.format(self, cls)
if not self.name:
self.name = to_camel_case(attname)
self.attname = attname
self.object_type = cls
if self.type == 'self':
self.type = cls
cls._meta.add_field(self)
def internal_type(self, schema):
return GraphQLField(schema.T(self.type), args=self.get_arguments(schema), resolver=self.resolver,
description=self.description,)
def get_arguments(self, schema):
if not self.arguments:
return None
return OrderedDict([(arg.name, schema.T(arg)) for arg in self.arguments])
class InputField(OrderedType):
def __init__(self, type, description=None, default=None, name=None, _creation_counter=None):
super(InputField, self).__init__(_creation_counter=_creation_counter)
self.name = name
self.type = type
self.description = description
self.default = default
def contribute_to_class(self, cls, attname):
assert issubclass(cls, InputObjectType), 'InputField {} cannot be mounted in {}'.format(self, cls)
if not self.name:
self.name = to_camel_case(attname)
self.attname = attname
self.object_type = cls
cls._meta.add_field(self)
def internal_type(self, schema):
return GraphQLInputObjectField(schema.T(self.type), default_value=self.default,
description=self.description)

View File

@ -0,0 +1,40 @@
from graphql.core.type import (GraphQLBoolean, GraphQLFloat, GraphQLID,
GraphQLInt, GraphQLScalarType, GraphQLString)
from .base import MountedType
class String(MountedType):
T = GraphQLString
class Int(MountedType):
T = GraphQLInt
class Boolean(MountedType):
T = GraphQLBoolean
class ID(MountedType):
T = GraphQLID
class Float(MountedType):
T = GraphQLFloat
class Scalar(MountedType):
@classmethod
def internal_type(cls, schema):
serialize = getattr(cls, 'serialize')
parse_literal = getattr(cls, 'parse_literal')
parse_value = getattr(cls, 'parse_value')
return GraphQLScalarType(
name=cls.__name__,
description=cls.__doc__,
serialize=serialize,
parse_value=parse_value,
parse_literal=parse_literal
)

View File

View File

@ -0,0 +1,45 @@
from pytest import raises
from graphql.core.type import GraphQLArgument
from ..argument import Argument, to_arguments
from ..scalars import String
from graphene.core.types import ObjectType
from graphene.core.schema import Schema
def test_argument_internal_type():
class MyObjectType(ObjectType):
pass
schema = Schema(query=MyObjectType)
a = Argument(MyObjectType, description='My argument', default='3')
type = schema.T(a)
assert isinstance(type, GraphQLArgument)
assert type.description == 'My argument'
assert type.default_value == '3'
def test_to_arguments():
arguments = to_arguments(
Argument(String, name='myArg'),
String(name='otherArg'),
my_kwarg=String(),
other_kwarg=String(),
)
assert [a.name for a in arguments] == ['myArg', 'otherArg', 'myKwarg', 'otherKwarg']
def test_to_arguments_no_name():
with raises(AssertionError) as excinfo:
to_arguments(
String(),
)
assert 'must have a name' in str(excinfo.value)
def test_to_arguments_wrong_type():
with raises(ValueError) as excinfo:
to_arguments(
p=3
)
assert 'Unknown argument value type 3' == str(excinfo.value)

View File

@ -0,0 +1,66 @@
from mock import patch
from ..base import OrderedType, MountedType
from ..field import Field, InputField
from ..argument import Argument
from graphene.core.types import ObjectType, InputObjectType
def test_orderedtype_equal():
a = OrderedType()
assert a == a
assert hash(a) == hash(a)
def test_orderedtype_different():
a = OrderedType()
b = OrderedType()
assert a != b
assert hash(a) != hash(b)
assert a < b
assert b > a
@patch('graphene.core.ntypes.field.Field')
def test_type_as_field_called(Field):
resolver = lambda x: x
a = MountedType(2, description='A', resolver=resolver)
a.as_field()
Field.assert_called_with(MountedType, 2, _creation_counter=a.creation_counter, description='A', resolver=resolver)
@patch('graphene.core.ntypes.argument.Argument')
def test_type_as_argument_called(Argument):
a = MountedType(2, description='A')
a.as_argument()
Argument.assert_called_with(MountedType, 2, _creation_counter=a.creation_counter, description='A')
def test_type_as_field():
resolver = lambda x: x
class MyObjectType(ObjectType):
t = MountedType(description='A', resolver=resolver)
fields_map = MyObjectType._meta.fields_map
field = fields_map.get('t')
assert isinstance(field, Field)
assert field.description == 'A'
assert field.object_type == MyObjectType
def test_type_as_inputfield():
class MyObjectType(InputObjectType):
t = MountedType(description='A')
fields_map = MyObjectType._meta.fields_map
field = fields_map.get('t')
assert isinstance(field, InputField)
assert field.description == 'A'
assert field.object_type == MyObjectType
def test_type_as_argument():
a = MountedType(description='A')
argument = a.as_argument()
assert isinstance(argument, Argument)

View File

@ -0,0 +1,26 @@
from graphql.core.type import (GraphQLList, GraphQLString, GraphQLNonNull)
from ..definitions import List, NonNull
from ..scalars import String
from graphene.core.schema import Schema
schema = Schema()
def test_list_scalar():
type = schema.T(List(String()))
assert isinstance(type, GraphQLList)
assert type.of_type == GraphQLString
def test_nonnull_scalar():
type = schema.T(NonNull(String()))
assert isinstance(type, GraphQLNonNull)
assert type.of_type == GraphQLString
def test_mixed_scalar():
type = schema.T(NonNull(List(String())))
assert isinstance(type, GraphQLNonNull)
assert isinstance(type.of_type, GraphQLList)
assert type.of_type.of_type == GraphQLString

View File

@ -0,0 +1,62 @@
from graphql.core.type import GraphQLField, GraphQLInputObjectField, GraphQLString
from ..field import Field, InputField
from ..scalars import String
from graphene.core.types import ObjectType, InputObjectType
from graphene.core.schema import Schema
def test_field_internal_type():
resolver = lambda *args: args
field = Field(String, description='My argument', resolver=resolver)
class Query(ObjectType):
my_field = field
schema = Schema(query=Query)
type = schema.T(field)
assert field.name == 'myField'
assert isinstance(type, GraphQLField)
assert type.description == 'My argument'
assert type.resolver == resolver
assert type.type == GraphQLString
def test_field_custom_name():
field = Field(None, name='my_customName')
class MyObjectType(ObjectType):
my_field = field
assert field.name == 'my_customName'
def test_field_custom_arguments():
field = Field(None, name='my_customName', p=String())
class MyObjectType(ObjectType):
my_field = field
schema = Schema(query=MyObjectType)
args = field.get_arguments(schema)
assert 'p' in args
def test_inputfield_internal_type():
field = InputField(String, description='My input field', default='3')
class MyObjectType(InputObjectType):
my_field = field
class Query(ObjectType):
input_ot = Field(MyObjectType)
schema = Schema(query=MyObjectType)
type = schema.T(field)
assert field.name == 'myField'
assert isinstance(type, GraphQLInputObjectField)
assert type.description == 'My input field'
assert type.default_value == '3'

View File

@ -0,0 +1,52 @@
from graphql.core.type import (GraphQLBoolean, GraphQLFloat, GraphQLID,
GraphQLInt, GraphQLScalarType, GraphQLString)
from ..scalars import String, Int, Boolean, ID, Float, Scalar
from graphene.core.schema import Schema
schema = Schema()
def test_string_scalar():
assert schema.T(String()) == GraphQLString
def test_int_scalar():
assert schema.T(Int()) == GraphQLInt
def test_boolean_scalar():
assert schema.T(Boolean()) == GraphQLBoolean
def test_id_scalar():
assert schema.T(ID()) == GraphQLID
def test_float_scalar():
assert schema.T(Float()) == GraphQLFloat
def test_custom_scalar():
import datetime
from graphql.core.language import ast
class DateTimeScalar(Scalar):
'''DateTimeScalar Documentation'''
@staticmethod
def serialize(dt):
return dt.isoformat()
@staticmethod
def parse_literal(node):
if isinstance(node, ast.StringValue):
return datetime.datetime.strptime(node.value, "%Y-%m-%dT%H:%M:%S.%f")
@staticmethod
def parse_value(value):
return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")
scalar_type = schema.T(DateTimeScalar)
assert isinstance(scalar_type, GraphQLScalarType)
assert scalar_type.name == 'DateTimeScalar'
assert scalar_type.description == 'DateTimeScalar Documentation'

View File

@ -37,7 +37,9 @@ class Schema(object):
if object_type not in self._types:
internal_type = object_type.internal_type(self)
self._types[object_type] = internal_type
self._types_names[internal_type.name] = object_type
name = getattr(internal_type, 'name', None)
if name:
self._types_names[name] = object_type
return self._types[object_type]
@property

View File

@ -1,3 +1,6 @@
[flake8]
exclude = tests/*,setup.py
max-line-length = 160
[coverage:run]
omit = core/ntypes/tests/*

View File

@ -62,6 +62,7 @@ setup(
tests_require=[
'pytest>=2.7.2',
'pytest-django',
'mock',
],
extras_require={
'django': [