Skip to content

Commit f414d54

Browse files
committed
Add support for relay mutations.
1 parent 7f336ab commit f414d54

File tree

7 files changed

+159
-7
lines changed

7 files changed

+159
-7
lines changed

epoxy/contrib/relay/metaclasses/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from ....metaclasses.mutation import MutationMeta
2+
3+
4+
class RelayMutationMeta(MutationMeta):
5+
@staticmethod
6+
def _process_input_attrs(registry, input_attrs):
7+
input_attrs['client_mutation_id'] = registry.String
8+
return input_attrs
9+
10+
@staticmethod
11+
def _process_output_attrs(registry, output_attrs):
12+
output_attrs['client_mutation_id'] = registry.String
13+
return output_attrs
14+
15+
@staticmethod
16+
def _process_resolver(resolver, input_class, obj, args, info):
17+
input_obj = input_class(args.get('input'))
18+
result = resolver(obj, input_obj, info)
19+
result.client_mutation_id = input_obj.client_mutation_id
20+
return result

epoxy/contrib/relay/mixin.py

+22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from graphql.core.type import GraphQLArgument
22
from graphql.core.type.definition import GraphQLObjectType
3+
import six
4+
from ...bases.mutation import MutationBase
35
from .connections import connection_args
6+
from .metaclasses.mutation import RelayMutationMeta
47
from .utils import base64, unbase64
58

69

@@ -10,6 +13,7 @@ def __init__(self, registry, data_source):
1013
self.data_source = data_source
1114
self._node_field = None
1215
self._connections = {}
16+
self.Mutation = self._create_mutation_type_class()
1317

1418
@property
1519
def NodeField(self):
@@ -83,3 +87,21 @@ def Connection(self, name, object_type, args=None, resolver=None, **kwargs):
8387

8488
field = self.R.Field(self.connection_definitions(name, object_type)[0], args=args, resolver=resolver, **kwargs)
8589
return field
90+
91+
def _create_mutation_type_class(self):
92+
registry = self.R
93+
94+
class RelayRegistryMutationMeta(RelayMutationMeta):
95+
@staticmethod
96+
def _register(mutation_name, mutation):
97+
registry._register_mutation(mutation_name, mutation)
98+
99+
@staticmethod
100+
def _get_registry():
101+
return registry
102+
103+
@six.add_metaclass(RelayRegistryMutationMeta)
104+
class Mutation(MutationBase):
105+
abstract = True
106+
107+
return Mutation

epoxy/metaclasses/mutation.py

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import functools
12
from graphql.core.type import GraphQLField, GraphQLNonNull
23
from graphql.core.type.definition import GraphQLArgument
34

@@ -17,8 +18,11 @@ def __new__(mcs, name, bases, attrs):
1718
assert output and not hasattr(output, 'T'), 'A mutation must define a class named "Output" inside of it that ' \
1819
'does not subclass an R.ObjectType'
1920

20-
Input = type(name + 'Input', (registry.InputType,), dict(vars(input)))
21-
Output = type(name + 'Payload', (registry.ObjectType,), dict(vars(output)))
21+
input_attrs = mcs._process_input_attrs(registry, dict(vars(input)))
22+
output_attrs = mcs._process_output_attrs(registry, dict(vars(output)))
23+
24+
Input = type(name + 'Input', (registry.InputType,), input_attrs)
25+
Output = type(name + 'Payload', (registry.ObjectType,), output_attrs)
2226
attrs['Input'] = Input
2327
attrs['Output'] = Output
2428

@@ -36,7 +40,7 @@ def __new__(mcs, name, bases, attrs):
3640
args={
3741
'input': GraphQLArgument(GraphQLNonNull(R[Input]))
3842
},
39-
resolver=lambda obj, args, info: resolver(obj, Input(args.get('input')), info)
43+
resolver=functools.partial(mcs._process_resolver, resolver, Input)
4044
)))
4145

4246
@staticmethod
@@ -46,3 +50,15 @@ def _register(mutation_name, mutation):
4650
@staticmethod
4751
def _get_registry():
4852
raise NotImplementedError('_get_registry must be implemented in the sub-metaclass')
53+
54+
@staticmethod
55+
def _process_input_attrs(registry, input_attrs):
56+
return input_attrs
57+
58+
@staticmethod
59+
def _process_output_attrs(registry, output_attrs):
60+
return output_attrs
61+
62+
@staticmethod
63+
def _process_resolver(resolver, input_class, obj, args, info):
64+
return resolver(obj, input_class(args.get('input')), info)

