2025-12-01
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user