mirror of
https://github.com/graphql-python/graphene.git
synced 2025-02-08 23:50:38 +03:00
Next types
This commit is contained in:
parent
04492600e5
commit
550ad386f0
0
graphene/new_types/__init__.py
Normal file
0
graphene/new_types/__init__.py
Normal file
37
graphene/new_types/abstracttype.py
Normal file
37
graphene/new_types/abstracttype.py
Normal 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
|
70
graphene/new_types/field.py
Normal file
70
graphene/new_types/field.py
Normal 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
|
41
graphene/new_types/interface.py
Normal file
41
graphene/new_types/interface.py
Normal 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
|
73
graphene/new_types/objecttype.py
Normal file
73
graphene/new_types/objecttype.py
Normal 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__
|
||||||
|
)
|
||||||
|
)
|
37
graphene/new_types/options.py
Normal file
37
graphene/new_types/options.py
Normal 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))
|
23
graphene/new_types/structures.py
Normal file
23
graphene/new_types/structures.py
Normal 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
|
0
graphene/new_types/tests/__init__.py
Normal file
0
graphene/new_types/tests/__init__.py
Normal file
99
graphene/new_types/tests/test_abstracttype.py
Normal file
99
graphene/new_types/tests/test_abstracttype.py
Normal 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)
|
55
graphene/new_types/tests/test_field.py
Normal file
55
graphene/new_types/tests/test_field.py
Normal 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()
|
59
graphene/new_types/tests/test_interface.py
Normal file
59
graphene/new_types/tests/test_interface.py
Normal 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)
|
108
graphene/new_types/tests/test_objecttype.py
Normal file
108
graphene/new_types/tests/test_objecttype.py
Normal 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)
|
60
graphene/new_types/unmountedtype.py
Normal file
60
graphene/new_types/unmountedtype.py
Normal 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
|
||||||
|
# )
|
42
graphene/new_types/utils.py
Normal file
42
graphene/new_types/utils.py
Normal 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}
|
Loading…
Reference in New Issue
Block a user