Skip to content

Commit 2ad5bc2

Browse files
committed
Merge branch 'refs/heads/features/plugins-autocamelcase' into features/django-debug
2 parents 3586fdf + 12e4e2c commit 2ad5bc2

File tree

20 files changed

+245
-77
lines changed

20 files changed

+245
-77
lines changed

graphene/contrib/django/fields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def internal_type(self, schema):
2727
if not field_object_type:
2828
raise SkipField()
2929
if is_node(field_object_type):
30-
field = DjangoConnectionField(field_object_type)
30+
field = ConnectionField(field_object_type)
3131
else:
3232
field = Field(List(field_object_type))
3333
field.contribute_to_class(self.object_type, self.attname)

graphene/contrib/django/utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from django.db import models
22
from django.db.models.manager import Manager
3+
from django.db.models.query import QuerySet
4+
5+
from graphene.utils import LazyList
36

47

58
def get_type_for_model(schema, model):
@@ -19,7 +22,18 @@ def get_reverse_fields(model):
1922
yield related
2023

2124

25+
class WrappedQueryset(LazyList):
26+
27+
def __len__(self):
28+
# Dont calculate the length using len(queryset), as this will
29+
# evaluate the whole queryset and return it's length.
30+
# Use .count() instead
31+
return self._origin.count()
32+
33+
2234
def maybe_queryset(value):
2335
if isinstance(value, Manager):
2436
value = value.get_queryset()
37+
if isinstance(value, QuerySet):
38+
return WrappedQueryset(value)
2539
return value

graphene/core/classtypes/base.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import copy
22
import inspect
3+
from functools import partial
34
from collections import OrderedDict
45

56
import six
67

7-
from ..exceptions import SkipField
88
from .options import Options
99

1010

@@ -48,8 +48,8 @@ def construct(cls, bases, attrs):
4848

4949
if not cls._meta.abstract:
5050
from ..types import List, NonNull
51-
setattr(cls, 'NonNull', NonNull(cls))
52-
setattr(cls, 'List', List(cls))
51+
setattr(cls, 'NonNull', partial(NonNull, cls))
52+
setattr(cls, 'List', partial(List, cls))
5353

5454
return cls
5555

@@ -81,31 +81,36 @@ def fields(self):
8181
def fields_map(self):
8282
return OrderedDict([(f.attname, f) for f in self.fields])
8383

84+
@property
85+
def fields_group_type(self):
86+
from ..types.field import FieldsGroupType
87+
return FieldsGroupType(*self.local_fields)
88+
8489

8590
class FieldsClassTypeMeta(ClassTypeMeta):
8691
options_class = FieldsOptions
8792

8893
def extend_fields(cls, bases):
8994
new_fields = cls._meta.local_fields
90-
field_names = {f.name: f for f in new_fields}
95+
field_names = {f.attname: f for f in new_fields}
9196

9297
for base in bases:
9398
if not isinstance(base, FieldsClassTypeMeta):
9499
continue
95100

96101
parent_fields = base._meta.local_fields
97102
for field in parent_fields:
98-
if field.name in field_names and field.type.__class__ != field_names[
99-
field.name].type.__class__:
103+
if field.attname in field_names and field.type.__class__ != field_names[
104+
field.attname].type.__class__:
100105
raise Exception(
101106
'Local field %r in class %r (%r) clashes '
102107
'with field with similar name from '
103108
'Interface %s (%r)' % (
104-
field.name,
109+
field.attname,
105110
cls.__name__,
106111
field.__class__,
107112
base.__name__,
108-
field_names[field.name].__class__)
113+
field_names[field.attname].__class__)
109114
)
110115
new_field = copy.copy(field)
111116
cls.add_to_class(field.attname, new_field)
@@ -123,11 +128,4 @@ class Meta:
123128

124129
@classmethod
125130
def fields_internal_types(cls, schema):
126-
fields = []
127-
for field in cls._meta.fields:
128-
try:
129-
fields.append((field.name, schema.T(field)))
130-
except SkipField:
131-
continue
132-
133-
return OrderedDict(fields)
131+
return schema.T(cls._meta.fields_group_type)

