Merge branch 'refs/heads/features/plugins-autocamelcase' into features/django-debug

This commit is contained in:
Syrus Akbary 2015-12-06 02:37:21 -08:00
commit 2ad5bc203a
20 changed files with 244 additions and 76 deletions

View File

@ -27,7 +27,7 @@ class ConnectionOrListField(Field):
if not field_object_type: if not field_object_type:
raise SkipField() raise SkipField()
if is_node(field_object_type): if is_node(field_object_type):
field = DjangoConnectionField(field_object_type) field = ConnectionField(field_object_type)
else: else:
field = Field(List(field_object_type)) field = Field(List(field_object_type))
field.contribute_to_class(self.object_type, self.attname) field.contribute_to_class(self.object_type, self.attname)

View File

@ -1,5 +1,8 @@
from django.db import models from django.db import models
from django.db.models.manager import Manager from django.db.models.manager import Manager
from django.db.models.query import QuerySet
from graphene.utils import LazyList
def get_type_for_model(schema, model): def get_type_for_model(schema, model):
@ -19,7 +22,18 @@ def get_reverse_fields(model):
yield related yield related
class WrappedQueryset(LazyList):
def __len__(self):
# Dont calculate the length using len(queryset), as this will
# evaluate the whole queryset and return it's length.
# Use .count() instead
return self._origin.count()
def maybe_queryset(value): def maybe_queryset(value):
if isinstance(value, Manager): if isinstance(value, Manager):
value = value.get_queryset() value = value.get_queryset()
if isinstance(value, QuerySet):
return WrappedQueryset(value)
return value return value

View File

@ -1,10 +1,10 @@
import copy import copy
import inspect import inspect
from functools import partial
from collections import OrderedDict from collections import OrderedDict
import six import six
from ..exceptions import SkipField
from .options import Options from .options import Options
@ -48,8 +48,8 @@ class ClassTypeMeta(type):
if not cls._meta.abstract: if not cls._meta.abstract:
from ..types import List, NonNull from ..types import List, NonNull
setattr(cls, 'NonNull', NonNull(cls)) setattr(cls, 'NonNull', partial(NonNull, cls))
setattr(cls, 'List', List(cls)) setattr(cls, 'List', partial(List, cls))
return cls return cls
@ -81,13 +81,18 @@ class FieldsOptions(Options):
def fields_map(self): def fields_map(self):
return OrderedDict([(f.attname, f) for f in self.fields]) return OrderedDict([(f.attname, f) for f in self.fields])
@property
def fields_group_type(self):
from ..types.field import FieldsGroupType
return FieldsGroupType(*self.local_fields)
class FieldsClassTypeMeta(ClassTypeMeta): class FieldsClassTypeMeta(ClassTypeMeta):
options_class = FieldsOptions options_class = FieldsOptions
def extend_fields(cls, bases): def extend_fields(cls, bases):
new_fields = cls._meta.local_fields new_fields = cls._meta.local_fields
field_names = {f.name: f for f in new_fields} field_names = {f.attname: f for f in new_fields}
for base in bases: for base in bases:
if not isinstance(base, FieldsClassTypeMeta): if not isinstance(base, FieldsClassTypeMeta):
@ -95,17 +100,17 @@ class FieldsClassTypeMeta(ClassTypeMeta):
parent_fields = base._meta.local_fields parent_fields = base._meta.local_fields
for field in parent_fields: for field in parent_fields:
if field.name in field_names and field.type.__class__ != field_names[ if field.attname in field_names and field.type.__class__ != field_names[
field.name].type.__class__: field.attname].type.__class__:
raise Exception( raise Exception(
'Local field %r in class %r (%r) clashes ' 'Local field %r in class %r (%r) clashes '
'with field with similar name from ' 'with field with similar name from '
'Interface %s (%r)' % ( 'Interface %s (%r)' % (
field.name, field.attname,
cls.__name__, cls.__name__,
field.__class__, field.__class__,
base.__name__, base.__name__,
field_names[field.name].__class__) field_names[field.attname].__class__)
) )
new_field = copy.copy(field) new_field = copy.copy(field)
cls.add_to_class(field.attname, new_field) cls.add_to_class(field.attname, new_field)
@ -123,11 +128,4 @@ class FieldsClassType(six.with_metaclass(FieldsClassTypeMeta, ClassType)):
@classmethod @classmethod
def fields_internal_types(cls, schema): def fields_internal_types(cls, schema):
fields = [] return schema.T(cls._meta.fields_group_type)
for field in cls._meta.fields:
try:
fields.append((field.name, schema.T(field)))
except SkipField:
continue
return OrderedDict(fields)

