Added automatic snake casing to camel casing conversion in field names

This commit is contained in:
Syrus Akbary 2015-10-02 22:45:13 -07:00
parent 176696c1ac
commit 701c49db26
7 changed files with 46 additions and 29 deletions

View File

@ -41,7 +41,7 @@ class ConnectionOrListField(LazyField):
field = DjangoConnectionField(model_field) field = DjangoConnectionField(model_field)
else: else:
field = ListField(model_field) 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 return field

View File

@ -10,23 +10,26 @@ from graphql.core.type import (
GraphQLArgument, GraphQLArgument,
GraphQLFloat, GraphQLFloat,
) )
from graphene.utils import cached_property, memoize from graphene.utils import memoize, to_camel_case
from graphene.core.types import BaseObjectType from graphene.core.types import BaseObjectType
class Field(object): 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.field_type = field_type
self.resolve_fn = resolve self.resolve_fn = resolve
self.null = null self.null = null
self.args = args or {} self.args = args or {}
self.extra_args = extra_args self.extra_args = extra_args
self._type = None self._type = None
self.name = name
self.description = description or self.__doc__ self.description = description or self.__doc__
self.object_type = None self.object_type = None
def contribute_to_class(self, cls, name): def contribute_to_class(self, cls, name):
if not self.name:
self.name = to_camel_case(name)
self.field_name = name self.field_name = name
self.object_type = cls self.object_type = cls
if isinstance(self.field_type, Field) and not self.field_type.object_type: 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" % ( raise TypeError("Field %s.%s initiated with invalid args: %s" % (
self.object_type, self.object_type,
self.field_name, self.field_name,
','.join(meta_attrs.keys()) ','.join(extra_args.keys())
)) ))
internal_type = self.internal_type(schema) internal_type = self.internal_type(schema)
@ -107,7 +110,7 @@ class Field(object):
) )
def __str__(self): def __str__(self):
""" Return "object_type.field_name". """ """ Return "object_type.name". """
return '%s.%s' % (self.object_type, self.field_name) return '%s.%s' % (self.object_type, self.field_name)
def __repr__(self): def __repr__(self):

View File

@ -71,3 +71,7 @@ class Options(object):
@cached_property @cached_property
def fields_map(self): def fields_map(self):
return {f.field_name: f for f in self.fields} 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}

View File

@ -49,7 +49,7 @@ class ObjectTypeMeta(type):
new_class.add_extra_fields() new_class.add_extra_fields()
new_fields = new_class._meta.local_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: for base in parents:
original_base = base original_base = base
@ -65,12 +65,12 @@ class ObjectTypeMeta(type):
# on the base classes (we cannot handle shadowed fields at the # on the base classes (we cannot handle shadowed fields at the
# moment). # moment).
for field in parent_fields: for field in parent_fields:
if field.field_name in field_names: if field.name in field_names:
raise Exception( raise Exception(
'Local field %r in class %r clashes ' 'Local field %r in class %r clashes '
'with field of similar name from ' 'with field of similar name from '
'base class %r' % ( 'base class %r' % (
field.field_name, name, base.__name__) field.name, name, base.__name__)
) )
new_class._meta.parents.append(base) new_class._meta.parents.append(base)
if base._meta.interface: if base._meta.interface:
@ -99,7 +99,7 @@ class BaseObjectType(object):
def __new__(cls, instance=None, *args, **kwargs): def __new__(cls, instance=None, *args, **kwargs):
if cls._meta.interface: if cls._meta.interface:
raise Exception("An interface cannot be initialized") raise Exception("An interface cannot be initialized")
if instance == None: if instance is None:
return None return None
return super(BaseObjectType, cls).__new__(cls, instance, *args, **kwargs) return super(BaseObjectType, cls).__new__(cls, instance, *args, **kwargs)
@ -137,7 +137,7 @@ class BaseObjectType(object):
@memoize @memoize
@register_internal_type @register_internal_type
def internal_type(cls, schema): def internal_type(cls, schema):
fields_map = cls._meta.fields_map fields_map = cls._meta.internal_fields_map
fields = lambda: { fields = lambda: {
name: field.internal_field(schema) name: field.internal_field(schema)
for name, field in fields_map.items() for name, field in fields_map.items()

View File

@ -32,3 +32,12 @@ def memoize(fun):
return ret return ret
cache = {} cache = {}
return wrapper 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:])

