Files
zenml/venv/lib/python3.9/site-packages/graphql/utilities/build_client_schema.py
Christian Mantha 2ca0b9ef7c star
2026-03-02 19:10:52 -05:00

432 lines
17 KiB
Python

from itertools import chain
from typing import cast, Callable, Collection, Dict, List, Union
from ..language import DirectiveLocation, parse_value
from ..pyutils import inspect, Undefined
from ..type import (
GraphQLArgument,
GraphQLDirective,
GraphQLEnumType,
GraphQLEnumValue,
GraphQLField,
GraphQLInputField,
GraphQLInputObjectType,
GraphQLInputType,
GraphQLInterfaceType,
GraphQLList,
GraphQLNamedType,
GraphQLNonNull,
GraphQLObjectType,
GraphQLOutputType,
GraphQLScalarType,
GraphQLSchema,
GraphQLType,
GraphQLUnionType,
TypeKind,
assert_interface_type,
assert_nullable_type,
assert_object_type,
introspection_types,
is_input_type,
is_output_type,
specified_scalar_types,
)
from .get_introspection_query import (
IntrospectionDirective,
IntrospectionEnumType,
IntrospectionField,
IntrospectionInterfaceType,
IntrospectionInputObjectType,
IntrospectionInputValue,
IntrospectionObjectType,
IntrospectionQuery,
IntrospectionScalarType,
IntrospectionType,
IntrospectionTypeRef,
IntrospectionUnionType,
)
from .value_from_ast import value_from_ast
__all__ = ["build_client_schema"]
def build_client_schema(
introspection: IntrospectionQuery, assume_valid: bool = False
) -> GraphQLSchema:
"""Build a GraphQLSchema for use by client tools.
Given the result of a client running the introspection query, creates and returns
a GraphQLSchema instance which can be then used with all GraphQL-core 3 tools,
but cannot be used to execute a query, as introspection does not represent the
"resolver", "parse" or "serialize" functions or any other server-internal
mechanisms.
This function expects a complete introspection result. Don't forget to check the
"errors" field of a server response before calling this function.
"""
if not isinstance(introspection, dict) or not isinstance(
introspection.get("__schema"), dict
):
raise TypeError(
"Invalid or incomplete introspection result. Ensure that you"
" are passing the 'data' attribute of an introspection response"
f" and no 'errors' were returned alongside: {inspect(introspection)}."
)
# Get the schema from the introspection result.
schema_introspection = introspection["__schema"]
# Given a type reference in introspection, return the GraphQLType instance,
# preferring cached instances before building new instances.
def get_type(type_ref: IntrospectionTypeRef) -> GraphQLType:
kind = type_ref.get("kind")
if kind == TypeKind.LIST.name:
item_ref = type_ref.get("ofType")
if not item_ref:
raise TypeError("Decorated type deeper than introspection query.")
item_ref = cast(IntrospectionTypeRef, item_ref)
return GraphQLList(get_type(item_ref))
if kind == TypeKind.NON_NULL.name:
nullable_ref = type_ref.get("ofType")
if not nullable_ref:
raise TypeError("Decorated type deeper than introspection query.")
nullable_ref = cast(IntrospectionTypeRef, nullable_ref)
nullable_type = get_type(nullable_ref)
return GraphQLNonNull(assert_nullable_type(nullable_type))
type_ref = cast(IntrospectionType, type_ref)
return get_named_type(type_ref)
def get_named_type(type_ref: IntrospectionType) -> GraphQLNamedType:
type_name = type_ref.get("name")
if not type_name:
raise TypeError(f"Unknown type reference: {inspect(type_ref)}.")
type_ = type_map.get(type_name)
if not type_:
raise TypeError(
f"Invalid or incomplete schema, unknown type: {type_name}."
" Ensure that a full introspection query is used in order"
" to build a client schema."
)
return type_
def get_object_type(type_ref: IntrospectionObjectType) -> GraphQLObjectType:
return assert_object_type(get_type(type_ref))
def get_interface_type(
type_ref: IntrospectionInterfaceType,
) -> GraphQLInterfaceType:
return assert_interface_type(get_type(type_ref))
# Given a type's introspection result, construct the correct GraphQLType instance.
def build_type(type_: IntrospectionType) -> GraphQLNamedType:
if type_ and "name" in type_ and "kind" in type_:
builder = type_builders.get(type_["kind"])
if builder: # pragma: no cover else
return builder(type_)
raise TypeError(
"Invalid or incomplete introspection result."
" Ensure that a full introspection query is used in order"
f" to build a client schema: {inspect(type_)}."
)
def build_scalar_def(
scalar_introspection: IntrospectionScalarType,
) -> GraphQLScalarType:
name = scalar_introspection["name"]
try:
return cast(GraphQLScalarType, GraphQLScalarType.reserved_types[name])
except KeyError:
return GraphQLScalarType(
name=name,
description=scalar_introspection.get("description"),
specified_by_url=scalar_introspection.get("specifiedByURL"),
)
def build_implementations_list(
implementing_introspection: Union[
IntrospectionObjectType, IntrospectionInterfaceType
],
) -> List[GraphQLInterfaceType]:
maybe_interfaces = implementing_introspection.get("interfaces")
if maybe_interfaces is None:
# Temporary workaround until GraphQL ecosystem will fully support
# 'interfaces' on interface types
if implementing_introspection["kind"] == TypeKind.INTERFACE.name:
return []
raise TypeError(
"Introspection result missing interfaces:"
f" {inspect(implementing_introspection)}."
)
interfaces = cast(Collection[IntrospectionInterfaceType], maybe_interfaces)
return [get_interface_type(interface) for interface in interfaces]
def build_object_def(
object_introspection: IntrospectionObjectType,
) -> GraphQLObjectType:
name = object_introspection["name"]
try:
return cast(GraphQLObjectType, GraphQLObjectType.reserved_types[name])
except KeyError:
return GraphQLObjectType(
name=name,
description=object_introspection.get("description"),
interfaces=lambda: build_implementations_list(object_introspection),
fields=lambda: build_field_def_map(object_introspection),
)
def build_interface_def(
interface_introspection: IntrospectionInterfaceType,
) -> GraphQLInterfaceType:
return GraphQLInterfaceType(
name=interface_introspection["name"],
description=interface_introspection.get("description"),
interfaces=lambda: build_implementations_list(interface_introspection),
fields=lambda: build_field_def_map(interface_introspection),
)
def build_union_def(
union_introspection: IntrospectionUnionType,
) -> GraphQLUnionType:
maybe_possible_types = union_introspection.get("possibleTypes")
if maybe_possible_types is None:
raise TypeError(
"Introspection result missing possibleTypes:"
f" {inspect(union_introspection)}."
)
possible_types = cast(Collection[IntrospectionObjectType], maybe_possible_types)
return GraphQLUnionType(
name=union_introspection["name"],
description=union_introspection.get("description"),
types=lambda: [get_object_type(type_) for type_ in possible_types],
)
def build_enum_def(enum_introspection: IntrospectionEnumType) -> GraphQLEnumType:
if enum_introspection.get("enumValues") is None:
raise TypeError(
"Introspection result missing enumValues:"
f" {inspect(enum_introspection)}."
)
name = enum_introspection["name"]
try:
return cast(GraphQLEnumType, GraphQLEnumType.reserved_types[name])
except KeyError:
return GraphQLEnumType(
name=name,
description=enum_introspection.get("description"),
values={
value_introspect["name"]: GraphQLEnumValue(
value=value_introspect["name"],
description=value_introspect.get("description"),
deprecation_reason=value_introspect.get("deprecationReason"),
)
for value_introspect in enum_introspection["enumValues"]
},
)
def build_input_object_def(
input_object_introspection: IntrospectionInputObjectType,
) -> GraphQLInputObjectType:
if input_object_introspection.get("inputFields") is None:
raise TypeError(
"Introspection result missing inputFields:"
f" {inspect(input_object_introspection)}."
)
return GraphQLInputObjectType(
name=input_object_introspection["name"],
description=input_object_introspection.get("description"),
fields=lambda: build_input_value_def_map(
input_object_introspection["inputFields"]
),
is_one_of=input_object_introspection.get("isOneOf", False),
)
type_builders: Dict[str, Callable[[IntrospectionType], GraphQLNamedType]] = {
TypeKind.SCALAR.name: build_scalar_def, # type: ignore
TypeKind.OBJECT.name: build_object_def, # type: ignore
TypeKind.INTERFACE.name: build_interface_def, # type: ignore
TypeKind.UNION.name: build_union_def, # type: ignore
TypeKind.ENUM.name: build_enum_def, # type: ignore
TypeKind.INPUT_OBJECT.name: build_input_object_def, # type: ignore
}
def build_field_def_map(
type_introspection: Union[IntrospectionObjectType, IntrospectionInterfaceType],
) -> Dict[str, GraphQLField]:
if type_introspection.get("fields") is None:
raise TypeError(
f"Introspection result missing fields: {type_introspection}."
)
return {
field_introspection["name"]: build_field(field_introspection)
for field_introspection in type_introspection["fields"]
}
def build_field(field_introspection: IntrospectionField) -> GraphQLField:
type_introspection = cast(IntrospectionType, field_introspection["type"])
type_ = get_type(type_introspection)
if not is_output_type(type_):
raise TypeError(
"Introspection must provide output type for fields,"
f" but received: {inspect(type_)}."
)
type_ = cast(GraphQLOutputType, type_)
args_introspection = field_introspection.get("args")
if args_introspection is None:
raise TypeError(
"Introspection result missing field args:"
f" {inspect(field_introspection)}."
)
return GraphQLField(
type_,
args=build_argument_def_map(args_introspection),
description=field_introspection.get("description"),
deprecation_reason=field_introspection.get("deprecationReason"),
)
def build_argument_def_map(
argument_value_introspections: Collection[IntrospectionInputValue],
) -> Dict[str, GraphQLArgument]:
return {
argument_introspection["name"]: build_argument(argument_introspection)
for argument_introspection in argument_value_introspections
}
def build_argument(
argument_introspection: IntrospectionInputValue,
) -> GraphQLArgument:
type_introspection = cast(IntrospectionType, argument_introspection["type"])
type_ = get_type(type_introspection)
if not is_input_type(type_):
raise TypeError(
"Introspection must provide input type for arguments,"
f" but received: {inspect(type_)}."
)
type_ = cast(GraphQLInputType, type_)
default_value_introspection = argument_introspection.get("defaultValue")
default_value = (
Undefined
if default_value_introspection is None
else value_from_ast(parse_value(default_value_introspection), type_)
)
return GraphQLArgument(
type_,
default_value=default_value,
description=argument_introspection.get("description"),
deprecation_reason=argument_introspection.get("deprecationReason"),
)
def build_input_value_def_map(
input_value_introspections: Collection[IntrospectionInputValue],
) -> Dict[str, GraphQLInputField]:
return {
input_value_introspection["name"]: build_input_value(
input_value_introspection
)
for input_value_introspection in input_value_introspections
}
def build_input_value(
input_value_introspection: IntrospectionInputValue,
) -> GraphQLInputField:
type_introspection = cast(IntrospectionType, input_value_introspection["type"])
type_ = get_type(type_introspection)
if not is_input_type(type_):
raise TypeError(
"Introspection must provide input type for input fields,"
f" but received: {inspect(type_)}."
)
type_ = cast(GraphQLInputType, type_)
default_value_introspection = input_value_introspection.get("defaultValue")
default_value = (
Undefined
if default_value_introspection is None
else value_from_ast(parse_value(default_value_introspection), type_)
)
return GraphQLInputField(
type_,
default_value=default_value,
description=input_value_introspection.get("description"),
deprecation_reason=input_value_introspection.get("deprecationReason"),
)
def build_directive(
directive_introspection: IntrospectionDirective,
) -> GraphQLDirective:
if directive_introspection.get("args") is None:
raise TypeError(
"Introspection result missing directive args:"
f" {inspect(directive_introspection)}."
)
if directive_introspection.get("locations") is None:
raise TypeError(
"Introspection result missing directive locations:"
f" {inspect(directive_introspection)}."
)
return GraphQLDirective(
name=directive_introspection["name"],
description=directive_introspection.get("description"),
is_repeatable=directive_introspection.get("isRepeatable", False),
locations=list(
cast(
Collection[DirectiveLocation],
directive_introspection.get("locations"),
)
),
args=build_argument_def_map(directive_introspection["args"]),
)
# Iterate through all types, getting the type definition for each.
type_map: Dict[str, GraphQLNamedType] = {
type_introspection["name"]: build_type(type_introspection)
for type_introspection in schema_introspection["types"]
}
# Include standard types only if they are used.
for std_type_name, std_type in chain(
specified_scalar_types.items(), introspection_types.items()
):
if std_type_name in type_map:
type_map[std_type_name] = std_type
# Get the root Query, Mutation, and Subscription types.
query_type_ref = schema_introspection.get("queryType")
query_type = None if query_type_ref is None else get_object_type(query_type_ref)
mutation_type_ref = schema_introspection.get("mutationType")
mutation_type = (
None if mutation_type_ref is None else get_object_type(mutation_type_ref)
)
subscription_type_ref = schema_introspection.get("subscriptionType")
subscription_type = (
None
if subscription_type_ref is None
else get_object_type(subscription_type_ref)
)
# Get the directives supported by Introspection, assuming empty-set if directives
# were not queried for.
directive_introspections = schema_introspection.get("directives")
directives = (
[
build_directive(directive_introspection)
for directive_introspection in directive_introspections
]
if directive_introspections
else []
)
# Then produce and return a Schema with these types.
return GraphQLSchema(
query=query_type,
mutation=mutation_type,
subscription=subscription_type,
types=list(type_map.values()),
directives=directives,
description=schema_introspection.get("description"),
assume_valid=assume_valid,
)