View File

@ -23,15 +23,26 @@ def test_classtype_advanced():
def test_classtype_definition_list(): def test_classtype_definition_list():
class Character(ClassType): class Character(ClassType):
'''Character description''' '''Character description'''
assert isinstance(Character.List, List) assert isinstance(Character.List(), List)
assert Character.List.of_type == Character assert Character.List().of_type == Character
def test_classtype_definition_nonnull(): def test_classtype_definition_nonnull():
class Character(ClassType): class Character(ClassType):
'''Character description''' '''Character description'''
assert isinstance(Character.NonNull, NonNull) assert isinstance(Character.NonNull(), NonNull)
assert Character.NonNull.of_type == Character assert Character.NonNull().of_type == Character
def test_fieldsclasstype_definition_order():
class Character(ClassType):
'''Character description'''
class Query(FieldsClassType):
name = String()
char = Character.NonNull()
assert list(Query._meta.fields_map.keys()) == ['name', 'char']
def test_fieldsclasstype(): def test_fieldsclasstype():

View File

@ -24,4 +24,4 @@ def test_mutation():
assert list(object_type.get_fields().keys()) == ['name'] assert list(object_type.get_fields().keys()) == ['name']
assert MyMutation._meta.fields_map['name'].object_type == MyMutation assert MyMutation._meta.fields_map['name'].object_type == MyMutation
assert isinstance(MyMutation.arguments, ArgumentsGroup) assert isinstance(MyMutation.arguments, ArgumentsGroup)
assert 'argName' in MyMutation.arguments assert 'argName' in schema.T(MyMutation.arguments)

View File

@ -12,6 +12,7 @@ from graphene import signals
from .classtypes.base import ClassType from .classtypes.base import ClassType
from .types.base import BaseType from .types.base import BaseType
from ..plugins import Plugin, CamelCase
class GraphQLSchema(_GraphQLSchema): class GraphQLSchema(_GraphQLSchema):
@ -25,7 +26,7 @@ class Schema(object):
_executor = None _executor = None
def __init__(self, query=None, mutation=None, subscription=None, def __init__(self, query=None, mutation=None, subscription=None,
name='Schema', executor=None): name='Schema', executor=None, plugins=None, auto_camelcase=True):
self._types_names = {} self._types_names = {}
self._types = {} self._types = {}
self.mutation = mutation self.mutation = mutation
@ -33,11 +34,27 @@ class Schema(object):
self.subscription = subscription self.subscription = subscription
self.name = name self.name = name
self.executor = executor self.executor = executor
self.plugins = []
plugins = plugins or []
if auto_camelcase:
plugins.append(CamelCase())
for plugin in plugins:
self.add_plugin(plugin)
signals.init_schema.send(self) signals.init_schema.send(self)
def __repr__(self): def __repr__(self):
return '<Schema: %s (%s)>' % (str(self.name), hash(self)) return '<Schema: %s (%s)>' % (str(self.name), hash(self))
def add_plugin(self, plugin):
assert isinstance(plugin, Plugin), 'A plugin need to subclass graphene.Plugin and be instantiated'
plugin.contribute_to_schema(self)
self.plugins.append(plugin)
def get_internal_type(self, objecttype):
for plugin in self.plugins:
objecttype = plugin.transform_type(objecttype)
return objecttype.internal_type(self)
def T(self, object_type): def T(self, object_type):
if not object_type: if not object_type:
return return
@ -45,7 +62,7 @@ class Schema(object):
object_type, (BaseType, ClassType)) or isinstance( object_type, (BaseType, ClassType)) or isinstance(
object_type, BaseType): object_type, BaseType):
if object_type not in self._types: if object_type not in self._types:
internal_type = object_type.internal_type(self) internal_type = self.get_internal_type(object_type)
self._types[object_type] = internal_type self._types[object_type] = internal_type
is_objecttype = inspect.isclass( is_objecttype = inspect.isclass(
object_type) and issubclass(object_type, ClassType) object_type) and issubclass(object_type, ClassType)

View File

@ -34,10 +34,11 @@ def test_field_type():
assert schema.T(f).type == GraphQLString assert schema.T(f).type == GraphQLString
def test_field_name_automatic_camelcase(): def test_field_name():
f = Field(GraphQLString) f = Field(GraphQLString)
f.contribute_to_class(MyOt, 'field_name') f.contribute_to_class(MyOt, 'field_name')
assert f.name == 'fieldName' assert f.name is None
assert f.attname == 'field_name'
def test_field_name_use_name_if_exists(): def test_field_name_use_name_if_exists():