epoxy/registry.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from .bases.class_type_creator import ClassTypeCreator
2222
from .bases.input_type import InputTypeBase
23+
from .bases.mutation import MutationBase
2324
from .bases.object_type import ObjectTypeBase
2425
from .field import Field, InputField
2526
from .metaclasses.input_type import InputTypeMeta
@@ -216,7 +217,7 @@ class InputType(InputTypeBase):
216217
def _create_mutation_type_class(self):
217218
registry = self
218219

219-
class RegistryInputTypeMeta(MutationMeta):
220+
class RegistryMutationMeta(MutationMeta):
220221
@staticmethod
221222
def _register(mutation_name, mutation):
222223
registry._register_mutation(mutation_name, mutation)
@@ -225,11 +226,11 @@ def _register(mutation_name, mutation):
225226
def _get_registry():
226227
return registry
227228

228-
@six.add_metaclass(RegistryInputTypeMeta)
229-
class InputType(InputTypeBase):
229+
@six.add_metaclass(RegistryMutationMeta)
230+
class Mutation(MutationBase):
230231
abstract = True
231232

232-
return InputType
233+
return Mutation
233234

234235
def _register_mutation(self, mutation_name, mutation):
235236
assert mutation_name not in self._mutations, \

tests/test_mutation.py

+24
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import operator
2+
from functools import reduce
13
from graphql.core import graphql
24
from epoxy import TypeRegistry
35

@@ -16,6 +18,20 @@ class Output:
1618
def execute(self, obj, input, info):
1719
return self.Output(sum=input.a + input.b)
1820

21+
class SimpleMultiplication(R.Mutation):
22+
class Input:
23+
a = R.Int.List
24+
25+
class Output:
26+
product = R.Int
27+
input = R.Int.List
28+
29+
def execute(self, obj, input, info):
30+
return self.Output(
31+
input=input.a,
32+
product=reduce(operator.mul, input.a[1:], input.a[0])
33+
)
34+
1935
# Dummy query -- does nothing.
2036
class Query(R.ObjectType):
2137
foo = R.String
@@ -27,6 +43,10 @@ class Query(R.ObjectType):
2743
simpleAddition(input: {a: 5, b: 10}) {
2844
sum
2945
}
46+
simpleMultiplication(input: {a: [1, 2, 3, 4, 5]}) {
47+
product
48+
input
49+
}
3050
}
3151
'''
3252

@@ -35,5 +55,9 @@ class Query(R.ObjectType):
3555
assert result.data == {
3656
'simpleAddition': {
3757
'sum': 15
58+
},
59+
'simpleMultiplication': {
60+
'product': 120,
61+
'input': [1, 2, 3, 4, 5]
3862
}
3963
}
+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import operator
2+
from functools import reduce
3+
from graphql.core import graphql
4+
from epoxy import TypeRegistry
5+
from epoxy.contrib.relay import RelayMixin
6+
7+
8+
def test_simple_mutation():
9+
R = TypeRegistry()
10+
Relay = R.Mixin(RelayMixin, None)
11+
12+
class SimpleAddition(Relay.Mutation):
13+
class Input:
14+
a = R.Int
15+
b = R.Int
16+
17+
class Output:
18+
sum = R.Int
19+
20+
def execute(self, obj, input, info):
21+
return self.Output(sum=input.a + input.b)
22+
23+
class SimpleMultiplication(Relay.Mutation):
24+
class Input:
25+
a = R.Int.List
26+
27+
class Output:
28+
product = R.Int
29+
input = R.Int.List
30+
31+
def execute(self, obj, input, info):
32+
return self.Output(
33+
input=input.a,
34+
product=reduce(operator.mul, input.a[1:], input.a[0])
35+
)
36+
37+
# Dummy query -- does nothing.
38+
class Query(R.ObjectType):
39+
foo = R.String
40+
41+
Schema = R.Schema(R.Query, R.Mutations)
42+
43+
mutation_query = '''
44+
mutation testSimpleAdd {
45+
simpleAddition(input: {a: 5, b: 10, clientMutationId: "test123"}) {
46+
clientMutationId
47+
sum
48+
}
49+
simpleMultiplication(input: {a: [1, 2, 3, 4, 5], clientMutationId: "0xdeadbeef"}) {
50+
clientMutationId
51+
product
52+
input
53+
}
54+
}
55+
'''
56+
57+
result = graphql(Schema, mutation_query)
58+
assert not result.errors
59+
assert result.data == {
60+
'simpleAddition': {
61+
'sum': 15,
62+
'clientMutationId': 'test123'
63+
},
64+
'simpleMultiplication': {
65+
'clientMutationId': '0xdeadbeef',
66+
'product': 120,
67+
'input': [1, 2, 3, 4, 5]
68+
}
69+
}

0 commit comments

Comments
 (0)