From 7260efbdfaaafa58dc6bcdbdaff163029b3e54fe Mon Sep 17 00:00:00 2001 From: uttam12331 Date: Fri, 24 Jul 2026 22:23:49 +0530 Subject: [PATCH 1/2] Coerce a single value to a list when serializing list variables serialize_value assumed a value for a list type was already a list and iterated it, so a string variable was split into its characters -- {'ids': 'abc'} was serialized as ['a', 'b', 'c'] and silently sent to the server -- and a non-iterable such as an int raised TypeError. The GraphQL spec's input coercion says a non-list value provided for a list type is coerced to a list of one, which is what graphql-core does in coerce_input_value. Use pyutils.is_iterable (already the module the file imports inspect from) to match that behaviour; it also excludes str and bytes, which is the case that was silently wrong. --- gql/utilities/serialize_variable_values.py | 5 ++++- tests/test_graphql_request.py | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) 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..63c1dbfa 100644 --- a/tests/test_graphql_request.py +++ b/tests/test_graphql_request.py @@ -16,7 +16,7 @@ GraphQLScalarType, GraphQLSchema, ) -from graphql.utilities import value_from_ast_untyped +from graphql.utilities import build_schema, value_from_ast_untyped from gql import GraphQLRequest @@ -206,6 +206,21 @@ 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_graphql_request_using_string_instead_of_document(): request = GraphQLRequest("{balance}") From 91e9d4b5ab988dd57d43d3e46bf36fe683b684b4 Mon Sep 17 00:00:00 2001 From: Leszek Hanusz Date: Fri, 24 Jul 2026 21:33:27 +0200 Subject: [PATCH 2/2] Add tests for list variable coercion behaviors --- tests/test_graphql_request.py | 62 +++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tests/test_graphql_request.py b/tests/test_graphql_request.py index 63c1dbfa..8f314f34 100644 --- a/tests/test_graphql_request.py +++ b/tests/test_graphql_request.py @@ -15,6 +15,7 @@ GraphQLObjectType, GraphQLScalarType, GraphQLSchema, + GraphQLString, ) from graphql.utilities import build_schema, value_from_ast_untyped @@ -221,6 +222,67 @@ def test_serialize_variables_single_value_for_list_type(): 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}")