View File

@ -1,19 +1,19 @@
from collections import OrderedDict
from functools import wraps from functools import wraps
from itertools import chain from itertools import chain
from graphql.core.type import GraphQLArgument from graphql.core.type import GraphQLArgument
from ...utils import ProxySnakeDict, to_camel_case from ...utils import ProxySnakeDict
from .base import ArgumentType, BaseType, OrderedType from .base import ArgumentType, GroupNamedType, NamedType, OrderedType
class Argument(OrderedType): class Argument(NamedType, OrderedType):
def __init__(self, type, description=None, default=None, def __init__(self, type, description=None, default=None,
name=None, _creation_counter=None): name=None, _creation_counter=None):
super(Argument, self).__init__(_creation_counter=_creation_counter) super(Argument, self).__init__(_creation_counter=_creation_counter)
self.name = name self.name = name
self.attname = None
self.type = type self.type = type
self.description = description self.description = description
self.default = default self.default = default
@ -27,47 +27,32 @@ class Argument(OrderedType):
return self.name return self.name
class ArgumentsGroup(BaseType): class ArgumentsGroup(GroupNamedType):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
arguments = to_arguments(*args, **kwargs) arguments = to_arguments(*args, **kwargs)
self.arguments = OrderedDict([(arg.name, arg) for arg in arguments]) super(ArgumentsGroup, self).__init__(*arguments)
def internal_type(self, schema):
return OrderedDict([(arg.name, schema.T(arg))
for arg in self.arguments.values()])
def __len__(self):
return len(self.arguments)
def __iter__(self):
return iter(self.arguments)
def __contains__(self, *args):
return self.arguments.__contains__(*args)
def __getitem__(self, *args):
return self.arguments.__getitem__(*args)
def to_arguments(*args, **kwargs): def to_arguments(*args, **kwargs):
arguments = {} arguments = {}
iter_arguments = chain(kwargs.items(), [(None, a) for a in args]) iter_arguments = chain(kwargs.items(), [(None, a) for a in args])
for name, arg in iter_arguments: for attname, arg in iter_arguments:
if isinstance(arg, Argument): if isinstance(arg, Argument):
argument = arg argument = arg
elif isinstance(arg, ArgumentType): elif isinstance(arg, ArgumentType):
argument = arg.as_argument() argument = arg.as_argument()
else: else:
raise ValueError('Unknown argument %s=%r' % (name, arg)) raise ValueError('Unknown argument %s=%r' % (attname, arg))
if name: if attname:
argument.name = to_camel_case(name) argument.attname = attname
assert argument.name, 'Argument in field must have a name'
assert argument.name not in arguments, 'Found more than one Argument with same name {}'.format( name = argument.name or argument.attname
argument.name) assert name, 'Argument in field must have a name'
arguments[argument.name] = argument assert name not in arguments, 'Found more than one Argument with same name {}'.format(name)
arguments[name] = argument
return sorted(arguments.values()) return sorted(arguments.values())

View File

@ -1,7 +1,10 @@
from functools import total_ordering from collections import OrderedDict
from functools import total_ordering, partial
import six import six
from ...utils import to_camel_case
class BaseType(object): class BaseType(object):
@ -126,3 +129,31 @@ class FieldType(MirroredType):
class MountedType(FieldType, ArgumentType): class MountedType(FieldType, ArgumentType):
pass pass
class NamedType(BaseType):
pass
class GroupNamedType(BaseType):
def __init__(self, *types):
self.types = types
def get_named_type(self, schema, type):
name = type.name or type.attname
return name, schema.T(type)
def internal_type(self, schema):
return OrderedDict(map(partial(self.get_named_type, schema), self.types))
def __len__(self):
return len(self.types)
def __iter__(self):
return iter(self.types)
def __contains__(self, *args):
return self.types.__contains__(*args)
def __getitem__(self, *args):
return self.types.__getitem__(*args)

View File

