Repository: strawberry-graphql/strawberry

Issue:
Add support for renaming fields
We should be able to specify a custom name for a field when needed, like this:

```python
@strawberry.type
class Query:
    example_field: str = strawberry.field(name="example")
```


Discussion:

Resolving patch:
diff --git a/strawberry/field.py b/strawberry/field.py
--- a/strawberry/field.py
+++ b/strawberry/field.py
@@ -30,9 +30,10 @@ class LazyFieldWrapper:
     >>>         return TypeB()
     """
 
-    def __init__(self, obj, is_subscription, **kwargs):
+    def __init__(self, obj, is_subscription, name=None, **kwargs):
         self._wrapped_obj = obj
         self.is_subscription = is_subscription
+        self.name = name
         self.kwargs = kwargs
 
         if callable(self._wrapped_obj):
@@ -106,6 +107,7 @@ def __init__(self, *, is_subscription=False, **kwargs):
         self.field = dataclasses.field()
         self.is_subscription = is_subscription
         self.description = kwargs.get("description", None)
+        self.name = kwargs.pop("name", None)
         self.kwargs = kwargs
 
     def __call__(self, wrap):
@@ -113,7 +115,7 @@ def __call__(self, wrap):
 
         self.kwargs["description"] = self.description or wrap.__doc__
 
-        return LazyFieldWrapper(wrap, self.is_subscription, **self.kwargs)
+        return LazyFieldWrapper(wrap, self.is_subscription, self.name, **self.kwargs)
 
 
 def convert_args(args, annotations):
@@ -186,7 +188,7 @@ def _resolve(event, info):
     return GraphQLField(field_type, args=arguments, **kwargs)
 
 
-def field(wrap=None, *, is_subscription=False, description=None):
+def field(wrap=None, *, is_subscription=False, name=None, description=None):
     """Annotates a method or property as a GraphQL field.
 
     This is normally used inside a type declaration:
@@ -202,7 +204,9 @@ def field(wrap=None, *, is_subscription=False, description=None):
     it can be used both as decorator and as a normal function.
     """
 
-    field = strawberry_field(description=description, is_subscription=is_subscription)
+    field = strawberry_field(
+        name=name, description=description, is_subscription=is_subscription
+    )
 
     # when calling this with parens we are going to return a strawberry_field
     # instance, so it can be used as both decorator and function.
diff --git a/strawberry/type.py b/strawberry/type.py
--- a/strawberry/type.py
+++ b/strawberry/type.py
@@ -12,6 +12,7 @@
 from graphql.utilities.schema_printer import print_type
 
 from .constants import IS_STRAWBERRY_FIELD, IS_STRAWBERRY_INPUT, IS_STRAWBERRY_INTERFACE
+from .field import strawberry_field
 from .type_converter import REGISTRY, get_graphql_type_for_annotation
 from .utils.str_converters import to_camel_case
 
@@ -26,6 +27,10 @@ def _resolver(obj, info):
         if getattr(field_resolver, IS_STRAWBERRY_FIELD, False):
             return field_resolver(obj, info)
 
+        elif field_resolver.__class__ is strawberry_field:
+            # TODO: support default values
+            return None
+
         return field_resolver
 
     return _resolver
@@ -38,10 +43,12 @@ def _convert_annotations_fields(cls, *, is_input=False):
     fields = {}
 
     for key, annotation in annotations.items():
-        field_name = to_camel_case(key)
         class_field = getattr(cls, key, None)
 
         description = getattr(class_field, "description", None)
+        name = getattr(class_field, "name", None)
+
+        field_name = name or to_camel_case(key)
 
         fields[field_name] = FieldClass(
             get_graphql_type_for_annotation(annotation, key),
@@ -64,13 +71,16 @@ def repr_(self):
     def _get_fields():
         fields = _convert_annotations_fields(cls, is_input=is_input)
 
-        fields.update(
-            {
-                to_camel_case(key): value.field
-                for key, value in cls.__dict__.items()
-                if getattr(value, IS_STRAWBERRY_FIELD, False)
-            }
-        )
+        strawberry_fields = {
+            key: value
+            for key, value in cls.__dict__.items()
+            if getattr(value, IS_STRAWBERRY_FIELD, False)
+        }
+
+        for key, value in strawberry_fields.items():
+            name = getattr(value, "name", None) or to_camel_case(key)
+
+            fields[name] = value.field
 
         return fields
 


Test patch:
diff --git a/tests/test_schema.py b/tests/test_schema.py
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -1,3 +1,4 @@
+import typing
 from enum import Enum
 
 import strawberry
@@ -132,6 +133,29 @@ def example(self, info, query_param: str) -> str:
     assert result.data["example"] == "hi"
 
 
+def test_can_rename_fields():
+    @strawberry.type
+    class Query:
+        hello_world: typing.Optional[str] = strawberry.field(name="hello")
+
+        @strawberry.field(name="example1")
+        def example(self, info, query_param: str) -> str:
+            return query_param
+
+    schema = strawberry.Schema(query=Query)
+
+    query = """{
+        hello
+        example1(queryParam: "hi")
+    }"""
+
+    result = graphql_sync(schema, query)
+
+    assert not result.errors
+    assert result.data["hello"] is None
+    assert result.data["example1"] == "hi"
+
+
 def test_type_description():
     @strawberry.type(description="Decorator argument description")
     class TypeA: