Make new_types the deafult types

This commit is contained in:
Syrus Akbary 2016-08-13 13:47:10 -07:00
parent c339afc1ce
commit 2696ae9b73
57 changed files with 633 additions and 2537 deletions

View File

@ -1,33 +0,0 @@
from collections import OrderedDict
from itertools import chain
from ..utils.orderedtype import OrderedType
class Argument(OrderedType):
def __init__(self, type, default_value=None, description=None, name=None, _creation_counter=None):
super(Argument, self).__init__(_creation_counter=_creation_counter)
self.name = name
self.type = type
self.default_value = default_value
self.description = description
def to_arguments(args, extra_args):
from .unmountedtype import UnmountedType
extra_args = sorted(extra_args.items(), key=lambda f: f[1])
iter_arguments = chain(args.items() + extra_args)
arguments = OrderedDict()
for default_name, arg in iter_arguments:
if isinstance(arg, UnmountedType):
arg = arg.as_argument()
if not isinstance(arg, Argument):
raise ValueError('Unknown argument "{}".'.format(default_name))
arg_name = default_name or arg.name
assert arg_name not in arguments, 'More than one Argument have same name "{}".'.format(arg.name)
arguments[arg_name] = arg
return arguments

View File

@ -1,39 +0,0 @@
from __future__ import absolute_import
import datetime
from graphql.language import ast
from .scalars import Scalar
try:
import iso8601
except:
raise ImportError(
"iso8601 package is required for DateTime Scalar.\n"
"You can install it using: pip install iso8601."
)
class DateTime(Scalar):
'''
The `DateTime` scalar type represents a DateTime
value as specified by
[iso8601](https://en.wikipedia.org/wiki/ISO_8601).
'''
@staticmethod
def serialize(dt):
assert isinstance(dt, (datetime.datetime, datetime.date)), (
'Received not compatible datetime "{}"'.format(repr(dt))
)
return dt.isoformat()
@staticmethod
def parse_literal(node):
if isinstance(node, ast.StringValue):
return iso8601.parse_date(node.value)
@staticmethod
def parse_value(value):
return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")

View File

@ -1,54 +0,0 @@
from collections import OrderedDict
import six
from ..generators import generate_enum
from ..utils.is_base_type import is_base_type
from .options import Options
from .unmountedtype import UnmountedType
try:
from enum import Enum as PyEnum
except ImportError:
from ..utils.enum import Enum as PyEnum
class EnumTypeMeta(type):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of Model
# (excluding Model class itself).
if not is_base_type(bases, EnumTypeMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=attrs.get('__doc__'),
enum=None,
)
if not options.enum:
options.enum = PyEnum(cls.__name__, attrs)
new_attrs = OrderedDict(attrs, _meta=options, **options.enum.__members__)
return type.__new__(cls, name, bases, new_attrs)
def __prepare__(name, bases, **kwargs): # noqa: N805
return OrderedDict()
def __call__(cls, *args, **kwargs): # noqa: N805
if cls is Enum:
description = kwargs.pop('description', None)
return cls.from_enum(PyEnum(*args, **kwargs), description=description)
return super(EnumTypeMeta, cls).__call__(*args, **kwargs)
def from_enum(cls, enum, description=None):
meta_class = type('Meta', (object,), {'enum': enum, 'description': description})
return type(meta_class.enum.__name__, (Enum,), {'Meta': meta_class})
def __str__(cls):
return cls._meta.name
class Enum(six.with_metaclass(EnumTypeMeta, UnmountedType)):
pass

View File

@ -1,46 +0,0 @@
import inspect
from functools import partial
from collections import OrderedDict, Mapping
from ..utils.orderedtype import OrderedType
from .structures import NonNull
from .argument import to_arguments
def source_resolver(source, root, args, context, info):
resolved = getattr(root, source, None)
if inspect.isfunction(resolved):
return resolved()
return resolved
class Field(OrderedType):
def __init__(self, type, args=None, resolver=None, source=None,
deprecation_reason=None, name=None, description=None,
required=False, _creation_counter=None, **extra_args):
super(Field, self).__init__(_creation_counter=_creation_counter)
assert not args or isinstance(args, Mapping), (
'Arguments in a field have to be a mapping, received "{}".'
).format(args)
assert not (source and resolver), (
'A Field cannot have a source and a resolver in at the same time.'
)
if required:
type = NonNull(type)
self.name = name
self._type = type
self.args = to_arguments(args or OrderedDict(), extra_args)
if source:
resolver = partial(source_resolver, source)
self.resolver = resolver
self.deprecation_reason = deprecation_reason
self.description = description
@property
def type(self):
if inspect.isfunction(self._type):
return self._type()
return self._type

View File

@ -1,36 +0,0 @@
import six
from ..utils.is_base_type import is_base_type
from .options import Options
from .abstracttype import AbstractTypeMeta
from .utils import get_fields_in_type, yank_fields_from_attrs, merge_fields_in_attrs
class InputObjectTypeMeta(AbstractTypeMeta):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# InputObjectType
if not is_base_type(bases, InputObjectTypeMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=attrs.get('__doc__'),
)
attrs = merge_fields_in_attrs(bases, attrs)
options.fields = get_fields_in_type(InputObjectType, attrs)
yank_fields_from_attrs(attrs, options.fields)
return type.__new__(cls, name, bases, dict(attrs, _meta=options))
def __str__(cls):
return cls._meta.name
class InputObjectType(six.with_metaclass(InputObjectTypeMeta)):
def __init__(self, *args, **kwargs):
raise Exception("An InputObjectType cannot be intitialized")

View File

@ -1,42 +0,0 @@
import six
from ..utils.is_base_type import is_base_type
from .options import Options
from .abstracttype import AbstractTypeMeta
from .utils import get_fields_in_type, yank_fields_from_attrs, merge_fields_in_attrs
class InterfaceMeta(AbstractTypeMeta):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# Interface
if not is_base_type(bases, InterfaceMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=attrs.get('__doc__'),
)
attrs = merge_fields_in_attrs(bases, attrs)
options.fields = get_fields_in_type(Interface, attrs)
yank_fields_from_attrs(attrs, options.fields)
return type.__new__(cls, name, bases, dict(attrs, _meta=options))
def __str__(cls):
return cls._meta.name
class Interface(six.with_metaclass(InterfaceMeta)):
resolve_type = None
def __init__(self, *args, **kwargs):
raise Exception("An Interface cannot be intitialized")
# @classmethod
# def implements(cls, objecttype):
# pass

View File

@ -1,30 +0,0 @@
from functools import partial
import six
from ..utils.is_base_type import is_base_type
from ..utils.props import props
from .field import Field
from .objecttype import ObjectType, ObjectTypeMeta
class MutationMeta(ObjectTypeMeta):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# Mutation
if not is_base_type(bases, MutationMeta):
return type.__new__(cls, name, bases, attrs)
input_class = attrs.pop('Input', None)
cls = ObjectTypeMeta.__new__(cls, name, bases, attrs)
field_args = props(input_class) if input_class else {}
resolver = getattr(cls, 'mutate', None)
assert resolver, 'All mutations must define a mutate method in it'
cls.Field = partial(Field, cls, args=field_args, resolver=resolver)
return cls
class Mutation(six.with_metaclass(MutationMeta, ObjectType)):
pass

View File

@ -1,76 +0,0 @@
import six
from ..utils.is_base_type import is_base_type
from .options import Options
from .abstracttype import AbstractTypeMeta
from .utils import get_fields_in_type, yank_fields_from_attrs, merge_fields_in_attrs
class ObjectTypeMeta(AbstractTypeMeta):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# ObjectType
if not is_base_type(bases, ObjectTypeMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=attrs.get('__doc__'),
interfaces=(),
)
attrs = merge_fields_in_attrs(bases, attrs)
options.fields = get_fields_in_type(ObjectType, attrs)
yank_fields_from_attrs(attrs, options.fields)
return type.__new__(cls, name, bases, dict(attrs, _meta=options))
def __str__(cls):
return cls._meta.name
class ObjectType(six.with_metaclass(ObjectTypeMeta)):
is_type_of = None
def __init__(self, *args, **kwargs):
# GraphQL ObjectType acting as container
args_len = len(args)
fields = self._meta.fields.items()
if args_len > len(fields):
# Daft, but matches old exception sans the err msg.
raise IndexError("Number of args exceeds number of fields")
fields_iter = iter(fields)
if not kwargs:
for val, (name, field) in zip(args, fields_iter):
setattr(self, name, val)
else:
for val, (name, field) in zip(args, fields_iter):
setattr(self, name, val)
kwargs.pop(name, None)
for name, field in fields_iter:
try:
val = kwargs.pop(name)
setattr(self, name, val)
except KeyError:
pass
if kwargs:
for prop in list(kwargs):
try:
if isinstance(getattr(self.__class__, prop), property):
setattr(self, prop, kwargs.pop(prop))
except AttributeError:
pass
if kwargs:
raise TypeError(
"'{}' is an invalid keyword argument for {}".format(
list(kwargs)[0],
self.__class__.__name__
)
)

View File

@ -1,35 +0,0 @@
import inspect
from ..utils.props import props
class Options(object):
'''
This is the class wrapper around Meta.
It helps to validate and cointain the attributes inside
'''
def __init__(self, meta=None, **defaults):
if meta:
assert inspect.isclass(meta), (
'Meta have to be a class, received "{}".'.format(repr(meta))
)
meta_attrs = props(meta) if meta else {}
for attr_name, value in defaults.items():
if attr_name in meta_attrs:
value = meta_attrs.pop(attr_name)
elif hasattr(meta, attr_name):
value = getattr(meta, attr_name)
setattr(self, attr_name, value)
# If meta_attrs is not empty, it implicit means
# it received invalid attributes
if meta_attrs:
raise TypeError(
"Invalid attributes: {}".format(
','.join(meta_attrs.keys())
)
)
def __repr__(self):
return '<Meta \n{} >'.format(props(self))

View File

@ -1,155 +0,0 @@
import six
from graphql.language.ast import BooleanValue, FloatValue, IntValue, StringValue
from ..utils.is_base_type import is_base_type
from .options import Options
from .unmountedtype import UnmountedType
class ScalarTypeMeta(type):
def __new__(cls, name, bases, attrs):
super_new = super(ScalarTypeMeta, cls).__new__
# Also ensure initialization is only performed for subclasses of Model
# (excluding Model class itself).
if not is_base_type(bases, ScalarTypeMeta):
return super_new(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=attrs.get('__doc__'),
)
return super_new(cls, name, bases, dict(attrs, _meta=options))
def __str__(cls):
return cls._meta.name
class Scalar(six.with_metaclass(ScalarTypeMeta, UnmountedType)):
serialize = None
parse_value = None
parse_literal = None
@classmethod
def get_type(cls):
return cls
# As per the GraphQL Spec, Integers are only treated as valid when a valid
# 32-bit signed integer, providing the broadest support across platforms.
#
# n.b. JavaScript's integers are safe between -(2^53 - 1) and 2^53 - 1 because
# they are internally represented as IEEE 754 doubles.
MAX_INT = 2147483647
MIN_INT = -2147483648
class Int(Scalar):
'''
The `Int` scalar type represents non-fractional signed whole numeric
values. Int can represent values between -(2^53 - 1) and 2^53 - 1 since
represented in JSON as double-precision floating point numbers specified
by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).
'''
@staticmethod
def coerce_int(value):
try:
num = int(value)
except ValueError:
try:
num = int(float(value))
except ValueError:
return None
if MIN_INT <= num <= MAX_INT:
return num
serialize = coerce_int
parse_value = coerce_int
@staticmethod
def parse_literal(ast):
if isinstance(ast, IntValue):
num = int(ast.value)
if MIN_INT <= num <= MAX_INT:
return num
class Float(Scalar):
'''
The `Float` scalar type represents signed double-precision fractional
values as specified by
[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).
'''
@staticmethod
def coerce_float(value):
try:
return float(value)
except ValueError:
return None
serialize = coerce_float
parse_value = coerce_float
@staticmethod
def parse_literal(ast):
if isinstance(ast, (FloatValue, IntValue)):
return float(ast.value)
class String(Scalar):
'''
The `String` scalar type represents textual data, represented as UTF-8
character sequences. The String type is most often used by GraphQL to
represent free-form human-readable text.
'''
@staticmethod
def coerce_string(value):
if isinstance(value, bool):
return u'true' if value else u'false'
return six.text_type(value)
serialize = coerce_string
parse_value = coerce_string
@staticmethod
def parse_literal(ast):
if isinstance(ast, StringValue):
return ast.value
class Boolean(Scalar):
'''
The `Boolean` scalar type represents `true` or `false`.
'''
serialize = bool
parse_value = bool
@staticmethod
def parse_literal(ast):
if isinstance(ast, BooleanValue):
return ast.value
class ID(Scalar):
'''
The `ID` scalar type represents a unique identifier, often used to
refetch an object or as key for a cache. The ID type appears in a JSON
response as a String; however, it is not intended to be human-readable.
When expected as an input type, any string (such as `"4"`) or integer
(such as `4`) input value will be accepted as an ID.
'''
serialize = str
parse_value = str
@staticmethod
def parse_literal(ast):
if isinstance(ast, (StringValue, IntValue)):
return ast.value