graphene/core/classtypes/tests/test_base.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,26 @@ class Meta:
2323
def test_classtype_definition_list():
2424
class Character(ClassType):
2525
'''Character description'''
26-
assert isinstance(Character.List, List)
27-
assert Character.List.of_type == Character
26+
assert isinstance(Character.List(), List)
27+
assert Character.List().of_type == Character
2828

2929

3030
def test_classtype_definition_nonnull():
3131
class Character(ClassType):
3232
'''Character description'''
33-
assert isinstance(Character.NonNull, NonNull)
34-
assert Character.NonNull.of_type == Character
33+
assert isinstance(Character.NonNull(), NonNull)
34+
assert Character.NonNull().of_type == Character
35+
36+
37+
def test_fieldsclasstype_definition_order():
38+
class Character(ClassType):
39+
'''Character description'''
40+
41+
class Query(FieldsClassType):
42+
name = String()
43+
char = Character.NonNull()
44+
45+
assert list(Query._meta.fields_map.keys()) == ['name', 'char']
3546

3647

3748
def test_fieldsclasstype():

graphene/core/classtypes/tests/test_mutation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ class Input:
2424
assert list(object_type.get_fields().keys()) == ['name']
2525
assert MyMutation._meta.fields_map['name'].object_type == MyMutation
2626
assert isinstance(MyMutation.arguments, ArgumentsGroup)
27-
assert 'argName' in MyMutation.arguments
27+
assert 'argName' in schema.T(MyMutation.arguments)

graphene/core/schema.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from .classtypes.base import ClassType
1414
from .types.base import BaseType
15+
from ..plugins import Plugin, CamelCase
1516

1617

1718
class GraphQLSchema(_GraphQLSchema):
@@ -25,27 +26,43 @@ class Schema(object):
2526
_executor = None
2627

2728
def __init__(self, query=None, mutation=None, subscription=None,
28-
name='Schema', executor=None):
29+
name='Schema', executor=None, plugins=None, auto_camelcase=True):
2930
self._types_names = {}
3031
self._types = {}
3132
self.mutation = mutation
3233
self.query = query
3334
self.subscription = subscription
3435
self.name = name
3536
self.executor = executor
37+
self.plugins = []
38+
plugins = plugins or []
39+
if auto_camelcase:
40+
plugins.append(CamelCase())
41+
for plugin in plugins:
42+
self.add_plugin(plugin)
3643
signals.init_schema.send(self)
3744

3845
def __repr__(self):
3946
return '<Schema: %s (%s)>' % (str(self.name), hash(self))
4047

48+
def add_plugin(self, plugin):
49+
assert isinstance(plugin, Plugin), 'A plugin need to subclass graphene.Plugin and be instantiated'
50+
plugin.contribute_to_schema(self)
51+
self.plugins.append(plugin)
52+
53+
def get_internal_type(self, objecttype):
54+
for plugin in self.plugins:
55+
objecttype = plugin.transform_type(objecttype)
56+
return objecttype.internal_type(self)
57+
4158
def T(self, object_type):
4259
if not object_type:
4360
return
4461
if inspect.isclass(object_type) and issubclass(
4562
object_type, (BaseType, ClassType)) or isinstance(
4663
object_type, BaseType):
4764
if object_type not in self._types:
48-
internal_type = object_type.internal_type(self)
65+
internal_type = self.get_internal_type(object_type)
4966
self._types[object_type] = internal_type
5067
is_objecttype = inspect.isclass(
5168
object_type) and issubclass(object_type, ClassType)

graphene/core/tests/test_old_fields.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ def test_field_type():
3434
assert schema.T(f).type == GraphQLString
3535

3636

37-
def test_field_name_automatic_camelcase():
37+
def test_field_name():
3838
f = Field(GraphQLString)
3939
f.contribute_to_class(MyOt, 'field_name')
40-
assert f.name == 'fieldName'
40+
assert f.name is None
41+
assert f.attname == 'field_name'
4142

4243

4344
def test_field_name_use_name_if_exists():

graphene/core/types/argument.py

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
from collections import OrderedDict
21
from functools import wraps
32
from itertools import chain
43

