1from math import isfinite 2from typing import Any 3 4from ..error import GraphQLError 5from ..pyutils import inspect, is_finite, is_integer, FrozenDict 6from ..language.ast import ( 7 BooleanValueNode, 8 FloatValueNode, 9 IntValueNode, 10 StringValueNode, 11 ValueNode, 12) 13from ..language.printer import print_ast 14from .definition import GraphQLNamedType, GraphQLScalarType 15 16__all__ = [ 17 "is_specified_scalar_type", 18 "specified_scalar_types", 19 "GraphQLInt", 20 "GraphQLFloat", 21 "GraphQLString", 22 "GraphQLBoolean", 23 "GraphQLID", 24] 25 26 27# As per the GraphQL Spec, Integers are only treated as valid when a valid 28# 32-bit signed integer, providing the broadest support across platforms. 29# 30# n.b. JavaScript's integers are safe between -(2^53 - 1) and 2^53 - 1 because 31# they are internally represented as IEEE 754 doubles, 32# while Python's integers may be arbitrarily large. 33MAX_INT = 2_147_483_647 34MIN_INT = -2_147_483_648 35 36 37def serialize_int(output_value: Any) -> int: 38 if isinstance(output_value, bool): 39 return 1 if output_value else 0 40 try: 41 if isinstance(output_value, int): 42 num = output_value 43 elif isinstance(output_value, float): 44 num = int(output_value) 45 if num != output_value: 46 raise ValueError 47 elif not output_value and isinstance(output_value, str): 48 output_value = "" 49 raise ValueError 50 else: 51 num = int(output_value) # raises ValueError if not an integer 52 except (OverflowError, ValueError, TypeError): 53 raise GraphQLError( 54 "Int cannot represent non-integer value: " + inspect(output_value) 55 ) 56 if not MIN_INT <= num <= MAX_INT: 57 raise GraphQLError( 58 "Int cannot represent non 32-bit signed integer value: " 59 + inspect(output_value) 60 ) 61 return num 62 63 64def coerce_int(input_value: Any) -> int: 65 if not is_integer(input_value): 66 raise GraphQLError( 67 "Int cannot represent non-integer value: " + inspect(input_value) 68 ) 69 if not MIN_INT <= input_value <= MAX_INT: 70 raise GraphQLError( 71 "Int cannot represent non 32-bit signed integer value: " 72 + inspect(input_value) 73 ) 74 return int(input_value) 75 76 77def parse_int_literal(value_node: ValueNode, _variables: Any = None) -> int: 78 """Parse an integer value node in the AST.""" 79 if not isinstance(value_node, IntValueNode): 80 raise GraphQLError( 81 "Int cannot represent non-integer value: " + print_ast(value_node), 82 value_node, 83 ) 84 num = int(value_node.value) 85 if not MIN_INT <= num <= MAX_INT: 86 raise GraphQLError( 87 "Int cannot represent non 32-bit signed integer value: " 88 + print_ast(value_node), 89 value_node, 90 ) 91 return num 92 93 94GraphQLInt = GraphQLScalarType( 95 name="Int", 96 description="The `Int` scalar type represents" 97 " non-fractional signed whole numeric values." 98 " Int can represent values between -(2^31) and 2^31 - 1.", 99 serialize=serialize_int, 100 parse_value=coerce_int, 101 parse_literal=parse_int_literal, 102) 103 104 105def serialize_float(output_value: Any) -> float: 106 if isinstance(output_value, bool): 107 return 1 if output_value else 0 108 try: 109 if not output_value and isinstance(output_value, str): 110 output_value = "" 111 raise ValueError 112 num = output_value if isinstance(output_value, float) else float(output_value) 113 if not isfinite(num): 114 raise ValueError 115 except (ValueError, TypeError): 116 raise GraphQLError( 117 "Float cannot represent non numeric value: " + inspect(output_value) 118 ) 119 return num 120 121 122def coerce_float(input_value: Any) -> float: 123 if not is_finite(input_value): 124 raise GraphQLError( 125 "Float cannot represent non numeric value: " + inspect(input_value) 126 ) 127 return float(input_value) 128 129 130def parse_float_literal(value_node: ValueNode, _variables: Any = None) -> float: 131 """Parse a float value node in the AST.""" 132 if not isinstance(value_node, (FloatValueNode, IntValueNode)): 133 raise GraphQLError( 134 "Float cannot represent non numeric value: " + print_ast(value_node), 135 value_node, 136 ) 137 return float(value_node.value) 138 139 140GraphQLFloat = GraphQLScalarType( 141 name="Float", 142 description="The `Float` scalar type represents" 143 " signed double-precision fractional values" 144 " as specified by [IEEE 754]" 145 "(https://en.wikipedia.org/wiki/IEEE_floating_point).", 146 serialize=serialize_float, 147 parse_value=coerce_float, 148 parse_literal=parse_float_literal, 149) 150 151 152def serialize_string(output_value: Any) -> str: 153 if isinstance(output_value, str): 154 return output_value 155 if isinstance(output_value, bool): 156 return "true" if output_value else "false" 157 if is_finite(output_value): 158 return str(output_value) 159 # do not serialize builtin types as strings, but allow serialization of custom 160 # types via their `__str__` method 161 if type(output_value).__module__ == "builtins": 162 raise GraphQLError("String cannot represent value: " + inspect(output_value)) 163 return str(output_value) 164 165 166def coerce_string(input_value: Any) -> str: 167 if not isinstance(input_value, str): 168 raise GraphQLError( 169 "String cannot represent a non string value: " + inspect(input_value) 170 ) 171 return input_value 172 173 174def parse_string_literal(value_node: ValueNode, _variables: Any = None) -> str: 175 """Parse a string value node in the AST.""" 176 if not isinstance(value_node, StringValueNode): 177 raise GraphQLError( 178 "String cannot represent a non string value: " + print_ast(value_node), 179 value_node, 180 ) 181 return value_node.value 182 183 184GraphQLString = GraphQLScalarType( 185 name="String", 186 description="The `String` scalar type represents textual data," 187 " represented as UTF-8 character sequences." 188 " The String type is most often used by GraphQL" 189 " to represent free-form human-readable text.", 190 serialize=serialize_string, 191 parse_value=coerce_string, 192 parse_literal=parse_string_literal, 193) 194 195 196def serialize_boolean(output_value: Any) -> bool: 197 if isinstance(output_value, bool): 198 return output_value 199 if is_finite(output_value): 200 return bool(output_value) 201 raise GraphQLError( 202 "Boolean cannot represent a non boolean value: " + inspect(output_value) 203 ) 204 205 206def coerce_boolean(input_value: Any) -> bool: 207 if not isinstance(input_value, bool): 208 raise GraphQLError( 209 "Boolean cannot represent a non boolean value: " + inspect(input_value) 210 ) 211 return input_value 212 213 214def parse_boolean_literal(value_node: ValueNode, _variables: Any = None) -> bool: 215 """Parse a boolean value node in the AST.""" 216 if not isinstance(value_node, BooleanValueNode): 217 raise GraphQLError( 218 "Boolean cannot represent a non boolean value: " + print_ast(value_node), 219 value_node, 220 ) 221 return value_node.value 222 223 224GraphQLBoolean = GraphQLScalarType( 225 name="Boolean", 226 description="The `Boolean` scalar type represents `true` or `false`.", 227 serialize=serialize_boolean, 228 parse_value=coerce_boolean, 229 parse_literal=parse_boolean_literal, 230) 231 232 233def serialize_id(output_value: Any) -> str: 234 if isinstance(output_value, str): 235 return output_value 236 if is_integer(output_value): 237 return str(int(output_value)) 238 # do not serialize builtin types as IDs, but allow serialization of custom types 239 # via their `__str__` method 240 if type(output_value).__module__ == "builtins": 241 raise GraphQLError("ID cannot represent value: " + inspect(output_value)) 242 return str(output_value) 243 244 245def coerce_id(input_value: Any) -> str: 246 if isinstance(input_value, str): 247 return input_value 248 if is_integer(input_value): 249 return str(input_value) 250 raise GraphQLError("ID cannot represent value: " + inspect(input_value)) 251 252 253def parse_id_literal(value_node: ValueNode, _variables: Any = None) -> str: 254 """Parse an ID value node in the AST.""" 255 if not isinstance(value_node, (StringValueNode, IntValueNode)): 256 raise GraphQLError( 257 "ID cannot represent a non-string and non-integer value: " 258 + print_ast(value_node), 259 value_node, 260 ) 261 return value_node.value 262 263 264GraphQLID = GraphQLScalarType( 265 name="ID", 266 description="The `ID` scalar type represents a unique identifier," 267 " often used to refetch an object or as key for a cache." 268 " The ID type appears in a JSON response as a String; however," 269 " it is not intended to be human-readable. When expected as an" 270 ' input type, any string (such as `"4"`) or integer (such as' 271 " `4`) input value will be accepted as an ID.", 272 serialize=serialize_id, 273 parse_value=coerce_id, 274 parse_literal=parse_id_literal, 275) 276 277 278specified_scalar_types: FrozenDict[str, GraphQLScalarType] = FrozenDict( 279 { 280 type_.name: type_ 281 for type_ in ( 282 GraphQLString, 283 GraphQLInt, 284 GraphQLFloat, 285 GraphQLBoolean, 286 GraphQLID, 287 ) 288 } 289) 290 291 292def is_specified_scalar_type(type_: GraphQLNamedType) -> bool: 293 """Check whether the given named GraphQL type is a specified scalar type.""" 294 return type_.name in specified_scalar_types 295