@ -4,16 +4,16 @@ from functools import wraps
import six import six
from graphql.core.type import GraphQLField, GraphQLInputObjectField from graphql.core.type import GraphQLField, GraphQLInputObjectField
from ...utils import to_camel_case
from ..classtypes.base import FieldsClassType from ..classtypes.base import FieldsClassType
from ..classtypes.inputobjecttype import InputObjectType from ..classtypes.inputobjecttype import InputObjectType
from ..classtypes.mutation import Mutation from ..classtypes.mutation import Mutation
from ..exceptions import SkipField
from .argument import ArgumentsGroup, snake_case_args from .argument import ArgumentsGroup, snake_case_args
from .base import LazyType, MountType, OrderedType from .base import LazyType, NamedType, MountType, OrderedType, GroupNamedType
from .definitions import NonNull from .definitions import NonNull
class Field(OrderedType): class Field(NamedType, OrderedType):
def __init__( def __init__(
self, type, description=None, args=None, name=None, resolver=None, self, type, description=None, args=None, name=None, resolver=None,
@ -36,8 +36,6 @@ class Field(OrderedType):
assert issubclass( assert issubclass(
cls, (FieldsClassType)), 'Field {} cannot be mounted in {}'.format( cls, (FieldsClassType)), 'Field {} cannot be mounted in {}'.format(
self, cls) self, cls)
if not self.name:
self.name = to_camel_case(attname)
self.attname = attname self.attname = attname
self.object_type = cls self.object_type = cls
self.mount(cls) self.mount(cls)
@ -63,6 +61,9 @@ class Field(OrderedType):
return NonNull(self.type) return NonNull(self.type)
return self.type return self.type
def decorate_resolver(self, resolver):
return snake_case_args(resolver)
def internal_type(self, schema): def internal_type(self, schema):
resolver = self.resolver resolver = self.resolver
description = self.description description = self.description
@ -85,9 +86,9 @@ class Field(OrderedType):
return my_resolver(instance, args, info) return my_resolver(instance, args, info)
resolver = wrapped_func resolver = wrapped_func
resolver = snake_case_args(resolver)
assert type, 'Internal type for field %s is None' % str(self) assert type, 'Internal type for field %s is None' % str(self)
return GraphQLField(type, args=schema.T(arguments), resolver=resolver, return GraphQLField(type, args=schema.T(arguments),
resolver=self.decorate_resolver(resolver),
description=description,) description=description,)
def __repr__(self): def __repr__(self):
@ -114,7 +115,7 @@ class Field(OrderedType):
return hash((self.creation_counter, self.object_type)) return hash((self.creation_counter, self.object_type))
class InputField(OrderedType): class InputField(NamedType, OrderedType):
def __init__(self, type, description=None, default=None, def __init__(self, type, description=None, default=None,
name=None, _creation_counter=None, required=False): name=None, _creation_counter=None, required=False):
@ -130,8 +131,6 @@ class InputField(OrderedType):
assert issubclass( assert issubclass(
cls, (InputObjectType)), 'InputField {} cannot be mounted in {}'.format( cls, (InputObjectType)), 'InputField {} cannot be mounted in {}'.format(
self, cls) self, cls)
if not self.name:
self.name = to_camel_case(attname)
self.attname = attname self.attname = attname
self.object_type = cls self.object_type = cls
self.mount(cls) self.mount(cls)
@ -143,3 +142,14 @@ class InputField(OrderedType):
return GraphQLInputObjectField( return GraphQLInputObjectField(
schema.T(self.type), schema.T(self.type),
default_value=self.default, description=self.description) default_value=self.default, description=self.description)
class FieldsGroupType(GroupNamedType):
def internal_type(self, schema):
fields = []
for field in sorted(self.types):
try:
fields.append(self.get_named_type(schema, field))
except SkipField:
continue
return OrderedDict(fields)

View File

@ -27,8 +27,8 @@ def test_to_arguments():
other_kwarg=String(), other_kwarg=String(),
) )
assert [a.name for a in arguments] == [ assert [a.name or a.attname for a in arguments] == [
'myArg', 'otherArg', 'myKwarg', 'otherKwarg'] 'myArg', 'otherArg', 'my_kwarg', 'other_kwarg']
def test_to_arguments_no_name(): def test_to_arguments_no_name():

View File

@ -20,7 +20,7 @@ def test_field_internal_type():
schema = Schema(query=Query) schema = Schema(query=Query)
type = schema.T(field) type = schema.T(field)
assert field.name == 'myField' assert field.name is None
assert field.attname == 'my_field' assert field.attname == 'my_field'
assert isinstance(type, GraphQLField) assert isinstance(type, GraphQLField)
assert type.description == 'My argument' assert type.description == 'My argument'
@ -98,9 +98,10 @@ def test_field_string_reference():
def test_field_custom_arguments(): def test_field_custom_arguments():
field = Field(None, name='my_customName', p=String()) field = Field(None, name='my_customName', p=String())
schema = Schema()
args = field.arguments args = field.arguments
assert 'p' in args assert 'p' in schema.T(args)
def test_inputfield_internal_type(): def test_inputfield_internal_type():
@ -115,7 +116,7 @@ def test_inputfield_internal_type():
schema = Schema(query=MyObjectType) schema = Schema(query=MyObjectType)
type = schema.T(field) type = schema.T(field)
assert field.name == 'myField' assert field.name is None
assert field.attname == 'my_field' assert field.attname == 'my_field'
assert isinstance(type, GraphQLInputObjectField) assert isinstance(type, GraphQLInputObjectField)
assert type.description == 'My input field' assert type.description == 'My input field'

