From 48f249af3ba35d204b74db1a5e1d4768408d5994 Mon Sep 17 00:00:00 2001 From: Jonathan Kim Date: Sat, 27 Jun 2020 12:58:16 +0100 Subject: [PATCH] Add experimental mutation decorator --- graphene/experimental/__init__.py | 0 graphene/experimental/decorators/__init__.py | 0 graphene/experimental/decorators/mutation.py | 23 +++ .../experimental/decorators/tests/__init__.py | 0 .../decorators/tests/test_mutation.py | 162 ++++++++++++++++++ 5 files changed, 185 insertions(+) create mode 100644 graphene/experimental/__init__.py create mode 100644 graphene/experimental/decorators/__init__.py create mode 100644 graphene/experimental/decorators/mutation.py create mode 100644 graphene/experimental/decorators/tests/__init__.py create mode 100644 graphene/experimental/decorators/tests/test_mutation.py diff --git a/graphene/experimental/__init__.py b/graphene/experimental/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/graphene/experimental/decorators/__init__.py b/graphene/experimental/decorators/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/graphene/experimental/decorators/mutation.py b/graphene/experimental/decorators/mutation.py new file mode 100644 index 00000000..5bf3e3bd --- /dev/null +++ b/graphene/experimental/decorators/mutation.py @@ -0,0 +1,23 @@ +from graphene.types.field import Field +from graphene.utils.str_converters import to_camel_case + + +def mutation(return_type, arguments=None, **kwargs): + # TODO: validate input arguments + if arguments is None: + arguments = {} + + def decorate(resolver_function): + name = kwargs.pop("name", None) or resolver_function.__name__ + description = kwargs.pop("description", None) or resolver_function.__doc__ + + return Field( + return_type, + args=arguments, + name=to_camel_case(name), + resolver=resolver_function, + description=description, + **kwargs + ) + + return decorate diff --git a/graphene/experimental/decorators/tests/__init__.py b/graphene/experimental/decorators/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/graphene/experimental/decorators/tests/test_mutation.py b/graphene/experimental/decorators/tests/test_mutation.py new file mode 100644 index 00000000..e4c03fd9 --- /dev/null +++ b/graphene/experimental/decorators/tests/test_mutation.py @@ -0,0 +1,162 @@ +from textwrap import dedent + +from graphene import String, ObjectType, Schema, Union, Field + +from ..mutation import mutation + + +def test_mutation_basic(): + @mutation(String, required=True) + def my_mutation(root, info): + return "hi" + + class Query(ObjectType): + a = String() + + schema = Schema(query=Query, mutations=[my_mutation]) + result = schema.execute( + """ + mutation MyMutation { + myMutation + } + """ + ) + + assert not result.errors + assert result.data == {"myMutation": "hi"} + + +def test_mutation_arguments(): + @mutation(String, required=True, arguments={"name": String(required=True)}) + def my_mutation(root, info, name): + return f"hi {name}" + + class Query(ObjectType): + a = String() + + schema = Schema(query=Query, mutations=[my_mutation]) + result = schema.execute( + """ + mutation MyMutation { + myMutation(name: "world") + } + """ + ) + + assert not result.errors + assert result.data == {"myMutation": "hi world"} + + +def test_mutation_field_options(): + @mutation( + String, + required=True, + arguments={"name": String(required=True)}, + name="other_mutation", + deprecation_reason="Don't use this mutation", + description="Some description", + ) + def my_mutation(root, info, name): + return f"hi {name}" + + class Query(ObjectType): + a = String() + + schema = Schema(query=Query, mutations=[my_mutation]) + result = schema.execute( + """ + mutation MyMutation { + otherMutation(name: "world") + } + """ + ) + + assert not result.errors + assert result.data == {"otherMutation": "hi world"} + + assert str(schema) == dedent( + """\ + type Query { + a: String + } + + type Mutation { + \"\"\"Some description\"\"\" + otherMutation(name: String!): String! @deprecated(reason: \"Don't use this mutation\") + } + """ + ) + + +def test_mutation_complex_return(): + class User(ObjectType): + name = String(required=True) + + class CreateUserSuccess(ObjectType): + user = Field(User, required=True) + + class CreateUserError(ObjectType): + error_message = String(required=True) + + class CreateUserOutput(Union): + class Meta: + types = [ + CreateUserSuccess, + CreateUserError, + ] + + @mutation( + CreateUserOutput, required=True, arguments={"name": String(required=True)} + ) + def create_user(root, info, name): + return CreateUserSuccess(user=User(name=name)) + + class Query(ObjectType): + a = String() + + schema = Schema(query=Query, mutations=[create_user]) + result = schema.execute( + """ + mutation CreateUserMutation { + createUser(name: "Kate") { + __typename + ... on CreateUserSuccess { + user { + name + } + } + } + } + """ + ) + + assert not result.errors + assert result.data == { + "createUser": {"__typename": "CreateUserSuccess", "user": {"name": "Kate"}} + } + + assert str(schema) == dedent( + """\ + type Query { + a: String + } + + type Mutation { + createUser(name: String!): CreateUserOutput! + } + + union CreateUserOutput = CreateUserSuccess | CreateUserError + + type CreateUserSuccess { + user: User! + } + + type User { + name: String! + } + + type CreateUserError { + errorMessage: String! + } + """ + )