299 lines
8.9 KiB
Python
299 lines
8.9 KiB
Python
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
|