View File

@ -0,0 +1,6 @@
from .base import Plugin
from .camel_case import CamelCase
__all__ = [
'Plugin', 'CamelCase'
]

6
graphene/plugins/base.py Normal file
View File

@ -0,0 +1,6 @@
class Plugin(object):
def contribute_to_schema(self, schema):
self.schema = schema
def transform_type(self, objecttype):
return objecttype

View File

@ -0,0 +1,22 @@
from .base import Plugin
from ..core.types.base import GroupNamedType
from ..utils import memoize, to_camel_case
def camelcase_named_type(schema, type):
name = type.name or to_camel_case(type.attname)
return name, schema.T(type)
class CamelCase(Plugin):
@memoize
def transform_group(self, _type):
new_type = _type.__class__(*_type.types)
setattr(new_type, 'get_named_type', camelcase_named_type)
return new_type
def transform_type(self, _type):
if isinstance(_type, GroupNamedType):
return self.transform_group(_type)
return _type

View File

@ -34,8 +34,7 @@ schema = Schema(query=Query, mutation=MyResultMutation)
def test_mutation_arguments(): def test_mutation_arguments():
assert ChangeNumber.arguments assert ChangeNumber.arguments
assert list(ChangeNumber.arguments) == ['input'] assert 'input' in schema.T(ChangeNumber.arguments)
assert 'input' in ChangeNumber.arguments
inner_type = ChangeNumber.input_type inner_type = ChangeNumber.input_type
client_mutation_id_field = inner_type._meta.fields_map[ client_mutation_id_field = inner_type._meta.fields_map[
'client_mutation_id'] 'client_mutation_id']

View File

@ -3,8 +3,9 @@ from .proxy_snake_dict import ProxySnakeDict
from .caching import cached_property, memoize from .caching import cached_property, memoize
from .misc import enum_to_graphql_enum from .misc import enum_to_graphql_enum
from .resolve_only_args import resolve_only_args from .resolve_only_args import resolve_only_args
from .lazylist import LazyList
__all__ = ['to_camel_case', 'to_snake_case', 'ProxySnakeDict', __all__ = ['to_camel_case', 'to_snake_case', 'ProxySnakeDict',
'cached_property', 'memoize', 'enum_to_graphql_enum', 'cached_property', 'memoize', 'enum_to_graphql_enum',
'resolve_only_args'] 'resolve_only_args', 'LazyList']

View File

@ -0,0 +1,43 @@
class LazyList(object):
def __init__(self, origin, state=None):
self._origin = origin
self._state = state or []
self._origin_iter = None
self._finished = False
def __iter__(self):
return self if not self._finished else iter(self._state)
def iter(self):
return self.__iter__()
def __len__(self):
return self._origin.__len__()
def __next__(self):
try:
if not self._origin_iter:
self._origin_iter = self._origin.__iter__()
n = next(self._origin_iter)
except StopIteration as e:
self._finished = True
raise e
else:
self._state.append(n)
return n
def next(self):
return self.__next__()
def __getitem__(self, key):
item = self._origin[key]
if isinstance(key, slice):
return self.__class__(item)
return item
def __getattr__(self, name):
return getattr(self._origin, name)
def __repr__(self):
return "<{} {}>".format(self.__class__.__name__, repr(self._origin))

View File

@ -0,0 +1,23 @@
from py.test import raises
from ..lazylist import LazyList
def test_lazymap():
data = list(range(10))
lm = LazyList(data)
assert len(lm) == 10
assert lm[1] == 1
assert isinstance(lm[1:4], LazyList)
assert lm.append == data.append
assert repr(lm) == '<LazyList [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>'
def test_lazymap_iter():
data = list(range(2))
lm = LazyList(data)
iter_lm = iter(lm)
assert iter_lm.next() == 0
assert iter_lm.next() == 1
with raises(StopIteration):
iter_lm.next()

View File

@ -24,7 +24,7 @@ class PyTest(TestCommand):
setup( setup(
name='graphene', name='graphene',
version='0.4.2', version='0.4.3',
description='Graphene: Python DSL for GraphQL', description='Graphene: Python DSL for GraphQL',
long_description=open('README.rst').read(), long_description=open('README.rst').read(),