Skip to content

Commit a6bd2a0

Browse files
committed
Drop support for non-iterable array like objects
Replicates graphql/graphql-js@31e8484
1 parent 21ac52a commit a6bd2a0

13 files changed

Lines changed: 317 additions & 98 deletions

File tree

.coveragerc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ source = src
44
omit =
55
*/conftest.py
66
*/cached_property.py
7-
*/is_collection.py
7+
*/is_iterable.py
88
*/test_*_fuzz.py
99

1010
[report]

docs/modules/pyutils.rst

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ PyUtils
1010
.. autofunction:: camel_to_snake
1111
.. autofunction:: snake_to_camel
1212
.. autofunction:: cached_property
13-
.. autofunction:: did_you_mean
1413
.. autofunction:: register_description
1514
.. autofunction:: unregister_description
16-
.. autoclass:: SimplePubSub
17-
.. autoclass:: SimplePubSubIterator
15+
.. autofunction:: did_you_mean
1816
.. autofunction:: identity_func
1917
.. autofunction:: inspect
18+
.. autofunction:: is_awaitable
19+
.. autofunction:: is_collection
20+
.. autofunction:: is_iterable
21+
.. autofunction:: natural_comparison_key
2022
.. autoclass:: AwaitableOrValue
2123
.. autofunction:: suggestion_list
2224
.. autoclass:: FrozenError
@@ -33,5 +35,6 @@ PyUtils
3335
:no-special-members:
3436
.. autoclass:: Path
3537
.. autofunction:: print_path_list
36-
38+
.. autoclass:: SimplePubSub
39+
.. autoclass:: SimplePubSubIterator
3740
.. autodata:: Undefined

