mutation.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. from collections.abc import Mapping
  2. from inspect import iscoroutinefunction
  3. from typing import Any, Callable, Dict, Optional
  4. from graphql import (
  5. resolve_thunk,
  6. GraphQLArgument,
  7. GraphQLField,
  8. GraphQLFieldMap,
  9. GraphQLInputField,
  10. GraphQLInputFieldMap,
  11. GraphQLInputObjectType,
  12. GraphQLNonNull,
  13. GraphQLObjectType,
  14. GraphQLResolveInfo,
  15. GraphQLString,
  16. ThunkMapping,
  17. )
  18. from graphql.pyutils import AwaitableOrValue
  19. __all__ = [
  20. "mutation_with_client_mutation_id",
  21. "MutationFn",
  22. "MutationFnWithoutArgs",
  23. "NullResult",
  24. ]
  25. # Note: Contrary to the Javascript implementation of MutationFn,
  26. # the context is passed as part of the GraphQLResolveInfo and any arguments
  27. # are passed individually as keyword arguments.
  28. MutationFnWithoutArgs = Callable[[GraphQLResolveInfo], AwaitableOrValue[Any]]
  29. # Unfortunately there is currently no syntax to indicate optional or keyword
  30. # arguments in Python, so we also allow any other Callable as a workaround:
  31. MutationFn = Callable[..., AwaitableOrValue[Any]]
  32. class NullResult:
  33. def __init__(self, clientMutationId: Optional[str] = None) -> None:
  34. self.clientMutationId = clientMutationId
  35. def mutation_with_client_mutation_id(
  36. name: str,
  37. input_fields: ThunkMapping[GraphQLInputField],
  38. output_fields: ThunkMapping[GraphQLField],
  39. mutate_and_get_payload: MutationFn,
  40. description: Optional[str] = None,
  41. deprecation_reason: Optional[str] = None,
  42. extensions: Optional[Dict[str, Any]] = None,
  43. ) -> GraphQLField:
  44. """
  45. Returns a GraphQLFieldConfig for the specified mutation.
  46. The input_fields and output_fields should not include `clientMutationId`,
  47. as this will be provided automatically.
  48. An input object will be created containing the input fields, and an
  49. object will be created containing the output fields.
  50. mutate_and_get_payload will receive a GraphQLResolveInfo as first argument,
  51. and the input fields as keyword arguments, and it should return an object
  52. (or a dict) with an attribute (or a key) for each output field.
  53. It may return synchronously or asynchronously.
  54. """
  55. def augmented_input_fields() -> GraphQLInputFieldMap:
  56. return dict(
  57. resolve_thunk(input_fields),
  58. clientMutationId=GraphQLInputField(GraphQLString),
  59. )
  60. def augmented_output_fields() -> GraphQLFieldMap:
  61. return dict(
  62. resolve_thunk(output_fields),
  63. clientMutationId=GraphQLField(GraphQLString),
  64. )
  65. output_type = GraphQLObjectType(name + "Payload", fields=augmented_output_fields)
  66. input_type = GraphQLInputObjectType(name + "Input", fields=augmented_input_fields)
  67. if iscoroutinefunction(mutate_and_get_payload):
  68. # noinspection PyShadowingBuiltins
  69. async def resolve(_root: Any, info: GraphQLResolveInfo, input: Dict) -> Any:
  70. payload = await mutate_and_get_payload(info, **input)
  71. clientMutationId = input.get("clientMutationId")
  72. if payload is None:
  73. return NullResult(clientMutationId)
  74. if isinstance(payload, Mapping):
  75. payload["clientMutationId"] = clientMutationId # type: ignore
  76. else:
  77. payload.clientMutationId = clientMutationId
  78. return payload
  79. else:
  80. # noinspection PyShadowingBuiltins
  81. def resolve( # type: ignore
  82. _root: Any, info: GraphQLResolveInfo, input: Dict
  83. ) -> Any:
  84. payload = mutate_and_get_payload(info, **input)
  85. clientMutationId = input.get("clientMutationId")
  86. if payload is None:
  87. return NullResult(clientMutationId)
  88. if isinstance(payload, Mapping):
  89. payload["clientMutationId"] = clientMutationId # type: ignore
  90. else:
  91. payload.clientMutationId = clientMutationId # type: ignore
  92. return payload
  93. return GraphQLField(
  94. output_type,
  95. description=description,
  96. deprecation_reason=deprecation_reason,
  97. args={"input": GraphQLArgument(GraphQLNonNull(input_type))},
  98. resolve=resolve,
  99. extensions=extensions,
  100. )