133 lines
4.0 KiB
Python
133 lines
4.0 KiB
Python
from typing import Any, Callable, NamedTuple, Optional, Union
|
|
|
|
from graphql_relay.utils.base64 import base64, unbase64
|
|
|
|
from graphql import (
|
|
GraphQLArgument,
|
|
GraphQLNonNull,
|
|
GraphQLID,
|
|
GraphQLField,
|
|
GraphQLInterfaceType,
|
|
GraphQLList,
|
|
GraphQLResolveInfo,
|
|
GraphQLTypeResolver,
|
|
)
|
|
|
|
__all__ = [
|
|
"from_global_id",
|
|
"global_id_field",
|
|
"node_definitions",
|
|
"to_global_id",
|
|
"GraphQLNodeDefinitions",
|
|
"ResolvedGlobalId",
|
|
]
|
|
|
|
|
|
class GraphQLNodeDefinitions(NamedTuple):
|
|
|
|
node_interface: GraphQLInterfaceType
|
|
node_field: GraphQLField
|
|
nodes_field: GraphQLField
|
|
|
|
|
|
def node_definitions(
|
|
fetch_by_id: Callable[[str, GraphQLResolveInfo], Any],
|
|
type_resolver: Optional[GraphQLTypeResolver] = None,
|
|
) -> GraphQLNodeDefinitions:
|
|
"""
|
|
Given a function to map from an ID to an underlying object, and a function
|
|
to map from an underlying object to the concrete GraphQLObjectType it
|
|
corresponds to, constructs a `Node` interface that objects can implement,
|
|
and a field object to be used as a `node` root field.
|
|
|
|
If the type_resolver is omitted, object resolution on the interface will be
|
|
handled with the `is_type_of` method on object types, as with any GraphQL
|
|
interface without a provided `resolve_type` method.
|
|
"""
|
|
node_interface = GraphQLInterfaceType(
|
|
"Node",
|
|
description="An object with an ID",
|
|
fields=lambda: {
|
|
"id": GraphQLField(
|
|
GraphQLNonNull(GraphQLID), description="The id of the object."
|
|
)
|
|
},
|
|
resolve_type=type_resolver,
|
|
)
|
|
|
|
# noinspection PyShadowingBuiltins
|
|
node_field = GraphQLField(
|
|
node_interface,
|
|
description="Fetches an object given its ID",
|
|
args={
|
|
"id": GraphQLArgument(
|
|
GraphQLNonNull(GraphQLID), description="The ID of an object"
|
|
)
|
|
},
|
|
resolve=lambda _obj, info, id: fetch_by_id(id, info),
|
|
)
|
|
|
|
nodes_field = GraphQLField(
|
|
GraphQLNonNull(GraphQLList(node_interface)),
|
|
description="Fetches objects given their IDs",
|
|
args={
|
|
"ids": GraphQLArgument(
|
|
GraphQLNonNull(GraphQLList(GraphQLNonNull(GraphQLID))),
|
|
description="The IDs of objects",
|
|
)
|
|
},
|
|
resolve=lambda _obj, info, ids: [fetch_by_id(id_, info) for id_ in ids],
|
|
)
|
|
|
|
return GraphQLNodeDefinitions(node_interface, node_field, nodes_field)
|
|
|
|
|
|
class ResolvedGlobalId(NamedTuple):
|
|
|
|
type: str
|
|
id: str
|
|
|
|
|
|
def to_global_id(type_: str, id_: Union[str, int]) -> str:
|
|
"""
|
|
Takes a type name and an ID specific to that type name, and returns a
|
|
"global ID" that is unique among all types.
|
|
"""
|
|
return base64(f"{type_}:{GraphQLID.serialize(id_)}")
|
|
|
|
|
|
def from_global_id(global_id: str) -> ResolvedGlobalId:
|
|
"""
|
|
Takes the "global ID" created by to_global_id, and returns the type name and ID
|
|
used to create it.
|
|
"""
|
|
global_id = unbase64(global_id)
|
|
if ":" not in global_id:
|
|
return ResolvedGlobalId("", global_id)
|
|
return ResolvedGlobalId(*global_id.split(":", 1))
|
|
|
|
|
|
def global_id_field(
|
|
type_name: Optional[str] = None,
|
|
id_fetcher: Optional[Callable[[Any, GraphQLResolveInfo], str]] = None,
|
|
) -> GraphQLField:
|
|
"""
|
|
Creates the configuration for an id field on a node, using `to_global_id` to
|
|
construct the ID from the provided typename. The type-specific ID is fetched
|
|
by calling id_fetcher on the object, or if not provided, by accessing the `id`
|
|
attribute of the object, or the `id` if the object is a dict.
|
|
"""
|
|
|
|
def resolve(obj: Any, info: GraphQLResolveInfo, **_args: Any) -> str:
|
|
type_ = type_name or info.parent_type.name
|
|
id_ = (
|
|
id_fetcher(obj, info)
|
|
if id_fetcher
|
|
else (obj["id"] if isinstance(obj, dict) else obj.id)
|
|
)
|
|
return to_global_id(type_, id_)
|
|
|
|
return GraphQLField(
|
|
GraphQLNonNull(GraphQLID), description="The ID of an object", resolve=resolve
|
|
)
|