View File

@ -71,16 +71,16 @@ def test_should_map_fields():
query = ''' query = '''
query ReporterQuery { query ReporterQuery {
reporter { reporter {
first_name, firstName,
last_name, lastName,
email email
} }
} }
''' '''
expected = { expected = {
'reporter': { 'reporter': {
'first_name': 'ABA', 'firstName': 'ABA',
'last_name': 'X', 'lastName': 'X',
'email': '' 'email': ''
} }
} }
@ -133,7 +133,7 @@ def test_should_node():
query ReporterQuery { query ReporterQuery {
reporter { reporter {
id, id,
first_name, firstName,
articles { articles {
edges { edges {
node { node {
@ -141,13 +141,13 @@ def test_should_node():
} }
} }
} }
last_name, lastName,
email email
} }
my_article: node(id:"QXJ0aWNsZU5vZGVUeXBlOjE=") { myArticle: node(id:"QXJ0aWNsZU5vZGVUeXBlOjE=") {
id id
... on ReporterNodeType { ... on ReporterNodeType {
first_name firstName
} }
... on ArticleNodeType { ... on ArticleNodeType {
headline headline
@ -158,8 +158,8 @@ def test_should_node():
expected = { expected = {
'reporter': { 'reporter': {
'id': 'UmVwb3J0ZXJOb2RlVHlwZTox', 'id': 'UmVwb3J0ZXJOb2RlVHlwZTox',
'first_name': 'ABA', 'firstName': 'ABA',
'last_name': 'X', 'lastName': 'X',
'email': '', 'email': '',
'articles': { 'articles': {
'edges': [{ 'edges': [{
@ -169,7 +169,7 @@ def test_should_node():
}] }]
}, },
}, },
'my_article': { 'myArticle': {
'id': 'QXJ0aWNsZU5vZGVUeXBlOjE=', 'id': 'QXJ0aWNsZU5vZGVUeXBlOjE=',
'headline': 'Article node' 'headline': 'Article node'
} }

View File

@ -40,16 +40,16 @@ schema = Schema()
def test_django_interface(): def test_django_interface():
assert DjangoNode._meta.interface == True assert DjangoNode._meta.interface is True
def test_pseudo_interface(): def test_pseudo_interface():
object_type = Character.internal_type(schema) object_type = Character.internal_type(schema)
assert Character._meta.interface == True assert Character._meta.interface is True
assert isinstance(object_type, GraphQLInterfaceType) assert isinstance(object_type, GraphQLInterfaceType)
assert Character._meta.model == Reporter assert Character._meta.model == Reporter
assert object_type.get_fields().keys() == [ assert object_type.get_fields().keys() == [
'articles', 'first_name', 'last_name', 'id', 'email'] 'lastName', 'email', 'id', 'firstName', 'articles']
def test_interface_resolve_type(): def test_interface_resolve_type():
@ -59,12 +59,13 @@ def test_interface_resolve_type():
def test_object_type(): def test_object_type():
object_type = Human.internal_type(schema) 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 isinstance(object_type, GraphQLObjectType)
assert object_type.get_fields() == { assert object_type.get_fields() == {
'headline': Human._meta.fields_map['headline'].internal_field(schema), 'headline': internal_fields_map['headline'].internal_field(schema),
'id': Human._meta.fields_map['id'].internal_field(schema), 'id': internal_fields_map['id'].internal_field(schema),
'reporter': Human._meta.fields_map['reporter'].internal_field(schema), 'reporter': internal_fields_map['reporter'].internal_field(schema),
'pub_date': Human._meta.fields_map['pub_date'].internal_field(schema), 'pubDate': internal_fields_map['pubDate'].internal_field(schema),
} }
assert object_type.get_interfaces() == [DjangoNode.internal_type(schema)] assert object_type.get_interfaces() == [DjangoNode.internal_type(schema)]