Skip to content

Coerce a single value to a list when serializing list variables - #611

Merged
leszekhanusz merged 2 commits into
graphql-python:masterfrom
uttam12331:fix-list-variable-single-value
Jul 24, 2026
Merged

Coerce a single value to a list when serializing list variables#611
leszekhanusz merged 2 commits into
graphql-python:masterfrom
uttam12331:fix-list-variable-single-value

Conversation

@uttam12331

Copy link
Copy Markdown
Contributor

Summary

serialize_value assumes the value given for a list type is already a list and iterates it:

elif isinstance(type_, GraphQLList):
    return [serialize_value(inner_type, v) for v in value]

So a single value passed for a list variable is taken apart instead of being wrapped. A string is split into its characters and sent to the server without any error:

>>> serialize_variable_values(schema, document, {"ids": "abc"})
{'ids': ['a', 'b', 'c']}      # expected {'ids': ['abc']}
>>> serialize_variable_values(schema, document, {"ns": 5})
TypeError: 'int' object is not iterable    # expected {'ns': [5]}

The string case is the worrying one — no exception, just wrong data on the wire.

Fix

The GraphQL spec's input coercion says a non-list value provided for a list type is coerced to a list of one, and graphql-core implements exactly that in coerce_input_value:

if is_list_type(type_):
    item_type = type_.of_type
    if not is_iterable(input_value):
        # Lists accept a non-list value as a list of one.
        coerced_item = coerce_input_value(input_value, item_type)
        ...
        return [coerced_item]

This mirrors it using the same graphql.pyutils.is_iterable helper — the module this file already imports inspect from. is_iterable also excludes str and bytes, which is precisely the silently-wrong case above.

After the change:

{"ids": "abc"}      -> {'ids': ['abc']}
{"ns": 5}           -> {'ns': [5]}
{"ids": ["a", "b"]} -> {'ids': ['a', 'b']}   # unchanged
{"ids": None}       -> {'ids': None}         # unchanged

Tests

Added test_serialize_variables_single_value_for_list_type in tests/test_graphql_request.py, which fails before this change and passes after.

pytest tests/test_graphql_request.py tests/custom_scalars/ → 57 passed. The 2 failures in custom_scalars/test_money.py (sync_transport) also fail on a clean checkout here — they need transport deps I don't have locally, and are unrelated to this change. black, flake8 and isort are clean.

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.
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (7fb869a) to head (91e9d4b).
⚠️ Report is 75 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##            master      #611    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           38        40     +2     
  Lines         2908      3329   +421     
==========================================
+ Hits          2908      3329   +421     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@leszekhanusz
leszekhanusz merged commit 3d6714d into graphql-python:master Jul 24, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants