diff --git a/gql/utilities/serialize_variable_values.py b/gql/utilities/serialize_variable_values.py index 87185fd5..9007f54a 100644 --- a/gql/utilities/serialize_variable_values.py +++ b/gql/utilities/serialize_variable_values.py @@ -14,7 +14,7 @@ OperationDefinitionNode, type_from_ast, ) -from graphql.pyutils import inspect +from graphql.pyutils import inspect, is_iterable def _get_document_operation( @@ -76,6 +76,9 @@ def serialize_value(type_: GraphQLType, value: Any) -> Any: return serialize_value(inner_type, value) elif isinstance(type_, GraphQLList): + if not is_iterable(value): + # Lists accept a non-list value as a list of one. + return [serialize_value(inner_type, value)] return [serialize_value(inner_type, v) for v in value] elif isinstance(type_, (GraphQLScalarType, GraphQLEnumType)): diff --git a/tests/test_graphql_request.py b/tests/test_graphql_request.py index 5f844681..8f314f34 100644 --- a/tests/test_graphql_request.py +++ b/tests/test_graphql_request.py @@ -15,8 +15,9 @@ GraphQLObjectType, GraphQLScalarType, GraphQLSchema, + GraphQLString, ) -from graphql.utilities import value_from_ast_untyped +from graphql.utilities import build_schema, value_from_ast_untyped from gql import GraphQLRequest @@ -206,6 +207,82 @@ def test_serialize_variables_using_money_example(): assert req.variable_values == {"money": {"amount": 10, "currency": "DM"}} +def test_serialize_variables_single_value_for_list_type(): + # A value which is not a list, provided for a list type, should be + # coerced into a list of one instead of being iterated over. + list_schema = build_schema("type Query {f(ids: [String!], ns: [Int]): String}") + + req = GraphQLRequest( + "query q($ids: [String!], $ns: [Int]) {f(ids: $ids, ns: $ns)}", + variable_values={"ids": "abc", "ns": 5}, + ) + + req = req.serialize_variable_values(list_schema) + + assert req.variable_values == {"ids": ["abc"], "ns": [5]} + + +def test_serialize_variables_mapping_as_singleton_input_object(): + schema = build_schema( + "input ItemInput { name: String } type Query { f(items: [ItemInput]): String }" + ) + req = GraphQLRequest( + "query q($items: [ItemInput]) { f(items: $items) }", + variable_values={"items": {"name": "abc"}}, + ) + req = req.serialize_variable_values(schema) + assert req.variable_values == {"items": [{"name": "abc"}]} + + +def test_serialize_variables_recursive_list_coercion(): + schema = build_schema("type Query { f(values: [[Int]]): String }") + + req1 = GraphQLRequest( + "query q($values: [[Int]]) { f(values: $values) }", + variable_values={"values": 1}, + ) + req1 = req1.serialize_variable_values(schema) + assert req1.variable_values == {"values": [[1]]} + + req2 = GraphQLRequest( + "query q($values: [[Int]]) { f(values: $values) }", + variable_values={"values": [1, 2]}, + ) + req2 = req2.serialize_variable_values(schema) + assert req2.variable_values == {"values": [[1], [2]]} + + +def test_serialize_variables_collection_behavior(): + schema = build_schema("type Query { f(values: [Int]): String }") + req = GraphQLRequest( + "query q($values: [Int]) { f(values: $values) }", + variable_values={"values": (1, 2)}, + ) + req = req.serialize_variable_values(schema) + assert req.variable_values == {"values": [1, 2]} + + +def test_serialize_variables_bytes_behavior(): + bytes_scalar = GraphQLScalarType(name="Bytes", serialize=lambda v: v) + schema = GraphQLSchema( + query=GraphQLObjectType( + "Query", + fields={ + "f": GraphQLField( + GraphQLString, + args={"values": GraphQLArgument(GraphQLList(bytes_scalar))}, + ) + }, + ) + ) + req = GraphQLRequest( + "query q($values: [Bytes]) { f(values: $values) }", + variable_values={"values": b"abc"}, + ) + req = req.serialize_variable_values(schema) + assert req.variable_values == {"values": [b"abc"]} + + def test_graphql_request_using_string_instead_of_document(): request = GraphQLRequest("{balance}")