View File

@ -1,112 +0,0 @@
import inspect
from graphql import GraphQLSchema, graphql, is_type
from graphql.utils.introspection_query import introspection_query
from graphql.utils.schema_printer import print_schema
from .objecttype import ObjectType
from .structures import List, NonNull
from .scalars import Scalar, String
# from ..utils.get_graphql_type import get_graphql_type
# from graphql.type.schema import assert_object_implements_interface
# from collections import defaultdict
from graphql.type.directives import (GraphQLDirective, GraphQLIncludeDirective,
GraphQLSkipDirective)
from graphql.type.introspection import IntrospectionSchema
from .typemap import TypeMap, is_graphene_type
class Schema(GraphQLSchema):
def __init__(self, query=None, mutation=None, subscription=None, directives=None, types=None, executor=None):
self._query = query
self._mutation = mutation
self._subscription = subscription
self.types = types
self._executor = executor
if directives is None:
directives = [
GraphQLIncludeDirective,
GraphQLSkipDirective
]
assert all(isinstance(d, GraphQLDirective) for d in directives), \
'Schema directives must be List[GraphQLDirective] if provided but got: {}.'.format(
directives
)
self._directives = directives
initial_types = [
query,
mutation,
subscription,
IntrospectionSchema
]
if types:
initial_types += types
self._type_map = TypeMap(initial_types)
def get_query_type(self):
return self.get_graphql_type(self._query)
def get_mutation_type(self):
return self.get_graphql_type(self._mutation)
def get_subscription_type(self):
return self.get_graphql_type(self._subscription)
def get_graphql_type(self, _type):
if is_type(_type):
return _type
if is_graphene_type(_type):
graphql_type = self.get_type(_type._meta.name)
assert graphql_type, "Type {} not found in this schema.".format(_type._meta.name)
assert graphql_type.graphene_type == _type
return graphql_type
raise Exception("{} is not a valid GraphQL type.".format(_type))
def execute(self, request_string='', root_value=None, variable_values=None,
context_value=None, operation_name=None, executor=None):
return graphql(
schema=self,
request_string=request_string,
root_value=root_value,
context_value=context_value,
variable_values=variable_values,
operation_name=operation_name,
executor=executor or self._executor
)
def register(self, object_type):
self.types.append(object_type)
def introspect(self):
return self.execute(introspection_query).data
def __str__(self):
return print_schema(self)
def lazy(self, _type):
return lambda: self.get_type(_type)
# def rebuild(self):
# self._possible_type_map = defaultdict(set)
# self._type_map = self._build_type_map(self.types)
# # Keep track of all implementations by interface name.
# self._implementations = defaultdict(list)
# for type in self._type_map.values():
# if isinstance(type, GraphQLObjectType):
# for interface in type.get_interfaces():
# self._implementations[interface.name].append(type)
# # Enforce correct interface implementations.
# for type in self._type_map.values():
# if isinstance(type, GraphQLObjectType):
# for interface in type.get_interfaces():
# assert_object_implements_interface(self, type, interface)

View File

@ -1,25 +0,0 @@
from .unmountedtype import UnmountedType
class Structure(UnmountedType):
'''
A structure is a GraphQL type instance that
wraps a main type with certain structure.
'''
def __init__(self, of_type, *args, **kwargs):
super(Structure, self).__init__(*args, **kwargs)
self.of_type = of_type
def get_type(self):
return self
class List(Structure):
def __str__(self):
return '[{}]'.format(self.of_type)
class NonNull(Structure):
def __str__(self):
return '{}!'.format(self.of_type)

View File

@ -1,73 +0,0 @@
from ..enum import Enum, PyEnum
def test_enum_construction():
class RGB(Enum):
'''Description'''
RED = 1
GREEN = 2
BLUE = 3
@property
def description(self):
return "Description {}".format(self.name)
assert RGB._meta.name == 'RGB'
assert RGB._meta.description == 'Description'
values = RGB._meta.enum.__members__.values()
assert sorted([v.name for v in values]) == [
'BLUE',
'GREEN',
'RED'
]
assert sorted([v.description for v in values]) == [
'Description BLUE',
'Description GREEN',
'Description RED'
]
def test_enum_construction_meta():
class RGB(Enum):
class Meta:
name = 'RGBEnum'
description = 'Description'
RED = 1
GREEN = 2
BLUE = 3
assert RGB._meta.name == 'RGBEnum'
assert RGB._meta.description == 'Description'
def test_enum_instance_construction():
RGB = Enum('RGB', 'RED,GREEN,BLUE')
values = RGB._meta.enum.__members__.values()
assert sorted([v.name for v in values]) == [
'BLUE',
'GREEN',
'RED'
]
def test_enum_from_builtin_enum():
PyRGB = PyEnum('RGB', 'RED,GREEN,BLUE')
RGB = Enum.from_enum(PyRGB)
assert RGB._meta.enum == PyRGB
assert RGB.RED
assert RGB.GREEN
assert RGB.BLUE
def test_enum_value_from_class():
class RGB(Enum):
RED = 1
GREEN = 2
BLUE = 3
assert RGB.RED.value == 1
assert RGB.GREEN.value == 2
assert RGB.BLUE.value == 3

View File

@ -1,77 +0,0 @@
import pytest
from ..field import Field
from ..structures import NonNull
from ..argument import Argument
class MyInstance(object):
value = 'value'
value_func = staticmethod(lambda: 'value_func')
def test_field_basic():
MyType = object()
args = {'my arg': Argument(True)}
resolver = lambda: None
deprecation_reason = 'Deprecated now'
description = 'My Field'
field = Field(
MyType,
name='name',
args=args,
resolver=resolver,
description=description,
deprecation_reason=deprecation_reason
)
assert field.name == 'name'
assert field.args == args
assert field.resolver == resolver
assert field.deprecation_reason == deprecation_reason
assert field.description == description
def test_field_required():
MyType = object()
field = Field(MyType, required=True)
assert isinstance(field.type, NonNull)
assert field.type.of_type == MyType
def test_field_source():
MyType = object()
field = Field(MyType, source='value')
assert field.resolver(MyInstance, {}, None, None) == MyInstance.value
def test_field_with_lazy_type():
MyType = object()
field = Field(lambda: MyType)
assert field.type == MyType
def test_field_not_source_and_resolver():
MyType = object()
with pytest.raises(Exception) as exc_info:
Field(MyType, source='value', resolver=lambda: None)
assert str(exc_info.value) == 'A Field cannot have a source and a resolver in at the same time.'
def test_field_source_func():
MyType = object()
field = Field(MyType, source='value_func')
assert field.resolver(MyInstance(), {}, None, None) == MyInstance.value_func()
def test_field_source_argument_as_kw():
MyType = object()
field = Field(MyType, b=NonNull(True), c=Argument(None), a=NonNull(False))
assert field.args.keys() == ['b', 'c', 'a']
assert isinstance(field.args['b'], Argument)
assert isinstance(field.args['b'].type, NonNull)
assert field.args['b'].type.of_type is True
assert isinstance(field.args['c'], Argument)
assert field.args['c'].type is None
assert isinstance(field.args['a'], Argument)
assert isinstance(field.args['a'].type, NonNull)
assert field.args['a'].type.of_type is False

View File

@ -1,83 +0,0 @@
import pytest
from ..field import Field
from ..inputfield import InputField
from ..inputobjecttype import InputObjectType
from ..unmountedtype import UnmountedType
from ..abstracttype import AbstractType
class MyType(object):
pass
class MyScalar(UnmountedType):
def get_type(self):
return MyType
def test_generate_inputobjecttype():
class MyInputObjectType(InputObjectType):
'''Documentation'''
assert MyInputObjectType._meta.name == "MyInputObjectType"
assert MyInputObjectType._meta.description == "Documentation"
assert MyInputObjectType._meta.fields == {}
def test_generate_inputobjecttype_with_meta():
class MyInputObjectType(InputObjectType):
class Meta:
name = 'MyOtherInputObjectType'
description = 'Documentation'
assert MyInputObjectType._meta.name == "MyOtherInputObjectType"
assert MyInputObjectType._meta.description == "Documentation"
def test_generate_inputobjecttype_with_fields():
class MyInputObjectType(InputObjectType):
field = Field(MyType)
assert 'field' in MyInputObjectType._meta.fields
def test_ordered_fields_in_inputobjecttype():
class MyInputObjectType(InputObjectType):
b = InputField(MyType)
a = InputField(MyType)
field = MyScalar()
asa = InputField(MyType)
assert list(MyInputObjectType._meta.fields.keys()) == ['b', 'a', 'field', 'asa']
def test_generate_inputobjecttype_unmountedtype():
class MyInputObjectType(InputObjectType):
field = MyScalar(MyType)
assert 'field' in MyInputObjectType._meta.fields
assert isinstance(MyInputObjectType._meta.fields['field'], InputField)
def test_generate_inputobjecttype_inherit_abstracttype():
class MyAbstractType(AbstractType):
field1 = MyScalar(MyType)
class MyInputObjectType(InputObjectType, MyAbstractType):
field2 = MyScalar(MyType)
assert MyInputObjectType._meta.fields.keys() == ['field1', 'field2']
assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [InputField, InputField]
def test_generate_inputobjecttype_inherit_abstracttype_reversed():
class MyAbstractType(AbstractType):
field1 = MyScalar(MyType)
class MyInputObjectType(MyAbstractType, InputObjectType):
field2 = MyScalar(MyType)
assert MyInputObjectType._meta.fields.keys() == ['field1', 'field2']
assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [InputField, InputField]

View File

