2025-12-01
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
"""GraphQL Utilities
|
||||
|
||||
The :mod:`graphql.utilities` package contains common useful computations to use with
|
||||
the GraphQL language and type objects.
|
||||
"""
|
||||
|
||||
# Produce the GraphQL query recommended for a full schema introspection.
|
||||
from .get_introspection_query import get_introspection_query, IntrospectionQuery
|
||||
|
||||
# Get the target Operation from a Document.
|
||||
from .get_operation_ast import get_operation_ast
|
||||
|
||||
# Get the Type for the target Operation AST.
|
||||
from .get_operation_root_type import get_operation_root_type
|
||||
|
||||
# Convert a GraphQLSchema to an IntrospectionQuery.
|
||||
from .introspection_from_schema import introspection_from_schema
|
||||
|
||||
# Build a GraphQLSchema from an introspection result.
|
||||
from .build_client_schema import build_client_schema
|
||||
|
||||
# Build a GraphQLSchema from GraphQL Schema language.
|
||||
from .build_ast_schema import build_ast_schema, build_schema
|
||||
|
||||
# Extend an existing GraphQLSchema from a parsed GraphQL Schema language AST.
|
||||
from .extend_schema import extend_schema
|
||||
|
||||
# Sort a GraphQLSchema.
|
||||
from .lexicographic_sort_schema import lexicographic_sort_schema
|
||||
|
||||
# Print a GraphQLSchema to GraphQL Schema language.
|
||||
from .print_schema import (
|
||||
print_introspection_schema,
|
||||
print_schema,
|
||||
print_type,
|
||||
print_value, # deprecated
|
||||
)
|
||||
|
||||
# Create a GraphQLType from a GraphQL language AST.
|
||||
from .type_from_ast import type_from_ast
|
||||
|
||||
# Convert a language AST to a dictionary.
|
||||
from .ast_to_dict import ast_to_dict
|
||||
|
||||
# Create a Python value from a GraphQL language AST with a type.
|
||||
from .value_from_ast import value_from_ast
|
||||
|
||||
# Create a Python value from a GraphQL language AST without a type.
|
||||
from .value_from_ast_untyped import value_from_ast_untyped
|
||||
|
||||
# Create a GraphQL language AST from a Python value.
|
||||
from .ast_from_value import ast_from_value
|
||||
|
||||
# A helper to use within recursive-descent visitors which need to be aware of
|
||||
# the GraphQL type system
|
||||
from .type_info import TypeInfo, TypeInfoVisitor
|
||||
|
||||
# Coerce a Python value to a GraphQL type, or produce errors.
|
||||
from .coerce_input_value import coerce_input_value
|
||||
|
||||
# Concatenate multiple ASTs together.
|
||||
from .concat_ast import concat_ast
|
||||
|
||||
# Separate an AST into an AST per Operation.
|
||||
from .separate_operations import separate_operations
|
||||
|
||||
# Strip characters that are not significant to the validity or execution
|
||||
# of a GraphQL document.
|
||||
from .strip_ignored_characters import strip_ignored_characters
|
||||
|
||||
# Comparators for types
|
||||
from .type_comparators import is_equal_type, is_type_sub_type_of, do_types_overlap
|
||||
|
||||
# Assert that a string is a valid GraphQL name.
|
||||
from .assert_valid_name import assert_valid_name, is_valid_name_error
|
||||
|
||||
# Compare two GraphQLSchemas and detect breaking changes.
|
||||
from .find_breaking_changes import (
|
||||
BreakingChange,
|
||||
BreakingChangeType,
|
||||
DangerousChange,
|
||||
DangerousChangeType,
|
||||
find_breaking_changes,
|
||||
find_dangerous_changes,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"BreakingChange",
|
||||
"BreakingChangeType",
|
||||
"DangerousChange",
|
||||
"DangerousChangeType",
|
||||
"IntrospectionQuery",
|
||||
"TypeInfo",
|
||||
"TypeInfoVisitor",
|
||||
"assert_valid_name",
|
||||
"ast_from_value",
|
||||
"ast_to_dict",
|
||||
"build_ast_schema",
|
||||
"build_client_schema",
|
||||
"build_schema",
|
||||
"coerce_input_value",
|
||||
"concat_ast",
|
||||
"do_types_overlap",
|
||||
"extend_schema",
|
||||
"find_breaking_changes",
|
||||
"find_dangerous_changes",
|
||||
"get_introspection_query",
|
||||
"get_operation_ast",
|
||||
"get_operation_root_type",
|
||||
"is_equal_type",
|
||||
"is_type_sub_type_of",
|
||||
"is_valid_name_error",
|
||||
"introspection_from_schema",
|
||||
"lexicographic_sort_schema",
|
||||
"print_introspection_schema",
|
||||
"print_schema",
|
||||
"print_type",
|
||||
"print_value",
|
||||
"separate_operations",
|
||||
"strip_ignored_characters",
|
||||
"type_from_ast",
|
||||
"value_from_ast",
|
||||
"value_from_ast_untyped",
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
from typing import Optional
|
||||
|
||||
from ..type.assert_name import assert_name
|
||||
from ..error import GraphQLError
|
||||
|
||||
__all__ = ["assert_valid_name", "is_valid_name_error"]
|
||||
|
||||
|
||||
def assert_valid_name(name: str) -> str:
|
||||
"""Uphold the spec rules about naming.
|
||||
|
||||
.. deprecated:: 3.2
|
||||
Please use ``assert_name`` instead. Will be removed in v3.3.
|
||||
"""
|
||||
error = is_valid_name_error(name)
|
||||
if error:
|
||||
raise error
|
||||
return name
|
||||
|
||||
|
||||
def is_valid_name_error(name: str) -> Optional[GraphQLError]:
|
||||
"""Return an Error if a name is invalid.
|
||||
|
||||
.. deprecated:: 3.2
|
||||
Please use ``assert_name`` instead. Will be removed in v3.3.
|
||||
"""
|
||||
if not isinstance(name, str):
|
||||
raise TypeError("Expected name to be a string.")
|
||||
if name.startswith("__"):
|
||||
return GraphQLError(
|
||||
f"Name {name!r} must not begin with '__',"
|
||||
" which is reserved by GraphQL introspection."
|
||||
)
|
||||
try:
|
||||
assert_name(name)
|
||||
except GraphQLError as error:
|
||||
return error
|
||||
return None
|
||||
@@ -0,0 +1,139 @@
|
||||
import re
|
||||
from math import isfinite
|
||||
from typing import Any, Mapping, Optional, cast
|
||||
|
||||
from ..language import (
|
||||
BooleanValueNode,
|
||||
EnumValueNode,
|
||||
FloatValueNode,
|
||||
IntValueNode,
|
||||
ListValueNode,
|
||||
NameNode,
|
||||
NullValueNode,
|
||||
ObjectFieldNode,
|
||||
ObjectValueNode,
|
||||
StringValueNode,
|
||||
ValueNode,
|
||||
)
|
||||
from ..pyutils import inspect, is_iterable, Undefined
|
||||
from ..type import (
|
||||
GraphQLID,
|
||||
GraphQLInputType,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLList,
|
||||
GraphQLNonNull,
|
||||
is_enum_type,
|
||||
is_input_object_type,
|
||||
is_leaf_type,
|
||||
is_list_type,
|
||||
is_non_null_type,
|
||||
)
|
||||
|
||||
__all__ = ["ast_from_value"]
|
||||
|
||||
_re_integer_string = re.compile("^-?(?:0|[1-9][0-9]*)$")
|
||||
|
||||
|
||||
def ast_from_value(value: Any, type_: GraphQLInputType) -> Optional[ValueNode]:
|
||||
"""Produce a GraphQL Value AST given a Python object.
|
||||
|
||||
This function will match Python/JSON values to GraphQL AST schema format by using
|
||||
the suggested GraphQLInputType. For example::
|
||||
|
||||
ast_from_value('value', GraphQLString)
|
||||
|
||||
A GraphQL type must be provided, which will be used to interpret different Python
|
||||
values.
|
||||
|
||||
================ =======================
|
||||
JSON Value GraphQL Value
|
||||
================ =======================
|
||||
Object Input Object
|
||||
Array List
|
||||
Boolean Boolean
|
||||
String String / Enum Value
|
||||
Number Int / Float
|
||||
Mixed Enum Value
|
||||
null NullValue
|
||||
================ =======================
|
||||
|
||||
"""
|
||||
if is_non_null_type(type_):
|
||||
type_ = cast(GraphQLNonNull, type_)
|
||||
ast_value = ast_from_value(value, type_.of_type)
|
||||
if isinstance(ast_value, NullValueNode):
|
||||
return None
|
||||
return ast_value
|
||||
|
||||
# only explicit None, not Undefined or NaN
|
||||
if value is None:
|
||||
return NullValueNode()
|
||||
|
||||
# undefined
|
||||
if value is Undefined:
|
||||
return None
|
||||
|
||||
# Convert Python list to GraphQL list. If the GraphQLType is a list, but the value
|
||||
# is not a list, convert the value using the list's item type.
|
||||
if is_list_type(type_):
|
||||
type_ = cast(GraphQLList, type_)
|
||||
item_type = type_.of_type
|
||||
if is_iterable(value):
|
||||
maybe_value_nodes = (ast_from_value(item, item_type) for item in value)
|
||||
value_nodes = tuple(node for node in maybe_value_nodes if node)
|
||||
return ListValueNode(values=value_nodes)
|
||||
return ast_from_value(value, item_type)
|
||||
|
||||
# Populate the fields of the input object by creating ASTs from each value in the
|
||||
# Python dict according to the fields in the input type.
|
||||
if is_input_object_type(type_):
|
||||
if value is None or not isinstance(value, Mapping):
|
||||
return None
|
||||
type_ = cast(GraphQLInputObjectType, type_)
|
||||
field_items = (
|
||||
(field_name, ast_from_value(value[field_name], field.type))
|
||||
for field_name, field in type_.fields.items()
|
||||
if field_name in value
|
||||
)
|
||||
field_nodes = tuple(
|
||||
ObjectFieldNode(name=NameNode(value=field_name), value=field_value)
|
||||
for field_name, field_value in field_items
|
||||
if field_value
|
||||
)
|
||||
return ObjectValueNode(fields=field_nodes)
|
||||
|
||||
if is_leaf_type(type_):
|
||||
# Since value is an internally represented value, it must be serialized to an
|
||||
# externally represented value before converting into an AST.
|
||||
serialized = type_.serialize(value) # type: ignore
|
||||
if serialized is None or serialized is Undefined:
|
||||
return None
|
||||
|
||||
# Others serialize based on their corresponding Python scalar types.
|
||||
if isinstance(serialized, bool):
|
||||
return BooleanValueNode(value=serialized)
|
||||
|
||||
# Python ints and floats correspond nicely to Int and Float values.
|
||||
if isinstance(serialized, int):
|
||||
return IntValueNode(value=str(serialized))
|
||||
if isinstance(serialized, float) and isfinite(serialized):
|
||||
value = str(serialized)
|
||||
if value.endswith(".0"):
|
||||
value = value[:-2]
|
||||
return FloatValueNode(value=value)
|
||||
|
||||
if isinstance(serialized, str):
|
||||
# Enum types use Enum literals.
|
||||
if is_enum_type(type_):
|
||||
return EnumValueNode(value=serialized)
|
||||
|
||||
# ID types can use Int literals.
|
||||
if type_ is GraphQLID and _re_integer_string.match(serialized):
|
||||
return IntValueNode(value=serialized)
|
||||
|
||||
return StringValueNode(value=serialized)
|
||||
|
||||
raise TypeError(f"Cannot convert value to AST: {inspect(serialized)}.")
|
||||
|
||||
# Not reachable. All possible input types have been considered.
|
||||
raise TypeError(f"Unexpected input type: {inspect(type_)}.")
|
||||
@@ -0,0 +1,59 @@
|
||||
from typing import Any, Collection, Dict, List, Optional, overload
|
||||
|
||||
from ..language import Node, OperationType
|
||||
from ..pyutils import is_iterable
|
||||
|
||||
__all__ = ["ast_to_dict"]
|
||||
|
||||
|
||||
@overload
|
||||
def ast_to_dict(
|
||||
node: Node, locations: bool = False, cache: Optional[Dict[Node, Any]] = None
|
||||
) -> Dict: ...
|
||||
|
||||
|
||||
@overload
|
||||
def ast_to_dict(
|
||||
node: Collection[Node],
|
||||
locations: bool = False,
|
||||
cache: Optional[Dict[Node, Any]] = None,
|
||||
) -> List[Node]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def ast_to_dict(
|
||||
node: OperationType,
|
||||
locations: bool = False,
|
||||
cache: Optional[Dict[Node, Any]] = None,
|
||||
) -> str: ...
|
||||
|
||||
|
||||
def ast_to_dict(
|
||||
node: Any, locations: bool = False, cache: Optional[Dict[Node, Any]] = None
|
||||
) -> Any:
|
||||
"""Convert a language AST to a nested Python dictionary.
|
||||
|
||||
Set `locations` to True in order to get the locations as well.
|
||||
"""
|
||||
if isinstance(node, Node):
|
||||
if cache is None:
|
||||
cache = {}
|
||||
elif node in cache:
|
||||
return cache[node]
|
||||
cache[node] = res = {}
|
||||
res.update(
|
||||
{
|
||||
key: ast_to_dict(getattr(node, key), locations, cache)
|
||||
for key in ("kind",) + node.keys[1:]
|
||||
}
|
||||
)
|
||||
if locations:
|
||||
loc = node.loc
|
||||
if loc:
|
||||
res["loc"] = dict(start=loc.start, end=loc.end)
|
||||
return res
|
||||
if is_iterable(node):
|
||||
return [ast_to_dict(sub_node, locations, cache) for sub_node in node]
|
||||
if isinstance(node, OperationType):
|
||||
return node.value
|
||||
return node
|
||||
@@ -0,0 +1,103 @@
|
||||
from typing import cast, Union
|
||||
|
||||
from ..language import DocumentNode, Source, parse
|
||||
from ..type import (
|
||||
GraphQLObjectType,
|
||||
GraphQLSchema,
|
||||
GraphQLSchemaKwargs,
|
||||
specified_directives,
|
||||
)
|
||||
from .extend_schema import extend_schema_impl
|
||||
|
||||
__all__ = [
|
||||
"build_ast_schema",
|
||||
"build_schema",
|
||||
]
|
||||
|
||||
|
||||
def build_ast_schema(
|
||||
document_ast: DocumentNode,
|
||||
assume_valid: bool = False,
|
||||
assume_valid_sdl: bool = False,
|
||||
) -> GraphQLSchema:
|
||||
"""Build a GraphQL Schema from a given AST.
|
||||
|
||||
This takes the ast of a schema document produced by the parse function in
|
||||
src/language/parser.py.
|
||||
|
||||
If no schema definition is provided, then it will look for types named Query,
|
||||
Mutation and Subscription.
|
||||
|
||||
Given that AST it constructs a GraphQLSchema. The resulting schema has no
|
||||
resolve methods, so execution will use default resolvers.
|
||||
|
||||
When building a schema from a GraphQL service's introspection result, it might
|
||||
be safe to assume the schema is valid. Set ``assume_valid`` to ``True`` to assume
|
||||
the produced schema is valid. Set ``assume_valid_sdl`` to ``True`` to assume it is
|
||||
already a valid SDL document.
|
||||
"""
|
||||
if not isinstance(document_ast, DocumentNode):
|
||||
raise TypeError("Must provide valid Document AST.")
|
||||
|
||||
if not (assume_valid or assume_valid_sdl):
|
||||
from ..validation.validate import assert_valid_sdl
|
||||
|
||||
assert_valid_sdl(document_ast)
|
||||
|
||||
empty_schema_kwargs = GraphQLSchemaKwargs(
|
||||
query=None,
|
||||
mutation=None,
|
||||
subscription=None,
|
||||
description=None,
|
||||
types=(),
|
||||
directives=(),
|
||||
extensions={},
|
||||
ast_node=None,
|
||||
extension_ast_nodes=(),
|
||||
assume_valid=False,
|
||||
)
|
||||
schema_kwargs = extend_schema_impl(empty_schema_kwargs, document_ast, assume_valid)
|
||||
|
||||
if not schema_kwargs["ast_node"]:
|
||||
for type_ in schema_kwargs["types"] or ():
|
||||
# Note: While this could make early assertions to get the correctly
|
||||
# typed values below, that would throw immediately while type system
|
||||
# validation with validate_schema() will produce more actionable results.
|
||||
type_name = type_.name
|
||||
if type_name == "Query":
|
||||
schema_kwargs["query"] = cast(GraphQLObjectType, type_)
|
||||
elif type_name == "Mutation":
|
||||
schema_kwargs["mutation"] = cast(GraphQLObjectType, type_)
|
||||
elif type_name == "Subscription":
|
||||
schema_kwargs["subscription"] = cast(GraphQLObjectType, type_)
|
||||
|
||||
# If specified directives were not explicitly declared, add them.
|
||||
directives = schema_kwargs["directives"]
|
||||
directive_names = set(directive.name for directive in directives)
|
||||
missing_directives = []
|
||||
for directive in specified_directives:
|
||||
if directive.name not in directive_names:
|
||||
missing_directives.append(directive)
|
||||
if missing_directives:
|
||||
schema_kwargs["directives"] = directives + tuple(missing_directives)
|
||||
|
||||
return GraphQLSchema(**schema_kwargs)
|
||||
|
||||
|
||||
def build_schema(
|
||||
source: Union[str, Source],
|
||||
assume_valid: bool = False,
|
||||
assume_valid_sdl: bool = False,
|
||||
no_location: bool = False,
|
||||
allow_legacy_fragment_variables: bool = False,
|
||||
) -> GraphQLSchema:
|
||||
"""Build a GraphQLSchema directly from a source document."""
|
||||
return build_ast_schema(
|
||||
parse(
|
||||
source,
|
||||
no_location=no_location,
|
||||
allow_legacy_fragment_variables=allow_legacy_fragment_variables,
|
||||
),
|
||||
assume_valid=assume_valid,
|
||||
assume_valid_sdl=assume_valid_sdl,
|
||||
)
|
||||
@@ -0,0 +1,418 @@
|
||||
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:
|
||||
return GraphQLScalarType(
|
||||
name=scalar_introspection["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:
|
||||
return GraphQLObjectType(
|
||||
name=object_introspection["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)}."
|
||||
)
|
||||
return GraphQLEnumType(
|
||||
name=enum_introspection["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"]
|
||||
),
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
@@ -0,0 +1,159 @@
|
||||
from typing import Any, Callable, Dict, List, Optional, Union, cast
|
||||
|
||||
|
||||
from ..error import GraphQLError
|
||||
from ..pyutils import (
|
||||
Path,
|
||||
did_you_mean,
|
||||
inspect,
|
||||
is_iterable,
|
||||
print_path_list,
|
||||
suggestion_list,
|
||||
Undefined,
|
||||
)
|
||||
from ..type import (
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInputType,
|
||||
GraphQLList,
|
||||
GraphQLScalarType,
|
||||
is_leaf_type,
|
||||
is_input_object_type,
|
||||
is_list_type,
|
||||
is_non_null_type,
|
||||
GraphQLNonNull,
|
||||
)
|
||||
|
||||
__all__ = ["coerce_input_value"]
|
||||
|
||||
|
||||
OnErrorCB = Callable[[List[Union[str, int]], Any, GraphQLError], None]
|
||||
|
||||
|
||||
def default_on_error(
|
||||
path: List[Union[str, int]], invalid_value: Any, error: GraphQLError
|
||||
) -> None:
|
||||
error_prefix = "Invalid value " + inspect(invalid_value)
|
||||
if path:
|
||||
error_prefix += f" at 'value{print_path_list(path)}'"
|
||||
error.message = error_prefix + ": " + error.message
|
||||
raise error
|
||||
|
||||
|
||||
def coerce_input_value(
|
||||
input_value: Any,
|
||||
type_: GraphQLInputType,
|
||||
on_error: OnErrorCB = default_on_error,
|
||||
path: Optional[Path] = None,
|
||||
) -> Any:
|
||||
"""Coerce a Python value given a GraphQL Input Type."""
|
||||
if is_non_null_type(type_):
|
||||
if input_value is not None and input_value is not Undefined:
|
||||
type_ = cast(GraphQLNonNull, type_)
|
||||
return coerce_input_value(input_value, type_.of_type, on_error, path)
|
||||
on_error(
|
||||
path.as_list() if path else [],
|
||||
input_value,
|
||||
GraphQLError(
|
||||
f"Expected non-nullable type '{inspect(type_)}' not to be None."
|
||||
),
|
||||
)
|
||||
return Undefined
|
||||
|
||||
if input_value is None or input_value is Undefined:
|
||||
# Explicitly return the value null.
|
||||
return None
|
||||
|
||||
if is_list_type(type_):
|
||||
type_ = cast(GraphQLList, type_)
|
||||
item_type = type_.of_type
|
||||
if is_iterable(input_value):
|
||||
coerced_list: List[Any] = []
|
||||
append_item = coerced_list.append
|
||||
for index, item_value in enumerate(input_value):
|
||||
append_item(
|
||||
coerce_input_value(
|
||||
item_value, item_type, on_error, Path(path, index, None)
|
||||
)
|
||||
)
|
||||
return coerced_list
|
||||
# Lists accept a non-list value as a list of one.
|
||||
return [coerce_input_value(input_value, item_type, on_error, path)]
|
||||
|
||||
if is_input_object_type(type_):
|
||||
type_ = cast(GraphQLInputObjectType, type_)
|
||||
if not isinstance(input_value, dict):
|
||||
on_error(
|
||||
path.as_list() if path else [],
|
||||
input_value,
|
||||
GraphQLError(f"Expected type '{type_.name}' to be a mapping."),
|
||||
)
|
||||
return Undefined
|
||||
|
||||
coerced_dict: Dict[str, Any] = {}
|
||||
fields = type_.fields
|
||||
|
||||
for field_name, field in fields.items():
|
||||
field_value = input_value.get(field_name, Undefined)
|
||||
|
||||
if field_value is Undefined:
|
||||
if field.default_value is not Undefined:
|
||||
# Use out name as name if it exists (extension of GraphQL.js).
|
||||
coerced_dict[field.out_name or field_name] = field.default_value
|
||||
elif is_non_null_type(field.type): # pragma: no cover else
|
||||
type_str = inspect(field.type)
|
||||
on_error(
|
||||
path.as_list() if path else [],
|
||||
input_value,
|
||||
GraphQLError(
|
||||
f"Field '{field_name}' of required type '{type_str}'"
|
||||
" was not provided."
|
||||
),
|
||||
)
|
||||
continue
|
||||
|
||||
coerced_dict[field.out_name or field_name] = coerce_input_value(
|
||||
field_value, field.type, on_error, Path(path, field_name, type_.name)
|
||||
)
|
||||
|
||||
# Ensure every provided field is defined.
|
||||
for field_name in input_value:
|
||||
if field_name not in fields:
|
||||
suggestions = suggestion_list(field_name, fields)
|
||||
on_error(
|
||||
path.as_list() if path else [],
|
||||
input_value,
|
||||
GraphQLError(
|
||||
f"Field '{field_name}' is not defined by type '{type_.name}'."
|
||||
+ did_you_mean(suggestions)
|
||||
),
|
||||
)
|
||||
return type_.out_type(coerced_dict)
|
||||
|
||||
if is_leaf_type(type_):
|
||||
# Scalars determine if a value is valid via `parse_value()`, which can throw to
|
||||
# indicate failure. If it throws, maintain a reference to the original error.
|
||||
type_ = cast(GraphQLScalarType, type_)
|
||||
try:
|
||||
parse_result = type_.parse_value(input_value)
|
||||
except GraphQLError as error:
|
||||
on_error(path.as_list() if path else [], input_value, error)
|
||||
return Undefined
|
||||
except Exception as error:
|
||||
on_error(
|
||||
path.as_list() if path else [],
|
||||
input_value,
|
||||
GraphQLError(
|
||||
f"Expected type '{type_.name}'. {error}", original_error=error
|
||||
),
|
||||
)
|
||||
return Undefined
|
||||
if parse_result is Undefined:
|
||||
on_error(
|
||||
path.as_list() if path else [],
|
||||
input_value,
|
||||
GraphQLError(f"Expected type '{type_.name}'."),
|
||||
)
|
||||
return parse_result
|
||||
|
||||
# Not reachable. All possible input types have been considered.
|
||||
raise TypeError(f"Unexpected input type: {inspect(type_)}.")
|
||||
@@ -0,0 +1,18 @@
|
||||
from itertools import chain
|
||||
from typing import Collection
|
||||
|
||||
from ..language.ast import DocumentNode
|
||||
|
||||
__all__ = ["concat_ast"]
|
||||
|
||||
|
||||
def concat_ast(asts: Collection[DocumentNode]) -> DocumentNode:
|
||||
"""Concat ASTs.
|
||||
|
||||
Provided a collection of ASTs, presumably each from different files, concatenate
|
||||
the ASTs together into batched AST, useful for validating many GraphQL source files
|
||||
which together represent one conceptual application.
|
||||
"""
|
||||
return DocumentNode(
|
||||
definitions=list(chain.from_iterable(document.definitions for document in asts))
|
||||
)
|
||||
@@ -0,0 +1,700 @@
|
||||
from collections import defaultdict
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Collection,
|
||||
DefaultDict,
|
||||
Dict,
|
||||
List,
|
||||
Mapping,
|
||||
Optional,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from ..language import (
|
||||
DirectiveDefinitionNode,
|
||||
DirectiveLocation,
|
||||
DocumentNode,
|
||||
EnumTypeDefinitionNode,
|
||||
EnumTypeExtensionNode,
|
||||
EnumValueDefinitionNode,
|
||||
FieldDefinitionNode,
|
||||
InputObjectTypeDefinitionNode,
|
||||
InputObjectTypeExtensionNode,
|
||||
InputValueDefinitionNode,
|
||||
InterfaceTypeDefinitionNode,
|
||||
InterfaceTypeExtensionNode,
|
||||
ListTypeNode,
|
||||
NamedTypeNode,
|
||||
NonNullTypeNode,
|
||||
ObjectTypeDefinitionNode,
|
||||
ObjectTypeExtensionNode,
|
||||
OperationType,
|
||||
ScalarTypeDefinitionNode,
|
||||
ScalarTypeExtensionNode,
|
||||
SchemaExtensionNode,
|
||||
SchemaDefinitionNode,
|
||||
TypeDefinitionNode,
|
||||
TypeExtensionNode,
|
||||
TypeNode,
|
||||
UnionTypeDefinitionNode,
|
||||
UnionTypeExtensionNode,
|
||||
)
|
||||
from ..pyutils import inspect, merge_kwargs
|
||||
from ..type import (
|
||||
GraphQLArgument,
|
||||
GraphQLArgumentMap,
|
||||
GraphQLDeprecatedDirective,
|
||||
GraphQLDirective,
|
||||
GraphQLEnumType,
|
||||
GraphQLEnumValue,
|
||||
GraphQLEnumValueMap,
|
||||
GraphQLField,
|
||||
GraphQLFieldMap,
|
||||
GraphQLInputField,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInputType,
|
||||
GraphQLInputFieldMap,
|
||||
GraphQLInterfaceType,
|
||||
GraphQLList,
|
||||
GraphQLNamedType,
|
||||
GraphQLNonNull,
|
||||
GraphQLNullableType,
|
||||
GraphQLObjectType,
|
||||
GraphQLOutputType,
|
||||
GraphQLScalarType,
|
||||
GraphQLSchema,
|
||||
GraphQLSchemaKwargs,
|
||||
GraphQLSpecifiedByDirective,
|
||||
GraphQLType,
|
||||
GraphQLUnionType,
|
||||
assert_schema,
|
||||
is_enum_type,
|
||||
is_input_object_type,
|
||||
is_interface_type,
|
||||
is_list_type,
|
||||
is_non_null_type,
|
||||
is_object_type,
|
||||
is_scalar_type,
|
||||
is_union_type,
|
||||
is_introspection_type,
|
||||
is_specified_scalar_type,
|
||||
introspection_types,
|
||||
specified_scalar_types,
|
||||
)
|
||||
from .value_from_ast import value_from_ast
|
||||
|
||||
__all__ = [
|
||||
"extend_schema",
|
||||
"extend_schema_impl",
|
||||
]
|
||||
|
||||
|
||||
def extend_schema(
|
||||
schema: GraphQLSchema,
|
||||
document_ast: DocumentNode,
|
||||
assume_valid: bool = False,
|
||||
assume_valid_sdl: bool = False,
|
||||
) -> GraphQLSchema:
|
||||
"""Extend the schema with extensions from a given document.
|
||||
|
||||
Produces a new schema given an existing schema and a document which may contain
|
||||
GraphQL type extensions and definitions. The original schema will remain unaltered.
|
||||
|
||||
Because a schema represents a graph of references, a schema cannot be extended
|
||||
without effectively making an entire copy. We do not know until it's too late if
|
||||
subgraphs remain unchanged.
|
||||
|
||||
This algorithm copies the provided schema, applying extensions while producing the
|
||||
copy. The original schema remains unaltered.
|
||||
|
||||
When extending a schema with a known valid extension, it might be safe to assume the
|
||||
schema is valid. Set ``assume_valid`` to ``True`` to assume the produced schema is
|
||||
valid. Set ``assume_valid_sdl`` to ``True`` to assume it is already a valid SDL
|
||||
document.
|
||||
"""
|
||||
assert_schema(schema)
|
||||
|
||||
if not isinstance(document_ast, DocumentNode):
|
||||
raise TypeError("Must provide valid Document AST.")
|
||||
|
||||
if not (assume_valid or assume_valid_sdl):
|
||||
from ..validation.validate import assert_valid_sdl_extension
|
||||
|
||||
assert_valid_sdl_extension(document_ast, schema)
|
||||
|
||||
schema_kwargs = schema.to_kwargs()
|
||||
extended_kwargs = extend_schema_impl(schema_kwargs, document_ast, assume_valid)
|
||||
return (
|
||||
schema if schema_kwargs is extended_kwargs else GraphQLSchema(**extended_kwargs)
|
||||
)
|
||||
|
||||
|
||||
def extend_schema_impl(
|
||||
schema_kwargs: GraphQLSchemaKwargs,
|
||||
document_ast: DocumentNode,
|
||||
assume_valid: bool = False,
|
||||
) -> GraphQLSchemaKwargs:
|
||||
"""Extend the given schema arguments with extensions from a given document.
|
||||
|
||||
For internal use only.
|
||||
"""
|
||||
# Note: schema_kwargs should become a TypedDict once we require Python 3.8
|
||||
|
||||
# Collect the type definitions and extensions found in the document.
|
||||
type_defs: List[TypeDefinitionNode] = []
|
||||
type_extensions_map: DefaultDict[str, Any] = defaultdict(list)
|
||||
|
||||
# New directives and types are separate because a directives and types can have the
|
||||
# same name. For example, a type named "skip".
|
||||
directive_defs: List[DirectiveDefinitionNode] = []
|
||||
|
||||
schema_def: Optional[SchemaDefinitionNode] = None
|
||||
# Schema extensions are collected which may add additional operation types.
|
||||
schema_extensions: List[SchemaExtensionNode] = []
|
||||
|
||||
for def_ in document_ast.definitions:
|
||||
if isinstance(def_, SchemaDefinitionNode):
|
||||
schema_def = def_
|
||||
elif isinstance(def_, SchemaExtensionNode):
|
||||
schema_extensions.append(def_)
|
||||
elif isinstance(def_, TypeDefinitionNode):
|
||||
type_defs.append(def_)
|
||||
elif isinstance(def_, TypeExtensionNode):
|
||||
extended_type_name = def_.name.value
|
||||
type_extensions_map[extended_type_name].append(def_)
|
||||
elif isinstance(def_, DirectiveDefinitionNode):
|
||||
directive_defs.append(def_)
|
||||
|
||||
# If this document contains no new types, extensions, or directives then return the
|
||||
# same unmodified GraphQLSchema instance.
|
||||
if (
|
||||
not type_extensions_map
|
||||
and not type_defs
|
||||
and not directive_defs
|
||||
and not schema_extensions
|
||||
and not schema_def
|
||||
):
|
||||
return schema_kwargs
|
||||
|
||||
# Below are functions used for producing this schema that have closed over this
|
||||
# scope and have access to the schema, cache, and newly defined types.
|
||||
|
||||
# noinspection PyTypeChecker,PyUnresolvedReferences
|
||||
def replace_type(type_: GraphQLType) -> GraphQLType:
|
||||
if is_list_type(type_):
|
||||
return GraphQLList(replace_type(type_.of_type)) # type: ignore
|
||||
if is_non_null_type(type_):
|
||||
return GraphQLNonNull(replace_type(type_.of_type)) # type: ignore
|
||||
return replace_named_type(type_) # type: ignore
|
||||
|
||||
def replace_named_type(type_: GraphQLNamedType) -> GraphQLNamedType:
|
||||
# Note: While this could make early assertions to get the correctly
|
||||
# typed values below, that would throw immediately while type system
|
||||
# validation with validate_schema() will produce more actionable results.
|
||||
return type_map[type_.name]
|
||||
|
||||
# noinspection PyShadowingNames
|
||||
def replace_directive(directive: GraphQLDirective) -> GraphQLDirective:
|
||||
kwargs = directive.to_kwargs()
|
||||
return GraphQLDirective(
|
||||
**merge_kwargs(
|
||||
kwargs,
|
||||
args={name: extend_arg(arg) for name, arg in kwargs["args"].items()},
|
||||
)
|
||||
)
|
||||
|
||||
def extend_named_type(type_: GraphQLNamedType) -> GraphQLNamedType:
|
||||
if is_introspection_type(type_) or is_specified_scalar_type(type_):
|
||||
# Builtin types are not extended.
|
||||
return type_
|
||||
if is_scalar_type(type_):
|
||||
type_ = cast(GraphQLScalarType, type_)
|
||||
return extend_scalar_type(type_)
|
||||
if is_object_type(type_):
|
||||
type_ = cast(GraphQLObjectType, type_)
|
||||
return extend_object_type(type_)
|
||||
if is_interface_type(type_):
|
||||
type_ = cast(GraphQLInterfaceType, type_)
|
||||
return extend_interface_type(type_)
|
||||
if is_union_type(type_):
|
||||
type_ = cast(GraphQLUnionType, type_)
|
||||
return extend_union_type(type_)
|
||||
if is_enum_type(type_):
|
||||
type_ = cast(GraphQLEnumType, type_)
|
||||
return extend_enum_type(type_)
|
||||
if is_input_object_type(type_):
|
||||
type_ = cast(GraphQLInputObjectType, type_)
|
||||
return extend_input_object_type(type_)
|
||||
|
||||
# Not reachable. All possible types have been considered.
|
||||
raise TypeError(f"Unexpected type: {inspect(type_)}.") # pragma: no cover
|
||||
|
||||
# noinspection PyShadowingNames
|
||||
def extend_input_object_type(
|
||||
type_: GraphQLInputObjectType,
|
||||
) -> GraphQLInputObjectType:
|
||||
kwargs = type_.to_kwargs()
|
||||
extensions = tuple(type_extensions_map[kwargs["name"]])
|
||||
|
||||
return GraphQLInputObjectType(
|
||||
**merge_kwargs(
|
||||
kwargs,
|
||||
fields=lambda: {
|
||||
**{
|
||||
name: GraphQLInputField(
|
||||
**merge_kwargs(
|
||||
field.to_kwargs(),
|
||||
type_=replace_type(field.type),
|
||||
)
|
||||
)
|
||||
for name, field in kwargs["fields"].items()
|
||||
},
|
||||
**build_input_field_map(extensions),
|
||||
},
|
||||
extension_ast_nodes=kwargs["extension_ast_nodes"] + extensions,
|
||||
)
|
||||
)
|
||||
|
||||
def extend_enum_type(type_: GraphQLEnumType) -> GraphQLEnumType:
|
||||
kwargs = type_.to_kwargs()
|
||||
extensions = tuple(type_extensions_map[kwargs["name"]])
|
||||
|
||||
return GraphQLEnumType(
|
||||
**merge_kwargs(
|
||||
kwargs,
|
||||
values={**kwargs["values"], **build_enum_value_map(extensions)},
|
||||
extension_ast_nodes=kwargs["extension_ast_nodes"] + extensions,
|
||||
)
|
||||
)
|
||||
|
||||
def extend_scalar_type(type_: GraphQLScalarType) -> GraphQLScalarType:
|
||||
kwargs = type_.to_kwargs()
|
||||
extensions = tuple(type_extensions_map[kwargs["name"]])
|
||||
|
||||
specified_by_url = kwargs["specified_by_url"]
|
||||
for extension_node in extensions:
|
||||
specified_by_url = get_specified_by_url(extension_node) or specified_by_url
|
||||
|
||||
return GraphQLScalarType(
|
||||
**merge_kwargs(
|
||||
kwargs,
|
||||
specified_by_url=specified_by_url,
|
||||
extension_ast_nodes=kwargs["extension_ast_nodes"] + extensions,
|
||||
)
|
||||
)
|
||||
|
||||
# noinspection PyShadowingNames
|
||||
def extend_object_type(type_: GraphQLObjectType) -> GraphQLObjectType:
|
||||
kwargs = type_.to_kwargs()
|
||||
extensions = tuple(type_extensions_map[kwargs["name"]])
|
||||
|
||||
return GraphQLObjectType(
|
||||
**merge_kwargs(
|
||||
kwargs,
|
||||
interfaces=lambda: [
|
||||
cast(GraphQLInterfaceType, replace_named_type(interface))
|
||||
for interface in kwargs["interfaces"]
|
||||
]
|
||||
+ build_interfaces(extensions),
|
||||
fields=lambda: {
|
||||
**{
|
||||
name: extend_field(field)
|
||||
for name, field in kwargs["fields"].items()
|
||||
},
|
||||
**build_field_map(extensions),
|
||||
},
|
||||
extension_ast_nodes=kwargs["extension_ast_nodes"] + extensions,
|
||||
)
|
||||
)
|
||||
|
||||
# noinspection PyShadowingNames
|
||||
def extend_interface_type(type_: GraphQLInterfaceType) -> GraphQLInterfaceType:
|
||||
kwargs = type_.to_kwargs()
|
||||
extensions = tuple(type_extensions_map[kwargs["name"]])
|
||||
|
||||
return GraphQLInterfaceType(
|
||||
**merge_kwargs(
|
||||
kwargs,
|
||||
interfaces=lambda: [
|
||||
cast(GraphQLInterfaceType, replace_named_type(interface))
|
||||
for interface in kwargs["interfaces"]
|
||||
]
|
||||
+ build_interfaces(extensions),
|
||||
fields=lambda: {
|
||||
**{
|
||||
name: extend_field(field)
|
||||
for name, field in kwargs["fields"].items()
|
||||
},
|
||||
**build_field_map(extensions),
|
||||
},
|
||||
extension_ast_nodes=kwargs["extension_ast_nodes"] + extensions,
|
||||
)
|
||||
)
|
||||
|
||||
def extend_union_type(type_: GraphQLUnionType) -> GraphQLUnionType:
|
||||
kwargs = type_.to_kwargs()
|
||||
extensions = tuple(type_extensions_map[kwargs["name"]])
|
||||
|
||||
return GraphQLUnionType(
|
||||
**merge_kwargs(
|
||||
kwargs,
|
||||
types=lambda: [
|
||||
cast(GraphQLObjectType, replace_named_type(member_type))
|
||||
for member_type in kwargs["types"]
|
||||
]
|
||||
+ build_union_types(extensions),
|
||||
extension_ast_nodes=kwargs["extension_ast_nodes"] + extensions,
|
||||
)
|
||||
)
|
||||
|
||||
# noinspection PyShadowingNames
|
||||
def extend_field(field: GraphQLField) -> GraphQLField:
|
||||
return GraphQLField(
|
||||
**merge_kwargs(
|
||||
field.to_kwargs(),
|
||||
type_=replace_type(field.type),
|
||||
args={name: extend_arg(arg) for name, arg in field.args.items()},
|
||||
)
|
||||
)
|
||||
|
||||
def extend_arg(arg: GraphQLArgument) -> GraphQLArgument:
|
||||
return GraphQLArgument(
|
||||
**merge_kwargs(
|
||||
arg.to_kwargs(),
|
||||
type_=replace_type(arg.type),
|
||||
)
|
||||
)
|
||||
|
||||
# noinspection PyShadowingNames
|
||||
def get_operation_types(
|
||||
nodes: Collection[Union[SchemaDefinitionNode, SchemaExtensionNode]]
|
||||
) -> Dict[OperationType, GraphQLNamedType]:
|
||||
# Note: While this could make early assertions to get the correctly
|
||||
# typed values below, that would throw immediately while type system
|
||||
# validation with validate_schema() will produce more actionable results.
|
||||
return {
|
||||
operation_type.operation: get_named_type(operation_type.type)
|
||||
for node in nodes
|
||||
for operation_type in node.operation_types or []
|
||||
}
|
||||
|
||||
# noinspection PyShadowingNames
|
||||
def get_named_type(node: NamedTypeNode) -> GraphQLNamedType:
|
||||
name = node.name.value
|
||||
type_ = std_type_map.get(name) or type_map.get(name)
|
||||
|
||||
if not type_:
|
||||
raise TypeError(f"Unknown type: '{name}'.")
|
||||
return type_
|
||||
|
||||
def get_wrapped_type(node: TypeNode) -> GraphQLType:
|
||||
if isinstance(node, ListTypeNode):
|
||||
return GraphQLList(get_wrapped_type(node.type))
|
||||
if isinstance(node, NonNullTypeNode):
|
||||
return GraphQLNonNull(
|
||||
cast(GraphQLNullableType, get_wrapped_type(node.type))
|
||||
)
|
||||
return get_named_type(cast(NamedTypeNode, node))
|
||||
|
||||
def build_directive(node: DirectiveDefinitionNode) -> GraphQLDirective:
|
||||
locations = [DirectiveLocation[node.value] for node in node.locations]
|
||||
|
||||
return GraphQLDirective(
|
||||
name=node.name.value,
|
||||
description=node.description.value if node.description else None,
|
||||
locations=locations,
|
||||
is_repeatable=node.repeatable,
|
||||
args=build_argument_map(node.arguments),
|
||||
ast_node=node,
|
||||
)
|
||||
|
||||
def build_field_map(
|
||||
nodes: Collection[
|
||||
Union[
|
||||
InterfaceTypeDefinitionNode,
|
||||
InterfaceTypeExtensionNode,
|
||||
ObjectTypeDefinitionNode,
|
||||
ObjectTypeExtensionNode,
|
||||
]
|
||||
],
|
||||
) -> GraphQLFieldMap:
|
||||
field_map: GraphQLFieldMap = {}
|
||||
for node in nodes:
|
||||
for field in node.fields or []:
|
||||
# Note: While this could make assertions to get the correctly typed
|
||||
# value, that would throw immediately while type system validation
|
||||
# with validate_schema() will produce more actionable results.
|
||||
field_map[field.name.value] = GraphQLField(
|
||||
type_=cast(GraphQLOutputType, get_wrapped_type(field.type)),
|
||||
description=field.description.value if field.description else None,
|
||||
args=build_argument_map(field.arguments),
|
||||
deprecation_reason=get_deprecation_reason(field),
|
||||
ast_node=field,
|
||||
)
|
||||
return field_map
|
||||
|
||||
def build_argument_map(
|
||||
args: Optional[Collection[InputValueDefinitionNode]],
|
||||
) -> GraphQLArgumentMap:
|
||||
arg_map: GraphQLArgumentMap = {}
|
||||
for arg in args or []:
|
||||
# Note: While this could make assertions to get the correctly typed
|
||||
# value, that would throw immediately while type system validation
|
||||
# with validate_schema() will produce more actionable results.
|
||||
type_ = cast(GraphQLInputType, get_wrapped_type(arg.type))
|
||||
arg_map[arg.name.value] = GraphQLArgument(
|
||||
type_=type_,
|
||||
description=arg.description.value if arg.description else None,
|
||||
default_value=value_from_ast(arg.default_value, type_),
|
||||
deprecation_reason=get_deprecation_reason(arg),
|
||||
ast_node=arg,
|
||||
)
|
||||
return arg_map
|
||||
|
||||
def build_input_field_map(
|
||||
nodes: Collection[
|
||||
Union[InputObjectTypeDefinitionNode, InputObjectTypeExtensionNode]
|
||||
],
|
||||
) -> GraphQLInputFieldMap:
|
||||
input_field_map: GraphQLInputFieldMap = {}
|
||||
for node in nodes:
|
||||
for field in node.fields or []:
|
||||
# Note: While this could make assertions to get the correctly typed
|
||||
# value, that would throw immediately while type system validation
|
||||
# with validate_schema() will produce more actionable results.
|
||||
type_ = cast(GraphQLInputType, get_wrapped_type(field.type))
|
||||
input_field_map[field.name.value] = GraphQLInputField(
|
||||
type_=type_,
|
||||
description=field.description.value if field.description else None,
|
||||
default_value=value_from_ast(field.default_value, type_),
|
||||
deprecation_reason=get_deprecation_reason(field),
|
||||
ast_node=field,
|
||||
)
|
||||
return input_field_map
|
||||
|
||||
def build_enum_value_map(
|
||||
nodes: Collection[Union[EnumTypeDefinitionNode, EnumTypeExtensionNode]]
|
||||
) -> GraphQLEnumValueMap:
|
||||
enum_value_map: GraphQLEnumValueMap = {}
|
||||
for node in nodes:
|
||||
for value in node.values or []:
|
||||
# Note: While this could make assertions to get the correctly typed
|
||||
# value, that would throw immediately while type system validation
|
||||
# with validate_schema() will produce more actionable results.
|
||||
value_name = value.name.value
|
||||
enum_value_map[value_name] = GraphQLEnumValue(
|
||||
value=value_name,
|
||||
description=value.description.value if value.description else None,
|
||||
deprecation_reason=get_deprecation_reason(value),
|
||||
ast_node=value,
|
||||
)
|
||||
return enum_value_map
|
||||
|
||||
def build_interfaces(
|
||||
nodes: Collection[
|
||||
Union[
|
||||
InterfaceTypeDefinitionNode,
|
||||
InterfaceTypeExtensionNode,
|
||||
ObjectTypeDefinitionNode,
|
||||
ObjectTypeExtensionNode,
|
||||
]
|
||||
],
|
||||
) -> List[GraphQLInterfaceType]:
|
||||
interfaces: List[GraphQLInterfaceType] = []
|
||||
for node in nodes:
|
||||
for type_ in node.interfaces or []:
|
||||
# Note: While this could make assertions to get the correctly typed
|
||||
# value, that would throw immediately while type system validation
|
||||
# with validate_schema() will produce more actionable results.
|
||||
interfaces.append(cast(GraphQLInterfaceType, get_named_type(type_)))
|
||||
return interfaces
|
||||
|
||||
def build_union_types(
|
||||
nodes: Collection[Union[UnionTypeDefinitionNode, UnionTypeExtensionNode]],
|
||||
) -> List[GraphQLObjectType]:
|
||||
types: List[GraphQLObjectType] = []
|
||||
for node in nodes:
|
||||
for type_ in node.types or []:
|
||||
# Note: While this could make assertions to get the correctly typed
|
||||
# value, that would throw immediately while type system validation
|
||||
# with validate_schema() will produce more actionable results.
|
||||
types.append(cast(GraphQLObjectType, get_named_type(type_)))
|
||||
return types
|
||||
|
||||
def build_object_type(ast_node: ObjectTypeDefinitionNode) -> GraphQLObjectType:
|
||||
extension_nodes = type_extensions_map[ast_node.name.value]
|
||||
all_nodes: List[Union[ObjectTypeDefinitionNode, ObjectTypeExtensionNode]] = [
|
||||
ast_node,
|
||||
*extension_nodes,
|
||||
]
|
||||
return GraphQLObjectType(
|
||||
name=ast_node.name.value,
|
||||
description=ast_node.description.value if ast_node.description else None,
|
||||
interfaces=lambda: build_interfaces(all_nodes),
|
||||
fields=lambda: build_field_map(all_nodes),
|
||||
ast_node=ast_node,
|
||||
extension_ast_nodes=extension_nodes,
|
||||
)
|
||||
|
||||
def build_interface_type(
|
||||
ast_node: InterfaceTypeDefinitionNode,
|
||||
) -> GraphQLInterfaceType:
|
||||
extension_nodes = type_extensions_map[ast_node.name.value]
|
||||
all_nodes: List[
|
||||
Union[InterfaceTypeDefinitionNode, InterfaceTypeExtensionNode]
|
||||
] = [ast_node, *extension_nodes]
|
||||
return GraphQLInterfaceType(
|
||||
name=ast_node.name.value,
|
||||
description=ast_node.description.value if ast_node.description else None,
|
||||
interfaces=lambda: build_interfaces(all_nodes),
|
||||
fields=lambda: build_field_map(all_nodes),
|
||||
ast_node=ast_node,
|
||||
extension_ast_nodes=extension_nodes,
|
||||
)
|
||||
|
||||
def build_enum_type(ast_node: EnumTypeDefinitionNode) -> GraphQLEnumType:
|
||||
extension_nodes = type_extensions_map[ast_node.name.value]
|
||||
all_nodes: List[Union[EnumTypeDefinitionNode, EnumTypeExtensionNode]] = [
|
||||
ast_node,
|
||||
*extension_nodes,
|
||||
]
|
||||
return GraphQLEnumType(
|
||||
name=ast_node.name.value,
|
||||
description=ast_node.description.value if ast_node.description else None,
|
||||
values=build_enum_value_map(all_nodes),
|
||||
ast_node=ast_node,
|
||||
extension_ast_nodes=extension_nodes,
|
||||
)
|
||||
|
||||
def build_union_type(ast_node: UnionTypeDefinitionNode) -> GraphQLUnionType:
|
||||
extension_nodes = type_extensions_map[ast_node.name.value]
|
||||
all_nodes: List[Union[UnionTypeDefinitionNode, UnionTypeExtensionNode]] = [
|
||||
ast_node,
|
||||
*extension_nodes,
|
||||
]
|
||||
return GraphQLUnionType(
|
||||
name=ast_node.name.value,
|
||||
description=ast_node.description.value if ast_node.description else None,
|
||||
types=lambda: build_union_types(all_nodes),
|
||||
ast_node=ast_node,
|
||||
extension_ast_nodes=extension_nodes,
|
||||
)
|
||||
|
||||
def build_scalar_type(ast_node: ScalarTypeDefinitionNode) -> GraphQLScalarType:
|
||||
extension_nodes = type_extensions_map[ast_node.name.value]
|
||||
return GraphQLScalarType(
|
||||
name=ast_node.name.value,
|
||||
description=ast_node.description.value if ast_node.description else None,
|
||||
specified_by_url=get_specified_by_url(ast_node),
|
||||
ast_node=ast_node,
|
||||
extension_ast_nodes=extension_nodes,
|
||||
)
|
||||
|
||||
def build_input_object_type(
|
||||
ast_node: InputObjectTypeDefinitionNode,
|
||||
) -> GraphQLInputObjectType:
|
||||
extension_nodes = type_extensions_map[ast_node.name.value]
|
||||
all_nodes: List[
|
||||
Union[InputObjectTypeDefinitionNode, InputObjectTypeExtensionNode]
|
||||
] = [ast_node, *extension_nodes]
|
||||
return GraphQLInputObjectType(
|
||||
name=ast_node.name.value,
|
||||
description=ast_node.description.value if ast_node.description else None,
|
||||
fields=lambda: build_input_field_map(all_nodes),
|
||||
ast_node=ast_node,
|
||||
extension_ast_nodes=extension_nodes,
|
||||
)
|
||||
|
||||
build_type_for_kind = cast(
|
||||
Dict[str, Callable[[TypeDefinitionNode], GraphQLNamedType]],
|
||||
{
|
||||
"object_type_definition": build_object_type,
|
||||
"interface_type_definition": build_interface_type,
|
||||
"enum_type_definition": build_enum_type,
|
||||
"union_type_definition": build_union_type,
|
||||
"scalar_type_definition": build_scalar_type,
|
||||
"input_object_type_definition": build_input_object_type,
|
||||
},
|
||||
)
|
||||
|
||||
def build_type(ast_node: TypeDefinitionNode) -> GraphQLNamedType:
|
||||
try:
|
||||
# object_type_definition_node is built with _build_object_type etc.
|
||||
build_function = build_type_for_kind[ast_node.kind]
|
||||
except KeyError: # pragma: no cover
|
||||
# Not reachable. All possible type definition nodes have been considered.
|
||||
raise TypeError( # pragma: no cover
|
||||
f"Unexpected type definition node: {inspect(ast_node)}."
|
||||
)
|
||||
else:
|
||||
return build_function(ast_node)
|
||||
|
||||
type_map: Dict[str, GraphQLNamedType] = {}
|
||||
for existing_type in schema_kwargs["types"] or ():
|
||||
type_map[existing_type.name] = extend_named_type(existing_type)
|
||||
for type_node in type_defs:
|
||||
name = type_node.name.value
|
||||
type_map[name] = std_type_map.get(name) or build_type(type_node)
|
||||
|
||||
# Get the extended root operation types.
|
||||
operation_types: Dict[OperationType, GraphQLNamedType] = {}
|
||||
for operation_type in OperationType:
|
||||
original_type = schema_kwargs[operation_type.value]
|
||||
if original_type:
|
||||
operation_types[operation_type] = replace_named_type(original_type)
|
||||
# Then, incorporate schema definition and all schema extensions.
|
||||
if schema_def:
|
||||
operation_types.update(get_operation_types([schema_def]))
|
||||
if schema_extensions:
|
||||
operation_types.update(get_operation_types(schema_extensions))
|
||||
|
||||
# Then produce and return the kwargs for a Schema with these types.
|
||||
get_operation = operation_types.get
|
||||
return GraphQLSchemaKwargs(
|
||||
query=get_operation(OperationType.QUERY), # type: ignore
|
||||
mutation=get_operation(OperationType.MUTATION), # type: ignore
|
||||
subscription=get_operation(OperationType.SUBSCRIPTION), # type: ignore
|
||||
types=tuple(type_map.values()),
|
||||
directives=tuple(
|
||||
replace_directive(directive) for directive in schema_kwargs["directives"]
|
||||
)
|
||||
+ tuple(build_directive(directive) for directive in directive_defs),
|
||||
description=(
|
||||
schema_def.description.value
|
||||
if schema_def and schema_def.description
|
||||
else None
|
||||
),
|
||||
extensions={},
|
||||
ast_node=schema_def or schema_kwargs["ast_node"],
|
||||
extension_ast_nodes=schema_kwargs["extension_ast_nodes"]
|
||||
+ tuple(schema_extensions),
|
||||
assume_valid=assume_valid,
|
||||
)
|
||||
|
||||
|
||||
std_type_map: Mapping[str, Union[GraphQLNamedType, GraphQLObjectType]] = {
|
||||
**specified_scalar_types,
|
||||
**introspection_types,
|
||||
}
|
||||
|
||||
|
||||
def get_deprecation_reason(
|
||||
node: Union[EnumValueDefinitionNode, FieldDefinitionNode, InputValueDefinitionNode]
|
||||
) -> Optional[str]:
|
||||
"""Given a field or enum value node, get deprecation reason as string."""
|
||||
from ..execution import get_directive_values
|
||||
|
||||
deprecated = get_directive_values(GraphQLDeprecatedDirective, node)
|
||||
return deprecated["reason"] if deprecated else None
|
||||
|
||||
|
||||
def get_specified_by_url(
|
||||
node: Union[ScalarTypeDefinitionNode, ScalarTypeExtensionNode]
|
||||
) -> Optional[str]:
|
||||
"""Given a scalar node, return the string value for the specifiedByURL."""
|
||||
from ..execution import get_directive_values
|
||||
|
||||
specified_by_url = get_directive_values(GraphQLSpecifiedByDirective, node)
|
||||
return specified_by_url["url"] if specified_by_url else None
|
||||
@@ -0,0 +1,620 @@
|
||||
from enum import Enum
|
||||
from typing import Any, Collection, Dict, List, NamedTuple, Union, cast
|
||||
|
||||
from ..language import print_ast
|
||||
from ..pyutils import inspect, Undefined
|
||||
from ..type import (
|
||||
GraphQLEnumType,
|
||||
GraphQLField,
|
||||
GraphQLList,
|
||||
GraphQLNamedType,
|
||||
GraphQLNonNull,
|
||||
GraphQLInputType,
|
||||
GraphQLInterfaceType,
|
||||
GraphQLObjectType,
|
||||
GraphQLSchema,
|
||||
GraphQLType,
|
||||
GraphQLUnionType,
|
||||
is_enum_type,
|
||||
is_input_object_type,
|
||||
is_interface_type,
|
||||
is_list_type,
|
||||
is_named_type,
|
||||
is_non_null_type,
|
||||
is_object_type,
|
||||
is_required_argument,
|
||||
is_required_input_field,
|
||||
is_scalar_type,
|
||||
is_specified_scalar_type,
|
||||
is_union_type,
|
||||
)
|
||||
from ..utilities.sort_value_node import sort_value_node
|
||||
from .ast_from_value import ast_from_value
|
||||
|
||||
__all__ = [
|
||||
"BreakingChange",
|
||||
"BreakingChangeType",
|
||||
"DangerousChange",
|
||||
"DangerousChangeType",
|
||||
"find_breaking_changes",
|
||||
"find_dangerous_changes",
|
||||
]
|
||||
|
||||
|
||||
class BreakingChangeType(Enum):
|
||||
TYPE_REMOVED = 10
|
||||
TYPE_CHANGED_KIND = 11
|
||||
TYPE_REMOVED_FROM_UNION = 20
|
||||
VALUE_REMOVED_FROM_ENUM = 21
|
||||
REQUIRED_INPUT_FIELD_ADDED = 22
|
||||
IMPLEMENTED_INTERFACE_REMOVED = 23
|
||||
FIELD_REMOVED = 30
|
||||
FIELD_CHANGED_KIND = 31
|
||||
REQUIRED_ARG_ADDED = 40
|
||||
ARG_REMOVED = 41
|
||||
ARG_CHANGED_KIND = 42
|
||||
DIRECTIVE_REMOVED = 50
|
||||
DIRECTIVE_ARG_REMOVED = 51
|
||||
REQUIRED_DIRECTIVE_ARG_ADDED = 52
|
||||
DIRECTIVE_REPEATABLE_REMOVED = 53
|
||||
DIRECTIVE_LOCATION_REMOVED = 54
|
||||
|
||||
|
||||
class DangerousChangeType(Enum):
|
||||
VALUE_ADDED_TO_ENUM = 60
|
||||
TYPE_ADDED_TO_UNION = 61
|
||||
OPTIONAL_INPUT_FIELD_ADDED = 62
|
||||
OPTIONAL_ARG_ADDED = 63
|
||||
IMPLEMENTED_INTERFACE_ADDED = 64
|
||||
ARG_DEFAULT_VALUE_CHANGE = 65
|
||||
|
||||
|
||||
class BreakingChange(NamedTuple):
|
||||
type: BreakingChangeType
|
||||
description: str
|
||||
|
||||
|
||||
class DangerousChange(NamedTuple):
|
||||
type: DangerousChangeType
|
||||
description: str
|
||||
|
||||
|
||||
Change = Union[BreakingChange, DangerousChange]
|
||||
|
||||
|
||||
def find_breaking_changes(
|
||||
old_schema: GraphQLSchema, new_schema: GraphQLSchema
|
||||
) -> List[BreakingChange]:
|
||||
"""Find breaking changes.
|
||||
|
||||
Given two schemas, returns a list containing descriptions of all the types of
|
||||
breaking changes covered by the other functions down below.
|
||||
"""
|
||||
return [
|
||||
change
|
||||
for change in find_schema_changes(old_schema, new_schema)
|
||||
if isinstance(change.type, BreakingChangeType)
|
||||
]
|
||||
|
||||
|
||||
def find_dangerous_changes(
|
||||
old_schema: GraphQLSchema, new_schema: GraphQLSchema
|
||||
) -> List[DangerousChange]:
|
||||
"""Find dangerous changes.
|
||||
|
||||
Given two schemas, returns a list containing descriptions of all the types of
|
||||
potentially dangerous changes covered by the other functions down below.
|
||||
"""
|
||||
return [
|
||||
change
|
||||
for change in find_schema_changes(old_schema, new_schema)
|
||||
if isinstance(change.type, DangerousChangeType)
|
||||
]
|
||||
|
||||
|
||||
def find_schema_changes(
|
||||
old_schema: GraphQLSchema, new_schema: GraphQLSchema
|
||||
) -> List[Change]:
|
||||
return find_type_changes(old_schema, new_schema) + find_directive_changes(
|
||||
old_schema, new_schema
|
||||
)
|
||||
|
||||
|
||||
def find_directive_changes(
|
||||
old_schema: GraphQLSchema, new_schema: GraphQLSchema
|
||||
) -> List[Change]:
|
||||
schema_changes: List[Change] = []
|
||||
|
||||
directives_diff = list_diff(old_schema.directives, new_schema.directives)
|
||||
|
||||
for directive in directives_diff.removed:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.DIRECTIVE_REMOVED, f"{directive.name} was removed."
|
||||
)
|
||||
)
|
||||
|
||||
for old_directive, new_directive in directives_diff.persisted:
|
||||
args_diff = dict_diff(old_directive.args, new_directive.args)
|
||||
|
||||
for arg_name, new_arg in args_diff.added.items():
|
||||
if is_required_argument(new_arg):
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.REQUIRED_DIRECTIVE_ARG_ADDED,
|
||||
f"A required arg {arg_name} on directive"
|
||||
f" {old_directive.name} was added.",
|
||||
)
|
||||
)
|
||||
|
||||
for arg_name in args_diff.removed:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.DIRECTIVE_ARG_REMOVED,
|
||||
f"{arg_name} was removed from {new_directive.name}.",
|
||||
)
|
||||
)
|
||||
|
||||
if old_directive.is_repeatable and not new_directive.is_repeatable:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.DIRECTIVE_REPEATABLE_REMOVED,
|
||||
f"Repeatable flag was removed from {old_directive.name}.",
|
||||
)
|
||||
)
|
||||
|
||||
for location in old_directive.locations:
|
||||
if location not in new_directive.locations:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.DIRECTIVE_LOCATION_REMOVED,
|
||||
f"{location.name} was removed from {new_directive.name}.",
|
||||
)
|
||||
)
|
||||
|
||||
return schema_changes
|
||||
|
||||
|
||||
def find_type_changes(
|
||||
old_schema: GraphQLSchema, new_schema: GraphQLSchema
|
||||
) -> List[Change]:
|
||||
schema_changes: List[Change] = []
|
||||
types_diff = dict_diff(old_schema.type_map, new_schema.type_map)
|
||||
|
||||
for type_name, old_type in types_diff.removed.items():
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.TYPE_REMOVED,
|
||||
(
|
||||
f"Standard scalar {type_name} was removed"
|
||||
" because it is not referenced anymore."
|
||||
if is_specified_scalar_type(old_type)
|
||||
else f"{type_name} was removed."
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
for type_name, (old_type, new_type) in types_diff.persisted.items():
|
||||
if is_enum_type(old_type) and is_enum_type(new_type):
|
||||
schema_changes.extend(find_enum_type_changes(old_type, new_type))
|
||||
elif is_union_type(old_type) and is_union_type(new_type):
|
||||
schema_changes.extend(find_union_type_changes(old_type, new_type))
|
||||
elif is_input_object_type(old_type) and is_input_object_type(new_type):
|
||||
schema_changes.extend(find_input_object_type_changes(old_type, new_type))
|
||||
elif is_object_type(old_type) and is_object_type(new_type):
|
||||
schema_changes.extend(find_field_changes(old_type, new_type))
|
||||
schema_changes.extend(
|
||||
find_implemented_interfaces_changes(old_type, new_type)
|
||||
)
|
||||
elif is_interface_type(old_type) and is_interface_type(new_type):
|
||||
schema_changes.extend(find_field_changes(old_type, new_type))
|
||||
schema_changes.extend(
|
||||
find_implemented_interfaces_changes(old_type, new_type)
|
||||
)
|
||||
elif old_type.__class__ is not new_type.__class__:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.TYPE_CHANGED_KIND,
|
||||
f"{type_name} changed from {type_kind_name(old_type)}"
|
||||
f" to {type_kind_name(new_type)}.",
|
||||
)
|
||||
)
|
||||
|
||||
return schema_changes
|
||||
|
||||
|
||||
def find_input_object_type_changes(
|
||||
old_type: Union[GraphQLObjectType, GraphQLInterfaceType],
|
||||
new_type: Union[GraphQLObjectType, GraphQLInterfaceType],
|
||||
) -> List[Change]:
|
||||
schema_changes: List[Change] = []
|
||||
fields_diff = dict_diff(old_type.fields, new_type.fields)
|
||||
|
||||
for field_name, new_field in fields_diff.added.items():
|
||||
if is_required_input_field(new_field):
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.REQUIRED_INPUT_FIELD_ADDED,
|
||||
f"A required field {field_name} on"
|
||||
f" input type {old_type.name} was added.",
|
||||
)
|
||||
)
|
||||
else:
|
||||
schema_changes.append(
|
||||
DangerousChange(
|
||||
DangerousChangeType.OPTIONAL_INPUT_FIELD_ADDED,
|
||||
f"An optional field {field_name} on"
|
||||
f" input type {old_type.name} was added.",
|
||||
)
|
||||
)
|
||||
|
||||
for field_name in fields_diff.removed:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.FIELD_REMOVED,
|
||||
f"{old_type.name}.{field_name} was removed.",
|
||||
)
|
||||
)
|
||||
|
||||
for field_name, (old_field, new_field) in fields_diff.persisted.items():
|
||||
is_safe = is_change_safe_for_input_object_field_or_field_arg(
|
||||
old_field.type, new_field.type
|
||||
)
|
||||
if not is_safe:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.FIELD_CHANGED_KIND,
|
||||
f"{old_type.name}.{field_name} changed type"
|
||||
f" from {old_field.type} to {new_field.type}.",
|
||||
)
|
||||
)
|
||||
|
||||
return schema_changes
|
||||
|
||||
|
||||
def find_union_type_changes(
|
||||
old_type: GraphQLUnionType, new_type: GraphQLUnionType
|
||||
) -> List[Change]:
|
||||
schema_changes: List[Change] = []
|
||||
possible_types_diff = list_diff(old_type.types, new_type.types)
|
||||
|
||||
for possible_type in possible_types_diff.added:
|
||||
schema_changes.append(
|
||||
DangerousChange(
|
||||
DangerousChangeType.TYPE_ADDED_TO_UNION,
|
||||
f"{possible_type.name} was added" f" to union type {old_type.name}.",
|
||||
)
|
||||
)
|
||||
|
||||
for possible_type in possible_types_diff.removed:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.TYPE_REMOVED_FROM_UNION,
|
||||
f"{possible_type.name} was removed from union type {old_type.name}.",
|
||||
)
|
||||
)
|
||||
|
||||
return schema_changes
|
||||
|
||||
|
||||
def find_enum_type_changes(
|
||||
old_type: GraphQLEnumType, new_type: GraphQLEnumType
|
||||
) -> List[Change]:
|
||||
schema_changes: List[Change] = []
|
||||
values_diff = dict_diff(old_type.values, new_type.values)
|
||||
|
||||
for value_name in values_diff.added:
|
||||
schema_changes.append(
|
||||
DangerousChange(
|
||||
DangerousChangeType.VALUE_ADDED_TO_ENUM,
|
||||
f"{value_name} was added to enum type {old_type.name}.",
|
||||
)
|
||||
)
|
||||
|
||||
for value_name in values_diff.removed:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.VALUE_REMOVED_FROM_ENUM,
|
||||
f"{value_name} was removed from enum type {old_type.name}.",
|
||||
)
|
||||
)
|
||||
|
||||
return schema_changes
|
||||
|
||||
|
||||
def find_implemented_interfaces_changes(
|
||||
old_type: Union[GraphQLObjectType, GraphQLInterfaceType],
|
||||
new_type: Union[GraphQLObjectType, GraphQLInterfaceType],
|
||||
) -> List[Change]:
|
||||
schema_changes: List[Change] = []
|
||||
interfaces_diff = list_diff(old_type.interfaces, new_type.interfaces)
|
||||
|
||||
for interface in interfaces_diff.added:
|
||||
schema_changes.append(
|
||||
DangerousChange(
|
||||
DangerousChangeType.IMPLEMENTED_INTERFACE_ADDED,
|
||||
f"{interface.name} added to interfaces implemented by {old_type.name}.",
|
||||
)
|
||||
)
|
||||
|
||||
for interface in interfaces_diff.removed:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.IMPLEMENTED_INTERFACE_REMOVED,
|
||||
f"{old_type.name} no longer implements interface {interface.name}.",
|
||||
)
|
||||
)
|
||||
|
||||
return schema_changes
|
||||
|
||||
|
||||
def find_field_changes(
|
||||
old_type: Union[GraphQLObjectType, GraphQLInterfaceType],
|
||||
new_type: Union[GraphQLObjectType, GraphQLInterfaceType],
|
||||
) -> List[Change]:
|
||||
schema_changes: List[Change] = []
|
||||
fields_diff = dict_diff(old_type.fields, new_type.fields)
|
||||
|
||||
for field_name in fields_diff.removed:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.FIELD_REMOVED,
|
||||
f"{old_type.name}.{field_name} was removed.",
|
||||
)
|
||||
)
|
||||
|
||||
for field_name, (old_field, new_field) in fields_diff.persisted.items():
|
||||
schema_changes.extend(
|
||||
find_arg_changes(old_type, field_name, old_field, new_field)
|
||||
)
|
||||
is_safe = is_change_safe_for_object_or_interface_field(
|
||||
old_field.type, new_field.type
|
||||
)
|
||||
if not is_safe:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.FIELD_CHANGED_KIND,
|
||||
f"{old_type.name}.{field_name} changed type"
|
||||
f" from {old_field.type} to {new_field.type}.",
|
||||
)
|
||||
)
|
||||
|
||||
return schema_changes
|
||||
|
||||
|
||||
def find_arg_changes(
|
||||
old_type: Union[GraphQLObjectType, GraphQLInterfaceType],
|
||||
field_name: str,
|
||||
old_field: GraphQLField,
|
||||
new_field: GraphQLField,
|
||||
) -> List[Change]:
|
||||
schema_changes: List[Change] = []
|
||||
args_diff = dict_diff(old_field.args, new_field.args)
|
||||
|
||||
for arg_name in args_diff.removed:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.ARG_REMOVED,
|
||||
f"{old_type.name}.{field_name} arg" f" {arg_name} was removed.",
|
||||
)
|
||||
)
|
||||
|
||||
for arg_name, (old_arg, new_arg) in args_diff.persisted.items():
|
||||
is_safe = is_change_safe_for_input_object_field_or_field_arg(
|
||||
old_arg.type, new_arg.type
|
||||
)
|
||||
if not is_safe:
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.ARG_CHANGED_KIND,
|
||||
f"{old_type.name}.{field_name} arg"
|
||||
f" {arg_name} has changed type from"
|
||||
f" {old_arg.type} to {new_arg.type}.",
|
||||
)
|
||||
)
|
||||
elif old_arg.default_value is not Undefined:
|
||||
if new_arg.default_value is Undefined:
|
||||
schema_changes.append(
|
||||
DangerousChange(
|
||||
DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
|
||||
f"{old_type.name}.{field_name} arg"
|
||||
f" {arg_name} defaultValue was removed.",
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Since we are looking only for client's observable changes we should
|
||||
# compare default values in the same representation as they are
|
||||
# represented inside introspection.
|
||||
old_value_str = stringify_value(old_arg.default_value, old_arg.type)
|
||||
new_value_str = stringify_value(new_arg.default_value, new_arg.type)
|
||||
|
||||
if old_value_str != new_value_str:
|
||||
schema_changes.append(
|
||||
DangerousChange(
|
||||
DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
|
||||
f"{old_type.name}.{field_name} arg"
|
||||
f" {arg_name} has changed defaultValue"
|
||||
f" from {old_value_str} to {new_value_str}.",
|
||||
)
|
||||
)
|
||||
|
||||
for arg_name, new_arg in args_diff.added.items():
|
||||
if is_required_argument(new_arg):
|
||||
schema_changes.append(
|
||||
BreakingChange(
|
||||
BreakingChangeType.REQUIRED_ARG_ADDED,
|
||||
f"A required arg {arg_name} on"
|
||||
f" {old_type.name}.{field_name} was added.",
|
||||
)
|
||||
)
|
||||
else:
|
||||
schema_changes.append(
|
||||
DangerousChange(
|
||||
DangerousChangeType.OPTIONAL_ARG_ADDED,
|
||||
f"An optional arg {arg_name} on"
|
||||
f" {old_type.name}.{field_name} was added.",
|
||||
)
|
||||
)
|
||||
|
||||
return schema_changes
|
||||
|
||||
|
||||
def is_change_safe_for_object_or_interface_field(
|
||||
old_type: GraphQLType, new_type: GraphQLType
|
||||
) -> bool:
|
||||
if is_list_type(old_type):
|
||||
return (
|
||||
# if they're both lists, make sure underlying types are compatible
|
||||
is_list_type(new_type)
|
||||
and is_change_safe_for_object_or_interface_field(
|
||||
cast(GraphQLList, old_type).of_type, cast(GraphQLList, new_type).of_type
|
||||
)
|
||||
) or (
|
||||
# moving from nullable to non-null of same underlying type is safe
|
||||
is_non_null_type(new_type)
|
||||
and is_change_safe_for_object_or_interface_field(
|
||||
old_type, cast(GraphQLNonNull, new_type).of_type
|
||||
)
|
||||
)
|
||||
|
||||
if is_non_null_type(old_type):
|
||||
# if they're both non-null, make sure underlying types are compatible
|
||||
return is_non_null_type(
|
||||
new_type
|
||||
) and is_change_safe_for_object_or_interface_field(
|
||||
cast(GraphQLNonNull, old_type).of_type,
|
||||
cast(GraphQLNonNull, new_type).of_type,
|
||||
)
|
||||
|
||||
return (
|
||||
# if they're both named types, see if their names are equivalent
|
||||
is_named_type(new_type)
|
||||
and cast(GraphQLNamedType, old_type).name
|
||||
== cast(GraphQLNamedType, new_type).name
|
||||
) or (
|
||||
# moving from nullable to non-null of same underlying type is safe
|
||||
is_non_null_type(new_type)
|
||||
and is_change_safe_for_object_or_interface_field(
|
||||
old_type, cast(GraphQLNonNull, new_type).of_type
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def is_change_safe_for_input_object_field_or_field_arg(
|
||||
old_type: GraphQLType, new_type: GraphQLType
|
||||
) -> bool:
|
||||
if is_list_type(old_type):
|
||||
|
||||
return is_list_type(
|
||||
# if they're both lists, make sure underlying types are compatible
|
||||
new_type
|
||||
) and is_change_safe_for_input_object_field_or_field_arg(
|
||||
cast(GraphQLList, old_type).of_type, cast(GraphQLList, new_type).of_type
|
||||
)
|
||||
|
||||
if is_non_null_type(old_type):
|
||||
return (
|
||||
# if they're both non-null, make sure the underlying types are compatible
|
||||
is_non_null_type(new_type)
|
||||
and is_change_safe_for_input_object_field_or_field_arg(
|
||||
cast(GraphQLNonNull, old_type).of_type,
|
||||
cast(GraphQLNonNull, new_type).of_type,
|
||||
)
|
||||
) or (
|
||||
# moving from non-null to nullable of same underlying type is safe
|
||||
not is_non_null_type(new_type)
|
||||
and is_change_safe_for_input_object_field_or_field_arg(
|
||||
cast(GraphQLNonNull, old_type).of_type, new_type
|
||||
)
|
||||
)
|
||||
|
||||
return (
|
||||
# if they're both named types, see if their names are equivalent
|
||||
is_named_type(new_type)
|
||||
and cast(GraphQLNamedType, old_type).name
|
||||
== cast(GraphQLNamedType, new_type).name
|
||||
)
|
||||
|
||||
|
||||
def type_kind_name(type_: GraphQLNamedType) -> str:
|
||||
if is_scalar_type(type_):
|
||||
return "a Scalar type"
|
||||
if is_object_type(type_):
|
||||
return "an Object type"
|
||||
if is_interface_type(type_):
|
||||
return "an Interface type"
|
||||
if is_union_type(type_):
|
||||
return "a Union type"
|
||||
if is_enum_type(type_):
|
||||
return "an Enum type"
|
||||
if is_input_object_type(type_):
|
||||
return "an Input type"
|
||||
|
||||
# Not reachable. All possible output types have been considered.
|
||||
raise TypeError(f"Unexpected type {inspect(type)}")
|
||||
|
||||
|
||||
def stringify_value(value: Any, type_: GraphQLInputType) -> str:
|
||||
ast = ast_from_value(value, type_)
|
||||
if ast is None: # pragma: no cover
|
||||
raise TypeError(f"Invalid value: {inspect(value)}")
|
||||
return print_ast(sort_value_node(ast))
|
||||
|
||||
|
||||
class ListDiff(NamedTuple):
|
||||
"""Tuple with added, removed and persisted list items."""
|
||||
|
||||
added: List
|
||||
removed: List
|
||||
persisted: List
|
||||
|
||||
|
||||
def list_diff(old_list: Collection, new_list: Collection) -> ListDiff:
|
||||
"""Get differences between two lists of named items."""
|
||||
added = []
|
||||
persisted = []
|
||||
removed = []
|
||||
|
||||
old_set = {item.name for item in old_list}
|
||||
new_map = {item.name: item for item in new_list}
|
||||
|
||||
for old_item in old_list:
|
||||
new_item = new_map.get(old_item.name)
|
||||
if new_item:
|
||||
persisted.append([old_item, new_item])
|
||||
else:
|
||||
removed.append(old_item)
|
||||
|
||||
for new_item in new_list:
|
||||
if new_item.name not in old_set:
|
||||
added.append(new_item)
|
||||
|
||||
return ListDiff(added, removed, persisted)
|
||||
|
||||
|
||||
class DictDiff(NamedTuple):
|
||||
"""Tuple with added, removed and persisted dict entries."""
|
||||
|
||||
added: Dict
|
||||
removed: Dict
|
||||
persisted: Dict
|
||||
|
||||
|
||||
def dict_diff(old_dict: Dict, new_dict: Dict) -> DictDiff:
|
||||
"""Get differences between two dicts."""
|
||||
added = {}
|
||||
removed = {}
|
||||
persisted = {}
|
||||
|
||||
for old_name, old_item in old_dict.items():
|
||||
new_item = new_dict.get(old_name)
|
||||
if new_item:
|
||||
persisted[old_name] = [old_item, new_item]
|
||||
else:
|
||||
removed[old_name] = old_item
|
||||
|
||||
for new_name, new_item in new_dict.items():
|
||||
if new_name not in old_dict:
|
||||
added[new_name] = new_item
|
||||
|
||||
return DictDiff(added, removed, persisted)
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
from textwrap import dedent
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from ..language import DirectiveLocation
|
||||
|
||||
try:
|
||||
from typing import Literal, TypedDict
|
||||
except ImportError: # Python < 3.8
|
||||
from typing_extensions import Literal, TypedDict # type: ignore
|
||||
|
||||
__all__ = [
|
||||
"get_introspection_query",
|
||||
"IntrospectionDirective",
|
||||
"IntrospectionEnumType",
|
||||
"IntrospectionField",
|
||||
"IntrospectionInputObjectType",
|
||||
"IntrospectionInputValue",
|
||||
"IntrospectionInterfaceType",
|
||||
"IntrospectionListType",
|
||||
"IntrospectionNonNullType",
|
||||
"IntrospectionObjectType",
|
||||
"IntrospectionQuery",
|
||||
"IntrospectionScalarType",
|
||||
"IntrospectionSchema",
|
||||
"IntrospectionType",
|
||||
"IntrospectionTypeRef",
|
||||
"IntrospectionUnionType",
|
||||
]
|
||||
|
||||
|
||||
def get_introspection_query(
|
||||
descriptions: bool = True,
|
||||
specified_by_url: bool = False,
|
||||
directive_is_repeatable: bool = False,
|
||||
schema_description: bool = False,
|
||||
input_value_deprecation: bool = False,
|
||||
) -> str:
|
||||
"""Get a query for introspection.
|
||||
|
||||
Optionally, you can exclude descriptions, include specification URLs,
|
||||
include repeatability of directives, and specify whether to include
|
||||
the schema description as well.
|
||||
"""
|
||||
maybe_description = "description" if descriptions else ""
|
||||
maybe_specified_by_url = "specifiedByURL" if specified_by_url else ""
|
||||
maybe_directive_is_repeatable = "isRepeatable" if directive_is_repeatable else ""
|
||||
maybe_schema_description = maybe_description if schema_description else ""
|
||||
|
||||
def input_deprecation(string: str) -> Optional[str]:
|
||||
return string if input_value_deprecation else ""
|
||||
|
||||
return dedent(
|
||||
f"""
|
||||
query IntrospectionQuery {{
|
||||
__schema {{
|
||||
{maybe_schema_description}
|
||||
queryType {{ name }}
|
||||
mutationType {{ name }}
|
||||
subscriptionType {{ name }}
|
||||
types {{
|
||||
...FullType
|
||||
}}
|
||||
directives {{
|
||||
name
|
||||
{maybe_description}
|
||||
{maybe_directive_is_repeatable}
|
||||
locations
|
||||
args{input_deprecation("(includeDeprecated: true)")} {{
|
||||
...InputValue
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
fragment FullType on __Type {{
|
||||
kind
|
||||
name
|
||||
{maybe_description}
|
||||
{maybe_specified_by_url}
|
||||
fields(includeDeprecated: true) {{
|
||||
name
|
||||
{maybe_description}
|
||||
args{input_deprecation("(includeDeprecated: true)")} {{
|
||||
...InputValue
|
||||
}}
|
||||
type {{
|
||||
...TypeRef
|
||||
}}
|
||||
isDeprecated
|
||||
deprecationReason
|
||||
}}
|
||||
inputFields{input_deprecation("(includeDeprecated: true)")} {{
|
||||
...InputValue
|
||||
}}
|
||||
interfaces {{
|
||||
...TypeRef
|
||||
}}
|
||||
enumValues(includeDeprecated: true) {{
|
||||
name
|
||||
{maybe_description}
|
||||
isDeprecated
|
||||
deprecationReason
|
||||
}}
|
||||
possibleTypes {{
|
||||
...TypeRef
|
||||
}}
|
||||
}}
|
||||
|
||||
fragment InputValue on __InputValue {{
|
||||
name
|
||||
{maybe_description}
|
||||
type {{ ...TypeRef }}
|
||||
defaultValue
|
||||
{input_deprecation("isDeprecated")}
|
||||
{input_deprecation("deprecationReason")}
|
||||
}}
|
||||
|
||||
fragment TypeRef on __Type {{
|
||||
kind
|
||||
name
|
||||
ofType {{
|
||||
kind
|
||||
name
|
||||
ofType {{
|
||||
kind
|
||||
name
|
||||
ofType {{
|
||||
kind
|
||||
name
|
||||
ofType {{
|
||||
kind
|
||||
name
|
||||
ofType {{
|
||||
kind
|
||||
name
|
||||
ofType {{
|
||||
kind
|
||||
name
|
||||
ofType {{
|
||||
kind
|
||||
name
|
||||
ofType {{
|
||||
kind
|
||||
name
|
||||
ofType {{
|
||||
kind
|
||||
name
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
# Unfortunately, the following type definitions are a bit simplistic
|
||||
# because of current restrictions in the typing system (mypy):
|
||||
# - no recursion, see https://github.com/python/mypy/issues/731
|
||||
# - no generic typed dicts, see https://github.com/python/mypy/issues/3863
|
||||
|
||||
# simplified IntrospectionNamedType to avoids cycles
|
||||
SimpleIntrospectionType = Dict[str, Any]
|
||||
|
||||
|
||||
class MaybeWithDescription(TypedDict, total=False):
|
||||
description: Optional[str]
|
||||
|
||||
|
||||
class WithName(MaybeWithDescription):
|
||||
name: str
|
||||
|
||||
|
||||
class MaybeWithSpecifiedByUrl(TypedDict, total=False):
|
||||
specifiedByURL: Optional[str]
|
||||
|
||||
|
||||
class WithDeprecated(TypedDict):
|
||||
isDeprecated: bool
|
||||
deprecationReason: Optional[str]
|
||||
|
||||
|
||||
class MaybeWithDeprecated(TypedDict, total=False):
|
||||
isDeprecated: bool
|
||||
deprecationReason: Optional[str]
|
||||
|
||||
|
||||
class IntrospectionInputValue(WithName, MaybeWithDeprecated):
|
||||
type: SimpleIntrospectionType # should be IntrospectionInputType
|
||||
defaultValue: Optional[str]
|
||||
|
||||
|
||||
class IntrospectionField(WithName, WithDeprecated):
|
||||
args: List[IntrospectionInputValue]
|
||||
type: SimpleIntrospectionType # should be IntrospectionOutputType
|
||||
|
||||
|
||||
class IntrospectionEnumValue(WithName, WithDeprecated):
|
||||
pass
|
||||
|
||||
|
||||
class MaybeWithIsRepeatable(TypedDict, total=False):
|
||||
isRepeatable: bool
|
||||
|
||||
|
||||
class IntrospectionDirective(WithName, MaybeWithIsRepeatable):
|
||||
locations: List[DirectiveLocation]
|
||||
args: List[IntrospectionInputValue]
|
||||
|
||||
|
||||
class IntrospectionScalarType(WithName, MaybeWithSpecifiedByUrl):
|
||||
kind: Literal["scalar"]
|
||||
|
||||
|
||||
class IntrospectionInterfaceType(WithName):
|
||||
kind: Literal["interface"]
|
||||
fields: List[IntrospectionField]
|
||||
interfaces: List[SimpleIntrospectionType] # should be InterfaceType
|
||||
possibleTypes: List[SimpleIntrospectionType] # should be NamedType
|
||||
|
||||
|
||||
class IntrospectionObjectType(WithName):
|
||||
kind: Literal["object"]
|
||||
fields: List[IntrospectionField]
|
||||
interfaces: List[SimpleIntrospectionType] # should be InterfaceType
|
||||
|
||||
|
||||
class IntrospectionUnionType(WithName):
|
||||
kind: Literal["union"]
|
||||
possibleTypes: List[SimpleIntrospectionType] # should be NamedType
|
||||
|
||||
|
||||
class IntrospectionEnumType(WithName):
|
||||
kind: Literal["enum"]
|
||||
enumValues: List[IntrospectionEnumValue]
|
||||
|
||||
|
||||
class IntrospectionInputObjectType(WithName):
|
||||
kind: Literal["input_object"]
|
||||
inputFields: List[IntrospectionInputValue]
|
||||
|
||||
|
||||
IntrospectionType = Union[
|
||||
IntrospectionScalarType,
|
||||
IntrospectionObjectType,
|
||||
IntrospectionInterfaceType,
|
||||
IntrospectionUnionType,
|
||||
IntrospectionEnumType,
|
||||
IntrospectionInputObjectType,
|
||||
]
|
||||
|
||||
|
||||
IntrospectionOutputType = Union[
|
||||
IntrospectionScalarType,
|
||||
IntrospectionObjectType,
|
||||
IntrospectionInterfaceType,
|
||||
IntrospectionUnionType,
|
||||
IntrospectionEnumType,
|
||||
]
|
||||
|
||||
|
||||
IntrospectionInputType = Union[
|
||||
IntrospectionScalarType, IntrospectionEnumType, IntrospectionInputObjectType
|
||||
]
|
||||
|
||||
|
||||
class IntrospectionListType(TypedDict):
|
||||
kind: Literal["list"]
|
||||
ofType: SimpleIntrospectionType # should be IntrospectionType
|
||||
|
||||
|
||||
class IntrospectionNonNullType(TypedDict):
|
||||
kind: Literal["non_null"]
|
||||
ofType: SimpleIntrospectionType # should be IntrospectionType
|
||||
|
||||
|
||||
IntrospectionTypeRef = Union[
|
||||
IntrospectionType, IntrospectionListType, IntrospectionNonNullType
|
||||
]
|
||||
|
||||
|
||||
class IntrospectionSchema(MaybeWithDescription):
|
||||
queryType: IntrospectionObjectType
|
||||
mutationType: Optional[IntrospectionObjectType]
|
||||
subscriptionType: Optional[IntrospectionObjectType]
|
||||
types: List[IntrospectionType]
|
||||
directives: List[IntrospectionDirective]
|
||||
|
||||
|
||||
class IntrospectionQuery(TypedDict):
|
||||
"""The root typed dictionary for schema introspections."""
|
||||
|
||||
__schema: IntrospectionSchema
|
||||
@@ -0,0 +1,29 @@
|
||||
from typing import Optional
|
||||
|
||||
from ..language import DocumentNode, OperationDefinitionNode
|
||||
|
||||
__all__ = ["get_operation_ast"]
|
||||
|
||||
|
||||
def get_operation_ast(
|
||||
document_ast: DocumentNode, operation_name: Optional[str] = None
|
||||
) -> Optional[OperationDefinitionNode]:
|
||||
"""Get operation AST node.
|
||||
|
||||
Returns an operation AST given a document AST and optionally an operation
|
||||
name. If a name is not provided, an operation is only returned if only one
|
||||
is provided in the document.
|
||||
"""
|
||||
operation = None
|
||||
for definition in document_ast.definitions:
|
||||
if isinstance(definition, OperationDefinitionNode):
|
||||
if operation_name is None:
|
||||
# If no operation name was provided, only return an Operation if there
|
||||
# is one defined in the document.
|
||||
# Upon encountering the second, return None.
|
||||
if operation:
|
||||
return None
|
||||
operation = definition
|
||||
elif definition.name and definition.name.value == operation_name:
|
||||
return definition
|
||||
return operation
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
from typing import Union
|
||||
|
||||
from ..error import GraphQLError
|
||||
from ..language import (
|
||||
OperationType,
|
||||
OperationDefinitionNode,
|
||||
OperationTypeDefinitionNode,
|
||||
)
|
||||
from ..type import GraphQLObjectType, GraphQLSchema
|
||||
|
||||
__all__ = ["get_operation_root_type"]
|
||||
|
||||
|
||||
def get_operation_root_type(
|
||||
schema: GraphQLSchema,
|
||||
operation: Union[OperationDefinitionNode, OperationTypeDefinitionNode],
|
||||
) -> GraphQLObjectType:
|
||||
"""Extract the root type of the operation from the schema.
|
||||
|
||||
.. deprecated:: 3.2
|
||||
Please use `GraphQLSchema.getRootType` instead. Will be removed in v3.3.
|
||||
"""
|
||||
operation_type = operation.operation
|
||||
if operation_type == OperationType.QUERY:
|
||||
query_type = schema.query_type
|
||||
if not query_type:
|
||||
raise GraphQLError(
|
||||
"Schema does not define the required query root type.", operation
|
||||
)
|
||||
return query_type
|
||||
|
||||
if operation_type == OperationType.MUTATION:
|
||||
mutation_type = schema.mutation_type
|
||||
if not mutation_type:
|
||||
raise GraphQLError("Schema is not configured for mutations.", operation)
|
||||
return mutation_type
|
||||
|
||||
if operation_type == OperationType.SUBSCRIPTION:
|
||||
subscription_type = schema.subscription_type
|
||||
if not subscription_type:
|
||||
raise GraphQLError("Schema is not configured for subscriptions.", operation)
|
||||
return subscription_type
|
||||
|
||||
raise GraphQLError(
|
||||
"Can only have query, mutation and subscription operations.", operation
|
||||
)
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
from typing import cast
|
||||
|
||||
from ..error import GraphQLError
|
||||
from ..language import parse
|
||||
from ..type import GraphQLSchema
|
||||
from .get_introspection_query import get_introspection_query, IntrospectionQuery
|
||||
|
||||
__all__ = ["introspection_from_schema"]
|
||||
|
||||
|
||||
def introspection_from_schema(
|
||||
schema: GraphQLSchema,
|
||||
descriptions: bool = True,
|
||||
specified_by_url: bool = True,
|
||||
directive_is_repeatable: bool = True,
|
||||
schema_description: bool = True,
|
||||
input_value_deprecation: bool = True,
|
||||
) -> IntrospectionQuery:
|
||||
"""Build an IntrospectionQuery from a GraphQLSchema
|
||||
|
||||
IntrospectionQuery is useful for utilities that care about type and field
|
||||
relationships, but do not need to traverse through those relationships.
|
||||
|
||||
This is the inverse of build_client_schema. The primary use case is outside of the
|
||||
server context, for instance when doing schema comparisons.
|
||||
"""
|
||||
document = parse(
|
||||
get_introspection_query(
|
||||
descriptions,
|
||||
specified_by_url,
|
||||
directive_is_repeatable,
|
||||
schema_description,
|
||||
input_value_deprecation,
|
||||
)
|
||||
)
|
||||
|
||||
from ..execution.execute import execute_sync, ExecutionResult
|
||||
|
||||
result = execute_sync(schema, document)
|
||||
if not isinstance(result, ExecutionResult): # pragma: no cover
|
||||
raise RuntimeError("Introspection cannot be executed")
|
||||
if result.errors: # pragma: no cover
|
||||
raise result.errors[0]
|
||||
if not result.data: # pragma: no cover
|
||||
raise GraphQLError("Introspection did not return a result")
|
||||
return cast(IntrospectionQuery, result.data)
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
from typing import Collection, Dict, Optional, Tuple, Union, cast
|
||||
|
||||
from ..language import DirectiveLocation
|
||||
from ..pyutils import inspect, merge_kwargs, natural_comparison_key
|
||||
from ..type import (
|
||||
GraphQLArgument,
|
||||
GraphQLDirective,
|
||||
GraphQLEnumType,
|
||||
GraphQLEnumValue,
|
||||
GraphQLField,
|
||||
GraphQLInputField,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInputType,
|
||||
GraphQLInterfaceType,
|
||||
GraphQLList,
|
||||
GraphQLNamedType,
|
||||
GraphQLNonNull,
|
||||
GraphQLObjectType,
|
||||
GraphQLSchema,
|
||||
GraphQLUnionType,
|
||||
is_enum_type,
|
||||
is_input_object_type,
|
||||
is_interface_type,
|
||||
is_introspection_type,
|
||||
is_list_type,
|
||||
is_non_null_type,
|
||||
is_object_type,
|
||||
is_scalar_type,
|
||||
is_union_type,
|
||||
)
|
||||
|
||||
__all__ = ["lexicographic_sort_schema"]
|
||||
|
||||
|
||||
def lexicographic_sort_schema(schema: GraphQLSchema) -> GraphQLSchema:
|
||||
"""Sort GraphQLSchema.
|
||||
|
||||
This function returns a sorted copy of the given GraphQLSchema.
|
||||
"""
|
||||
|
||||
def replace_type(
|
||||
type_: Union[GraphQLList, GraphQLNonNull, GraphQLNamedType]
|
||||
) -> Union[GraphQLList, GraphQLNonNull, GraphQLNamedType]:
|
||||
if is_list_type(type_):
|
||||
return GraphQLList(replace_type(cast(GraphQLList, type_).of_type))
|
||||
if is_non_null_type(type_):
|
||||
return GraphQLNonNull(replace_type(cast(GraphQLNonNull, type_).of_type))
|
||||
return replace_named_type(cast(GraphQLNamedType, type_))
|
||||
|
||||
def replace_named_type(type_: GraphQLNamedType) -> GraphQLNamedType:
|
||||
return type_map[type_.name]
|
||||
|
||||
def replace_maybe_type(
|
||||
maybe_type: Optional[GraphQLNamedType],
|
||||
) -> Optional[GraphQLNamedType]:
|
||||
return maybe_type and replace_named_type(maybe_type)
|
||||
|
||||
def sort_directive(directive: GraphQLDirective) -> GraphQLDirective:
|
||||
return GraphQLDirective(
|
||||
**merge_kwargs(
|
||||
directive.to_kwargs(),
|
||||
locations=sorted(directive.locations, key=sort_by_name_key),
|
||||
args=sort_args(directive.args),
|
||||
)
|
||||
)
|
||||
|
||||
def sort_args(args_map: Dict[str, GraphQLArgument]) -> Dict[str, GraphQLArgument]:
|
||||
args = {}
|
||||
for name, arg in sorted(args_map.items()):
|
||||
args[name] = GraphQLArgument(
|
||||
**merge_kwargs(
|
||||
arg.to_kwargs(),
|
||||
type_=replace_type(cast(GraphQLNamedType, arg.type)),
|
||||
)
|
||||
)
|
||||
return args
|
||||
|
||||
def sort_fields(fields_map: Dict[str, GraphQLField]) -> Dict[str, GraphQLField]:
|
||||
fields = {}
|
||||
for name, field in sorted(fields_map.items()):
|
||||
fields[name] = GraphQLField(
|
||||
**merge_kwargs(
|
||||
field.to_kwargs(),
|
||||
type_=replace_type(cast(GraphQLNamedType, field.type)),
|
||||
args=sort_args(field.args),
|
||||
)
|
||||
)
|
||||
return fields
|
||||
|
||||
def sort_input_fields(
|
||||
fields_map: Dict[str, GraphQLInputField]
|
||||
) -> Dict[str, GraphQLInputField]:
|
||||
return {
|
||||
name: GraphQLInputField(
|
||||
cast(
|
||||
GraphQLInputType, replace_type(cast(GraphQLNamedType, field.type))
|
||||
),
|
||||
description=field.description,
|
||||
default_value=field.default_value,
|
||||
ast_node=field.ast_node,
|
||||
)
|
||||
for name, field in sorted(fields_map.items())
|
||||
}
|
||||
|
||||
def sort_types(array: Collection[GraphQLNamedType]) -> Tuple[GraphQLNamedType, ...]:
|
||||
return tuple(
|
||||
replace_named_type(type_) for type_ in sorted(array, key=sort_by_name_key)
|
||||
)
|
||||
|
||||
def sort_named_type(type_: GraphQLNamedType) -> GraphQLNamedType:
|
||||
if is_scalar_type(type_) or is_introspection_type(type_):
|
||||
return type_
|
||||
if is_object_type(type_):
|
||||
type_ = cast(GraphQLObjectType, type_)
|
||||
return GraphQLObjectType(
|
||||
**merge_kwargs(
|
||||
type_.to_kwargs(),
|
||||
interfaces=lambda: sort_types(type_.interfaces),
|
||||
fields=lambda: sort_fields(type_.fields),
|
||||
)
|
||||
)
|
||||
if is_interface_type(type_):
|
||||
type_ = cast(GraphQLInterfaceType, type_)
|
||||
return GraphQLInterfaceType(
|
||||
**merge_kwargs(
|
||||
type_.to_kwargs(),
|
||||
interfaces=lambda: sort_types(type_.interfaces),
|
||||
fields=lambda: sort_fields(type_.fields),
|
||||
)
|
||||
)
|
||||
if is_union_type(type_):
|
||||
type_ = cast(GraphQLUnionType, type_)
|
||||
return GraphQLUnionType(
|
||||
**merge_kwargs(type_.to_kwargs(), types=lambda: sort_types(type_.types))
|
||||
)
|
||||
if is_enum_type(type_):
|
||||
type_ = cast(GraphQLEnumType, type_)
|
||||
return GraphQLEnumType(
|
||||
**merge_kwargs(
|
||||
type_.to_kwargs(),
|
||||
values={
|
||||
name: GraphQLEnumValue(
|
||||
val.value,
|
||||
description=val.description,
|
||||
deprecation_reason=val.deprecation_reason,
|
||||
ast_node=val.ast_node,
|
||||
)
|
||||
for name, val in sorted(type_.values.items())
|
||||
},
|
||||
)
|
||||
)
|
||||
if is_input_object_type(type_):
|
||||
type_ = cast(GraphQLInputObjectType, type_)
|
||||
return GraphQLInputObjectType(
|
||||
**merge_kwargs(
|
||||
type_.to_kwargs(),
|
||||
fields=lambda: sort_input_fields(type_.fields),
|
||||
)
|
||||
)
|
||||
|
||||
# Not reachable. All possible types have been considered.
|
||||
raise TypeError(f"Unexpected type: {inspect(type_)}.")
|
||||
|
||||
type_map: Dict[str, GraphQLNamedType] = {
|
||||
type_.name: sort_named_type(type_)
|
||||
for type_ in sorted(schema.type_map.values(), key=sort_by_name_key)
|
||||
}
|
||||
|
||||
return GraphQLSchema(
|
||||
types=type_map.values(),
|
||||
directives=[
|
||||
sort_directive(directive)
|
||||
for directive in sorted(schema.directives, key=sort_by_name_key)
|
||||
],
|
||||
query=cast(Optional[GraphQLObjectType], replace_maybe_type(schema.query_type)),
|
||||
mutation=cast(
|
||||
Optional[GraphQLObjectType], replace_maybe_type(schema.mutation_type)
|
||||
),
|
||||
subscription=cast(
|
||||
Optional[GraphQLObjectType], replace_maybe_type(schema.subscription_type)
|
||||
),
|
||||
ast_node=schema.ast_node,
|
||||
)
|
||||
|
||||
|
||||
def sort_by_name_key(
|
||||
type_: Union[GraphQLNamedType, GraphQLDirective, DirectiveLocation]
|
||||
) -> Tuple:
|
||||
return natural_comparison_key(type_.name)
|
||||
@@ -0,0 +1,298 @@
|
||||
from typing import Any, Callable, Dict, List, Optional, Union, cast
|
||||
|
||||
from ..language import print_ast, StringValueNode
|
||||
from ..language.block_string import is_printable_as_block_string
|
||||
from ..pyutils import inspect
|
||||
from ..type import (
|
||||
DEFAULT_DEPRECATION_REASON,
|
||||
GraphQLArgument,
|
||||
GraphQLDirective,
|
||||
GraphQLEnumType,
|
||||
GraphQLEnumValue,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInputType,
|
||||
GraphQLInterfaceType,
|
||||
GraphQLNamedType,
|
||||
GraphQLObjectType,
|
||||
GraphQLScalarType,
|
||||
GraphQLSchema,
|
||||
GraphQLUnionType,
|
||||
is_enum_type,
|
||||
is_input_object_type,
|
||||
is_interface_type,
|
||||
is_introspection_type,
|
||||
is_object_type,
|
||||
is_scalar_type,
|
||||
is_specified_directive,
|
||||
is_specified_scalar_type,
|
||||
is_union_type,
|
||||
)
|
||||
from .ast_from_value import ast_from_value
|
||||
|
||||
__all__ = ["print_schema", "print_introspection_schema", "print_type", "print_value"]
|
||||
|
||||
|
||||
def print_schema(schema: GraphQLSchema) -> str:
|
||||
return print_filtered_schema(
|
||||
schema, lambda n: not is_specified_directive(n), is_defined_type
|
||||
)
|
||||
|
||||
|
||||
def print_introspection_schema(schema: GraphQLSchema) -> str:
|
||||
return print_filtered_schema(schema, is_specified_directive, is_introspection_type)
|
||||
|
||||
|
||||
def is_defined_type(type_: GraphQLNamedType) -> bool:
|
||||
return not is_specified_scalar_type(type_) and not is_introspection_type(type_)
|
||||
|
||||
|
||||
def print_filtered_schema(
|
||||
schema: GraphQLSchema,
|
||||
directive_filter: Callable[[GraphQLDirective], bool],
|
||||
type_filter: Callable[[GraphQLNamedType], bool],
|
||||
) -> str:
|
||||
directives = filter(directive_filter, schema.directives)
|
||||
types = filter(type_filter, schema.type_map.values())
|
||||
|
||||
return "\n\n".join(
|
||||
(
|
||||
*filter(None, (print_schema_definition(schema),)),
|
||||
*map(print_directive, directives),
|
||||
*map(print_type, types),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def print_schema_definition(schema: GraphQLSchema) -> Optional[str]:
|
||||
if schema.description is None and is_schema_of_common_names(schema):
|
||||
return None
|
||||
|
||||
operation_types = []
|
||||
|
||||
query_type = schema.query_type
|
||||
if query_type:
|
||||
operation_types.append(f" query: {query_type.name}")
|
||||
|
||||
mutation_type = schema.mutation_type
|
||||
if mutation_type:
|
||||
operation_types.append(f" mutation: {mutation_type.name}")
|
||||
|
||||
subscription_type = schema.subscription_type
|
||||
if subscription_type:
|
||||
operation_types.append(f" subscription: {subscription_type.name}")
|
||||
|
||||
return print_description(schema) + "schema {\n" + "\n".join(operation_types) + "\n}"
|
||||
|
||||
|
||||
def is_schema_of_common_names(schema: GraphQLSchema) -> bool:
|
||||
"""Check whether this schema uses the common naming convention.
|
||||
|
||||
GraphQL schema define root types for each type of operation. These types are the
|
||||
same as any other type and can be named in any manner, however there is a common
|
||||
naming convention:
|
||||
|
||||
schema {
|
||||
query: Query
|
||||
mutation: Mutation
|
||||
subscription: Subscription
|
||||
}
|
||||
|
||||
When using this naming convention, the schema description can be omitted.
|
||||
"""
|
||||
query_type = schema.query_type
|
||||
if query_type and query_type.name != "Query":
|
||||
return False
|
||||
|
||||
mutation_type = schema.mutation_type
|
||||
if mutation_type and mutation_type.name != "Mutation":
|
||||
return False
|
||||
|
||||
subscription_type = schema.subscription_type
|
||||
return not subscription_type or subscription_type.name == "Subscription"
|
||||
|
||||
|
||||
def print_type(type_: GraphQLNamedType) -> str:
|
||||
if is_scalar_type(type_):
|
||||
type_ = cast(GraphQLScalarType, type_)
|
||||
return print_scalar(type_)
|
||||
if is_object_type(type_):
|
||||
type_ = cast(GraphQLObjectType, type_)
|
||||
return print_object(type_)
|
||||
if is_interface_type(type_):
|
||||
type_ = cast(GraphQLInterfaceType, type_)
|
||||
return print_interface(type_)
|
||||
if is_union_type(type_):
|
||||
type_ = cast(GraphQLUnionType, type_)
|
||||
return print_union(type_)
|
||||
if is_enum_type(type_):
|
||||
type_ = cast(GraphQLEnumType, type_)
|
||||
return print_enum(type_)
|
||||
if is_input_object_type(type_):
|
||||
type_ = cast(GraphQLInputObjectType, type_)
|
||||
return print_input_object(type_)
|
||||
|
||||
# Not reachable. All possible types have been considered.
|
||||
raise TypeError(f"Unexpected type: {inspect(type_)}.")
|
||||
|
||||
|
||||
def print_scalar(type_: GraphQLScalarType) -> str:
|
||||
return (
|
||||
print_description(type_)
|
||||
+ f"scalar {type_.name}"
|
||||
+ print_specified_by_url(type_)
|
||||
)
|
||||
|
||||
|
||||
def print_implemented_interfaces(
|
||||
type_: Union[GraphQLObjectType, GraphQLInterfaceType]
|
||||
) -> str:
|
||||
interfaces = type_.interfaces
|
||||
return " implements " + " & ".join(i.name for i in interfaces) if interfaces else ""
|
||||
|
||||
|
||||
def print_object(type_: GraphQLObjectType) -> str:
|
||||
return (
|
||||
print_description(type_)
|
||||
+ f"type {type_.name}"
|
||||
+ print_implemented_interfaces(type_)
|
||||
+ print_fields(type_)
|
||||
)
|
||||
|
||||
|
||||
def print_interface(type_: GraphQLInterfaceType) -> str:
|
||||
return (
|
||||
print_description(type_)
|
||||
+ f"interface {type_.name}"
|
||||
+ print_implemented_interfaces(type_)
|
||||
+ print_fields(type_)
|
||||
)
|
||||
|
||||
|
||||
def print_union(type_: GraphQLUnionType) -> str:
|
||||
types = type_.types
|
||||
possible_types = " = " + " | ".join(t.name for t in types) if types else ""
|
||||
return print_description(type_) + f"union {type_.name}" + possible_types
|
||||
|
||||
|
||||
def print_enum(type_: GraphQLEnumType) -> str:
|
||||
values = [
|
||||
print_description(value, " ", not i)
|
||||
+ f" {name}"
|
||||
+ print_deprecated(value.deprecation_reason)
|
||||
for i, (name, value) in enumerate(type_.values.items())
|
||||
]
|
||||
return print_description(type_) + f"enum {type_.name}" + print_block(values)
|
||||
|
||||
|
||||
def print_input_object(type_: GraphQLInputObjectType) -> str:
|
||||
fields = [
|
||||
print_description(field, " ", not i) + " " + print_input_value(name, field)
|
||||
for i, (name, field) in enumerate(type_.fields.items())
|
||||
]
|
||||
return print_description(type_) + f"input {type_.name}" + print_block(fields)
|
||||
|
||||
|
||||
def print_fields(type_: Union[GraphQLObjectType, GraphQLInterfaceType]) -> str:
|
||||
fields = [
|
||||
print_description(field, " ", not i)
|
||||
+ f" {name}"
|
||||
+ print_args(field.args, " ")
|
||||
+ f": {field.type}"
|
||||
+ print_deprecated(field.deprecation_reason)
|
||||
for i, (name, field) in enumerate(type_.fields.items())
|
||||
]
|
||||
return print_block(fields)
|
||||
|
||||
|
||||
def print_block(items: List[str]) -> str:
|
||||
return " {\n" + "\n".join(items) + "\n}" if items else ""
|
||||
|
||||
|
||||
def print_args(args: Dict[str, GraphQLArgument], indentation: str = "") -> str:
|
||||
if not args:
|
||||
return ""
|
||||
|
||||
# If every arg does not have a description, print them on one line.
|
||||
if not any(arg.description for arg in args.values()):
|
||||
return (
|
||||
"("
|
||||
+ ", ".join(print_input_value(name, arg) for name, arg in args.items())
|
||||
+ ")"
|
||||
)
|
||||
|
||||
return (
|
||||
"(\n"
|
||||
+ "\n".join(
|
||||
print_description(arg, f" {indentation}", not i)
|
||||
+ f" {indentation}"
|
||||
+ print_input_value(name, arg)
|
||||
for i, (name, arg) in enumerate(args.items())
|
||||
)
|
||||
+ f"\n{indentation})"
|
||||
)
|
||||
|
||||
|
||||
def print_input_value(name: str, arg: GraphQLArgument) -> str:
|
||||
default_ast = ast_from_value(arg.default_value, arg.type)
|
||||
arg_decl = f"{name}: {arg.type}"
|
||||
if default_ast:
|
||||
arg_decl += f" = {print_ast(default_ast)}"
|
||||
return arg_decl + print_deprecated(arg.deprecation_reason)
|
||||
|
||||
|
||||
def print_directive(directive: GraphQLDirective) -> str:
|
||||
return (
|
||||
print_description(directive)
|
||||
+ f"directive @{directive.name}"
|
||||
+ print_args(directive.args)
|
||||
+ (" repeatable" if directive.is_repeatable else "")
|
||||
+ " on "
|
||||
+ " | ".join(location.name for location in directive.locations)
|
||||
)
|
||||
|
||||
|
||||
def print_deprecated(reason: Optional[str]) -> str:
|
||||
if reason is None:
|
||||
return ""
|
||||
if reason != DEFAULT_DEPRECATION_REASON:
|
||||
ast_value = print_ast(StringValueNode(value=reason))
|
||||
return f" @deprecated(reason: {ast_value})"
|
||||
return " @deprecated"
|
||||
|
||||
|
||||
def print_specified_by_url(scalar: GraphQLScalarType) -> str:
|
||||
if scalar.specified_by_url is None:
|
||||
return ""
|
||||
ast_value = print_ast(StringValueNode(value=scalar.specified_by_url))
|
||||
return f" @specifiedBy(url: {ast_value})"
|
||||
|
||||
|
||||
def print_description(
|
||||
def_: Union[
|
||||
GraphQLArgument,
|
||||
GraphQLDirective,
|
||||
GraphQLEnumValue,
|
||||
GraphQLNamedType,
|
||||
GraphQLSchema,
|
||||
],
|
||||
indentation: str = "",
|
||||
first_in_block: bool = True,
|
||||
) -> str:
|
||||
description = def_.description
|
||||
if description is None:
|
||||
return ""
|
||||
|
||||
block_string = print_ast(
|
||||
StringValueNode(
|
||||
value=description, block=is_printable_as_block_string(description)
|
||||
)
|
||||
)
|
||||
|
||||
prefix = "\n" + indentation if indentation and not first_in_block else indentation
|
||||
|
||||
return prefix + block_string.replace("\n", "\n" + indentation) + "\n"
|
||||
|
||||
|
||||
def print_value(value: Any, type_: GraphQLInputType) -> str:
|
||||
"""@deprecated: Convenience function for printing a Python value"""
|
||||
return print_ast(ast_from_value(value, type_)) # type: ignore
|
||||
@@ -0,0 +1,101 @@
|
||||
from typing import Any, Dict, List, Set
|
||||
|
||||
from ..language import (
|
||||
DocumentNode,
|
||||
FragmentDefinitionNode,
|
||||
FragmentSpreadNode,
|
||||
OperationDefinitionNode,
|
||||
SelectionSetNode,
|
||||
Visitor,
|
||||
visit,
|
||||
)
|
||||
|
||||
__all__ = ["separate_operations"]
|
||||
|
||||
|
||||
DepGraph = Dict[str, List[str]]
|
||||
|
||||
|
||||
def separate_operations(document_ast: DocumentNode) -> Dict[str, DocumentNode]:
|
||||
"""Separate operations in a given AST document.
|
||||
|
||||
This function accepts a single AST document which may contain many operations and
|
||||
fragments and returns a collection of AST documents each of which contains a single
|
||||
operation as well the fragment definitions it refers to.
|
||||
"""
|
||||
operations: List[OperationDefinitionNode] = []
|
||||
dep_graph: DepGraph = {}
|
||||
|
||||
# Populate metadata and build a dependency graph.
|
||||
for definition_node in document_ast.definitions:
|
||||
if isinstance(definition_node, OperationDefinitionNode):
|
||||
operations.append(definition_node)
|
||||
elif isinstance(
|
||||
definition_node, FragmentDefinitionNode
|
||||
): # pragma: no cover else
|
||||
dep_graph[definition_node.name.value] = collect_dependencies(
|
||||
definition_node.selection_set
|
||||
)
|
||||
|
||||
# For each operation, produce a new synthesized AST which includes only what is
|
||||
# necessary for completing that operation.
|
||||
separated_document_asts: Dict[str, DocumentNode] = {}
|
||||
for operation in operations:
|
||||
dependencies: Set[str] = set()
|
||||
|
||||
for fragment_name in collect_dependencies(operation.selection_set):
|
||||
collect_transitive_dependencies(dependencies, dep_graph, fragment_name)
|
||||
|
||||
# Provides the empty string for anonymous operations.
|
||||
operation_name = operation.name.value if operation.name else ""
|
||||
|
||||
# The list of definition nodes to be included for this operation, sorted
|
||||
# to retain the same order as the original document.
|
||||
separated_document_asts[operation_name] = DocumentNode(
|
||||
definitions=[
|
||||
node
|
||||
for node in document_ast.definitions
|
||||
if node is operation
|
||||
or (
|
||||
isinstance(node, FragmentDefinitionNode)
|
||||
and node.name.value in dependencies
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
return separated_document_asts
|
||||
|
||||
|
||||
def collect_transitive_dependencies(
|
||||
collected: Set[str], dep_graph: DepGraph, from_name: str
|
||||
) -> None:
|
||||
"""Collect transitive dependencies.
|
||||
|
||||
From a dependency graph, collects a list of transitive dependencies by recursing
|
||||
through a dependency graph.
|
||||
"""
|
||||
if from_name not in collected:
|
||||
collected.add(from_name)
|
||||
|
||||
immediate_deps = dep_graph.get(from_name)
|
||||
if immediate_deps is not None:
|
||||
for to_name in immediate_deps:
|
||||
collect_transitive_dependencies(collected, dep_graph, to_name)
|
||||
|
||||
|
||||
class DependencyCollector(Visitor):
|
||||
dependencies: List[str]
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.dependencies = []
|
||||
self.add_dependency = self.dependencies.append
|
||||
|
||||
def enter_fragment_spread(self, node: FragmentSpreadNode, *_args: Any) -> None:
|
||||
self.add_dependency(node.name.value)
|
||||
|
||||
|
||||
def collect_dependencies(selection_set: SelectionSetNode) -> List[str]:
|
||||
collector = DependencyCollector()
|
||||
visit(selection_set, collector)
|
||||
return collector.dependencies
|
||||
@@ -0,0 +1,38 @@
|
||||
from copy import copy
|
||||
from typing import Tuple
|
||||
|
||||
from ..language import ListValueNode, ObjectFieldNode, ObjectValueNode, ValueNode
|
||||
from ..pyutils import natural_comparison_key
|
||||
|
||||
__all__ = ["sort_value_node"]
|
||||
|
||||
|
||||
def sort_value_node(value_node: ValueNode) -> ValueNode:
|
||||
"""Sort ValueNode.
|
||||
|
||||
This function returns a sorted copy of the given ValueNode
|
||||
|
||||
For internal use only.
|
||||
"""
|
||||
if isinstance(value_node, ObjectValueNode):
|
||||
value_node = copy(value_node)
|
||||
value_node.fields = sort_fields(value_node.fields)
|
||||
elif isinstance(value_node, ListValueNode):
|
||||
value_node = copy(value_node)
|
||||
value_node.values = tuple(sort_value_node(value) for value in value_node.values)
|
||||
return value_node
|
||||
|
||||
|
||||
def sort_field(field: ObjectFieldNode) -> ObjectFieldNode:
|
||||
field = copy(field)
|
||||
field.value = sort_value_node(field.value)
|
||||
return field
|
||||
|
||||
|
||||
def sort_fields(fields: Tuple[ObjectFieldNode, ...]) -> Tuple[ObjectFieldNode, ...]:
|
||||
return tuple(
|
||||
sorted(
|
||||
(sort_field(field) for field in fields),
|
||||
key=lambda field: natural_comparison_key(field.name.value),
|
||||
)
|
||||
)
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
from typing import Union, cast
|
||||
|
||||
from ..language import Lexer, TokenKind
|
||||
from ..language.source import Source, is_source
|
||||
from ..language.block_string import print_block_string
|
||||
from ..language.lexer import is_punctuator_token_kind
|
||||
|
||||
__all__ = ["strip_ignored_characters"]
|
||||
|
||||
|
||||
def strip_ignored_characters(source: Union[str, Source]) -> str:
|
||||
"""Strip characters that are ignored anyway.
|
||||
|
||||
Strips characters that are not significant to the validity or execution
|
||||
of a GraphQL document:
|
||||
|
||||
- UnicodeBOM
|
||||
- WhiteSpace
|
||||
- LineTerminator
|
||||
- Comment
|
||||
- Comma
|
||||
- BlockString indentation
|
||||
|
||||
Note: It is required to have a delimiter character between neighboring
|
||||
non-punctuator tokes and this function always uses single space as delimiter.
|
||||
|
||||
It is guaranteed that both input and output documents if parsed would result
|
||||
in the exact same AST except for nodes location.
|
||||
|
||||
Warning: It is guaranteed that this function will always produce stable results.
|
||||
However, it's not guaranteed that it will stay the same between different
|
||||
releases due to bugfixes or changes in the GraphQL specification.
|
||||
""" '''
|
||||
|
||||
Query example::
|
||||
|
||||
query SomeQuery($foo: String!, $bar: String) {
|
||||
someField(foo: $foo, bar: $bar) {
|
||||
a
|
||||
b {
|
||||
c
|
||||
d
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Becomes::
|
||||
|
||||
query SomeQuery($foo:String!$bar:String){someField(foo:$foo bar:$bar){a b{c d}}}
|
||||
|
||||
SDL example::
|
||||
|
||||
"""
|
||||
Type description
|
||||
"""
|
||||
type Foo {
|
||||
"""
|
||||
Field description
|
||||
"""
|
||||
bar: String
|
||||
}
|
||||
|
||||
Becomes::
|
||||
|
||||
"""Type description""" type Foo{"""Field description""" bar:String}
|
||||
'''
|
||||
source = cast(Source, source) if is_source(source) else Source(cast(str, source))
|
||||
|
||||
body = source.body
|
||||
lexer = Lexer(source)
|
||||
stripped_body = ""
|
||||
was_last_added_token_non_punctuator = False
|
||||
while lexer.advance().kind != TokenKind.EOF:
|
||||
current_token = lexer.token
|
||||
token_kind = current_token.kind
|
||||
|
||||
# Every two non-punctuator tokens should have space between them.
|
||||
# Also prevent case of non-punctuator token following by spread resulting
|
||||
# in invalid token (e.g.`1...` is invalid Float token).
|
||||
is_non_punctuator = not is_punctuator_token_kind(current_token.kind)
|
||||
if was_last_added_token_non_punctuator and (
|
||||
is_non_punctuator or current_token.kind == TokenKind.SPREAD
|
||||
):
|
||||
stripped_body += " "
|
||||
|
||||
token_body = body[current_token.start : current_token.end]
|
||||
if token_kind == TokenKind.BLOCK_STRING:
|
||||
stripped_body += print_block_string(
|
||||
current_token.value or "", minimize=True
|
||||
)
|
||||
else:
|
||||
stripped_body += token_body
|
||||
|
||||
was_last_added_token_non_punctuator = is_non_punctuator
|
||||
|
||||
return stripped_body
|
||||
@@ -0,0 +1,131 @@
|
||||
from typing import cast
|
||||
|
||||
from ..type import (
|
||||
GraphQLAbstractType,
|
||||
GraphQLCompositeType,
|
||||
GraphQLList,
|
||||
GraphQLNonNull,
|
||||
GraphQLObjectType,
|
||||
GraphQLSchema,
|
||||
GraphQLType,
|
||||
is_abstract_type,
|
||||
is_interface_type,
|
||||
is_list_type,
|
||||
is_non_null_type,
|
||||
is_object_type,
|
||||
)
|
||||
|
||||
__all__ = ["is_equal_type", "is_type_sub_type_of", "do_types_overlap"]
|
||||
|
||||
|
||||
def is_equal_type(type_a: GraphQLType, type_b: GraphQLType) -> bool:
|
||||
"""Check whether two types are equal.
|
||||
|
||||
Provided two types, return true if the types are equal (invariant)."""
|
||||
# Equivalent types are equal.
|
||||
if type_a is type_b:
|
||||
return True
|
||||
|
||||
# If either type is non-null, the other must also be non-null.
|
||||
if is_non_null_type(type_a) and is_non_null_type(type_b):
|
||||
# noinspection PyUnresolvedReferences
|
||||
return is_equal_type(type_a.of_type, type_b.of_type) # type:ignore
|
||||
|
||||
# If either type is a list, the other must also be a list.
|
||||
if is_list_type(type_a) and is_list_type(type_b):
|
||||
# noinspection PyUnresolvedReferences
|
||||
return is_equal_type(type_a.of_type, type_b.of_type) # type:ignore
|
||||
|
||||
# Otherwise the types are not equal.
|
||||
return False
|
||||
|
||||
|
||||
def is_type_sub_type_of(
|
||||
schema: GraphQLSchema, maybe_subtype: GraphQLType, super_type: GraphQLType
|
||||
) -> bool:
|
||||
"""Check whether a type is subtype of another type in a given schema.
|
||||
|
||||
Provided a type and a super type, return true if the first type is either equal or
|
||||
a subset of the second super type (covariant).
|
||||
"""
|
||||
# Equivalent type is a valid subtype
|
||||
if maybe_subtype is super_type:
|
||||
return True
|
||||
|
||||
# If super_type is non-null, maybe_subtype must also be non-null.
|
||||
if is_non_null_type(super_type):
|
||||
if is_non_null_type(maybe_subtype):
|
||||
return is_type_sub_type_of(
|
||||
schema,
|
||||
cast(GraphQLNonNull, maybe_subtype).of_type,
|
||||
cast(GraphQLNonNull, super_type).of_type,
|
||||
)
|
||||
return False
|
||||
elif is_non_null_type(maybe_subtype):
|
||||
# If super_type is nullable, maybe_subtype may be non-null or nullable.
|
||||
return is_type_sub_type_of(
|
||||
schema, cast(GraphQLNonNull, maybe_subtype).of_type, super_type
|
||||
)
|
||||
|
||||
# If super_type type is a list, maybeSubType type must also be a list.
|
||||
if is_list_type(super_type):
|
||||
if is_list_type(maybe_subtype):
|
||||
return is_type_sub_type_of(
|
||||
schema,
|
||||
cast(GraphQLList, maybe_subtype).of_type,
|
||||
cast(GraphQLList, super_type).of_type,
|
||||
)
|
||||
return False
|
||||
elif is_list_type(maybe_subtype):
|
||||
# If super_type is not a list, maybe_subtype must also be not a list.
|
||||
return False
|
||||
|
||||
# If super_type type is abstract, check if it is super type of maybe_subtype.
|
||||
# Otherwise, the child type is not a valid subtype of the parent type.
|
||||
return (
|
||||
is_abstract_type(super_type)
|
||||
and (is_interface_type(maybe_subtype) or is_object_type(maybe_subtype))
|
||||
and schema.is_sub_type(
|
||||
cast(GraphQLAbstractType, super_type),
|
||||
cast(GraphQLObjectType, maybe_subtype),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def do_types_overlap(
|
||||
schema: GraphQLSchema, type_a: GraphQLCompositeType, type_b: GraphQLCompositeType
|
||||
) -> bool:
|
||||
"""Check whether two types overlap in a given schema.
|
||||
|
||||
Provided two composite types, determine if they "overlap". Two composite types
|
||||
overlap when the Sets of possible concrete types for each intersect.
|
||||
|
||||
This is often used to determine if a fragment of a given type could possibly be
|
||||
visited in a context of another type.
|
||||
|
||||
This function is commutative.
|
||||
"""
|
||||
# Equivalent types overlap
|
||||
if type_a is type_b:
|
||||
return True
|
||||
|
||||
if is_abstract_type(type_a):
|
||||
type_a = cast(GraphQLAbstractType, type_a)
|
||||
if is_abstract_type(type_b):
|
||||
# If both types are abstract, then determine if there is any intersection
|
||||
# between possible concrete types of each.
|
||||
type_b = cast(GraphQLAbstractType, type_b)
|
||||
return any(
|
||||
schema.is_sub_type(type_b, type_)
|
||||
for type_ in schema.get_possible_types(type_a)
|
||||
)
|
||||
# Determine if latter type is a possible concrete type of the former.
|
||||
return schema.is_sub_type(type_a, type_b)
|
||||
|
||||
if is_abstract_type(type_b):
|
||||
# Determine if former type is a possible concrete type of the latter.
|
||||
type_b = cast(GraphQLAbstractType, type_b)
|
||||
return schema.is_sub_type(type_b, type_a)
|
||||
|
||||
# Otherwise the types do not overlap.
|
||||
return False
|
||||
@@ -0,0 +1,65 @@
|
||||
from typing import Optional, cast, overload
|
||||
|
||||
from ..language import ListTypeNode, NamedTypeNode, NonNullTypeNode, TypeNode
|
||||
from ..pyutils import inspect
|
||||
from ..type import (
|
||||
GraphQLList,
|
||||
GraphQLNamedType,
|
||||
GraphQLNonNull,
|
||||
GraphQLNullableType,
|
||||
GraphQLSchema,
|
||||
GraphQLType,
|
||||
)
|
||||
|
||||
__all__ = ["type_from_ast"]
|
||||
|
||||
|
||||
@overload
|
||||
def type_from_ast(
|
||||
schema: GraphQLSchema, type_node: NamedTypeNode
|
||||
) -> Optional[GraphQLNamedType]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def type_from_ast(
|
||||
schema: GraphQLSchema, type_node: ListTypeNode
|
||||
) -> Optional[GraphQLList]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def type_from_ast(
|
||||
schema: GraphQLSchema, type_node: NonNullTypeNode
|
||||
) -> Optional[GraphQLNonNull]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def type_from_ast(
|
||||
schema: GraphQLSchema, type_node: TypeNode
|
||||
) -> Optional[GraphQLType]: ...
|
||||
|
||||
|
||||
def type_from_ast(
|
||||
schema: GraphQLSchema,
|
||||
type_node: TypeNode,
|
||||
) -> Optional[GraphQLType]:
|
||||
"""Get the GraphQL type definition from an AST node.
|
||||
|
||||
Given a Schema and an AST node describing a type, return a GraphQLType definition
|
||||
which applies to that type. For example, if provided the parsed AST node for
|
||||
``[User]``, a GraphQLList instance will be returned, containing the type called
|
||||
"User" found in the schema. If a type called "User" is not found in the schema,
|
||||
then None will be returned.
|
||||
"""
|
||||
inner_type: Optional[GraphQLType]
|
||||
if isinstance(type_node, ListTypeNode):
|
||||
inner_type = type_from_ast(schema, type_node.type)
|
||||
return GraphQLList(inner_type) if inner_type else None
|
||||
if isinstance(type_node, NonNullTypeNode):
|
||||
inner_type = type_from_ast(schema, type_node.type)
|
||||
inner_type = cast(GraphQLNullableType, inner_type)
|
||||
return GraphQLNonNull(inner_type) if inner_type else None
|
||||
if isinstance(type_node, NamedTypeNode):
|
||||
return schema.get_type(type_node.name.value)
|
||||
|
||||
# Not reachable. All possible type nodes have been considered.
|
||||
raise TypeError(f"Unexpected type node: {inspect(type_node)}.")
|
||||
@@ -0,0 +1,321 @@
|
||||
from typing import Any, Callable, List, Optional, Union, cast
|
||||
|
||||
from ..language import (
|
||||
ArgumentNode,
|
||||
DirectiveNode,
|
||||
EnumValueNode,
|
||||
FieldNode,
|
||||
InlineFragmentNode,
|
||||
ListValueNode,
|
||||
Node,
|
||||
ObjectFieldNode,
|
||||
OperationDefinitionNode,
|
||||
SelectionSetNode,
|
||||
VariableDefinitionNode,
|
||||
Visitor,
|
||||
)
|
||||
from ..pyutils import Undefined
|
||||
from ..type import (
|
||||
GraphQLArgument,
|
||||
GraphQLCompositeType,
|
||||
GraphQLDirective,
|
||||
GraphQLEnumType,
|
||||
GraphQLEnumValue,
|
||||
GraphQLField,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInputType,
|
||||
GraphQLInterfaceType,
|
||||
GraphQLList,
|
||||
GraphQLObjectType,
|
||||
GraphQLOutputType,
|
||||
GraphQLSchema,
|
||||
GraphQLType,
|
||||
is_composite_type,
|
||||
is_input_type,
|
||||
is_output_type,
|
||||
get_named_type,
|
||||
SchemaMetaFieldDef,
|
||||
TypeMetaFieldDef,
|
||||
TypeNameMetaFieldDef,
|
||||
is_object_type,
|
||||
is_interface_type,
|
||||
get_nullable_type,
|
||||
is_list_type,
|
||||
is_input_object_type,
|
||||
is_enum_type,
|
||||
)
|
||||
from .type_from_ast import type_from_ast
|
||||
|
||||
__all__ = ["TypeInfo", "TypeInfoVisitor"]
|
||||
|
||||
|
||||
GetFieldDefFn = Callable[
|
||||
[GraphQLSchema, GraphQLType, FieldNode], Optional[GraphQLField]
|
||||
]
|
||||
|
||||
|
||||
class TypeInfo:
|
||||
"""Utility class for keeping track of type definitions.
|
||||
|
||||
TypeInfo is a utility class which, given a GraphQL schema, can keep track of the
|
||||
current field and type definitions at any point in a GraphQL document AST during
|
||||
a recursive descent by calling :meth:`enter(node) <.TypeInfo.enter>` and
|
||||
:meth:`leave(node) <.TypeInfo.leave>`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
schema: GraphQLSchema,
|
||||
initial_type: Optional[GraphQLType] = None,
|
||||
get_field_def_fn: Optional[GetFieldDefFn] = None,
|
||||
) -> None:
|
||||
"""Initialize the TypeInfo for the given GraphQL schema.
|
||||
|
||||
Initial type may be provided in rare cases to facilitate traversals beginning
|
||||
somewhere other than documents.
|
||||
|
||||
The optional last parameter is deprecated and will be removed in v3.3.
|
||||
"""
|
||||
self._schema = schema
|
||||
self._type_stack: List[Optional[GraphQLOutputType]] = []
|
||||
self._parent_type_stack: List[Optional[GraphQLCompositeType]] = []
|
||||
self._input_type_stack: List[Optional[GraphQLInputType]] = []
|
||||
self._field_def_stack: List[Optional[GraphQLField]] = []
|
||||
self._default_value_stack: List[Any] = []
|
||||
self._directive: Optional[GraphQLDirective] = None
|
||||
self._argument: Optional[GraphQLArgument] = None
|
||||
self._enum_value: Optional[GraphQLEnumValue] = None
|
||||
self._get_field_def: GetFieldDefFn = get_field_def_fn or get_field_def
|
||||
if initial_type:
|
||||
if is_input_type(initial_type):
|
||||
self._input_type_stack.append(cast(GraphQLInputType, initial_type))
|
||||
if is_composite_type(initial_type):
|
||||
self._parent_type_stack.append(cast(GraphQLCompositeType, initial_type))
|
||||
if is_output_type(initial_type):
|
||||
self._type_stack.append(cast(GraphQLOutputType, initial_type))
|
||||
|
||||
def get_type(self) -> Optional[GraphQLOutputType]:
|
||||
if self._type_stack:
|
||||
return self._type_stack[-1]
|
||||
return None
|
||||
|
||||
def get_parent_type(self) -> Optional[GraphQLCompositeType]:
|
||||
if self._parent_type_stack:
|
||||
return self._parent_type_stack[-1]
|
||||
return None
|
||||
|
||||
def get_input_type(self) -> Optional[GraphQLInputType]:
|
||||
if self._input_type_stack:
|
||||
return self._input_type_stack[-1]
|
||||
return None
|
||||
|
||||
def get_parent_input_type(self) -> Optional[GraphQLInputType]:
|
||||
if len(self._input_type_stack) > 1:
|
||||
return self._input_type_stack[-2]
|
||||
return None
|
||||
|
||||
def get_field_def(self) -> Optional[GraphQLField]:
|
||||
if self._field_def_stack:
|
||||
return self._field_def_stack[-1]
|
||||
return None
|
||||
|
||||
def get_default_value(self) -> Any:
|
||||
if self._default_value_stack:
|
||||
return self._default_value_stack[-1]
|
||||
return None
|
||||
|
||||
def get_directive(self) -> Optional[GraphQLDirective]:
|
||||
return self._directive
|
||||
|
||||
def get_argument(self) -> Optional[GraphQLArgument]:
|
||||
return self._argument
|
||||
|
||||
def get_enum_value(self) -> Optional[GraphQLEnumValue]:
|
||||
return self._enum_value
|
||||
|
||||
def enter(self, node: Node) -> None:
|
||||
method = getattr(self, "enter_" + node.kind, None)
|
||||
if method:
|
||||
method(node)
|
||||
|
||||
def leave(self, node: Node) -> None:
|
||||
method = getattr(self, "leave_" + node.kind, None)
|
||||
if method:
|
||||
method()
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def enter_selection_set(self, node: SelectionSetNode) -> None:
|
||||
named_type = get_named_type(self.get_type())
|
||||
self._parent_type_stack.append(
|
||||
cast(GraphQLCompositeType, named_type)
|
||||
if is_composite_type(named_type)
|
||||
else None
|
||||
)
|
||||
|
||||
def enter_field(self, node: FieldNode) -> None:
|
||||
parent_type = self.get_parent_type()
|
||||
if parent_type:
|
||||
field_def = self._get_field_def(self._schema, parent_type, node)
|
||||
field_type = field_def.type if field_def else None
|
||||
else:
|
||||
field_def = field_type = None
|
||||
self._field_def_stack.append(field_def)
|
||||
self._type_stack.append(field_type if is_output_type(field_type) else None)
|
||||
|
||||
def enter_directive(self, node: DirectiveNode) -> None:
|
||||
self._directive = self._schema.get_directive(node.name.value)
|
||||
|
||||
def enter_operation_definition(self, node: OperationDefinitionNode) -> None:
|
||||
root_type = self._schema.get_root_type(node.operation)
|
||||
self._type_stack.append(root_type if is_object_type(root_type) else None)
|
||||
|
||||
def enter_inline_fragment(self, node: InlineFragmentNode) -> None:
|
||||
type_condition_ast = node.type_condition
|
||||
output_type = (
|
||||
type_from_ast(self._schema, type_condition_ast)
|
||||
if type_condition_ast
|
||||
else get_named_type(self.get_type())
|
||||
)
|
||||
self._type_stack.append(
|
||||
cast(GraphQLOutputType, output_type)
|
||||
if is_output_type(output_type)
|
||||
else None
|
||||
)
|
||||
|
||||
enter_fragment_definition = enter_inline_fragment
|
||||
|
||||
def enter_variable_definition(self, node: VariableDefinitionNode) -> None:
|
||||
input_type = type_from_ast(self._schema, node.type)
|
||||
self._input_type_stack.append(
|
||||
cast(GraphQLInputType, input_type) if is_input_type(input_type) else None
|
||||
)
|
||||
|
||||
def enter_argument(self, node: ArgumentNode) -> None:
|
||||
field_or_directive = self.get_directive() or self.get_field_def()
|
||||
if field_or_directive:
|
||||
arg_def = field_or_directive.args.get(node.name.value)
|
||||
arg_type = arg_def.type if arg_def else None
|
||||
else:
|
||||
arg_def = arg_type = None
|
||||
self._argument = arg_def
|
||||
self._default_value_stack.append(
|
||||
arg_def.default_value if arg_def else Undefined
|
||||
)
|
||||
self._input_type_stack.append(arg_type if is_input_type(arg_type) else None)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def enter_list_value(self, node: ListValueNode) -> None:
|
||||
list_type = get_nullable_type(self.get_input_type()) # type: ignore
|
||||
item_type = (
|
||||
cast(GraphQLList, list_type).of_type
|
||||
if is_list_type(list_type)
|
||||
else list_type
|
||||
)
|
||||
# List positions never have a default value.
|
||||
self._default_value_stack.append(Undefined)
|
||||
self._input_type_stack.append(item_type if is_input_type(item_type) else None)
|
||||
|
||||
def enter_object_field(self, node: ObjectFieldNode) -> None:
|
||||
object_type = get_named_type(self.get_input_type())
|
||||
if is_input_object_type(object_type):
|
||||
input_field = cast(GraphQLInputObjectType, object_type).fields.get(
|
||||
node.name.value
|
||||
)
|
||||
input_field_type = input_field.type if input_field else None
|
||||
else:
|
||||
input_field = input_field_type = None
|
||||
self._default_value_stack.append(
|
||||
input_field.default_value if input_field else Undefined
|
||||
)
|
||||
self._input_type_stack.append(
|
||||
input_field_type if is_input_type(input_field_type) else None
|
||||
)
|
||||
|
||||
def enter_enum_value(self, node: EnumValueNode) -> None:
|
||||
enum_type = get_named_type(self.get_input_type())
|
||||
if is_enum_type(enum_type):
|
||||
enum_value = cast(GraphQLEnumType, enum_type).values.get(node.value)
|
||||
else:
|
||||
enum_value = None
|
||||
self._enum_value = enum_value
|
||||
|
||||
def leave_selection_set(self) -> None:
|
||||
del self._parent_type_stack[-1:]
|
||||
|
||||
def leave_field(self) -> None:
|
||||
del self._field_def_stack[-1:]
|
||||
del self._type_stack[-1:]
|
||||
|
||||
def leave_directive(self) -> None:
|
||||
self._directive = None
|
||||
|
||||
def leave_operation_definition(self) -> None:
|
||||
del self._type_stack[-1:]
|
||||
|
||||
leave_inline_fragment = leave_operation_definition
|
||||
leave_fragment_definition = leave_operation_definition
|
||||
|
||||
def leave_variable_definition(self) -> None:
|
||||
del self._input_type_stack[-1:]
|
||||
|
||||
def leave_argument(self) -> None:
|
||||
self._argument = None
|
||||
del self._default_value_stack[-1:]
|
||||
del self._input_type_stack[-1:]
|
||||
|
||||
def leave_list_value(self) -> None:
|
||||
del self._default_value_stack[-1:]
|
||||
del self._input_type_stack[-1:]
|
||||
|
||||
leave_object_field = leave_list_value
|
||||
|
||||
def leave_enum_value(self) -> None:
|
||||
self._enum_value = None
|
||||
|
||||
|
||||
def get_field_def(
|
||||
schema: GraphQLSchema, parent_type: GraphQLType, field_node: FieldNode
|
||||
) -> Optional[GraphQLField]:
|
||||
"""Get field definition.
|
||||
|
||||
Not exactly the same as the executor's definition of
|
||||
:func:`graphql.execution.get_field_def`, in this statically evaluated environment
|
||||
we do not always have an Object type, and need to handle Interface and Union types.
|
||||
"""
|
||||
name = field_node.name.value
|
||||
if name == "__schema" and schema.query_type is parent_type:
|
||||
return SchemaMetaFieldDef
|
||||
if name == "__type" and schema.query_type is parent_type:
|
||||
return TypeMetaFieldDef
|
||||
if name == "__typename" and is_composite_type(parent_type):
|
||||
return TypeNameMetaFieldDef
|
||||
if is_object_type(parent_type) or is_interface_type(parent_type):
|
||||
parent_type = cast(Union[GraphQLObjectType, GraphQLInterfaceType], parent_type)
|
||||
return parent_type.fields.get(name)
|
||||
return None
|
||||
|
||||
|
||||
class TypeInfoVisitor(Visitor):
|
||||
"""A visitor which maintains a provided TypeInfo."""
|
||||
|
||||
def __init__(self, type_info: "TypeInfo", visitor: Visitor):
|
||||
super().__init__()
|
||||
self.type_info = type_info
|
||||
self.visitor = visitor
|
||||
|
||||
def enter(self, node: Node, *args: Any) -> Any:
|
||||
self.type_info.enter(node)
|
||||
fn = self.visitor.get_enter_leave_for_kind(node.kind).enter
|
||||
if fn:
|
||||
result = fn(node, *args)
|
||||
if result is not None:
|
||||
self.type_info.leave(node)
|
||||
if isinstance(result, Node):
|
||||
self.type_info.enter(result)
|
||||
return result
|
||||
|
||||
def leave(self, node: Node, *args: Any) -> Any:
|
||||
fn = self.visitor.get_enter_leave_for_kind(node.kind).leave
|
||||
result = fn(node, *args) if fn else None
|
||||
self.type_info.leave(node)
|
||||
return result
|
||||
@@ -0,0 +1,149 @@
|
||||
from typing import Any, Dict, List, Optional, cast
|
||||
|
||||
from ..language import (
|
||||
ListValueNode,
|
||||
NullValueNode,
|
||||
ObjectValueNode,
|
||||
ValueNode,
|
||||
VariableNode,
|
||||
)
|
||||
from ..pyutils import inspect, Undefined
|
||||
from ..type import (
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInputType,
|
||||
GraphQLList,
|
||||
GraphQLNonNull,
|
||||
GraphQLScalarType,
|
||||
is_input_object_type,
|
||||
is_leaf_type,
|
||||
is_list_type,
|
||||
is_non_null_type,
|
||||
)
|
||||
|
||||
__all__ = ["value_from_ast"]
|
||||
|
||||
|
||||
def value_from_ast(
|
||||
value_node: Optional[ValueNode],
|
||||
type_: GraphQLInputType,
|
||||
variables: Optional[Dict[str, Any]] = None,
|
||||
) -> Any:
|
||||
"""Produce a Python value given a GraphQL Value AST.
|
||||
|
||||
A GraphQL type must be provided, which will be used to interpret different GraphQL
|
||||
Value literals.
|
||||
|
||||
Returns ``Undefined`` when the value could not be validly coerced according
|
||||
to the provided type.
|
||||
|
||||
=================== ============== ================
|
||||
GraphQL Value JSON Value Python Value
|
||||
=================== ============== ================
|
||||
Input Object Object dict
|
||||
List Array list
|
||||
Boolean Boolean bool
|
||||
String String str
|
||||
Int / Float Number int / float
|
||||
Enum Value Mixed Any
|
||||
NullValue null None
|
||||
=================== ============== ================
|
||||
|
||||
"""
|
||||
if not value_node:
|
||||
# When there is no node, then there is also no value.
|
||||
# Importantly, this is different from returning the value null.
|
||||
return Undefined
|
||||
|
||||
if isinstance(value_node, VariableNode):
|
||||
variable_name = value_node.name.value
|
||||
if not variables:
|
||||
return Undefined
|
||||
variable_value = variables.get(variable_name, Undefined)
|
||||
if variable_value is None and is_non_null_type(type_):
|
||||
return Undefined
|
||||
# Note: This does no further checking that this variable is correct.
|
||||
# This assumes that this query has been validated and the variable usage here
|
||||
# is of the correct type.
|
||||
return variable_value
|
||||
|
||||
if is_non_null_type(type_):
|
||||
if isinstance(value_node, NullValueNode):
|
||||
return Undefined
|
||||
type_ = cast(GraphQLNonNull, type_)
|
||||
return value_from_ast(value_node, type_.of_type, variables)
|
||||
|
||||
if isinstance(value_node, NullValueNode):
|
||||
return None # This is explicitly returning the value None.
|
||||
|
||||
if is_list_type(type_):
|
||||
type_ = cast(GraphQLList, type_)
|
||||
item_type = type_.of_type
|
||||
if isinstance(value_node, ListValueNode):
|
||||
coerced_values: List[Any] = []
|
||||
append_value = coerced_values.append
|
||||
for item_node in value_node.values:
|
||||
if is_missing_variable(item_node, variables):
|
||||
# If an array contains a missing variable, it is either coerced to
|
||||
# None or if the item type is non-null, it is considered invalid.
|
||||
if is_non_null_type(item_type):
|
||||
return Undefined
|
||||
append_value(None)
|
||||
else:
|
||||
item_value = value_from_ast(item_node, item_type, variables)
|
||||
if item_value is Undefined:
|
||||
return Undefined
|
||||
append_value(item_value)
|
||||
return coerced_values
|
||||
coerced_value = value_from_ast(value_node, item_type, variables)
|
||||
if coerced_value is Undefined:
|
||||
return Undefined
|
||||
return [coerced_value]
|
||||
|
||||
if is_input_object_type(type_):
|
||||
if not isinstance(value_node, ObjectValueNode):
|
||||
return Undefined
|
||||
type_ = cast(GraphQLInputObjectType, type_)
|
||||
coerced_obj: Dict[str, Any] = {}
|
||||
fields = type_.fields
|
||||
field_nodes = {field.name.value: field for field in value_node.fields}
|
||||
for field_name, field in fields.items():
|
||||
field_node = field_nodes.get(field_name)
|
||||
if not field_node or is_missing_variable(field_node.value, variables):
|
||||
if field.default_value is not Undefined:
|
||||
# Use out name as name if it exists (extension of GraphQL.js).
|
||||
coerced_obj[field.out_name or field_name] = field.default_value
|
||||
elif is_non_null_type(field.type): # pragma: no cover else
|
||||
return Undefined
|
||||
continue
|
||||
field_value = value_from_ast(field_node.value, field.type, variables)
|
||||
if field_value is Undefined:
|
||||
return Undefined
|
||||
coerced_obj[field.out_name or field_name] = field_value
|
||||
|
||||
return type_.out_type(coerced_obj)
|
||||
|
||||
if is_leaf_type(type_):
|
||||
# Scalars fulfill parsing a literal value via `parse_literal()`. Invalid values
|
||||
# represent a failure to parse correctly, in which case Undefined is returned.
|
||||
type_ = cast(GraphQLScalarType, type_)
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
if variables:
|
||||
result = type_.parse_literal(value_node, variables)
|
||||
else:
|
||||
result = type_.parse_literal(value_node)
|
||||
except Exception:
|
||||
return Undefined
|
||||
return result
|
||||
|
||||
# Not reachable. All possible input types have been considered.
|
||||
raise TypeError(f"Unexpected input type: {inspect(type_)}.")
|
||||
|
||||
|
||||
def is_missing_variable(
|
||||
value_node: ValueNode, variables: Optional[Dict[str, Any]] = None
|
||||
) -> bool:
|
||||
"""Check if ``value_node`` is a variable not defined in the ``variables`` dict."""
|
||||
return isinstance(value_node, VariableNode) and (
|
||||
not variables or variables.get(value_node.name.value, Undefined) is Undefined
|
||||
)
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
from math import nan
|
||||
from typing import Any, Callable, Dict, Optional, Union
|
||||
|
||||
from ..language import (
|
||||
ValueNode,
|
||||
BooleanValueNode,
|
||||
EnumValueNode,
|
||||
FloatValueNode,
|
||||
IntValueNode,
|
||||
ListValueNode,
|
||||
NullValueNode,
|
||||
ObjectValueNode,
|
||||
StringValueNode,
|
||||
VariableNode,
|
||||
)
|
||||
|
||||
from ..pyutils import inspect, Undefined
|
||||
|
||||
__all__ = ["value_from_ast_untyped"]
|
||||
|
||||
|
||||
def value_from_ast_untyped(
|
||||
value_node: ValueNode, variables: Optional[Dict[str, Any]] = None
|
||||
) -> Any:
|
||||
"""Produce a Python value given a GraphQL Value AST.
|
||||
|
||||
Unlike :func:`~graphql.utilities.value_from_ast`, no type is provided.
|
||||
The resulting Python value will reflect the provided GraphQL value AST.
|
||||
|
||||
=================== ============== ================
|
||||
GraphQL Value JSON Value Python Value
|
||||
=================== ============== ================
|
||||
Input Object Object dict
|
||||
List Array list
|
||||
Boolean Boolean bool
|
||||
String / Enum String str
|
||||
Int / Float Number int / float
|
||||
Null null None
|
||||
=================== ============== ================
|
||||
|
||||
"""
|
||||
func = _value_from_kind_functions.get(value_node.kind)
|
||||
if func:
|
||||
return func(value_node, variables)
|
||||
|
||||
# Not reachable. All possible value nodes have been considered.
|
||||
raise TypeError( # pragma: no cover
|
||||
f"Unexpected value node: {inspect(value_node)}."
|
||||
)
|
||||
|
||||
|
||||
def value_from_null(_value_node: NullValueNode, _variables: Any) -> Any:
|
||||
return None
|
||||
|
||||
|
||||
def value_from_int(value_node: IntValueNode, _variables: Any) -> Any:
|
||||
try:
|
||||
return int(value_node.value)
|
||||
except ValueError:
|
||||
return nan
|
||||
|
||||
|
||||
def value_from_float(value_node: FloatValueNode, _variables: Any) -> Any:
|
||||
try:
|
||||
return float(value_node.value)
|
||||
except ValueError:
|
||||
return nan
|
||||
|
||||
|
||||
def value_from_string(
|
||||
value_node: Union[BooleanValueNode, EnumValueNode, StringValueNode], _variables: Any
|
||||
) -> Any:
|
||||
return value_node.value
|
||||
|
||||
|
||||
def value_from_list(
|
||||
value_node: ListValueNode, variables: Optional[Dict[str, Any]]
|
||||
) -> Any:
|
||||
return [value_from_ast_untyped(node, variables) for node in value_node.values]
|
||||
|
||||
|
||||
def value_from_object(
|
||||
value_node: ObjectValueNode, variables: Optional[Dict[str, Any]]
|
||||
) -> Any:
|
||||
return {
|
||||
field.name.value: value_from_ast_untyped(field.value, variables)
|
||||
for field in value_node.fields
|
||||
}
|
||||
|
||||
|
||||
def value_from_variable(
|
||||
value_node: VariableNode, variables: Optional[Dict[str, Any]]
|
||||
) -> Any:
|
||||
variable_name = value_node.name.value
|
||||
if not variables:
|
||||
return Undefined
|
||||
return variables.get(variable_name, Undefined)
|
||||
|
||||
|
||||
_value_from_kind_functions: Dict[str, Callable] = {
|
||||
"null_value": value_from_null,
|
||||
"int_value": value_from_int,
|
||||
"float_value": value_from_float,
|
||||
"string_value": value_from_string,
|
||||
"enum_value": value_from_string,
|
||||
"boolean_value": value_from_string,
|
||||
"list_value": value_from_list,
|
||||
"object_value": value_from_object,
|
||||
"variable": value_from_variable,
|
||||
}
|
||||
Reference in New Issue
Block a user