src/graphql/execution/execute.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from ..pyutils import (
3131
inspect,
3232
is_awaitable as default_is_awaitable,
33+
is_iterable,
3334
AwaitableOrValue,
3435
FrozenList,
3536
Path,
@@ -765,7 +766,7 @@ def complete_list_value(
765766
766767
Complete a list value by completing each item in the list with the inner type.
767768
"""
768-
if not isinstance(result, Iterable) or isinstance(result, str):
769+
if not is_iterable(result):
769770
raise GraphQLError(
770771
"Expected Iterable, but did not find one for field"
771772
f" '{info.parent_type.name}.{info.field_name}'."

src/graphql/pyutils/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from .identity_func import identity_func
2121
from .inspect import inspect
2222
from .is_awaitable import is_awaitable
23-
from .is_collection import is_collection
23+
from .is_iterable import is_collection, is_iterable
2424
from .natural_compare import natural_comparison_key
2525
from .awaitable_or_value import AwaitableOrValue
2626
from .suggestion_list import suggestion_list
@@ -45,6 +45,7 @@
4545
"inspect",
4646
"is_awaitable",
4747
"is_collection",
48+
"is_iterable",
4849
"natural_comparison_key",
4950
"AwaitableOrValue",
5051
"suggestion_list",

src/graphql/pyutils/is_collection.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/graphql/pyutils/is_iterable.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import (
2+
Any,
3+
ByteString,
4+
Collection,
5+
Iterable,
6+
Mapping,
7+
Text,
8+
ValuesView,
9+
)
10+
11+
__all__ = ["is_collection", "is_iterable"]
12+
13+
collection_types: Any = Collection
14+
if not isinstance({}.values(), Collection): # Python < 3.7.2
15+
collection_types = (Collection, ValuesView)
16+
iterable_types: Any = Iterable
17+
not_iterable_types: Any = (ByteString, Mapping, Text)
18+
19+
20+
def is_collection(value: Any) -> bool:
21+
"""Check if value is a collection, but not a string or a mapping."""
22+
return isinstance(value, collection_types) and not isinstance(
23+
value, not_iterable_types
24+
)
25+
26+
27+
def is_iterable(value: Any) -> bool:
28+
"""Check if value is an iterable, but not a string or a mapping."""
29+
return isinstance(value, iterable_types) and not isinstance(
30+
value, not_iterable_types
31+
)

src/graphql/utilities/ast_from_value.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import re
22
from math import isfinite
3-
from typing import Any, Iterable, Mapping, Optional, cast
3+
from typing import Any, Mapping, Optional, cast
44

55
from ..language import (
66
BooleanValueNode,
@@ -15,7 +15,7 @@
1515
StringValueNode,
1616
ValueNode,
1717
)
18-
from ..pyutils import inspect, FrozenList, Undefined
18+
from ..pyutils import inspect, is_iterable, FrozenList, Undefined
1919
from ..type import (
2020
GraphQLID,
2121
GraphQLInputType,
@@ -78,7 +78,7 @@ def ast_from_value(value: Any, type_: GraphQLInputType) -> Optional[ValueNode]:
7878
if is_list_type(type_):
7979
type_ = cast(GraphQLList, type_)
8080
item_type = type_.of_type
81-
if isinstance(value, Iterable) and not isinstance(value, str):
81+
if is_iterable(value):
8282
maybe_value_nodes = (ast_from_value(item, item_type) for item in value)
8383
value_nodes = filter(None, maybe_value_nodes)
8484
return ListValueNode(values=FrozenList(value_nodes))

src/graphql/utilities/coerce_input_value.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
from typing import Any, Callable, Dict, Iterable, List, Optional, Union, cast
1+
from typing import Any, Callable, Dict, List, Optional, Union, cast
22

33

44
from ..error import GraphQLError
55
from ..pyutils import (
66
Path,
77
did_you_mean,
88
inspect,
9+
is_iterable,
910
print_path_list,
1011
suggestion_list,
1112
Undefined,
@@ -65,7 +66,7 @@ def coerce_input_value(
6566
if is_list_type(type_):
6667
type_ = cast(GraphQLList, type_)
6768
item_type = type_.of_type
68-
if isinstance(input_value, Iterable) and not isinstance(input_value, str):
69+
if is_iterable(input_value):
6970
coerced_list: List[Any] = []
7071
append_item = coerced_list.append
7172
for index, item_value in enumerate(input_value):
@@ -84,7 +85,7 @@ def coerce_input_value(
8485
on_error(
8586
path.as_list() if path else [],
8687
input_value,
87-
GraphQLError(f"Expected type '{type_.name}' to be a dict."),
88+
GraphQLError(f"Expected type '{type_.name}' to be a mapping."),
8889
)
8990
return Undefined
9091

tests/execution/test_lists.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ def _complete(list_field):
2525
)
2626

2727
def accepts_a_set_as_a_list_value():
28-
# We need to use a dict instead of a set,
29-
# since sets are not ordered in Python.
30-
list_field = dict.fromkeys(["apple", "banana", "coconut"])
31-
assert _complete(list_field) == (
32-
{"listField": ["apple", "banana", "coconut"]},
33-
None,
34-
)
28+
# Note that sets are not ordered in Python.
29+
list_field = {"apple", "banana", "coconut"}
30+
result = _complete(list_field)
31+
assert result.errors is None
32+
assert isinstance(result.data, dict)
33+
assert list(result.data) == ["listField"]
34+
assert isinstance(result.data["listField"], list)
35+
assert set(result.data["listField"]) == list_field
3536

3637
def accepts_a_generator_as_a_list_value():
3738
def list_field():
@@ -53,6 +54,19 @@ def get_args(*args):
5354
None,
5455
)
5556

57+
def does_not_accept_a_dict_as_a_list_value():
58+
assert _complete({1: "one", 2: "two"}) == (
59+
{"listField": None},
60+
[
61+
{
62+
"message": "Expected Iterable,"
63+
" but did not find one for field 'Query.listField'.",
64+
"locations": [(1, 3)],
65+
"path": ["listField"],
66+
}
67+
],
68+
)
69+
5670
def does_not_accept_iterable_string_literal_as_a_list_value():
5771
assert _complete("Singular") == (
5872
{"listField": None},

tests/execution/test_variables.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ def errors_on_incorrect_type():
388388
[
389389
{
390390
"message": "Variable '$input' got invalid value 'foo bar';"
391-
" Expected type 'TestInputObject' to be a dict.",
391+
" Expected type 'TestInputObject' to be a mapping.",
392392
"locations": [(2, 24)],
393393
"path": None,
394394
}

0 commit comments

Comments
 (0)