3.5 Beta: overlapping_fields_can_be_merged: directives are None
theCapypara opened this issue · comments
Describe the bug
I upgraded to the 3.5 Beta and now queries fail in overlapping_fields_can_be_merged.py
in get_stream_directive
. The directives are None
, if I go up the stack they are always None
. They are read from node1
and node2
in lines 563 - 564, and they are already None
at the nodes.
...
File "/home/marco/dev/skytemple/skytemple/files/skytemple_files/common/spritecollab/client.py", line 671, in execute_query
return cast(Query, await self._session.execute(dsl_gql(*fragments, query)))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/gql/client.py", line 1629, in execute
result = await self._execute(
^^^^^^^^^^^^^^^^^^^^
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/gql/client.py", line 1817, in _execute
return await self._execute_with_retries(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/backoff/_async.py", line 151, in retry
ret = await target(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/gql/client.py", line 1790, in _execute_once
answer = await super()._execute(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/gql/client.py", line 1520, in _execute
self.client.validate(document)
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/gql/client.py", line 172, in validate
validation_errors = validate(self.schema, document)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/graphql/validation/validate.py", line 73, in validate
visit(document_ast, TypeInfoVisitor(type_info, ParallelVisitor(visitors)))
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/graphql/language/visitor.py", line 257, in visit
result = visit_fn(node, key, parent, path, ancestors)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/graphql/utilities/type_info.py", line 280, in enter
result = fn(node, *args)
^^^^^^^^^^^^^^^
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/graphql/language/visitor.py", line 339, in enter
result = fn(node, *args)
^^^^^^^^^^^^^^^
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/graphql/validation/rules/overlapping_fields_can_be_merged.py", line 76, in enter_selection_set
conflicts = find_conflicts_within_selection_set(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/graphql/validation/rules/overlapping_fields_can_be_merged.py", line 210, in find_conflicts_within_selection_set
collect_conflicts_between_fragments(
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/graphql/validation/rules/overlapping_fields_can_be_merged.py", line 324, in collect_conflicts_between_fragments
collect_conflicts_between(
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/graphql/validation/rules/overlapping_fields_can_be_merged.py", line 502, in collect_conflicts_between
conflict = find_conflict(
^^^^^^^^^^^^^^
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/graphql/validation/rules/overlapping_fields_can_be_merged.py", line 565, in find_conflict
if not same_streams(directives1, directives2):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/graphql/validation/rules/overlapping_fields_can_be_merged.py", line 622, in same_streams
stream1 = get_stream_directive(directives1)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/marco/.virtualenvs/skytemple/lib/python3.11/site-packages/graphql/validation/rules/overlapping_fields_can_be_merged.py", line 613, in get_stream_directive
for directive in directives:
TypeError: 'NoneType' object is not iterable
field1
and field2
are:
field1:
parent_type1.__dict__ = {'name': 'Sprite', 'description': 'A single sprite for a single action.', 'extensions': {}, 'ast_node': None, 'extension_ast_nodes': (), '_fields': <function build_client_schema.<locals>.build_object_def.<locals>.<lambda> at 0x7fd2293791c0>, '_interfaces': <function build_client_schema.<locals>.build_object_def.<locals>.<lambda> at 0x7fd229379120>, 'is_type_of': None, 'interfaces': (), 'fields': {'action': <GraphQLField <GraphQLNonNull <GraphQLScalarType 'String'>>>, 'locked': <GraphQLField <GraphQLNonNull <GraphQLScalarType 'Boolean'>>>, 'animUrl': <GraphQLField <GraphQLNonNull <GraphQLScalarType 'String'>>>, 'offsetsUrl': <GraphQLField <GraphQLNonNull <GraphQLScalarType 'String'>>>, 'shadowsUrl': <GraphQLField <GraphQLNonNull <GraphQLScalarType 'String'>>>}}
node1.to_dict() = {'alias': None, 'arguments': [], 'directives': None, 'kind': 'field', 'name': {'kind': 'name', 'value': 'action'}, 'nullability_assertion': None, 'selection_set': None}
def1.__dict__ = {'args': {}, 'ast_node': None, 'deprecation_reason': None, 'description': 'Action of this sprite.', 'extensions': {}, 'resolve': None, 'subscribe': None, 'type': String!}
field2:
parent_type2.__dict__ = {'name': 'CopyOf', 'description': 'A sprite, which is a copy of another sprite.', 'extensions': {}, 'ast_node': None, 'extension_ast_nodes': (), '_fields': <function build_client_schema.<locals>.build_object_def.<locals>.<lambda> at 0x7fd229378900>, '_interfaces': <function build_client_schema.<locals>.build_object_def.<locals>.<lambda> at 0x7fd229378860>, 'is_type_of': None, 'interfaces': (), 'fields': {'action': <GraphQLField <GraphQLNonNull <GraphQLScalarType 'String'>>>, 'locked': <GraphQLField <GraphQLNonNull <GraphQLScalarType 'Boolean'>>>, 'copyOf': <GraphQLField <GraphQLNonNull <GraphQLScalarType 'String'>>>}}
node2.to_dict() = {'alias': None, 'arguments': [], 'directives': None, 'kind': 'field', 'name': {'kind': 'name', 'value': 'action'}, 'nullability_assertion': None, 'selection_set': None}
def2.__dict__ = {'args': {}, 'ast_node': None, 'deprecation_reason': None, 'description': 'Action of this sprite.', 'extensions': {}, 'resolve': None, 'subscribe': None, 'type': String!}
To Reproduce
Using gql
and aiohttp
3.9., query the following GraphQL server with the following document (JSON encoded, sorry I don't know any way to get the raw query and variables out of the document:
Server: https://spriteserver.pmdcollab.org
Query document: https://gist.github.com/theCapypara/2c38f3c1da9607010627a4c03979d74f
Expected behavior
The query returns the result.
System info (please complete the following information):
- OS: Arch Linux
- Python version: Python 3.11
- gql version: 3.5.0b7
- graphql-core version: 3.3.0a3
This blocks my project from upgrading to Python 3.12. I have been anticipating aiohttp finally upgrading to be 3.12 compatible and am now stuck due to this.
PS: I know the dependencies are currently locked so that aiohttp 3.8 is used for Python 3.11, however I was unable to get 3.12 compiled locally. If this error is because the combination 3.11 + aiohttp 3.9 is not supported, I'm sorry!
If the info I provided doesn't help and the example is too complicated, I'd be happy to try and get a more compact example out of this, but I hope this may already be enough to pin-point the issue.
Please use the print_ast
method (print_ast(dsl_gql(*fragments, query))
) to get the GraphQL query and post it here.
Ah thanks!
fragment SpriteUnionAsSprite on Sprite {
action
animUrl
offsetsUrl
shadowsUrl
}
fragment SpriteUnionAsCopyOf on CopyOf {
action
copyOf
}
fragment CreditFields on Credit {
id
name
contact
discordHandle
}
{
monster(filter: [3]) {
id
name
f_0: manual(path: "") {
path
fullName
isShiny
isFemale
canon
portraits {
phase
modifiedDate
emotions {
emotion
url
}
emotionsFlipped {
emotion
url
}
sheetUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
sprites {
phase
modifiedDate
actions {
...SpriteUnionAsSprite
...SpriteUnionAsCopyOf
}
zipUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
}
f_1: manual(path: "0001") {
path
fullName
isShiny
isFemale
canon
portraits {
phase
modifiedDate
emotions {
emotion
url
}
emotionsFlipped {
emotion
url
}
sheetUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
sprites {
phase
modifiedDate
actions {
...SpriteUnionAsSprite
...SpriteUnionAsCopyOf
}
zipUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
}
f_2: manual(path: "0003") {
path
fullName
isShiny
isFemale
canon
portraits {
phase
modifiedDate
emotions {
emotion
url
}
emotionsFlipped {
emotion
url
}
sheetUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
sprites {
phase
modifiedDate
actions {
...SpriteUnionAsSprite
...SpriteUnionAsCopyOf
}
zipUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
}
f_3: manual(path: "0002") {
path
fullName
isShiny
isFemale
canon
portraits {
phase
modifiedDate
emotions {
emotion
url
}
emotionsFlipped {
emotion
url
}
sheetUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
sprites {
phase
modifiedDate
actions {
...SpriteUnionAsSprite
...SpriteUnionAsCopyOf
}
zipUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
}
f_4: manual(path: "0001/0001") {
path
fullName
isShiny
isFemale
canon
portraits {
phase
modifiedDate
emotions {
emotion
url
}
emotionsFlipped {
emotion
url
}
sheetUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
sprites {
phase
modifiedDate
actions {
...SpriteUnionAsSprite
...SpriteUnionAsCopyOf
}
zipUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
}
f_5: manual(path: "0000/0001") {
path
fullName
isShiny
isFemale
canon
portraits {
phase
modifiedDate
emotions {
emotion
url
}
emotionsFlipped {
emotion
url
}
sheetUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
sprites {
phase
modifiedDate
actions {
...SpriteUnionAsSprite
...SpriteUnionAsCopyOf
}
zipUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
}
f_6: manual(path: "0003/0001") {
path
fullName
isShiny
isFemale
canon
portraits {
phase
modifiedDate
emotions {
emotion
url
}
emotionsFlipped {
emotion
url
}
sheetUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
sprites {
phase
modifiedDate
actions {
...SpriteUnionAsSprite
...SpriteUnionAsCopyOf
}
zipUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
}
f_7: manual(path: "0002/0001") {
path
fullName
isShiny
isFemale
canon
portraits {
phase
modifiedDate
emotions {
emotion
url
}
emotionsFlipped {
emotion
url
}
sheetUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
sprites {
phase
modifiedDate
actions {
...SpriteUnionAsSprite
...SpriteUnionAsCopyOf
}
zipUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
}
f_8: manual(path: "0000/0000/0002") {
path
fullName
isShiny
isFemale
canon
portraits {
phase
modifiedDate
emotions {
emotion
url
}
emotionsFlipped {
emotion
url
}
sheetUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
sprites {
phase
modifiedDate
actions {
...SpriteUnionAsSprite
...SpriteUnionAsCopyOf
}
zipUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
}
f_9: manual(path: "0000/0001/0002") {
path
fullName
isShiny
isFemale
canon
portraits {
phase
modifiedDate
emotions {
emotion
url
}
emotionsFlipped {
emotion
url
}
sheetUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
sprites {
phase
modifiedDate
actions {
...SpriteUnionAsSprite
...SpriteUnionAsCopyOf
}
zipUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
}
f_10: manual(path: "0003/0001/0002") {
path
fullName
isShiny
isFemale
canon
portraits {
phase
modifiedDate
emotions {
emotion
url
}
emotionsFlipped {
emotion
url
}
sheetUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
sprites {
phase
modifiedDate
actions {
...SpriteUnionAsSprite
...SpriteUnionAsCopyOf
}
zipUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
}
f_11: manual(path: "0003/0000/0002") {
path
fullName
isShiny
isFemale
canon
portraits {
phase
modifiedDate
emotions {
emotion
url
}
emotionsFlipped {
emotion
url
}
sheetUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
sprites {
phase
modifiedDate
actions {
...SpriteUnionAsSprite
...SpriteUnionAsCopyOf
}
zipUrl
creditPrimary {
...CreditFields
}
creditSecondary {
...CreditFields
}
}
}
}
}
I am not able to reproduce your problem on my machine with gql v3.5.0b7 and Python 3.12
I downloaded the schema using gql-cli --print-schema https://spriteserver.pmdcollab.org/graphql > schema.graphql
Then I tried to run the test.py
file in attachement validating the query you provided, and it seems to work correctly.
Maybe the schema you provided is different than mine?
That's very odd. I don't provide the schema, I let gql handle this automatically, but I checked and the schema it uses internally is the same you shared.
The test script you sent also works for me. I can tell it's seemingly going the same code paths but the directives are actually empty tuples there and so it doesn't crash.
I was also able to reproduce this with a condensed version of the DSL my app generates. Here is essentially your test script but with DSL:
https://gist.github.com/theCapypara/8d4ef3d7229fa03b57d392b325e5bcba
So I guess this has to be a DSL issue.
Yes, quite odd...
I was able to reproduce the problem and reduce it to:
from gql import Client, gql
from gql.dsl import DSLSchema, DSLFragment, dsl_gql, DSLQuery, print_ast
with open("./schema.graphql") as f:
schema_str = f.read()
client = Client(schema=schema_str)
ds = DSLSchema(client.schema)
sprite = DSLFragment("SpriteUnionAsSprite")
sprite.on(ds.Sprite)
sprite.select(
ds.Sprite.action,
)
copy_of = DSLFragment("SpriteUnionAsCopyOf")
copy_of.on(ds.CopyOf)
copy_of.select(
ds.CopyOf.action,
)
query = ds.Query.monster.select(
ds.Monster.manual(path="").select(
ds.MonsterForm.sprites.select(
ds.MonsterFormSprites.actions.select(sprite, copy_of),
),
),
)
q = dsl_gql(sprite, copy_of, DSLQuery(query))
client.validate(q)
The PR #448 should fix your problem.
The DSL code was not initializing the directives correctly as you noticed.
Thanks for your bug report!
Fixed in pre-release v3.5.0b8
Thank you! I can confirm the PR fixes the issue both in the app and its tests.