54
from graphql.core.type import GraphQLArgument
65

7-
from ...utils import ProxySnakeDict, to_camel_case
8-
from .base import ArgumentType, BaseType, OrderedType
6+
from ...utils import ProxySnakeDict
7+
from .base import ArgumentType, GroupNamedType, NamedType, OrderedType
98

109

11-
class Argument(OrderedType):
10+
class Argument(NamedType, OrderedType):
1211

1312
def __init__(self, type, description=None, default=None,
1413
name=None, _creation_counter=None):
1514
super(Argument, self).__init__(_creation_counter=_creation_counter)
1615
self.name = name
16+
self.attname = None
1717
self.type = type
1818
self.description = description
1919
self.default = default
@@ -27,47 +27,32 @@ def __repr__(self):
2727
return self.name
2828

2929

30-
class ArgumentsGroup(BaseType):
30+
class ArgumentsGroup(GroupNamedType):
3131

3232
def __init__(self, *args, **kwargs):
3333
arguments = to_arguments(*args, **kwargs)
34-
self.arguments = OrderedDict([(arg.name, arg) for arg in arguments])
35-
36-
def internal_type(self, schema):
37-
return OrderedDict([(arg.name, schema.T(arg))
38-
for arg in self.arguments.values()])
39-
40-
def __len__(self):
41-
return len(self.arguments)
42-
43-
def __iter__(self):
44-
return iter(self.arguments)
45-
46-
def __contains__(self, *args):
47-
return self.arguments.__contains__(*args)
48-
49-
def __getitem__(self, *args):
50-
return self.arguments.__getitem__(*args)
34+
super(ArgumentsGroup, self).__init__(*arguments)
5135

5236

5337
def to_arguments(*args, **kwargs):
5438
arguments = {}
5539
iter_arguments = chain(kwargs.items(), [(None, a) for a in args])
5640

57-
for name, arg in iter_arguments:
41+
for attname, arg in iter_arguments:
5842
if isinstance(arg, Argument):
5943
argument = arg
6044
elif isinstance(arg, ArgumentType):
6145
argument = arg.as_argument()
6246
else:
63-
raise ValueError('Unknown argument %s=%r' % (name, arg))
64-
65-
if name:
66-
argument.name = to_camel_case(name)
67-
assert argument.name, 'Argument in field must have a name'
68-
assert argument.name not in arguments, 'Found more than one Argument with same name {}'.format(
69-
argument.name)
70-
arguments[argument.name] = argument
47+
raise ValueError('Unknown argument %s=%r' % (attname, arg))
48+
49+
if attname:
50+
argument.attname = attname
51+
52+
name = argument.name or argument.attname
53+
assert name, 'Argument in field must have a name'
54+
assert name not in arguments, 'Found more than one Argument with same name {}'.format(name)
55+
arguments[name] = argument
7156

7257
return sorted(arguments.values())
7358

graphene/core/types/base.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
from functools import total_ordering
1+
from collections import OrderedDict
2+
from functools import total_ordering, partial
23

34
import six
45

6+
from ...utils import to_camel_case
7+
58

69
class BaseType(object):
710

@@ -126,3 +129,31 @@ def as_inputfield(self):
126129

127130
class MountedType(FieldType, ArgumentType):
128131
pass
132+
133+
134+
class NamedType(BaseType):
135+
pass
136+
137+
138+
class GroupNamedType(BaseType):
139+
def __init__(self, *types):
140+
self.types = types
141+
142+
def get_named_type(self, schema, type):
143+
name = type.name or type.attname
144+
return name, schema.T(type)
145+
146+
def internal_type(self, schema):
147+
return OrderedDict(map(partial(self.get_named_type, schema), self.types))
148+
149+
def __len__(self):
150+
return len(self.types)
151+
152+
def __iter__(self):
153+
return iter(self.types)
154+
155+
def __contains__(self, *args):
156+
return self.types.__contains__(*args)
157+
158+
def __getitem__(self, *args):
159+
return self.types.__getitem__(*args)

0 commit comments

Comments
 (0)