@ -1,82 +0,0 @@
import pytest
from ..field import Field
from ..interface import Interface
from ..unmountedtype import UnmountedType
from ..abstracttype import AbstractType
class MyType(object):
pass
class MyScalar(UnmountedType):
def get_type(self):
return MyType
def test_generate_interface():
class MyInterface(Interface):
'''Documentation'''
assert MyInterface._meta.name == "MyInterface"
assert MyInterface._meta.description == "Documentation"
assert MyInterface._meta.fields == {}
def test_generate_interface_with_meta():
class MyInterface(Interface):
class Meta:
name = 'MyOtherInterface'
description = 'Documentation'
assert MyInterface._meta.name == "MyOtherInterface"
assert MyInterface._meta.description == "Documentation"
def test_generate_interface_with_fields():
class MyInterface(Interface):
field = Field(MyType)
assert 'field' in MyInterface._meta.fields
def test_ordered_fields_in_interface():
class MyInterface(Interface):
b = Field(MyType)
a = Field(MyType)
field = MyScalar()
asa = Field(MyType)
assert list(MyInterface._meta.fields.keys()) == ['b', 'a', 'field', 'asa']
def test_generate_interface_unmountedtype():
class MyInterface(Interface):
field = MyScalar()
assert 'field' in MyInterface._meta.fields
assert isinstance(MyInterface._meta.fields['field'], Field)
def test_generate_interface_inherit_abstracttype():
class MyAbstractType(AbstractType):
field1 = MyScalar()
class MyInterface(Interface, MyAbstractType):
field2 = MyScalar()
assert MyInterface._meta.fields.keys() == ['field1', 'field2']
assert [type(x) for x in MyInterface._meta.fields.values()] == [Field, Field]
def test_generate_interface_inherit_abstracttype_reversed():
class MyAbstractType(AbstractType):
field1 = MyScalar()
class MyInterface(MyAbstractType, Interface):
field2 = MyScalar()
assert MyInterface._meta.fields.keys() == ['field1', 'field2']
assert [type(x) for x in MyInterface._meta.fields.values()] == [Field, Field]

View File

@ -1,63 +0,0 @@
import pytest
from ..field import Field
from ..mutation import Mutation
from ..objecttype import ObjectType
from ..scalars import String
def test_generate_mutation_no_args():
class MyMutation(Mutation):
'''Documentation'''
@classmethod
def mutate(cls, *args, **kwargs):
pass
assert issubclass(MyMutation, ObjectType)
assert MyMutation._meta.name == "MyMutation"
assert MyMutation._meta.description == "Documentation"
assert MyMutation.Field().resolver == MyMutation.mutate
# def test_generate_mutation_with_args():
# class MyMutation(Mutation):
# '''Documentation'''
# class Input:
# s = String()
# @classmethod
# def mutate(cls, *args, **kwargs):
# pass
# graphql_type = MyMutation._meta.graphql_type
# field = MyMutation.Field()
# assert graphql_type.name == "MyMutation"
# assert graphql_type.description == "Documentation"
# assert isinstance(field, Field)
# assert field.type == MyMutation._meta.graphql_type
# assert 's' in field.args
# assert field.args['s'].type == String
def test_generate_mutation_with_meta():
class MyMutation(Mutation):
class Meta:
name = 'MyOtherMutation'
description = 'Documentation'
@classmethod
def mutate(cls, *args, **kwargs):
pass
assert MyMutation._meta.name == "MyOtherMutation"
assert MyMutation._meta.description == "Documentation"
assert MyMutation.Field().resolver == MyMutation.mutate
def test_mutation_raises_exception_if_no_mutate():
with pytest.raises(AssertionError) as excinfo:
class MyMutation(Mutation):
pass
assert "All mutations must define a mutate method in it" == str(excinfo.value)

View File

@ -1,131 +0,0 @@
import pytest
from ..field import Field
from ..objecttype import ObjectType
from ..unmountedtype import UnmountedType
from ..abstracttype import AbstractType
class MyType(object):
pass
class Container(ObjectType):
field1 = Field(MyType)
field2 = Field(MyType)
class MyScalar(UnmountedType):
def get_type(self):
return MyType
def test_generate_objecttype():
class MyObjectType(ObjectType):
'''Documentation'''
assert MyObjectType._meta.name == "MyObjectType"
assert MyObjectType._meta.description == "Documentation"
assert MyObjectType._meta.interfaces == tuple()
assert MyObjectType._meta.fields == {}
def test_generate_objecttype_with_meta():
class MyObjectType(ObjectType):
class Meta:
name = 'MyOtherObjectType'
description = 'Documentation'
interfaces = (MyType, )
assert MyObjectType._meta.name == "MyOtherObjectType"
assert MyObjectType._meta.description == "Documentation"
assert MyObjectType._meta.interfaces == (MyType, )
def test_generate_objecttype_with_fields():
class MyObjectType(ObjectType):
field = Field(MyType)
assert 'field' in MyObjectType._meta.fields
def test_ordered_fields_in_objecttype():
class MyObjectType(ObjectType):
b = Field(MyType)
a = Field(MyType)
field = MyScalar()
asa = Field(MyType)
assert list(MyObjectType._meta.fields.keys()) == ['b', 'a', 'field', 'asa']
def test_generate_objecttype_inherit_abstracttype():
class MyAbstractType(AbstractType):
field1 = MyScalar()
class MyObjectType(ObjectType, MyAbstractType):
field2 = MyScalar()
assert MyObjectType._meta.fields.keys() == ['field1', 'field2']
assert [type(x) for x in MyObjectType._meta.fields.values()] == [Field, Field]
def test_generate_objecttype_inherit_abstracttype_reversed():
class MyAbstractType(AbstractType):
field1 = MyScalar()
class MyObjectType(MyAbstractType, ObjectType):
field2 = MyScalar()
assert MyObjectType._meta.fields.keys() == ['field1', 'field2']
assert [type(x) for x in MyObjectType._meta.fields.values()] == [Field, Field]
def test_generate_objecttype_unmountedtype():
class MyObjectType(ObjectType):
field = MyScalar()
assert 'field' in MyObjectType._meta.fields
assert isinstance(MyObjectType._meta.fields['field'], Field)
def test_parent_container_get_fields():
assert list(Container._meta.fields.keys()) == ['field1', 'field2']
def test_objecttype_as_container_only_args():
container = Container("1", "2")
assert container.field1 == "1"
assert container.field2 == "2"
def test_objecttype_as_container_args_kwargs():
container = Container("1", field2="2")
assert container.field1 == "1"
assert container.field2 == "2"
def test_objecttype_as_container_few_kwargs():
container = Container(field2="2")
assert container.field2 == "2"
def test_objecttype_as_container_all_kwargs():
container = Container(field1="1", field2="2")
assert container.field1 == "1"
assert container.field2 == "2"
def test_objecttype_as_container_extra_args():
with pytest.raises(IndexError) as excinfo:
Container("1", "2", "3")
assert "Number of args exceeds number of fields" == str(excinfo.value)
def test_objecttype_as_container_invalid_kwargs():
with pytest.raises(TypeError) as excinfo:
Container(unexisting_field="3")
assert "'unexisting_field' is an invalid keyword argument for Container" == str(excinfo.value)

View File

@ -1,70 +0,0 @@
from ..utils.orderedtype import OrderedType
class UnmountedType(OrderedType):
'''
This class acts a proxy for a Graphene Type, so it can be mounted
as Field, InputField or Argument.
Instead of writing
>>> class MyObjectType(ObjectType):
>>> my_field = Field(String(), description='Description here')
It let you write
>>> class MyObjectType(ObjectType):
>>> my_field = String(description='Description here')
'''
def __init__(self, *args, **kwargs):
super(UnmountedType, self).__init__()
self.args = args
self.kwargs = kwargs
def get_type(self):
raise NotImplementedError("get_type not implemented in {}".format(self))
def as_field(self):
'''
Mount the UnmountedType as Field
'''
from .field import Field
return Field(
self.get_type(),
*self.args,
_creation_counter=self.creation_counter,
**self.kwargs
)
def as_inputfield(self):
'''
Mount the UnmountedType as InputField
'''
from .inputfield import InputField
return InputField(
self.get_type(),
*self.args,
_creation_counter=self.creation_counter,
**self.kwargs
)
def as_argument(self):
'''
Mount the UnmountedType as Argument
'''
from .argument import Argument
return Argument(
self.get_type(),
*self.args,
_creation_counter=self.creation_counter,
**self.kwargs
)
def __eq__(self, other):
return (
self is other or (
isinstance(other, UnmountedType) and
self.get_type() == other.get_type() and
self.args == other.args and
self.kwargs == other.kwargs
)
)

View File

@ -1,13 +1,17 @@
from .objecttype import ObjectType, Interface
from .objecttype import ObjectType
from .abstracttype import AbstractType
from .interface import Interface
from .scalars import Scalar, String, ID, Int, Float, Boolean
from .schema import Schema
from .structures import List, NonNull
from .enum import Enum
from .field import Field, InputField
from .field import Field
from .inputfield import InputField
from .argument import Argument
from .inputobjecttype import InputObjectType
__all__ = [
'AbstractType',
'ObjectType',
'InputObjectType',
'Interface',

View File

@ -1,77 +1,33 @@
import inspect
from collections import OrderedDict
from itertools import chain
from graphql.type.definition import GraphQLArgument, GraphQLArgumentDefinition
from graphql.utils.assert_valid_name import assert_valid_name
from ..utils.orderedtype import OrderedType
from ..utils.str_converters import to_camel_case
class Argument(GraphQLArgument, OrderedType):
class Argument(OrderedType):
def __init__(self, type, default_value=None, description=None, name=None, _creation_counter=None):
super(Argument, self).__init__(_creation_counter=_creation_counter)
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
@classmethod
def copy_from(cls, argument):
if isinstance(argument, (GraphQLArgumentDefinition, Argument)):
name = argument.name
else:
name = None
return cls(
type=argument.type,
default_value=argument.default_value,
description=argument.description,
name=name,
_creation_counter=argument.creation_counter if isinstance(argument, Argument) else None,
)
def to_arguments(*args, **extra):
def to_arguments(args, extra_args):
from .unmountedtype import UnmountedType
args = list(filter(None, args)) + [extra]
arguments = []
iter_arguments = chain(*[arg.items() for arg in args])
arguments_names = set()
extra_args = sorted(extra_args.items(), key=lambda f: f[1])
iter_arguments = chain(args.items() + extra_args)
arguments = OrderedDict()
for default_name, arg in iter_arguments:
if isinstance(arg, UnmountedType):
arg = arg.as_argument()
if not isinstance(arg, GraphQLArgument):
if not isinstance(arg, Argument):
raise ValueError('Unknown argument "{}".'.format(default_name))
arg = Argument.copy_from(arg)
arg.name = arg.name or default_name and to_camel_case(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)
arg_name = default_name or arg.name
assert arg_name not in arguments, 'More than one Argument have same name "{}".'.format(arg.name)
arguments[arg_name] = arg
return OrderedDict([(a.name, a) for a in sorted(arguments)])
return arguments

View File

@ -16,6 +16,11 @@ except:
class DateTime(Scalar):
'''
The `DateTime` scalar type represents a DateTime
value as specified by
[iso8601](https://en.wikipedia.org/wiki/ISO_8601).
'''
@staticmethod
def serialize(dt):

View File

@ -16,31 +16,22 @@ except ImportError:
class EnumTypeMeta(type):
def __new__(cls, name, bases, attrs):
super_new = type.__new__
# Also ensure initialization is only performed for subclasses of Model
# (excluding Model class itself).
if not is_base_type(bases, EnumTypeMeta):
return super_new(cls, name, bases, attrs)
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=None,
description=None,
name=name,
description=attrs.get('__doc__'),
enum=None,
graphql_type=None
)
if not options.enum:
options.enum = PyEnum(cls.__name__, attrs)
new_attrs = OrderedDict(attrs, _meta=options, **options.enum.__members__)
cls = super_new(cls, name, bases, new_attrs)
if not options.graphql_type:
options.graphql_type = generate_enum(cls)
return cls
return type.__new__(cls, name, bases, new_attrs)
def __prepare__(name, bases, **kwargs): # noqa: N805
return OrderedDict()
@ -51,10 +42,13 @@ class EnumTypeMeta(type):
return cls.from_enum(PyEnum(*args, **kwargs), description=description)
return super(EnumTypeMeta, cls).__call__(*args, **kwargs)
class Enum(six.with_metaclass(EnumTypeMeta, UnmountedType)):
@classmethod
def from_enum(cls, enum, description=None):
meta_class = type('Meta', (object,), {'enum': enum, 'description': description})
return type(meta_class.enum.__name__, (Enum,), {'Meta': meta_class})
def __str__(cls):
return cls._meta.name
class Enum(six.with_metaclass(EnumTypeMeta, UnmountedType)):
pass

