node.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. from typing import Any, Callable, NamedTuple, Optional, Union
  2. from graphql_relay.utils.base64 import base64, unbase64
  3. from graphql import (
  4. GraphQLArgument,
  5. GraphQLNonNull,
  6. GraphQLID,
  7. GraphQLField,
  8. GraphQLInterfaceType,
  9. GraphQLList,
  10. GraphQLResolveInfo,
  11. GraphQLTypeResolver,
  12. )
  13. __all__ = [
  14. "from_global_id",
  15. "global_id_field",
  16. "node_definitions",
  17. "to_global_id",
  18. "GraphQLNodeDefinitions",
  19. "ResolvedGlobalId",
  20. ]
  21. class GraphQLNodeDefinitions(NamedTuple):
  22. node_interface: GraphQLInterfaceType
  23. node_field: GraphQLField
  24. nodes_field: GraphQLField
  25. def node_definitions(
  26. fetch_by_id: Callable[[str, GraphQLResolveInfo], Any],
  27. type_resolver: Optional[GraphQLTypeResolver] = None,
  28. ) -> GraphQLNodeDefinitions:
  29. """
  30. Given a function to map from an ID to an underlying object, and a function
  31. to map from an underlying object to the concrete GraphQLObjectType it
  32. corresponds to, constructs a `Node` interface that objects can implement,
  33. and a field object to be used as a `node` root field.
  34. If the type_resolver is omitted, object resolution on the interface will be
  35. handled with the `is_type_of` method on object types, as with any GraphQL
  36. interface without a provided `resolve_type` method.
  37. """
  38. node_interface = GraphQLInterfaceType(
  39. "Node",
  40. description="An object with an ID",
  41. fields=lambda: {
  42. "id": GraphQLField(
  43. GraphQLNonNull(GraphQLID), description="The id of the object."
  44. )
  45. },
  46. resolve_type=type_resolver,
  47. )
  48. # noinspection PyShadowingBuiltins
  49. node_field = GraphQLField(
  50. node_interface,
  51. description="Fetches an object given its ID",
  52. args={
  53. "id": GraphQLArgument(
  54. GraphQLNonNull(GraphQLID), description="The ID of an object"
  55. )
  56. },
  57. resolve=lambda _obj, info, id: fetch_by_id(id, info),
  58. )
  59. nodes_field = GraphQLField(
  60. GraphQLNonNull(GraphQLList(node_interface)),
  61. description="Fetches objects given their IDs",
  62. args={
  63. "ids": GraphQLArgument(
  64. GraphQLNonNull(GraphQLList(GraphQLNonNull(GraphQLID))),
  65. description="The IDs of objects",
  66. )
  67. },
  68. resolve=lambda _obj, info, ids: [fetch_by_id(id_, info) for id_ in ids],
  69. )
  70. return GraphQLNodeDefinitions(node_interface, node_field, nodes_field)
  71. class ResolvedGlobalId(NamedTuple):
  72. type: str
  73. id: str
  74. def to_global_id(type_: str, id_: Union[str, int]) -> str:
  75. """
  76. Takes a type name and an ID specific to that type name, and returns a
  77. "global ID" that is unique among all types.
  78. """
  79. return base64(f"{type_}:{GraphQLID.serialize(id_)}")
  80. def from_global_id(global_id: str) -> ResolvedGlobalId:
  81. """
  82. Takes the "global ID" created by to_global_id, and returns the type name and ID
  83. used to create it.
  84. """
  85. global_id = unbase64(global_id)
  86. if ":" not in global_id:
  87. return ResolvedGlobalId("", global_id)
  88. return ResolvedGlobalId(*global_id.split(":", 1))
  89. def global_id_field(
  90. type_name: Optional[str] = None,
  91. id_fetcher: Optional[Callable[[Any, GraphQLResolveInfo], str]] = None,
  92. ) -> GraphQLField:
  93. """
  94. Creates the configuration for an id field on a node, using `to_global_id` to
  95. construct the ID from the provided typename. The type-specific ID is fetched
  96. by calling id_fetcher on the object, or if not provided, by accessing the `id`
  97. attribute of the object, or the `id` if the object is a dict.
  98. """
  99. def resolve(obj: Any, info: GraphQLResolveInfo, **_args: Any) -> str:
  100. type_ = type_name or info.parent_type.name
  101. id_ = (
  102. id_fetcher(obj, info)
  103. if id_fetcher
  104. else (obj["id"] if isinstance(obj, dict) else obj.id)
  105. )
  106. return to_global_id(type_, id_)
  107. return GraphQLField(
  108. GraphQLNonNull(GraphQLID), description="The ID of an object", resolve=resolve
  109. )