diff --git a/graphene/contrib/django/fields.py b/graphene/contrib/django/fields.py index 5bed67f5..b47aaf0d 100644 --- a/graphene/contrib/django/fields.py +++ b/graphene/contrib/django/fields.py @@ -41,7 +41,7 @@ class ConnectionOrListField(LazyField): field = DjangoConnectionField(model_field) else: field = ListField(model_field) - field.contribute_to_class(self.object_type, self.field_name) + field.contribute_to_class(self.object_type, self.name) return field diff --git a/graphene/core/fields.py b/graphene/core/fields.py index 40edce9f..4e95b6d2 100644 --- a/graphene/core/fields.py +++ b/graphene/core/fields.py @@ -10,23 +10,26 @@ from graphql.core.type import ( GraphQLArgument, GraphQLFloat, ) -from graphene.utils import cached_property, memoize +from graphene.utils import memoize, to_camel_case from graphene.core.types import BaseObjectType class Field(object): - def __init__(self, field_type, resolve=None, null=True, args=None, description='', **extra_args): + def __init__(self, field_type, name=None, resolve=None, null=True, args=None, description='', **extra_args): self.field_type = field_type self.resolve_fn = resolve self.null = null self.args = args or {} self.extra_args = extra_args self._type = None + self.name = name self.description = description or self.__doc__ self.object_type = None def contribute_to_class(self, cls, name): + if not self.name: + self.name = to_camel_case(name) self.field_name = name self.object_type = cls if isinstance(self.field_type, Field) and not self.field_type.object_type: @@ -94,7 +97,7 @@ class Field(object): raise TypeError("Field %s.%s initiated with invalid args: %s" % ( self.object_type, self.field_name, - ','.join(meta_attrs.keys()) + ','.join(extra_args.keys()) )) internal_type = self.internal_type(schema) @@ -107,7 +110,7 @@ class Field(object): ) def __str__(self): - """ Return "object_type.field_name". """ + """ Return "object_type.name". """ return '%s.%s' % (self.object_type, self.field_name) def __repr__(self): diff --git a/graphene/core/options.py b/graphene/core/options.py index e0abe144..6ff03b3d 100644 --- a/graphene/core/options.py +++ b/graphene/core/options.py @@ -71,3 +71,7 @@ class Options(object): @cached_property def fields_map(self): return {f.field_name: f for f in self.fields} + + @cached_property + def internal_fields_map(self): + return {f.name: f for f in self.fields} diff --git a/graphene/core/types.py b/graphene/core/types.py index 48ba3c5a..193a65a4 100644 --- a/graphene/core/types.py +++ b/graphene/core/types.py @@ -49,7 +49,7 @@ class ObjectTypeMeta(type): new_class.add_extra_fields() new_fields = new_class._meta.local_fields - field_names = {f.field_name for f in new_fields} + field_names = {f.name for f in new_fields} for base in parents: original_base = base @@ -65,12 +65,12 @@ class ObjectTypeMeta(type): # on the base classes (we cannot handle shadowed fields at the # moment). for field in parent_fields: - if field.field_name in field_names: + if field.name in field_names: raise Exception( 'Local field %r in class %r clashes ' 'with field of similar name from ' 'base class %r' % ( - field.field_name, name, base.__name__) + field.name, name, base.__name__) ) new_class._meta.parents.append(base) if base._meta.interface: @@ -99,7 +99,7 @@ class BaseObjectType(object): def __new__(cls, instance=None, *args, **kwargs): if cls._meta.interface: raise Exception("An interface cannot be initialized") - if instance == None: + if instance is None: return None return super(BaseObjectType, cls).__new__(cls, instance, *args, **kwargs) @@ -137,7 +137,7 @@ class BaseObjectType(object): @memoize @register_internal_type def internal_type(cls, schema): - fields_map = cls._meta.fields_map + fields_map = cls._meta.internal_fields_map fields = lambda: { name: field.internal_field(schema) for name, field in fields_map.items() diff --git a/graphene/utils.py b/graphene/utils.py index 020c6d3e..364e3ba1 100644 --- a/graphene/utils.py +++ b/graphene/utils.py @@ -32,3 +32,12 @@ def memoize(fun): return ret cache = {} return wrapper + + +# From this response in Stackoverflow +# http://stackoverflow.com/a/19053800/1072990 +def to_camel_case(snake_str): + components = snake_str.split('_') + # We capitalize the first letter of each component except the first one + # with the 'title' method and join them together. + return components[0] + "".join(x.title() for x in components[1:]) diff --git a/tests/contrib_django/test_schema.py b/tests/contrib_django/test_schema.py index a8f4d2fd..6fc51074 100644 --- a/tests/contrib_django/test_schema.py +++ b/tests/contrib_django/test_schema.py @@ -71,16 +71,16 @@ def test_should_map_fields(): query = ''' query ReporterQuery { reporter { - first_name, - last_name, + firstName, + lastName, email } } ''' expected = { 'reporter': { - 'first_name': 'ABA', - 'last_name': 'X', + 'firstName': 'ABA', + 'lastName': 'X', 'email': '' } } @@ -133,7 +133,7 @@ def test_should_node(): query ReporterQuery { reporter { id, - first_name, + firstName, articles { edges { node { @@ -141,13 +141,13 @@ def test_should_node(): } } } - last_name, + lastName, email } - my_article: node(id:"QXJ0aWNsZU5vZGVUeXBlOjE=") { + myArticle: node(id:"QXJ0aWNsZU5vZGVUeXBlOjE=") { id ... on ReporterNodeType { - first_name + firstName } ... on ArticleNodeType { headline @@ -158,8 +158,8 @@ def test_should_node(): expected = { 'reporter': { 'id': 'UmVwb3J0ZXJOb2RlVHlwZTox', - 'first_name': 'ABA', - 'last_name': 'X', + 'firstName': 'ABA', + 'lastName': 'X', 'email': '', 'articles': { 'edges': [{ @@ -169,7 +169,7 @@ def test_should_node(): }] }, }, - 'my_article': { + 'myArticle': { 'id': 'QXJ0aWNsZU5vZGVUeXBlOjE=', 'headline': 'Article node' } diff --git a/tests/contrib_django/test_types.py b/tests/contrib_django/test_types.py index a0ac08c7..1ab8bd47 100644 --- a/tests/contrib_django/test_types.py +++ b/tests/contrib_django/test_types.py @@ -40,16 +40,16 @@ schema = Schema() def test_django_interface(): - assert DjangoNode._meta.interface == True + assert DjangoNode._meta.interface is True def test_pseudo_interface(): object_type = Character.internal_type(schema) - assert Character._meta.interface == True + assert Character._meta.interface is True assert isinstance(object_type, GraphQLInterfaceType) assert Character._meta.model == Reporter assert object_type.get_fields().keys() == [ - 'articles', 'first_name', 'last_name', 'id', 'email'] + 'lastName', 'email', 'id', 'firstName', 'articles'] def test_interface_resolve_type(): @@ -59,12 +59,13 @@ def test_interface_resolve_type(): def test_object_type(): object_type = Human.internal_type(schema) - assert Human._meta.interface == False + internal_fields_map = Human._meta.internal_fields_map + assert Human._meta.interface is False assert isinstance(object_type, GraphQLObjectType) assert object_type.get_fields() == { - 'headline': Human._meta.fields_map['headline'].internal_field(schema), - 'id': Human._meta.fields_map['id'].internal_field(schema), - 'reporter': Human._meta.fields_map['reporter'].internal_field(schema), - 'pub_date': Human._meta.fields_map['pub_date'].internal_field(schema), + 'headline': internal_fields_map['headline'].internal_field(schema), + 'id': internal_fields_map['id'].internal_field(schema), + 'reporter': internal_fields_map['reporter'].internal_field(schema), + 'pubDate': internal_fields_map['pubDate'].internal_field(schema), } assert object_type.get_interfaces() == [DjangoNode.internal_type(schema)]