View File

@ -1,221 +1,46 @@
import inspect
from collections import OrderedDict
from graphql.type import (GraphQLField, GraphQLFieldDefinition,
GraphQLInputObjectField)
from graphql.utils.assert_valid_name import assert_valid_name
from functools import partial
from collections import OrderedDict, Mapping
from ..utils.orderedtype import OrderedType
from ..utils.str_converters import to_camel_case
from .structures import NonNull
from .argument import to_arguments
class AbstractField(object):
def source_resolver(source, root, args, context, info):
resolved = getattr(root, source, None)
if inspect.isfunction(resolved):
return resolved()
return resolved
@property
def name(self):
return self._name or self.attname and to_camel_case(self.attname)
@name.setter
def name(self, name):
if name is not None:
assert_valid_name(name)
self._name = name
class Field(OrderedType):
def __init__(self, type, args=None, resolver=None, source=None,
deprecation_reason=None, name=None, description=None,
required=False, _creation_counter=None, **extra_args):
super(Field, self).__init__(_creation_counter=_creation_counter)
assert not args or isinstance(args, Mapping), (
'Arguments in a field have to be a mapping, received "{}".'
).format(args)
assert not (source and resolver), (
'A Field cannot have a source and a resolver in at the same time.'
)
if required:
type = NonNull(type)
self.name = name
self._type = type
self.args = to_arguments(args or OrderedDict(), extra_args)
if source:
resolver = partial(source_resolver, source)
self.resolver = resolver
self.deprecation_reason = deprecation_reason
self.description = description
@property
def type(self):
from ..utils.get_graphql_type import get_graphql_type
from .structures import NonNull
if inspect.isfunction(self._type):
_type = self._type()
else:
_type = self._type
if self.required:
return NonNull(_type)
return get_graphql_type(_type)
@type.setter
def type(self, type):
self._type = type
class Field(AbstractField, GraphQLField, OrderedType):
def __init__(self, type, args=None, resolver=None, source=None, deprecation_reason=None,
name=None, description=None, required=False, _creation_counter=None, **extra_args):
self.name = name
self.attname = None
self.parent = None
self.type = type
self.args = to_arguments(args, extra_args)
assert not (source and resolver), ('You cannot have a source '
'and a resolver at the same time')
self.resolver = resolver
self.source = source
self.required = required
self.deprecation_reason = deprecation_reason
self.description = description
OrderedType.__init__(self, _creation_counter=_creation_counter)
def mount_error_message(self, where):
return 'Field "{}" can only be mounted in ObjectType or Interface, received {}.'.format(
self,
where.__name__
)
def mount(self, parent, attname=None):
from .objecttype import ObjectType
from .interface import Interface
assert issubclass(parent, (ObjectType, Interface)), self.mount_error_message(parent)
self.attname = attname
self.parent = parent
def default_resolver(self, root, args, context, info):
return getattr(root, self.source or self.attname, None)
@property
def resolver(self):
resolver = getattr(self.parent, 'resolve_{}'.format(self.attname), None)
# We try to get the resolver from the interfaces
# This is not needed anymore as Interfaces could be extended now with Python syntax
# if not resolver and issubclass(self.parent, ObjectType):
# graphql_type = self.parent._meta.graphql_type
# interfaces = graphql_type._provided_interfaces or []
# for interface in interfaces:
# if not isinstance(interface, GrapheneInterfaceType):
# continue
# fields = interface.get_fields()
# if self.attname in fields:
# resolver = getattr(interface.graphene_type, 'resolve_{}'.format(self.attname), None)
# if resolver:
# # We remove the bounding to the method
# resolver = resolver #.__func__
# break
if resolver:
resolver = getattr(resolver, '__func__', resolver)
else:
resolver = self.default_resolver
# def resolver_wrapper(root, *args, **kwargs):
# if not isinstance(root, self.parent):
# root = self.parent()
# return resolver(root, *args, **kwargs)
return self._resolver or resolver # resolver_wrapper
@resolver.setter
def resolver(self, resolver):
self._resolver = resolver
def __copy__(self):
return self.copy_and_extend(self)
@classmethod
def copy_and_extend(
cls, field, type=None, args=None, resolver=None, source=None, deprecation_reason=None, name=None,
description=None, required=False, _creation_counter=False, parent=None, attname=None, **extra_args):
if isinstance(field, Field):
type = type or field._type
resolver = resolver or field._resolver
source = source or field.source
name = name or field._name
required = required or field.required
_creation_counter = field.creation_counter if _creation_counter is False else None
attname = attname or field.attname
parent = parent or field.parent
args = to_arguments(args, field.args)
else:
# If is a GraphQLField
type = type or field.type
resolver = resolver or field.resolver
field_args = field.args
if isinstance(field, GraphQLFieldDefinition):
name = name or field.name
field_args = OrderedDict((a.name, a) for a in field_args)
args = to_arguments(args, field_args)
_creation_counter = None
attname = attname or name
parent = parent
new_field = cls(
type=type,
args=args,
resolver=resolver,
source=source,
deprecation_reason=field.deprecation_reason,
name=name,
required=required,
description=field.description,
_creation_counter=_creation_counter,
**extra_args
)
new_field.attname = attname
new_field.parent = parent
return new_field
def __str__(self):
if not self.parent:
return 'Not bounded field'
return "{}.{}".format(self.parent._meta.graphql_type, self.attname)
class InputField(AbstractField, GraphQLInputObjectField, OrderedType):
def __init__(self, type, default_value=None, description=None, name=None, required=False, _creation_counter=None):
self.name = name
self.type = type
self.default_value = default_value
self.description = description
self.required = required
self.attname = None
self.parent = None
OrderedType.__init__(self, _creation_counter=_creation_counter)
def mount_error_message(self, where):
return 'InputField {} can only be mounted in InputObjectType classes, received {}.'.format(
self,
where.__name__
)
def mount(self, parent, attname):
from .inputobjecttype import InputObjectType
assert issubclass(parent, (InputObjectType)), self.mount_error_message(parent)
self.attname = attname
self.parent = parent
def __copy__(self):
return self.copy_and_extend(self)
@classmethod
def copy_and_extend(cls, field, type=None, default_value=None, description=None, name=None,
required=False, parent=None, attname=None, _creation_counter=False):
if isinstance(field, Field):
type = type or field._type
name = name or field._name
required = required or field.required
_creation_counter = field.creation_counter if _creation_counter is False else None
attname = attname or field.attname
parent = parent or field.parent
else:
# If is a GraphQLField
type = type or field.type
name = field.name
_creation_counter = None
new_field = cls(
type=type,
name=name,
required=required,
default_value=default_value or field.default_value,
description=description or field.description,
_creation_counter=_creation_counter,
)
new_field.attname = attname
new_field.parent = parent
return new_field
return self._type()
return self._type

View File

@ -1,49 +1,36 @@
import six
from ..generators import generate_inputobjecttype
from ..utils.copy_fields import copy_fields
from ..utils.get_fields import get_fields
from ..utils.is_base_type import is_base_type
from .field import InputField
from .objecttype import attrs_without_fields
from .options import Options
from .unmountedtype import UnmountedType
from .abstracttype import AbstractTypeMeta
from .utils import get_fields_in_type, yank_fields_from_attrs, merge_fields_in_attrs
class InputObjectTypeMeta(type):
class InputObjectTypeMeta(AbstractTypeMeta):
def __new__(cls, name, bases, attrs):
super_new = super(InputObjectTypeMeta, cls).__new__
# Also ensure initialization is only performed for subclasses of Model
# (excluding Model class itself).
# Also ensure initialization is only performed for subclasses of
# InputObjectType
if not is_base_type(bases, InputObjectTypeMeta):
return super_new(cls, name, bases, attrs)
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=None,
description=None,
graphql_type=None,
name=name,
description=attrs.get('__doc__'),
)
fields = get_fields(InputObjectType, attrs, bases)
attrs = attrs_without_fields(attrs, fields)
cls = super_new(cls, name, bases, dict(attrs, _meta=options))
attrs = merge_fields_in_attrs(bases, attrs)
options.fields = get_fields_in_type(InputObjectType, attrs)
yank_fields_from_attrs(attrs, options.fields)
if not options.graphql_type:
fields = copy_fields(InputField, fields, parent=cls)
options.get_fields = lambda: fields
options.graphql_type = generate_inputobjecttype(cls)
else:
assert not fields, "Can't mount InputFields in an InputObjectType with a defined graphql_type"
fields = copy_fields(InputField, options.graphql_type.get_fields(), parent=cls)
return type.__new__(cls, name, bases, dict(attrs, _meta=options))
for name, field in fields.items():
setattr(cls, field.attname or name, field)
return cls
def __str__(cls):
return cls._meta.name
class InputObjectType(six.with_metaclass(InputObjectTypeMeta, UnmountedType)):
pass
class InputObjectType(six.with_metaclass(InputObjectTypeMeta)):
def __init__(self, *args, **kwargs):
raise Exception("An InputObjectType cannot be intitialized")

View File

@ -1,3 +1,42 @@
from .objecttype import Interface
import six
__all__ = ['Interface']
from ..utils.is_base_type import is_base_type
from .options import Options
from .abstracttype import AbstractTypeMeta
from .utils import get_fields_in_type, yank_fields_from_attrs, merge_fields_in_attrs
class InterfaceMeta(AbstractTypeMeta):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# Interface
if not is_base_type(bases, InterfaceMeta):
return type.__new__(cls, name, bases, attrs)
options = Options(
attrs.pop('Meta', None),
name=name,
description=attrs.get('__doc__'),
)
attrs = merge_fields_in_attrs(bases, attrs)
options.fields = get_fields_in_type(Interface, attrs)
yank_fields_from_attrs(attrs, options.fields)
return type.__new__(cls, name, bases, dict(attrs, _meta=options))
def __str__(cls):
return cls._meta.name
class Interface(six.with_metaclass(InterfaceMeta)):
resolve_type = None
def __init__(self, *args, **kwargs):
raise Exception("An Interface cannot be intitialized")
# @classmethod
# def implements(cls, objecttype):
# pass

View File

@ -18,7 +18,7 @@ class MutationMeta(ObjectTypeMeta):
input_class = attrs.pop('Input', None)
cls = cls._create_objecttype(cls, name, bases, attrs)
cls = ObjectTypeMeta.__new__(cls, name, bases, attrs)
field_args = props(input_class) if input_class else {}
resolver = getattr(cls, 'mutate', None)
assert resolver, 'All mutations must define a mutate method in it'

View File

@ -1,132 +1,45 @@
import inspect
import six
from ..utils.copy_fields import copy_fields
from ..utils.get_fields import get_fields
from ..utils.is_base_type import is_base_type
from .field import Field
from .options import Options
from ..generators import generate_interface, generate_objecttype
from .abstracttype import AbstractTypeMeta
from .utils import get_fields_in_type, yank_fields_from_attrs, merge_fields_in_attrs
def get_interfaces(interfaces):
from ..utils.get_graphql_type import get_graphql_type
for interface in interfaces:
graphql_type = get_graphql_type(interface)
yield graphql_type
def is_objecttype(bases):
for base in bases:
if issubclass(base, ObjectType):
return True
return False
def attrs_without_fields(attrs, fields):
return {k: v for k, v in attrs.items() if k not in fields}
# We inherit from InterfaceTypeMeta instead of type for being able
# to have ObjectTypes extending Interfaces using Python syntax, like:
# class MyObjectType(ObjectType, MyInterface)
class ObjectTypeMeta(type):
class ObjectTypeMeta(AbstractTypeMeta):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# ObjectType,or Interfaces
# ObjectType
if not is_base_type(bases, ObjectTypeMeta):
return type.__new__(cls, name, bases, attrs)
if not is_objecttype(bases):
return cls._create_interface(cls, name, bases, attrs)
return cls._create_objecttype(cls, name, bases, attrs)
def is_object_type(cls): # noqa: N805
return issubclass(cls, ObjectType)
@staticmethod
def _get_interface_options(meta):
return Options(
meta,
name=None,
description=None,
graphql_type=None,
)
@staticmethod
def _create_interface(cls, name, bases, attrs):
options = cls._get_interface_options(attrs.pop('Meta', None))
fields = get_fields(Interface, attrs, bases)
attrs = attrs_without_fields(attrs, fields)
cls = type.__new__(cls, name, bases, dict(attrs, _meta=options))
if not options.graphql_type:
fields = copy_fields(Field, fields, parent=cls)
options.get_fields = lambda: fields
options.graphql_type = generate_interface(cls)
else:
assert not fields, "Can't mount Fields in an Interface with a defined graphql_type"
fields = copy_fields(Field, options.graphql_type.get_fields(), parent=cls)
options.get_fields = lambda: fields
for name, field in fields.items():
setattr(cls, field.attname or name, field)
return cls
@staticmethod
def _create_objecttype(cls, name, bases, attrs):
options = Options(
attrs.pop('Meta', None),
name=None,
description=None,
graphql_type=None,
name=name,
description=attrs.get('__doc__'),
interfaces=(),
)
interfaces = tuple(options.interfaces)
fields = get_fields(ObjectType, attrs, bases, interfaces)
attrs = attrs_without_fields(attrs, fields)
cls = type.__new__(cls, name, bases, dict(attrs, _meta=options))
attrs = merge_fields_in_attrs(bases, attrs)
options.fields = get_fields_in_type(ObjectType, attrs)
yank_fields_from_attrs(attrs, options.fields)
if not options.graphql_type:
fields = copy_fields(Field, fields, parent=cls)
inherited_interfaces = tuple(b for b in bases if issubclass(b, Interface))
options.get_fields = lambda: fields
options.interfaces = interfaces + inherited_interfaces
options.get_interfaces = tuple(get_interfaces(options.interfaces))
options.graphql_type = generate_objecttype(cls)
for i in options.interfaces:
if inspect.isclass(i) and issubclass(i, Interface):
i.implements(cls)
else:
assert not fields, "Can't mount Fields in an ObjectType with a defined graphql_type"
assert not interfaces, "Can't have extra interfaces with a defined graphql_type"
fields = copy_fields(Field, options.graphql_type.get_fields(), parent=cls)
return type.__new__(cls, name, bases, dict(attrs, _meta=options))
options.get_fields = lambda: fields
for name, field in fields.items():
setattr(cls, field.attname or name, field)
return cls
def __str__(cls):
return cls._meta.name
class ObjectType(six.with_metaclass(ObjectTypeMeta)):
is_type_of = None
def __init__(self, *args, **kwargs):
# GraphQL ObjectType acting as container
args_len = len(args)
fields = self._meta.get_fields().items()
for name, f in fields:
setattr(self, getattr(f, 'attname', name), None)
fields = self._meta.fields.items()
if args_len > len(fields):
# Daft, but matches old exception sans the err msg.
raise IndexError("Number of args exceeds number of fields")
@ -134,19 +47,16 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta)):
if not kwargs:
for val, (name, field) in zip(args, fields_iter):
attname = getattr(field, 'attname', name)
setattr(self, attname, val)
setattr(self, name, val)
else:
for val, (name, field) in zip(args, fields_iter):
attname = getattr(field, 'attname', name)
setattr(self, attname, val)
kwargs.pop(attname, None)
setattr(self, name, val)
kwargs.pop(name, None)
for name, field in fields_iter:
try:
attname = getattr(field, 'attname', name)
val = kwargs.pop(attname)
setattr(self, attname, val)
val = kwargs.pop(name)
setattr(self, name, val)
except KeyError:
pass
@ -159,28 +69,8 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta)):
pass
if kwargs:
raise TypeError(
"'%s' is an invalid keyword argument for this function" %
list(kwargs)[0])
@classmethod
def is_type_of(cls, interface, context, info):
from ..utils.get_graphql_type import get_graphql_type
try:
graphql_type = get_graphql_type(type(interface))
return graphql_type.name == cls._meta.graphql_type.name
except:
return False
class Interface(six.with_metaclass(ObjectTypeMeta)):
resolve_type = None
def __init__(self, *args, **kwargs):
from .objecttype import ObjectType
if not isinstance(self, ObjectType):
raise Exception("An interface cannot be intitialized")
super(Interface, self).__init__(*args, **kwargs)
@classmethod
def implements(cls, objecttype):
pass
"'{}' is an invalid keyword argument for {}".format(
list(kwargs)[0],
self.__class__.__name__
)
)

View File

