Next types

This commit is contained in:
Syrus Akbary 2016-08-10 22:03:38 -07:00
parent 04492600e5
commit 550ad386f0
14 changed files with 704 additions and 0 deletions

View File

View File

@ -0,0 +1,37 @@
import six
from collections import OrderedDict
from ..utils.is_base_type import is_base_type
from .options import Options
from .utils import get_fields_in_type, attrs_without_fields
def merge_fields_in_attrs(bases, attrs):
for base in bases:
if not issubclass(base, AbstractType):
continue
for name, field in base._meta.fields.items():
if name in attrs:
continue
attrs[name] = field
return attrs
class AbstractTypeMeta(type):
def __new__(cls, name, bases, attrs):
options = attrs.get('_meta', Options())
attrs = merge_fields_in_attrs(bases, attrs)
fields = get_fields_in_type(cls, attrs)
options.fields = OrderedDict(sorted(fields, key=lambda f: f[1]))
attrs = attrs_without_fields(attrs, fields)
cls = type.__new__(cls, name, bases, dict(attrs, _meta=options))
return cls
class AbstractType(six.with_metaclass(AbstractTypeMeta)):
pass

View File

@ -0,0 +1,70 @@
# import inspect
from functools import partial
from collections import OrderedDict
# from graphql.type import (GraphQLField, GraphQLInputObjectField)
# from graphql.utils.assert_valid_name import assert_valid_name
from ..utils.orderedtype import OrderedType
from .structures import NonNull
# from ..utils.str_converters import to_camel_case
# from .argument import to_arguments
# class AbstractField(object):
# @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
# @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
def source_resolver(source, root, args, context, info):
resolved = getattr(root, source, None)
if callable(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)
self.name = name
# self.attname = None
# self.parent = None
if required:
type = NonNull(type)
self.type = type
self.args = args or OrderedDict()
# self.args = to_arguments(args, extra_args)
assert not (source and resolver), ('You cannot provide a source and a '
'resolver in a Field at the same time.')
if source:
resolver = partial(source_resolver, source)
self.resolver = resolver
self.deprecation_reason = deprecation_reason
self.description = description

View File

@ -0,0 +1,41 @@
import six
from collections import OrderedDict
from ..utils.is_base_type import is_base_type
from .options import Options
from .utils import get_fields_in_type, attrs_without_fields
class InterfaceMeta(type):
def __new__(cls, name, bases, attrs):
# Also ensure initialization is only performed for subclasses of
# ObjectType
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__'),
)
fields = get_fields_in_type(Interface, attrs)
options.fields = OrderedDict(sorted(fields, key=lambda f: f[1]))
attrs = attrs_without_fields(attrs, fields)
cls = super(InterfaceMeta, cls).__new__(cls, name, bases, dict(attrs, _meta=options))
return cls
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

@ -0,0 +1,73 @@
import six
from collections import OrderedDict
from ..utils.is_base_type import is_base_type
from .options import Options
from .utils import get_fields_in_type, attrs_without_fields
class ObjectTypeMeta(type):
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=(),
)
fields = get_fields_in_type(ObjectType, attrs)
options.fields = OrderedDict(sorted(fields, key=lambda f: f[1]))
attrs = attrs_without_fields(attrs, fields)
cls = super(ObjectTypeMeta, cls).__new__(cls, name, bases, dict(attrs, _meta=options))
return cls
class ObjectType(six.with_metaclass(ObjectTypeMeta)):
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

@ -0,0 +1,37 @@
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))
)
self.add_attrs_from_meta(meta, defaults)
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:
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

@ -0,0 +1,23 @@
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):
pass
class NonNull(Structure):
pass

View File

View File

@ -0,0 +1,99 @@
import pytest
from ..field import Field
from ..abstracttype import AbstractType
from ..unmountedtype import UnmountedType
class MyType(object):
pass
class MyScalar(UnmountedType):
def get_type(self):
return MyType
def test_generate_abstracttype_with_fields():
class MyAbstractType(AbstractType):
field = Field(MyType)
assert 'field' in MyAbstractType._meta.fields
assert isinstance(MyAbstractType._meta.fields['field'], Field)
def test_generate_abstracttype_with_unmountedfields():
class MyAbstractType(AbstractType):
field = UnmountedType(MyType)
assert 'field' in MyAbstractType._meta.fields
assert isinstance(MyAbstractType._meta.fields['field'], UnmountedType)
def test_generate_abstracttype_inheritance():
class MyAbstractType1(AbstractType):
field1 = UnmountedType(MyType)
class MyAbstractType2(MyAbstractType1):
field2 = UnmountedType(MyType)
assert MyAbstractType2._meta.fields.keys() == ['field1', 'field2']
# 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_unmountedtype():
# class MyObjectType(ObjectType):
# field = MyScalar(MyType)
# 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

@ -0,0 +1,55 @@
import pytest
from ..field import Field
from ..structures import NonNull
class MyInstance(object):
value = 'value'
value_func = staticmethod(lambda: 'value_func')
def test_field_basic():
MyType = object()
args = {}
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_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) == 'You cannot provide a source and a resolver in a Field 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()

View File

@ -0,0 +1,59 @@
import pytest
from ..field import Field
from ..interface import Interface
from ..unmountedtype import UnmountedType
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(MyType)
assert 'field' in MyInterface._meta.fields
assert isinstance(MyInterface._meta.fields['field'], Field)

View File

@ -0,0 +1,108 @@
import pytest
from ..field import Field
from ..objecttype import ObjectType
from ..unmountedtype import UnmountedType
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_unmountedtype():
class MyObjectType(ObjectType):
field = MyScalar(MyType)
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

@ -0,0 +1,60 @@
from ..utils.orderedtype import OrderedType
# from .argument import Argument
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
# '''
# return InputField(
# self.get_type(),
# *self.args,
# _creation_counter=self.creation_counter,
# **self.kwargs
# )
# def as_argument(self):
# '''
# Mount the UnmountedType as Argument
# '''
# return Argument(
# self.get_type(),
# *self.args,
# _creation_counter=self.creation_counter,
# **self.kwargs
# )

View File

@ -0,0 +1,42 @@
from .unmountedtype import UnmountedType
from .field import Field
def unmounted_field_in_type(attname, unmounted_field, type):
'''
Mount the UnmountedType dinamically as Field or InputField
depending on where mounted in.
ObjectType -> Field
InputObjectType -> InputField
'''
# from ..types.inputobjecttype import InputObjectType
from ..new_types.objecttype import ObjectTypeMeta
from ..new_types.interface import Interface
from ..new_types.abstracttype import AbstractTypeMeta
if issubclass(type, (ObjectTypeMeta, Interface)):
return unmounted_field.as_field()
elif issubclass(type, (AbstractTypeMeta)):
return unmounted_field
# elif issubclass(type, (InputObjectType)):
# return unmounted_field.as_inputfield()
raise Exception(
'Unmounted field "{}" cannot be mounted in {}.{}.'.format(
unmounted_field, type, attname
)
)
def get_fields_in_type(in_type, attrs):
for attname, value in list(attrs.items()):
if isinstance(value, (Field)): # , InputField
yield attname, value
elif isinstance(value, UnmountedType):
yield attname, unmounted_field_in_type(attname, value, in_type)
def attrs_without_fields(attrs, fields):
return {k: v for k, v in attrs.items() if k not in fields}