forked from STI/Astroport.ONE
322 lines
10 KiB
Python
322 lines
10 KiB
Python
from math import isfinite
|
|
from typing import Any, Mapping
|
|
|
|
from ..error import GraphQLError
|
|
from ..pyutils import inspect
|
|
from ..language.ast import (
|
|
BooleanValueNode,
|
|
FloatValueNode,
|
|
IntValueNode,
|
|
StringValueNode,
|
|
ValueNode,
|
|
)
|
|
from ..language.printer import print_ast
|
|
from .definition import GraphQLNamedType, GraphQLScalarType
|
|
|
|
__all__ = [
|
|
"is_specified_scalar_type",
|
|
"specified_scalar_types",
|
|
"GraphQLInt",
|
|
"GraphQLFloat",
|
|
"GraphQLString",
|
|
"GraphQLBoolean",
|
|
"GraphQLID",
|
|
"GRAPHQL_MAX_INT",
|
|
"GRAPHQL_MIN_INT",
|
|
]
|
|
|
|
# As per the GraphQL Spec, Integers are only treated as valid
|
|
# when they can be represented as a 32-bit signed integer,
|
|
# providing the broadest support across platforms.
|
|
# n.b. JavaScript's numbers are safe between -(2^53 - 1) and 2^53 - 1
|
|
# because they are internally represented as IEEE 754 doubles,
|
|
# while Python's integers may be arbitrarily large.
|
|
|
|
GRAPHQL_MAX_INT = 2_147_483_647
|
|
"""Maximum possible Int value as per GraphQL Spec (32-bit signed integer)"""
|
|
|
|
GRAPHQL_MIN_INT = -2_147_483_648
|
|
"""Minimum possible Int value as per GraphQL Spec (32-bit signed integer)"""
|
|
|
|
|
|
def serialize_int(output_value: Any) -> int:
|
|
if isinstance(output_value, bool):
|
|
return 1 if output_value else 0
|
|
try:
|
|
if isinstance(output_value, int):
|
|
num = output_value
|
|
elif isinstance(output_value, float):
|
|
num = int(output_value)
|
|
if num != output_value:
|
|
raise ValueError
|
|
elif not output_value and isinstance(output_value, str):
|
|
output_value = ""
|
|
raise ValueError
|
|
else:
|
|
num = int(output_value) # raises ValueError if not an integer
|
|
except (OverflowError, ValueError, TypeError):
|
|
raise GraphQLError(
|
|
"Int cannot represent non-integer value: " + inspect(output_value)
|
|
)
|
|
if not GRAPHQL_MIN_INT <= num <= GRAPHQL_MAX_INT:
|
|
raise GraphQLError(
|
|
"Int cannot represent non 32-bit signed integer value: "
|
|
+ inspect(output_value)
|
|
)
|
|
return num
|
|
|
|
|
|
def coerce_int(input_value: Any) -> int:
|
|
if not (
|
|
isinstance(input_value, int) and not isinstance(input_value, bool)
|
|
) and not (
|
|
isinstance(input_value, float)
|
|
and isfinite(input_value)
|
|
and int(input_value) == input_value
|
|
):
|
|
raise GraphQLError(
|
|
"Int cannot represent non-integer value: " + inspect(input_value)
|
|
)
|
|
if not GRAPHQL_MIN_INT <= input_value <= GRAPHQL_MAX_INT:
|
|
raise GraphQLError(
|
|
"Int cannot represent non 32-bit signed integer value: "
|
|
+ inspect(input_value)
|
|
)
|
|
return int(input_value)
|
|
|
|
|
|
def parse_int_literal(value_node: ValueNode, _variables: Any = None) -> int:
|
|
"""Parse an integer value node in the AST."""
|
|
if not isinstance(value_node, IntValueNode):
|
|
raise GraphQLError(
|
|
"Int cannot represent non-integer value: " + print_ast(value_node),
|
|
value_node,
|
|
)
|
|
num = int(value_node.value)
|
|
if not GRAPHQL_MIN_INT <= num <= GRAPHQL_MAX_INT:
|
|
raise GraphQLError(
|
|
"Int cannot represent non 32-bit signed integer value: "
|
|
+ print_ast(value_node),
|
|
value_node,
|
|
)
|
|
return num
|
|
|
|
|
|
GraphQLInt = GraphQLScalarType(
|
|
name="Int",
|
|
description="The `Int` scalar type represents"
|
|
" non-fractional signed whole numeric values."
|
|
" Int can represent values between -(2^31) and 2^31 - 1.",
|
|
serialize=serialize_int,
|
|
parse_value=coerce_int,
|
|
parse_literal=parse_int_literal,
|
|
)
|
|
|
|
|
|
def serialize_float(output_value: Any) -> float:
|
|
if isinstance(output_value, bool):
|
|
return 1 if output_value else 0
|
|
try:
|
|
if not output_value and isinstance(output_value, str):
|
|
output_value = ""
|
|
raise ValueError
|
|
num = output_value if isinstance(output_value, float) else float(output_value)
|
|
if not isfinite(num):
|
|
raise ValueError
|
|
except (ValueError, TypeError):
|
|
raise GraphQLError(
|
|
"Float cannot represent non numeric value: " + inspect(output_value)
|
|
)
|
|
return num
|
|
|
|
|
|
def coerce_float(input_value: Any) -> float:
|
|
if not (
|
|
isinstance(input_value, int) and not isinstance(input_value, bool)
|
|
) and not (isinstance(input_value, float) and isfinite(input_value)):
|
|
raise GraphQLError(
|
|
"Float cannot represent non numeric value: " + inspect(input_value)
|
|
)
|
|
return float(input_value)
|
|
|
|
|
|
def parse_float_literal(value_node: ValueNode, _variables: Any = None) -> float:
|
|
"""Parse a float value node in the AST."""
|
|
if not isinstance(value_node, (FloatValueNode, IntValueNode)):
|
|
raise GraphQLError(
|
|
"Float cannot represent non numeric value: " + print_ast(value_node),
|
|
value_node,
|
|
)
|
|
return float(value_node.value)
|
|
|
|
|
|
GraphQLFloat = GraphQLScalarType(
|
|
name="Float",
|
|
description="The `Float` scalar type represents"
|
|
" signed double-precision fractional values"
|
|
" as specified by [IEEE 754]"
|
|
"(https://en.wikipedia.org/wiki/IEEE_floating_point).",
|
|
serialize=serialize_float,
|
|
parse_value=coerce_float,
|
|
parse_literal=parse_float_literal,
|
|
)
|
|
|
|
|
|
def serialize_string(output_value: Any) -> str:
|
|
if isinstance(output_value, str):
|
|
return output_value
|
|
if isinstance(output_value, bool):
|
|
return "true" if output_value else "false"
|
|
if isinstance(output_value, int) or (
|
|
isinstance(output_value, float) and isfinite(output_value)
|
|
):
|
|
return str(output_value)
|
|
# do not serialize builtin types as strings, but allow serialization of custom
|
|
# types via their `__str__` method
|
|
if type(output_value).__module__ == "builtins":
|
|
raise GraphQLError("String cannot represent value: " + inspect(output_value))
|
|
return str(output_value)
|
|
|
|
|
|
def coerce_string(input_value: Any) -> str:
|
|
if not isinstance(input_value, str):
|
|
raise GraphQLError(
|
|
"String cannot represent a non string value: " + inspect(input_value)
|
|
)
|
|
return input_value
|
|
|
|
|
|
def parse_string_literal(value_node: ValueNode, _variables: Any = None) -> str:
|
|
"""Parse a string value node in the AST."""
|
|
if not isinstance(value_node, StringValueNode):
|
|
raise GraphQLError(
|
|
"String cannot represent a non string value: " + print_ast(value_node),
|
|
value_node,
|
|
)
|
|
return value_node.value
|
|
|
|
|
|
GraphQLString = GraphQLScalarType(
|
|
name="String",
|
|
description="The `String` scalar type represents textual data,"
|
|
" represented as UTF-8 character sequences."
|
|
" The String type is most often used by GraphQL"
|
|
" to represent free-form human-readable text.",
|
|
serialize=serialize_string,
|
|
parse_value=coerce_string,
|
|
parse_literal=parse_string_literal,
|
|
)
|
|
|
|
|
|
def serialize_boolean(output_value: Any) -> bool:
|
|
if isinstance(output_value, bool):
|
|
return output_value
|
|
if isinstance(output_value, int) or (
|
|
isinstance(output_value, float) and isfinite(output_value)
|
|
):
|
|
return bool(output_value)
|
|
raise GraphQLError(
|
|
"Boolean cannot represent a non boolean value: " + inspect(output_value)
|
|
)
|
|
|
|
|
|
def coerce_boolean(input_value: Any) -> bool:
|
|
if not isinstance(input_value, bool):
|
|
raise GraphQLError(
|
|
"Boolean cannot represent a non boolean value: " + inspect(input_value)
|
|
)
|
|
return input_value
|
|
|
|
|
|
def parse_boolean_literal(value_node: ValueNode, _variables: Any = None) -> bool:
|
|
"""Parse a boolean value node in the AST."""
|
|
if not isinstance(value_node, BooleanValueNode):
|
|
raise GraphQLError(
|
|
"Boolean cannot represent a non boolean value: " + print_ast(value_node),
|
|
value_node,
|
|
)
|
|
return value_node.value
|
|
|
|
|
|
GraphQLBoolean = GraphQLScalarType(
|
|
name="Boolean",
|
|
description="The `Boolean` scalar type represents `true` or `false`.",
|
|
serialize=serialize_boolean,
|
|
parse_value=coerce_boolean,
|
|
parse_literal=parse_boolean_literal,
|
|
)
|
|
|
|
|
|
def serialize_id(output_value: Any) -> str:
|
|
if isinstance(output_value, str):
|
|
return output_value
|
|
if isinstance(output_value, int) and not isinstance(output_value, bool):
|
|
return str(output_value)
|
|
if (
|
|
isinstance(output_value, float)
|
|
and isfinite(output_value)
|
|
and int(output_value) == output_value
|
|
):
|
|
return str(int(output_value))
|
|
# do not serialize builtin types as IDs, but allow serialization of custom types
|
|
# via their `__str__` method
|
|
if type(output_value).__module__ == "builtins":
|
|
raise GraphQLError("ID cannot represent value: " + inspect(output_value))
|
|
return str(output_value)
|
|
|
|
|
|
def coerce_id(input_value: Any) -> str:
|
|
if isinstance(input_value, str):
|
|
return input_value
|
|
if isinstance(input_value, int) and not isinstance(input_value, bool):
|
|
return str(input_value)
|
|
if (
|
|
isinstance(input_value, float)
|
|
and isfinite(input_value)
|
|
and int(input_value) == input_value
|
|
):
|
|
return str(int(input_value))
|
|
raise GraphQLError("ID cannot represent value: " + inspect(input_value))
|
|
|
|
|
|
def parse_id_literal(value_node: ValueNode, _variables: Any = None) -> str:
|
|
"""Parse an ID value node in the AST."""
|
|
if not isinstance(value_node, (StringValueNode, IntValueNode)):
|
|
raise GraphQLError(
|
|
"ID cannot represent a non-string and non-integer value: "
|
|
+ print_ast(value_node),
|
|
value_node,
|
|
)
|
|
return value_node.value
|
|
|
|
|
|
GraphQLID = GraphQLScalarType(
|
|
name="ID",
|
|
description="The `ID` scalar type represents a unique identifier,"
|
|
" often used to refetch an object or as key for a cache."
|
|
" The ID type appears in a JSON response as a String; however,"
|
|
" it is not intended to be human-readable. When expected as an"
|
|
' input type, any string (such as `"4"`) or integer (such as'
|
|
" `4`) input value will be accepted as an ID.",
|
|
serialize=serialize_id,
|
|
parse_value=coerce_id,
|
|
parse_literal=parse_id_literal,
|
|
)
|
|
|
|
|
|
specified_scalar_types: Mapping[str, GraphQLScalarType] = {
|
|
type_.name: type_
|
|
for type_ in (
|
|
GraphQLString,
|
|
GraphQLInt,
|
|
GraphQLFloat,
|
|
GraphQLBoolean,
|
|
GraphQLID,
|
|
)
|
|
}
|
|
|
|
|
|
def is_specified_scalar_type(type_: GraphQLNamedType) -> bool:
|
|
"""Check whether the given named GraphQL type is a specified scalar type."""
|
|
return type_.name in specified_scalar_types
|