@ -1,3 +1,4 @@
import inspect
from ..utils.props import props
@ -8,9 +9,11 @@ class Options(object):
'''
def __init__(self, meta=None, **defaults):
self.add_attrs_from_meta(meta, defaults)
if meta:
assert inspect.isclass(meta), (
'Meta have to be a class, received "{}".'.format(repr(meta))
)
def add_attrs_from_meta(self, meta, defaults):
meta_attrs = props(meta) if meta else {}
for attr_name, value in defaults.items():
if attr_name in meta_attrs:
@ -27,3 +30,6 @@ class Options(object):
','.join(meta_attrs.keys())
)
)
def __repr__(self):
return '<Meta \n{} >'.format(props(self))

View File

@ -1,9 +1,7 @@
import six
from graphql import (GraphQLBoolean, GraphQLFloat, GraphQLID, GraphQLInt,
GraphQLString)
from graphql.language.ast import BooleanValue, FloatValue, IntValue, StringValue
from ..generators import generate_scalar
from ..utils.is_base_type import is_base_type
from .options import Options
from .unmountedtype import UnmountedType
@ -21,34 +19,137 @@ class ScalarTypeMeta(type):
options = Options(
attrs.pop('Meta', None),
name=None,
description=None,
graphql_type=None
name=name,
description=attrs.get('__doc__'),
)
cls = super_new(cls, name, bases, dict(attrs, _meta=options))
return super_new(cls, name, bases, dict(attrs, _meta=options))
if not options.graphql_type:
options.graphql_type = generate_scalar(cls)
return cls
def __str__(cls):
return cls._meta.name
class Scalar(six.with_metaclass(ScalarTypeMeta, UnmountedType)):
pass
serialize = None
parse_value = None
parse_literal = None
@classmethod
def get_type(cls):
return cls
# As per the GraphQL Spec, Integers are only treated as valid when a valid
# 32-bit signed integer, providing the broadest support across platforms.
#
# n.b. JavaScript's integers are safe between -(2^53 - 1) and 2^53 - 1 because
# they are internally represented as IEEE 754 doubles.
MAX_INT = 2147483647
MIN_INT = -2147483648
def construct_scalar_class(graphql_type):
# This is equivalent to
# class String(Scalar):
# class Meta:
# graphql_type = graphql_type
meta_class = type('Meta', (object,), {'graphql_type': graphql_type})
return type(graphql_type.name, (Scalar, ), {'Meta': meta_class})
class Int(Scalar):
'''
The `Int` scalar type represents non-fractional signed whole numeric
values. Int can represent values between -(2^53 - 1) and 2^53 - 1 since
represented in JSON as double-precision floating point numbers specified
by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).
'''
@staticmethod
def coerce_int(value):
try:
num = int(value)
except ValueError:
try:
num = int(float(value))
except ValueError:
return None
if MIN_INT <= num <= MAX_INT:
return num
serialize = coerce_int
parse_value = coerce_int
@staticmethod
def parse_literal(ast):
if isinstance(ast, IntValue):
num = int(ast.value)
if MIN_INT <= num <= MAX_INT:
return num
String = construct_scalar_class(GraphQLString)
Int = construct_scalar_class(GraphQLInt)
Float = construct_scalar_class(GraphQLFloat)
Boolean = construct_scalar_class(GraphQLBoolean)
ID = construct_scalar_class(GraphQLID)
class Float(Scalar):
'''
The `Float` scalar type represents signed double-precision fractional
values as specified by
[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).
'''
@staticmethod
def coerce_float(value):
try:
return float(value)
except ValueError:
return None
serialize = coerce_float
parse_value = coerce_float
@staticmethod
def parse_literal(ast):
if isinstance(ast, (FloatValue, IntValue)):
return float(ast.value)
class String(Scalar):
'''
The `String` scalar type represents textual data, represented as UTF-8
character sequences. The String type is most often used by GraphQL to
represent free-form human-readable text.
'''
@staticmethod
def coerce_string(value):
if isinstance(value, bool):
return u'true' if value else u'false'
return six.text_type(value)
serialize = coerce_string
parse_value = coerce_string
@staticmethod
def parse_literal(ast):
if isinstance(ast, StringValue):
return ast.value
class Boolean(Scalar):
'''
The `Boolean` scalar type represents `true` or `false`.
'''
serialize = bool
parse_value = bool
@staticmethod
def parse_literal(ast):
if isinstance(ast, BooleanValue):
return ast.value
class ID(Scalar):
'''
The `ID` scalar type represents a unique identifier, often used to
refetch an object or as key for a cache. The ID type appears in a JSON
response as a String; however, it is not intended to be human-readable.
When expected as an input type, any string (such as `"4"`) or integer
(such as `4`) input value will be accepted as an ID.
'''
serialize = str
parse_value = str
@staticmethod
def parse_literal(ast):
if isinstance(ast, (StringValue, IntValue)):
return ast.value

View File

@ -1,8 +1,14 @@
from graphql import GraphQLSchema, graphql
import inspect
from graphql import GraphQLSchema, graphql, is_type
from graphql.utils.introspection_query import introspection_query
from graphql.utils.schema_printer import print_schema
from ..utils.get_graphql_type import get_graphql_type
from .objecttype import ObjectType
from .structures import List, NonNull
from .scalars import Scalar, String
# from ..utils.get_graphql_type import get_graphql_type
# from graphql.type.schema import assert_object_implements_interface
@ -10,32 +16,60 @@ from ..utils.get_graphql_type import get_graphql_type
# from collections import defaultdict
from graphql.type.directives import (GraphQLDirective, GraphQLIncludeDirective,
GraphQLSkipDirective)
from graphql.type.introspection import IntrospectionSchema
from .typemap import TypeMap, is_graphene_type
class Schema(GraphQLSchema):
def __init__(self, query=None, mutation=None, subscription=None, directives=None, types=None, executor=None):
if query:
query = get_graphql_type(query)
if mutation:
mutation = get_graphql_type(mutation)
if subscription:
subscription = get_graphql_type(subscription)
self._query = query
self._mutation = mutation
self._subscription = subscription
self.types = types
self._executor = executor
super(Schema, self).__init__(
query=query,
mutation=mutation,
subscription=subscription,
directives=directives,
types=self.types
if directives is None:
directives = [
GraphQLIncludeDirective,
GraphQLSkipDirective
]
assert all(isinstance(d, GraphQLDirective) for d in directives), \
'Schema directives must be List[GraphQLDirective] if provided but got: {}.'.format(
directives
)
@property
def types(self):
return map(get_graphql_type, self._types or [])
self._directives = directives
initial_types = [
query,
mutation,
subscription,
IntrospectionSchema
]
if types:
initial_types += types
self._type_map = TypeMap(initial_types)
@types.setter
def types(self, value):
self._types = value
def get_query_type(self):
return self.get_graphql_type(self._query)
def get_mutation_type(self):
return self.get_graphql_type(self._mutation)
def get_subscription_type(self):
return self.get_graphql_type(self._subscription)
def get_graphql_type(self, _type):
if is_type(_type):
return _type
if is_graphene_type(_type):
graphql_type = self.get_type(_type._meta.name)
assert graphql_type, "Type {} not found in this schema.".format(_type._meta.name)
assert graphql_type.graphene_type == _type
return graphql_type
raise Exception("{} is not a valid GraphQL type.".format(_type))
def execute(self, request_string='', root_value=None, variable_values=None,
context_value=None, operation_name=None, executor=None):
@ -50,7 +84,7 @@ class Schema(GraphQLSchema):
)
def register(self, object_type):
self._types.append(object_type)
self.types.append(object_type)
def introspect(self):
return self.execute(introspection_query).data
@ -58,9 +92,6 @@ class Schema(GraphQLSchema):
def __str__(self):
return print_schema(self)
def get_type(self, _type):
return self._type_map[_type]
def lazy(self, _type):
return lambda: self.get_type(_type)

View File

@ -1,7 +1,3 @@
import inspect
from graphql import GraphQLList, GraphQLNonNull
from .unmountedtype import UnmountedType
@ -18,21 +14,12 @@ class Structure(UnmountedType):
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):
def __str__(self):
return '[{}]'.format(self.of_type)
class List(Structure, GraphQLList):
pass
class NonNull(Structure, GraphQLNonNull):
pass
class NonNull(Structure):
def __str__(self):
return '{}!'.format(self.of_type)

View File

@ -1,64 +0,0 @@
import copy
import pytest
from graphql import GraphQLArgument, GraphQLString
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,12 +1,9 @@
from graphql.type import GraphQLEnumType
from ..argument import Argument
from ..enum import Enum, PyEnum
from ..field import Field
def test_enum_construction():
class RGB(Enum):
'''Description'''
RED = 1
GREEN = 2
BLUE = 3
@ -15,8 +12,10 @@ def test_enum_construction():
def description(self):
return "Description {}".format(self.name)
assert isinstance(RGB._meta.graphql_type, GraphQLEnumType)
values = RGB._meta.graphql_type.get_values()
assert RGB._meta.name == 'RGB'
assert RGB._meta.description == 'Description'
values = RGB._meta.enum.__members__.values()
assert sorted([v.name for v in values]) == [
'BLUE',
'GREEN',
@ -27,37 +26,40 @@ def test_enum_construction():
'Description GREEN',
'Description RED'
]
assert isinstance(RGB(name='field_name').as_field(), Field)
assert isinstance(RGB(name='field_name').as_argument(), Argument)
def test_enum_construction_meta():
class RGB(Enum):
class Meta:
name = 'RGBEnum'
description = 'Description'
RED = 1
GREEN = 2
BLUE = 3
assert RGB._meta.name == 'RGBEnum'
assert RGB._meta.description == 'Description'
def test_enum_instance_construction():
RGB = Enum('RGB', 'RED,GREEN,BLUE')
assert isinstance(RGB._meta.graphql_type, GraphQLEnumType)
values = RGB._meta.graphql_type.get_values()
values = RGB._meta.enum.__members__.values()
assert sorted([v.name for v in values]) == [
'BLUE',
'GREEN',
'RED'
]
assert isinstance(RGB(name='field_name').as_field(), Field)
assert isinstance(RGB(name='field_name').as_argument(), Argument)
def test_enum_from_builtin_enum():
PyRGB = PyEnum('RGB', 'RED,GREEN,BLUE')
RGB = Enum.from_enum(PyRGB)
assert isinstance(RGB._meta.graphql_type, GraphQLEnumType)
values = RGB._meta.graphql_type.get_values()
assert sorted([v.name for v in values]) == [
'BLUE',
'GREEN',
'RED'
]
assert isinstance(RGB(name='field_name').as_field(), Field)
assert isinstance(RGB(name='field_name').as_argument(), Argument)
assert RGB._meta.enum == PyRGB
assert RGB.RED
assert RGB.GREEN
assert RGB.BLUE
def test_enum_value_from_class():
@ -67,3 +69,5 @@ def test_enum_value_from_class():
BLUE = 3
assert RGB.RED.value == 1
assert RGB.GREEN.value == 2
assert RGB.BLUE.value == 3

View File

@ -1,67 +1,77 @@
import copy
import pytest
from graphql import GraphQLField, GraphQLInt, GraphQLNonNull, GraphQLString
from ..argument import Argument
from ..field import Field
from ..scalars import Int, String
from ..structures import NonNull
from ..argument import Argument
def test_field():
field = Field(GraphQLString, name="name", description="description")
assert isinstance(field, GraphQLField)
assert field.name == "name"
assert field.description == "description"
assert field.type == GraphQLString
class MyInstance(object):
value = 'value'
value_func = staticmethod(lambda: 'value_func')
def test_field_basic():
MyType = object()
args = {'my arg': Argument(True)}
resolver = lambda: None
deprecation_reason = 'Deprecated now'
description = 'My Field'
field = Field(
MyType,
name='name',
args=args,
resolver=resolver,
description=description,
deprecation_reason=deprecation_reason
)
assert field.name == 'name'
assert field.args == args
assert field.resolver == resolver
assert field.deprecation_reason == deprecation_reason
assert field.description == description
def test_field_required():
field = Field(GraphQLString, required=True)
assert isinstance(field, GraphQLField)
assert isinstance(field.type, GraphQLNonNull)
assert field.type.of_type == GraphQLString
MyType = object()
field = Field(MyType, required=True)
assert isinstance(field.type, NonNull)
assert field.type.of_type == MyType
def test_field_wrong_name():
with pytest.raises(AssertionError) as excinfo:
Field(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_field_source():
MyType = object()
field = Field(MyType, source='value')
assert field.resolver(MyInstance, {}, None, None) == MyInstance.value
def test_not_source_and_resolver():
with pytest.raises(AssertionError) as excinfo:
Field(GraphQLString, source="a", resolver=lambda *_: None)
assert "You cannot have a source and a resolver at the same time" == str(excinfo.value)
def test_field_with_lazy_type():
MyType = object()
field = Field(lambda: MyType)
assert field.type == MyType
def test_copy_field_works():
field = Field(GraphQLString)
copy.copy(field)
def test_field_not_source_and_resolver():
MyType = object()
with pytest.raises(Exception) as exc_info:
Field(MyType, source='value', resolver=lambda: None)
assert str(exc_info.value) == 'A Field cannot have a source and a resolver in at the same time.'
def test_field_callable_type():
field = Field(lambda: GraphQLString)
assert field.type == GraphQLString
def test_field_source_func():
MyType = object()
field = Field(MyType, source='value_func')
assert field.resolver(MyInstance(), {}, None, None) == MyInstance.value_func()
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
def test_field_source_argument_as_kw():
MyType = object()
field = Field(MyType, b=NonNull(True), c=Argument(None), a=NonNull(False))
assert field.args.keys() == ['b', 'c', 'a']
assert isinstance(field.args['b'], Argument)
assert isinstance(field.args['b'].type, NonNull)
assert field.args['b'].type.of_type is True
assert isinstance(field.args['c'], Argument)
assert field.args['c'].type is None
assert isinstance(field.args['a'], Argument)
assert isinstance(field.args['a'].type, NonNull)
assert field.args['a'].type.of_type is False

View File

@ -1,57 +1,83 @@
import pytest
from graphql import GraphQLInputObjectType, GraphQLString
from graphql.type.definition import GraphQLInputFieldDefinition
from ..field import InputField
from ..field import Field
from ..inputfield import InputField
from ..inputobjecttype import InputObjectType
from ..scalars import String
from ..unmountedtype import UnmountedType
from ..abstracttype import AbstractType
class MyType(object):
pass
class MyScalar(UnmountedType):
def get_type(self):
return MyType
def test_generate_inputobjecttype():
class MyObjectType(InputObjectType):
class MyInputObjectType(InputObjectType):
'''Documentation'''
graphql_type = MyObjectType._meta.graphql_type
assert isinstance(graphql_type, GraphQLInputObjectType)
assert graphql_type.name == "MyObjectType"
assert graphql_type.description == "Documentation"
assert MyInputObjectType._meta.name == "MyInputObjectType"
assert MyInputObjectType._meta.description == "Documentation"
assert MyInputObjectType._meta.fields == {}
def test_generate_inputobjecttype_with_meta():
class MyObjectType(InputObjectType):
class MyInputObjectType(InputObjectType):
class Meta:
name = 'MyOtherObjectType'
name = 'MyOtherInputObjectType'
description = 'Documentation'
graphql_type = MyObjectType._meta.graphql_type
assert isinstance(graphql_type, GraphQLInputObjectType)
assert graphql_type.name == "MyOtherObjectType"
assert graphql_type.description == "Documentation"
assert MyInputObjectType._meta.name == "MyOtherInputObjectType"
assert MyInputObjectType._meta.description == "Documentation"
def test_empty_inputobjecttype_has_meta():
class MyObjectType(InputObjectType):
pass
def test_generate_inputobjecttype_with_fields():
class MyInputObjectType(InputObjectType):
field = Field(MyType)
assert MyObjectType._meta
assert 'field' in MyInputObjectType._meta.fields
def test_generate_objecttype_with_fields():
class MyObjectType(InputObjectType):
field = InputField(GraphQLString)
def test_ordered_fields_in_inputobjecttype():
class MyInputObjectType(InputObjectType):
b = InputField(MyType)
a = InputField(MyType)
field = MyScalar()
asa = InputField(MyType)
graphql_type = MyObjectType._meta.graphql_type
fields = graphql_type.get_fields()
assert 'field' in fields
assert isinstance(fields['field'], GraphQLInputFieldDefinition)
assert list(MyInputObjectType._meta.fields.keys()) == ['b', 'a', 'field', 'asa']
def test_generate_objecttype_with_graphene_fields():
class MyObjectType(InputObjectType):
field = String()
def test_generate_inputobjecttype_unmountedtype():
class MyInputObjectType(InputObjectType):
field = MyScalar(MyType)
graphql_type = MyObjectType._meta.graphql_type
fields = graphql_type.get_fields()
assert 'field' in fields
assert isinstance(fields['field'], GraphQLInputFieldDefinition)
assert 'field' in MyInputObjectType._meta.fields
assert isinstance(MyInputObjectType._meta.fields['field'], InputField)
def test_generate_inputobjecttype_inherit_abstracttype():
class MyAbstractType(AbstractType):
field1 = MyScalar(MyType)
class MyInputObjectType(InputObjectType, MyAbstractType):
field2 = MyScalar(MyType)
assert MyInputObjectType._meta.fields.keys() == ['field1', 'field2']
assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [InputField, InputField]
def test_generate_inputobjecttype_inherit_abstracttype_reversed():
class MyAbstractType(AbstractType):
field1 = MyScalar(MyType)
class MyInputObjectType(MyAbstractType, InputObjectType):
field2 = MyScalar(MyType)
assert MyInputObjectType._meta.fields.keys() == ['field1', 'field2']
assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [InputField, InputField]

View File

@ -1,19 +1,27 @@
import pytest
from graphql import GraphQLField, GraphQLInterfaceType, GraphQLString
from ..field import Field
from ..interface import Interface
from ..unmountedtype import UnmountedType
from ..abstracttype import AbstractType
class MyType(object):
pass
class MyScalar(UnmountedType):
def get_type(self):
return MyType
def test_generate_interface():
class MyInterface(Interface):
'''Documentation'''
graphql_type = MyInterface._meta.graphql_type
assert isinstance(graphql_type, GraphQLInterfaceType)
assert graphql_type.name == "MyInterface"
assert graphql_type.description == "Documentation"
assert MyInterface._meta.name == "MyInterface"
assert MyInterface._meta.description == "Documentation"
assert MyInterface._meta.fields == {}
def test_generate_interface_with_meta():
@ -23,62 +31,52 @@ def test_generate_interface_with_meta():
name = 'MyOtherInterface'
description = 'Documentation'
graphql_type = MyInterface._meta.graphql_type
assert isinstance(graphql_type, GraphQLInterfaceType)
assert graphql_type.name == "MyOtherInterface"
assert graphql_type.description == "Documentation"
def test_empty_interface_has_meta():
class MyInterface(Interface):
pass
assert MyInterface._meta
assert MyInterface._meta.name == "MyOtherInterface"
assert MyInterface._meta.description == "Documentation"
def test_generate_interface_with_fields():
class MyInterface(Interface):
field = Field(GraphQLString)
field = Field(MyType)
graphql_type = MyInterface._meta.graphql_type
fields = graphql_type.get_fields()
assert 'field' in fields
assert 'field' in MyInterface._meta.fields
def test_interface_inheritance():
class MyInheritedInterface(Interface):
inherited = Field(GraphQLString)
class MyInterface(MyInheritedInterface):
field = Field(GraphQLString)
graphql_type = MyInterface._meta.graphql_type
fields = graphql_type.get_fields()
assert 'field' in fields
assert 'inherited' in fields
assert MyInterface.field > MyInheritedInterface.inherited
def test_interface_instance():
def test_ordered_fields_in_interface():
class MyInterface(Interface):
inherited = Field(GraphQLString)
b = Field(MyType)
a = Field(MyType)
field = MyScalar()
asa = Field(MyType)
with pytest.raises(Exception) as excinfo:
MyInterface()
assert "An interface cannot be intitialized" in str(excinfo.value)
assert list(MyInterface._meta.fields.keys()) == ['b', 'a', 'field', 'asa']
def test_interface_add_fields_in_reused_graphql_type():
MyGraphQLType = GraphQLInterfaceType('MyGraphQLType', fields={
'field': GraphQLField(GraphQLString)
})
def test_generate_interface_unmountedtype():
class MyInterface(Interface):
field = MyScalar()
with pytest.raises(AssertionError) as excinfo:
class GrapheneInterface(Interface):
field = Field(GraphQLString)
assert 'field' in MyInterface._meta.fields
assert isinstance(MyInterface._meta.fields['field'], Field)
class Meta:
graphql_type = MyGraphQLType
assert """Can't mount Fields in an Interface with a defined graphql_type""" == str(excinfo.value)
def test_generate_interface_inherit_abstracttype():
class MyAbstractType(AbstractType):
field1 = MyScalar()
class MyInterface(Interface, MyAbstractType):
field2 = MyScalar()
assert MyInterface._meta.fields.keys() == ['field1', 'field2']
assert [type(x) for x in MyInterface._meta.fields.values()] == [Field, Field]
def test_generate_interface_inherit_abstracttype_reversed():
class MyAbstractType(AbstractType):
field1 = MyScalar()
class MyInterface(MyAbstractType, Interface):
field2 = MyScalar()
assert MyInterface._meta.fields.keys() == ['field1', 'field2']
assert [type(x) for x in MyInterface._meta.fields.values()] == [Field, Field]

View File

@ -1,7 +1,5 @@
import pytest
from graphql import GraphQLObjectType, GraphQLString
from ..field import Field
from ..mutation import Mutation
from ..objecttype import ObjectType
@ -16,31 +14,29 @@ def test_generate_mutation_no_args():
pass
assert issubclass(MyMutation, ObjectType)
graphql_type = MyMutation._meta.graphql_type
assert isinstance(graphql_type, GraphQLObjectType)
assert graphql_type.name == "MyMutation"
assert graphql_type.description == "Documentation"
assert MyMutation._meta.name == "MyMutation"
assert MyMutation._meta.description == "Documentation"
assert MyMutation.Field().resolver == MyMutation.mutate
def test_generate_mutation_with_args():
class MyMutation(Mutation):
'''Documentation'''
class Input:
s = String()
# def test_generate_mutation_with_args():
# class MyMutation(Mutation):
# '''Documentation'''
# class Input:
# s = String()
@classmethod
def mutate(cls, *args, **kwargs):
pass
# @classmethod
# def mutate(cls, *args, **kwargs):
# pass
graphql_type = MyMutation._meta.graphql_type
field = MyMutation.Field()
assert isinstance(graphql_type, GraphQLObjectType)
assert graphql_type.name == "MyMutation"
assert graphql_type.description == "Documentation"
assert isinstance(field, Field)
assert field.type == MyMutation._meta.graphql_type
assert 's' in field.args
assert field.args['s'].type == GraphQLString
# graphql_type = MyMutation._meta.graphql_type
# field = MyMutation.Field()
# assert graphql_type.name == "MyMutation"
# assert graphql_type.description == "Documentation"
# assert isinstance(field, Field)
# assert field.type == MyMutation._meta.graphql_type
# assert 's' in field.args
# assert field.args['s'].type == String
def test_generate_mutation_with_meta():
@ -54,20 +50,9 @@ def test_generate_mutation_with_meta():
def mutate(cls, *args, **kwargs):
pass
graphql_type = MyMutation._meta.graphql_type
assert isinstance(graphql_type, GraphQLObjectType)
assert graphql_type.name == "MyOtherMutation"
assert graphql_type.description == "Documentation"
def test_empty_mutation_has_meta():
class MyMutation(Mutation):
@classmethod
def mutate(cls, *args, **kwargs):
pass
assert MyMutation._meta
assert MyMutation._meta.name == "MyOtherMutation"
assert MyMutation._meta.description == "Documentation"
assert MyMutation.Field().resolver == MyMutation.mutate
def test_mutation_raises_exception_if_no_mutate():

View File

@ -1,26 +1,33 @@
import pytest
from graphql import (GraphQLField, GraphQLInterfaceType, GraphQLObjectType,
GraphQLString)
from ..field import Field
from ..interface import Interface
from ..objecttype import ObjectType
from ..unmountedtype import UnmountedType
from ..abstracttype import AbstractType
class MyType(object):
pass
class Container(ObjectType):
field1 = Field(GraphQLString, name='field1')
field2 = Field(GraphQLString, name='field2')
field1 = Field(MyType)
field2 = Field(MyType)
class MyScalar(UnmountedType):
def get_type(self):
return MyType
def test_generate_objecttype():
class MyObjectType(ObjectType):
'''Documentation'''
graphql_type = MyObjectType._meta.graphql_type
assert isinstance(graphql_type, GraphQLObjectType)
assert graphql_type.name == "MyObjectType"
assert graphql_type.description == "Documentation"
assert MyObjectType._meta.name == "MyObjectType"
assert MyObjectType._meta.description == "Documentation"
assert MyObjectType._meta.interfaces == tuple()
assert MyObjectType._meta.fields == {}
def test_generate_objecttype_with_meta():
@ -29,66 +36,62 @@ def test_generate_objecttype_with_meta():
class Meta:
name = 'MyOtherObjectType'
description = 'Documentation'
interfaces = (MyType, )
graphql_type = MyObjectType._meta.graphql_type
assert isinstance(graphql_type, GraphQLObjectType)
assert graphql_type.name == "MyOtherObjectType"
assert graphql_type.description == "Documentation"
def test_empty_objecttype_has_meta():
class MyObjectType(ObjectType):
pass
assert MyObjectType._meta
assert MyObjectType._meta.name == "MyOtherObjectType"
assert MyObjectType._meta.description == "Documentation"
assert MyObjectType._meta.interfaces == (MyType, )
def test_generate_objecttype_with_fields():
class MyObjectType(ObjectType):
field = Field(GraphQLString)
field = Field(MyType)
graphql_type = MyObjectType._meta.graphql_type
fields = graphql_type.get_fields()
assert 'field' in fields
assert 'field' in MyObjectType._meta.fields
def test_ordered_fields_in_objecttype():
class MyObjectType(ObjectType):
b = Field(GraphQLString)
a = Field(GraphQLString)
field = Field(GraphQLString)
asa = Field(GraphQLString)
b = Field(MyType)
a = Field(MyType)
field = MyScalar()
asa = Field(MyType)
graphql_type = MyObjectType._meta.graphql_type
fields = graphql_type.get_fields()
assert list(fields.keys()) == ['b', 'a', 'field', 'asa']
assert list(MyObjectType._meta.fields.keys()) == ['b', 'a', 'field', 'asa']
def test_objecttype_inheritance():
class MyInheritedObjectType(ObjectType):
inherited = Field(GraphQLString)
def test_generate_objecttype_inherit_abstracttype():
class MyAbstractType(AbstractType):
field1 = MyScalar()
class MyObjectType(MyInheritedObjectType):
field1 = Field(GraphQLString)
field2 = Field(GraphQLString)
class MyObjectType(ObjectType, MyAbstractType):
field2 = MyScalar()
graphql_type = MyObjectType._meta.graphql_type
fields = graphql_type.get_fields()
assert list(fields.keys()) == ['inherited', 'field1', 'field2']
assert MyObjectType._meta.fields.keys() == ['field1', 'field2']
assert [type(x) for x in MyObjectType._meta.fields.values()] == [Field, Field]
def test_objecttype_as_container_get_fields():
def test_generate_objecttype_inherit_abstracttype_reversed():
class MyAbstractType(AbstractType):
field1 = MyScalar()
class Container(ObjectType):
field1 = Field(GraphQLString)
field2 = Field(GraphQLString)
class MyObjectType(MyAbstractType, ObjectType):
field2 = MyScalar()
assert list(Container._meta.graphql_type.get_fields().keys()) == ['field1', 'field2']
assert MyObjectType._meta.fields.keys() == ['field1', 'field2']
assert [type(x) for x in MyObjectType._meta.fields.values()] == [Field, Field]
def test_generate_objecttype_unmountedtype():
class MyObjectType(ObjectType):
field = MyScalar()
assert 'field' in MyObjectType._meta.fields
assert isinstance(MyObjectType._meta.fields['field'], Field)
def test_parent_container_get_fields():
fields = Container._meta.graphql_type.get_fields()
assert list(fields.keys()) == ['field1', 'field2']
assert list(Container._meta.fields.keys()) == ['field1', 'field2']
def test_objecttype_as_container_only_args():
@ -125,130 +128,4 @@ def test_objecttype_as_container_invalid_kwargs():
with pytest.raises(TypeError) as excinfo:
Container(unexisting_field="3")
assert "'unexisting_field' is an invalid keyword argument for this function" == str(excinfo.value)
def test_objecttype_reuse_graphql_type():
MyGraphQLType = GraphQLObjectType('MyGraphQLType', fields={
'field': GraphQLField(GraphQLString)
})
class GrapheneObjectType(ObjectType):
class Meta:
graphql_type = MyGraphQLType
graphql_type = GrapheneObjectType._meta.graphql_type
assert graphql_type == MyGraphQLType
instance = GrapheneObjectType(field="A")
assert instance.field == "A"
def test_objecttype_add_fields_in_reused_graphql_type():
MyGraphQLType = GraphQLObjectType('MyGraphQLType', fields={
'field': GraphQLField(GraphQLString)
})
with pytest.raises(AssertionError) as excinfo:
class GrapheneObjectType(ObjectType):
field = Field(GraphQLString)
class Meta:
graphql_type = MyGraphQLType
assert """Can't mount Fields in an ObjectType with a defined graphql_type""" == str(excinfo.value)
def test_objecttype_graphql_interface():
MyInterface = GraphQLInterfaceType('MyInterface', fields={
'field': GraphQLField(GraphQLString)
})
class GrapheneObjectType(ObjectType):
class Meta:
interfaces = [MyInterface]
graphql_type = GrapheneObjectType._meta.graphql_type
assert graphql_type.get_interfaces() == (MyInterface, )
# assert graphql_type.is_type_of(MyInterface, None, None)
fields = graphql_type.get_fields()
assert 'field' in fields
def test_objecttype_graphene_interface():
class GrapheneInterface(Interface):
name = Field(GraphQLString)
extended = Field(GraphQLString)
class GrapheneObjectType(ObjectType):
class Meta:
interfaces = [GrapheneInterface]
field = Field(GraphQLString)
graphql_type = GrapheneObjectType._meta.graphql_type
assert graphql_type.get_interfaces() == (GrapheneInterface._meta.graphql_type, )
assert graphql_type.is_type_of(GrapheneObjectType(), None, None)
fields = graphql_type.get_fields().keys() == ['name', 'extended', 'field']
def test_objecttype_graphene_inherit_interface():
class GrapheneInterface(Interface):
name = Field(GraphQLString)
extended = Field(GraphQLString)
class GrapheneObjectType(ObjectType, GrapheneInterface):
field = Field(GraphQLString)
graphql_type = GrapheneObjectType._meta.graphql_type
assert graphql_type.get_interfaces() == (GrapheneInterface._meta.graphql_type, )
assert graphql_type.is_type_of(GrapheneObjectType(), None, None)
fields = graphql_type.get_fields()
fields = graphql_type.get_fields().keys() == ['name', 'extended', 'field']
assert issubclass(GrapheneObjectType, GrapheneInterface)
# def test_objecttype_graphene_interface_extended():
# class GrapheneInterface(Interface):
# field = Field(GraphQLString)
# class GrapheneObjectType(ObjectType):
# class Meta:
# interfaces = [GrapheneInterface]
# schema = Schema(query=GrapheneObjectType)
# assert str(schema) == """
# schema {
# query: GrapheneObjectType
# }
# interface GrapheneInterface {
# field: String
# }
# type GrapheneObjectType implements GrapheneInterface {
# field: String
# }
# """.lstrip()
# GrapheneInterface._meta.graphql_type.add_field(Field(String, name='dynamic'))
# # GrapheneObjectType._meta.graphql_type._field_map = None
# assert GrapheneInterface._meta.graphql_type.get_fields().keys() == ['field', 'dynamic']
# assert GrapheneObjectType._meta.graphql_type.get_fields().keys() == ['field', 'dynamic']
# schema.rebuild()
# assert str(schema) == """
# schema {
# query: GrapheneObjectType
# }
# interface GrapheneInterface {
# field: String
# dynamic: String
# }
# type GrapheneObjectType implements GrapheneInterface {
# field: String
# dynamic: String
# }
# """.lstrip()
assert "'unexisting_field' is an invalid keyword argument for Container" == str(excinfo.value)

View File

@ -1,23 +0,0 @@
import pytest
from ..options import Options
def test_options_defaults():
class Meta:
valid_second = True
options = Options(Meta, valid_second=False, valid_first=False)
assert not options.valid_first
assert options.valid_second
def test_options_invalid_attrs():
class Meta:
invalid = True
with pytest.raises(TypeError) as excinfo:
Options(Meta, valid=True)
assert "Invalid attributes: invalid" == str(excinfo.value)

View File

@ -1,144 +0,0 @@
import datetime
import pytest
from graphene.utils.get_graphql_type import get_graphql_type
from graphql import graphql
from graphql.language import ast
from graphql.type import (GraphQLBoolean, GraphQLFieldDefinition, GraphQLFloat,
GraphQLInt, GraphQLScalarType, GraphQLString)
from ..field import Field
from ..objecttype import ObjectType
from ..scalars import Boolean, Float, Int, Scalar, String
from ..schema import Schema
class DatetimeScalar(Scalar):
class Meta:
name = 'DateTime'
@staticmethod
def serialize(dt):
assert isinstance(dt, datetime.datetime)
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")
def serialize_date_time(dt):
assert isinstance(dt, datetime.datetime)
return dt.isoformat()
def parse_literal(node):
if isinstance(node, ast.StringValue):
return datetime.datetime.strptime(node.value, "%Y-%m-%dT%H:%M:%S.%f")
def parse_value(value):
return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")
GraphQLDateTimeType = GraphQLScalarType(
name='DateTime',
serialize=serialize_date_time,
parse_literal=parse_literal,
parse_value=parse_value
)
class DatetimeScalarGraphQL(Scalar):
class Meta:
graphql_type = GraphQLDateTimeType
scalar_classes = {
DatetimeScalar: DatetimeScalar._meta.graphql_type,
DatetimeScalarGraphQL: GraphQLDateTimeType,
String: GraphQLString,
Int: GraphQLInt,
Float: GraphQLFloat,
Boolean: GraphQLBoolean,
}
@pytest.mark.parametrize("scalar_class,expected_graphql_type", scalar_classes.items())
def test_scalar_as_field(scalar_class, expected_graphql_type):
field_before = Field(None)
scalar = scalar_class()
field = scalar.as_field()
graphql_type = get_graphql_type(scalar_class)
field_after = Field(None)
assert isinstance(field, Field)
assert field.type == graphql_type
assert graphql_type == expected_graphql_type
assert field_before < field < field_after
@pytest.mark.parametrize("scalar_class,graphql_type", scalar_classes.items())
def test_scalar_in_objecttype(scalar_class, graphql_type):
class MyObjectType(ObjectType):
before = Field(scalar_class)
field = scalar_class()
after = Field(scalar_class)
graphql_type = get_graphql_type(MyObjectType)
fields = graphql_type.get_fields()
assert list(fields.keys()) == ['before', 'field', 'after']
assert isinstance(fields['field'], GraphQLFieldDefinition)
def test_custom_scalar_empty():
with pytest.raises(AssertionError) as excinfo:
class DatetimeScalar(Scalar):
pass
assert """DatetimeScalar must provide "serialize" function.""" in str(excinfo.value)
@pytest.mark.parametrize("scalar_class", (DatetimeScalar, DatetimeScalarGraphQL))
def test_custom_scalar_query(scalar_class):
class Query(ObjectType):
datetime = scalar_class(_in=scalar_class(name='in'))
def resolve_datetime(self, args, context, info):
return args.get('in')
now = datetime.datetime.now()
isoformat = now.isoformat()
schema = Schema(query=Query)
response = graphql(schema, '''
{
datetime(in: "%s")
}
''' % isoformat)
assert not response.errors
assert response.data == {
'datetime': isoformat
}
response = graphql(schema, '''
query Test($date: DateTime) {
datetime(in: $date)
}
''', variable_values={
'date': isoformat
})
assert not response.errors
assert response.data == {
'datetime': isoformat
}

View File

@ -1,96 +0,0 @@
from ..field import Field
from ..interface import Interface
from ..objecttype import ObjectType
from ..scalars import String
from ..schema import Schema
from ..structures import List
class Character(Interface):
name = String()
friends = List(lambda: Character)
best_friend = Field(lambda: Character)
def resolve_friends(self, *args):
return [Human(name='Peter')]
def resolve_best_friend(self, *args):
return Human(name='Best')
class Pet(ObjectType):
type = String()
class Human(ObjectType):
class Meta:
interfaces = [Character]
pet = Field(Pet)
def resolve_pet(self, *args):
return Pet(type='Dog')
class RootQuery(ObjectType):
character = Field(Character)
def resolve_character(self, *_):
return Human(name='Harry')
schema = Schema(query=RootQuery, types=[Human])
def test_schema():
executed = schema.execute(
'{ character {name, bestFriend { name }, friends { name}, ...on Human {pet { type } } } }')
assert not executed.errors
assert executed.data == {'character': {'name': 'Harry', 'bestFriend': {
'name': 'Best'}, 'friends': [{'name': 'Peter'}], 'pet': {'type': 'Dog'}}}
def test_schema_introspect():
introspection = schema.introspect()
assert '__schema' in introspection
def test_schema_str():
expected = """
schema {
query: RootQuery
}
interface Character {
name: String
friends: [Character]
bestFriend: Character
}
type Human implements Character {
name: String
friends: [Character]
bestFriend: Character
pet: Pet
}
type Pet {
type: String
}
type RootQuery {
character: Character
}
""".lstrip()
assert str(schema) == expected
def test_schema_get_type():
pet = schema.get_type('Pet')
assert pet == Pet._meta.graphql_type
def test_schema_lazy_type():
pet = schema.lazy('Pet')
assert pet() == Pet._meta.graphql_type

View File

@ -1,51 +0,0 @@
from graphql import GraphQLList, GraphQLNonNull, GraphQLString
from ..field import Field
from ..scalars import String
from ..structures import List, NonNull
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, GraphQLList)
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
def test_preserve_order():
field1 = List(lambda: None)
field2 = Field(lambda: None)
assert field1 < field2

View File

@ -1,6 +1,4 @@
from ..utils.orderedtype import OrderedType
from .argument import Argument
from .field import Field, InputField
class UnmountedType(OrderedType):
@ -18,17 +16,18 @@ class UnmountedType(OrderedType):
'''
def __init__(self, *args, **kwargs):
super(UnmountedType, self).__init__()
self.args = args
self.kwargs = kwargs
super(UnmountedType, self).__init__()
def get_type(self):
return self._meta.graphql_type
raise NotImplementedError("get_type not implemented in {}".format(self))
def as_field(self):
'''
Mount the UnmountedType as Field
'''
from .field import Field
return Field(
self.get_type(),
*self.args,
@ -40,6 +39,7 @@ class UnmountedType(OrderedType):
'''
Mount the UnmountedType as InputField
'''
from .inputfield import InputField
return InputField(
self.get_type(),
*self.args,
@ -51,9 +51,20 @@ class UnmountedType(OrderedType):
'''
Mount the UnmountedType as Argument
'''
from .argument import Argument
return Argument(
self.get_type(),
*self.args,
_creation_counter=self.creation_counter,
**self.kwargs
)
def __eq__(self, other):
return (
self is other or (
isinstance(other, UnmountedType) and
self.get_type() == other.get_type() and
self.args == other.args and
self.kwargs == other.kwargs
)
)

View File

@ -6,7 +6,7 @@ from .inputfield import InputField
def merge_fields_in_attrs(bases, attrs):
from ..new_types.abstracttype import AbstractType
from ..types.abstracttype import AbstractType
for base in bases:
if base == AbstractType or not issubclass(base, AbstractType):
continue
@ -26,10 +26,10 @@ def unmounted_field_in_type(attname, unmounted_field, type):
InputObjectType -> InputField
'''
# from ..types.inputobjecttype import InputObjectType
from ..new_types.objecttype import ObjectType
from ..new_types.interface import Interface
from ..new_types.abstracttype import AbstractType
from ..new_types.inputobjecttype import InputObjectType
from ..types.objecttype import ObjectType
from ..types.interface import Interface
from ..types.abstracttype import AbstractType
from ..types.inputobjecttype import InputObjectType
if issubclass(type, (ObjectType, Interface)):
return unmounted_field.as_field()