diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000000..8039d98744 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,11 @@ +Release Type: minor + +Added support for renaming fields. Example usage: + + +```python +@strawberry.type +class Query: + example: str = strawberry.field(name='test') +``` + diff --git a/strawberry/field.py b/strawberry/field.py index 2210799d7a..fc51e3db67 100644 --- 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 index 28781b5dd4..a1d3e1c864 100644 --- 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 diff --git a/tests/test_schema.py b/tests/test_schema.py index 0d9f6f4101..91fe2e7bfe 100644 --- 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: