From efe654d26613295f2541673ef33973590e2af62e Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Sun, 12 Dec 2021 23:01:59 +0100 Subject: [PATCH 01/11] Fix error 'graphql.error.graphql_error.GraphQLError: Names must only contain [_a-zA-Z0-9] but 'meta-field' does not.' --- gql/dsl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gql/dsl.py b/gql/dsl.py index 0cadef8b..5d363846 100644 --- a/gql/dsl.py +++ b/gql/dsl.py @@ -856,7 +856,7 @@ class DSLMetaField(DSLField): """ meta_type = GraphQLObjectType( - "meta-field", + "meta_field", fields={ "__typename": GraphQLField(GraphQLString), "__schema": GraphQLField( From 3d9dcc5d38ba1b8a4aa86c6de67764fee1773c06 Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Sun, 12 Dec 2021 23:06:33 +0100 Subject: [PATCH 02/11] Don't use format_error --- tests/starwars/test_query.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/starwars/test_query.py b/tests/starwars/test_query.py index 520018c1..430aa18e 100644 --- a/tests/starwars/test_query.py +++ b/tests/starwars/test_query.py @@ -1,5 +1,5 @@ import pytest -from graphql import GraphQLError, format_error +from graphql import GraphQLError from gql import Client, gql from tests.starwars.schema import StarWarsSchema @@ -302,9 +302,7 @@ def test_parse_error(client): ) error = exc_info.value assert isinstance(error, GraphQLError) - formatted_error = format_error(error) - assert formatted_error["locations"] == [{"column": 13, "line": 2}] - assert formatted_error["message"] == "Syntax Error: Unexpected Name 'qeury'." + assert "Syntax Error: Unexpected Name 'qeury'." in str(error) def test_mutation_result(client): From c7ca982db4a4e984884df3d981f90e7cc4ca17f2 Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Sun, 12 Dec 2021 23:08:22 +0100 Subject: [PATCH 03/11] Put the is_finite method in the test file --- tests/custom_scalars/test_money.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/custom_scalars/test_money.py b/tests/custom_scalars/test_money.py index 2e30b6b7..23dc281d 100644 --- a/tests/custom_scalars/test_money.py +++ b/tests/custom_scalars/test_money.py @@ -1,11 +1,12 @@ import asyncio +from math import isfinite from typing import Any, Dict, NamedTuple, Optional import pytest from graphql import graphql_sync from graphql.error import GraphQLError from graphql.language import ValueNode -from graphql.pyutils import inspect, is_finite +from graphql.pyutils import inspect from graphql.type import ( GraphQLArgument, GraphQLField, @@ -34,6 +35,13 @@ class Money(NamedTuple): currency: str +def is_finite(value: Any) -> bool: + """Return true if a value is a finite number.""" + return (isinstance(value, int) and not isinstance(value, bool)) or ( + isinstance(value, float) and isfinite(value) + ) + + def serialize_money(output_value: Any) -> Dict[str, Any]: if not isinstance(output_value, Money): raise GraphQLError("Cannot serialize money value: " + inspect(output_value)) From 7742bcedac30d9c980b27217959321b175cab5c4 Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Mon, 13 Dec 2021 12:15:48 +0100 Subject: [PATCH 04/11] Fix StarWars schema Fixes 'Support for returning GraphQLObjectType from resolve_type was removed in GraphQL-core 3.2, please return type name instead' --- tests/starwars/fixtures.py | 58 ++++++++++++---- tests/starwars/schema.py | 139 +++++++++++++++++++------------------ 2 files changed, 114 insertions(+), 83 deletions(-) diff --git a/tests/starwars/fixtures.py b/tests/starwars/fixtures.py index 7bc31037..36232147 100644 --- a/tests/starwars/fixtures.py +++ b/tests/starwars/fixtures.py @@ -1,7 +1,37 @@ import asyncio -from collections import namedtuple +from typing import Collection + + +class Character: + id: str + name: str + friends: Collection[str] + appearsIn: Collection[str] + + +# noinspection PyPep8Naming +class Human(Character): + type = "Human" + homePlanet: str + + # noinspection PyShadowingBuiltins + def __init__(self, id, name, friends, appearsIn, homePlanet): + self.id, self.name = id, name + self.friends, self.appearsIn = friends, appearsIn + self.homePlanet = homePlanet + + +# noinspection PyPep8Naming +class Droid(Character): + type = "Droid" + primaryFunction: str + + # noinspection PyShadowingBuiltins + def __init__(self, id, name, friends, appearsIn, primaryFunction): + self.id, self.name = id, name + self.friends, self.appearsIn = friends, appearsIn + self.primaryFunction = primaryFunction -Human = namedtuple("Human", "id name friends appearsIn homePlanet") luke = Human( id="1000", @@ -47,8 +77,6 @@ "1004": tarkin, } -Droid = namedtuple("Droid", "id name friends appearsIn primaryFunction") - threepio = Droid( id="2000", name="C-3PO", @@ -77,38 +105,38 @@ } -def getCharacter(id): +def get_character(id): return humanData.get(id) or droidData.get(id) -def getCharacters(ids): - return map(getCharacter, ids) +def get_characters(ids): + return map(get_character, ids) -def getFriends(character): - return map(getCharacter, character.friends) +def get_friends(character): + return map(get_character, character.friends) -def getHero(episode): +def get_hero(episode): if episode == 5: return luke return artoo -async def getHeroAsync(episode): +async def get_hero_async(episode): await asyncio.sleep(0.001) - return getHero(episode) + return get_hero(episode) -def getHuman(id): +def get_human(id): return humanData.get(id) -def getDroid(id): +def get_droid(id): return droidData.get(id) -def createReview(episode, review): +def create_review(episode, review): reviews[episode].append(review) review["episode"] = episode return review diff --git a/tests/starwars/schema.py b/tests/starwars/schema.py index 95320ffe..50e2420f 100644 --- a/tests/starwars/schema.py +++ b/tests/starwars/schema.py @@ -20,99 +20,103 @@ ) from .fixtures import ( - createReview, - getCharacters, - getDroid, - getFriends, - getHeroAsync, - getHuman, + create_review, + get_characters, + get_droid, + get_friends, + get_hero_async, + get_human, reviews, ) -episodeEnum = GraphQLEnumType( +episode_enum = GraphQLEnumType( "Episode", - description="One of the films in the Star Wars Trilogy", - values={ + { "NEWHOPE": GraphQLEnumValue(4, description="Released in 1977.",), "EMPIRE": GraphQLEnumValue(5, description="Released in 1980.",), "JEDI": GraphQLEnumValue(6, description="Released in 1983.",), }, + description="One of the films in the Star Wars Trilogy", ) -characterInterface = GraphQLInterfaceType( + +human_type: GraphQLObjectType +droid_type: GraphQLObjectType + +character_interface = GraphQLInterfaceType( "Character", - description="A character in the Star Wars Trilogy", - fields=lambda: { + lambda: { "id": GraphQLField( GraphQLNonNull(GraphQLString), description="The id of the character." ), "name": GraphQLField(GraphQLString, description="The name of the character."), "friends": GraphQLField( - GraphQLList(characterInterface), # type: ignore + GraphQLList(character_interface), # type: ignore description="The friends of the character," " or an empty list if they have none.", ), "appearsIn": GraphQLField( - GraphQLList(episodeEnum), description="Which movies they appear in." + GraphQLList(episode_enum), description="Which movies they appear in." ), }, - resolve_type=lambda character, *_: humanType # type: ignore - if getHuman(character.id) - else droidType, # type: ignore + resolve_type=lambda character, _info, _type: { + "Human": human_type.name, + "Droid": droid_type.name, + }[character.type], + description="A character in the Star Wars Trilogy", ) -humanType = GraphQLObjectType( +human_type = GraphQLObjectType( "Human", - description="A humanoid creature in the Star Wars universe.", - fields=lambda: { + lambda: { "id": GraphQLField( GraphQLNonNull(GraphQLString), description="The id of the human.", ), "name": GraphQLField(GraphQLString, description="The name of the human.",), "friends": GraphQLField( - GraphQLList(characterInterface), + GraphQLList(character_interface), description="The friends of the human, or an empty list if they have none.", - resolve=lambda human, info, **args: getFriends(human), + resolve=lambda human, _info: get_friends(human), ), "appearsIn": GraphQLField( - GraphQLList(episodeEnum), description="Which movies they appear in.", + GraphQLList(episode_enum), description="Which movies they appear in.", ), "homePlanet": GraphQLField( GraphQLString, description="The home planet of the human, or null if unknown.", ), }, - interfaces=[characterInterface], + interfaces=[character_interface], + description="A humanoid creature in the Star Wars universe.", ) -droidType = GraphQLObjectType( +droid_type = GraphQLObjectType( "Droid", - description="A mechanical creature in the Star Wars universe.", - fields=lambda: { + lambda: { "id": GraphQLField( GraphQLNonNull(GraphQLString), description="The id of the droid.", ), "name": GraphQLField(GraphQLString, description="The name of the droid.",), "friends": GraphQLField( - GraphQLList(characterInterface), + GraphQLList(character_interface), description="The friends of the droid, or an empty list if they have none.", - resolve=lambda droid, info, **args: getFriends(droid), + resolve=lambda droid, _info: get_friends(droid), ), "appearsIn": GraphQLField( - GraphQLList(episodeEnum), description="Which movies they appear in.", + GraphQLList(episode_enum), description="Which movies they appear in.", ), "primaryFunction": GraphQLField( GraphQLString, description="The primary function of the droid.", ), }, - interfaces=[characterInterface], + interfaces=[character_interface], + description="A mechanical creature in the Star Wars universe.", ) -reviewType = GraphQLObjectType( +review_type = GraphQLObjectType( "Review", - description="Represents a review for a movie", - fields=lambda: { - "episode": GraphQLField(episodeEnum, description="The movie"), + lambda: { + "episode": GraphQLField(episode_enum, description="The movie"), "stars": GraphQLField( GraphQLNonNull(GraphQLInt), description="The number of stars this review gave, 1-5", @@ -121,84 +125,83 @@ GraphQLString, description="Comment about the movie" ), }, + description="Represents a review for a movie", ) -reviewInputType = GraphQLInputObjectType( +review_input_type = GraphQLInputObjectType( "ReviewInput", - description="The input object sent when someone is creating a new review", - fields={ + lambda: { "stars": GraphQLInputField(GraphQLInt, description="0-5 stars"), "commentary": GraphQLInputField( GraphQLString, description="Comment about the movie, optional" ), }, + description="The input object sent when someone is creating a new review", ) -queryType = GraphQLObjectType( +query_type = GraphQLObjectType( "Query", - fields=lambda: { + lambda: { "hero": GraphQLField( - characterInterface, + character_interface, args={ "episode": GraphQLArgument( + episode_enum, description="If omitted, returns the hero of the whole saga. If " "provided, returns the hero of that particular episode.", - type_=episodeEnum, # type: ignore ) }, - resolve=lambda root, info, **args: getHeroAsync(args.get("episode")), + resolve=lambda _souce, _info, episode=None: get_hero_async(episode), ), "human": GraphQLField( - humanType, + human_type, args={ "id": GraphQLArgument( description="id of the human", type_=GraphQLNonNull(GraphQLString), ) }, - resolve=lambda root, info, **args: getHuman(args["id"]), + resolve=lambda _souce, _info, id: get_human(id), ), "droid": GraphQLField( - droidType, + droid_type, args={ "id": GraphQLArgument( description="id of the droid", type_=GraphQLNonNull(GraphQLString), ) }, - resolve=lambda root, info, **args: getDroid(args["id"]), + resolve=lambda _source, _info, id: get_droid(id), ), "characters": GraphQLField( - GraphQLList(characterInterface), + GraphQLList(character_interface), args={ "ids": GraphQLArgument( - description="list of character ids", - type_=GraphQLList(GraphQLString), + GraphQLList(GraphQLString), description="list of character ids", ) }, - resolve=lambda root, info, **args: getCharacters(args["ids"]), + resolve=lambda _source, _info, ids=None: get_characters(ids), ), }, ) -mutationType = GraphQLObjectType( +mutation_type = GraphQLObjectType( "Mutation", - description="The mutation type, represents all updates we can make to our data", - fields=lambda: { + lambda: { "createReview": GraphQLField( - reviewType, + review_type, args={ "episode": GraphQLArgument( - description="Episode to create review", - type_=episodeEnum, # type: ignore + episode_enum, description="Episode to create review", ), "review": GraphQLArgument( - description="set alive status", type_=reviewInputType, + description="set alive status", type_=review_input_type, ), }, - resolve=lambda root, info, **args: createReview( - args.get("episode"), args.get("review") + resolve=lambda _source, _info, episode=None, review=None: create_review( + episode, review ), ), }, + description="The mutation type, represents all updates we can make to our data", ) @@ -212,14 +215,14 @@ async def resolve_review(review, _info, **_args): return review -subscriptionType = GraphQLObjectType( +subscription_type = GraphQLObjectType( "Subscription", - fields=lambda: { + lambda: { "reviewAdded": GraphQLField( - reviewType, + review_type, args={ "episode": GraphQLArgument( - description="Episode to review", type_=episodeEnum, + episode_enum, description="Episode to review", ) }, subscribe=subscribe_reviews, @@ -230,10 +233,10 @@ async def resolve_review(review, _info, **_args): StarWarsSchema = GraphQLSchema( - query=queryType, - mutation=mutationType, - subscription=subscriptionType, - types=[humanType, droidType, reviewType, reviewInputType], + query=query_type, + mutation=mutation_type, + subscription=subscription_type, + types=[human_type, droid_type, review_type, review_input_type], ) From d3bbf667699f5384c9c9f98dca12778285a6fc76 Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Mon, 13 Dec 2021 12:27:00 +0100 Subject: [PATCH 05/11] fix print_ast removing last newline --- tests/fixtures/vcr_cassettes/queries.yaml | 10 +++++----- tests/starwars/test_dsl.py | 24 ++++++++--------------- tests/test_aiohttp.py | 6 +++--- tests/test_requests.py | 6 +++--- 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/tests/fixtures/vcr_cassettes/queries.yaml b/tests/fixtures/vcr_cassettes/queries.yaml index f3fa1c96..f2ff24ef 100644 --- a/tests/fixtures/vcr_cassettes/queries.yaml +++ b/tests/fixtures/vcr_cassettes/queries.yaml @@ -12,7 +12,7 @@ interactions: {\n ...TypeRef\n }\n defaultValue\n}\n\nfragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType - {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n}\n"}' + {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n}"}' headers: Accept: - '*/*' @@ -202,7 +202,7 @@ interactions: message: OK - request: body: '{"query": "{\n myFavoriteFilm: film(id: \"RmlsbToz\") {\n id\n title\n episodeId\n characters(first: - 5) {\n edges {\n node {\n name\n }\n }\n }\n }\n}\n"}' + 5) {\n edges {\n node {\n name\n }\n }\n }\n }\n}"}' headers: Accept: - '*/*' @@ -248,7 +248,7 @@ interactions: code: 200 message: OK - request: - body: '{"query": "query Planet($id: ID!) {\n planet(id: $id) {\n id\n name\n }\n}\n", + body: '{"query": "query Planet($id: ID!) {\n planet(id: $id) {\n id\n name\n }\n}", "variables": {"id": "UGxhbmV0OjEw"}}' headers: Accept: @@ -294,7 +294,7 @@ interactions: message: OK - request: body: '{"query": "query Planet1 {\n planet(id: \"UGxhbmV0OjEw\") {\n id\n name\n }\n}\n\nquery - Planet2 {\n planet(id: \"UGxhbmV0OjEx\") {\n id\n name\n }\n}\n", "operationName": + Planet2 {\n planet(id: \"UGxhbmV0OjEx\") {\n id\n name\n }\n}", "operationName": "Planet2"}' headers: Accept: @@ -339,7 +339,7 @@ interactions: code: 200 message: OK - request: - body: '{"query": "query Planet($id: ID!) {\n planet(id: $id) {\n id\n name\n }\n}\n"}' + body: '{"query": "query Planet($id: ID!) {\n planet(id: $id) {\n id\n name\n }\n}"}' headers: Accept: - '*/*' diff --git a/tests/starwars/test_dsl.py b/tests/starwars/test_dsl.py index 0335d721..6adc84a9 100644 --- a/tests/starwars/test_dsl.py +++ b/tests/starwars/test_dsl.py @@ -129,8 +129,7 @@ def test_use_variable_definition_multiple_times(ds): stars commentary } -} -""" +}""" ) @@ -151,8 +150,7 @@ def test_add_variable_definitions(ds): stars commentary } -} -""" +}""" ) @@ -177,8 +175,7 @@ def test_add_variable_definitions_in_input_object(ds): stars commentary } -} -""" +}""" ) @@ -376,8 +373,7 @@ def test_subscription(ds): stars commentary } -} -""" +}""" ) @@ -445,8 +441,7 @@ def test_operation_name(ds): hero { name } -} -""" +}""" ) @@ -476,8 +471,7 @@ def test_multiple_operations(ds): stars commentary } -} -""" +}""" ) @@ -535,8 +529,7 @@ def test_fragments(ds): hero { ...NameAndAppearances } -} -""" +}""" name_and_appearances = ( DSLFragment("NameAndAppearances") @@ -624,8 +617,7 @@ def test_dsl_nested_query_with_fragment(ds): } } } -} -""" +}""" name_and_appearances = ( DSLFragment("NameAndAppearances") diff --git a/tests/test_aiohttp.py b/tests/test_aiohttp.py index 682cea0d..f66dc1a9 100644 --- a/tests/test_aiohttp.py +++ b/tests/test_aiohttp.py @@ -496,7 +496,7 @@ def test_code(): file_upload_mutation_1_operations = ( '{"query": "mutation ($file: Upload!) {\\n uploadFile(input: {other_var: ' - '$other_var, file: $file}) {\\n success\\n }\\n}\\n", "variables": ' + '$other_var, file: $file}) {\\n success\\n }\\n}", "variables": ' '{"file": null, "other_var": 42}}' ) @@ -763,7 +763,7 @@ async def file_sender(file_name): file_upload_mutation_2_operations = ( '{"query": "mutation ($file1: Upload!, $file2: Upload!) {\\n ' - 'uploadFile(input: {file1: $file, file2: $file}) {\\n success\\n }\\n}\\n", ' + 'uploadFile(input: {file1: $file, file2: $file}) {\\n success\\n }\\n}", ' '"variables": {"file1": null, "file2": null}}' ) @@ -859,7 +859,7 @@ async def handler(request): file_upload_mutation_3_operations = ( '{"query": "mutation ($files: [Upload!]!) {\\n uploadFiles(input: {files: $files})' - ' {\\n success\\n }\\n}\\n", "variables": {"files": [null, null]}}' + ' {\\n success\\n }\\n}", "variables": {"files": [null, null]}}' ) file_upload_mutation_3_map = '{"0": ["variables.files.0"], "1": ["variables.files.1"]}' diff --git a/tests/test_requests.py b/tests/test_requests.py index c3123d72..1ed4ca56 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -347,7 +347,7 @@ def test_code(): file_upload_mutation_1_operations = ( '{"query": "mutation ($file: Upload!) {\\n uploadFile(input: {other_var: ' - '$other_var, file: $file}) {\\n success\\n }\\n}\\n", "variables": ' + '$other_var, file: $file}) {\\n success\\n }\\n}", "variables": ' '{"file": null, "other_var": 42}}' ) @@ -551,7 +551,7 @@ def test_code(): file_upload_mutation_2_operations = ( '{"query": "mutation ($file1: Upload!, $file2: Upload!) {\\n ' - 'uploadFile(input: {file1: $file, file2: $file}) {\\n success\\n }\\n}\\n", ' + 'uploadFile(input: {file1: $file, file2: $file}) {\\n success\\n }\\n}", ' '"variables": {"file1": null, "file2": null}}' ) @@ -651,7 +651,7 @@ def test_code(): file_upload_mutation_3_operations = ( '{"query": "mutation ($files: [Upload!]!) {\\n uploadFiles(input: {files: $files})' - ' {\\n success\\n }\\n}\\n", "variables": {"files": [null, null]}}' + ' {\\n success\\n }\\n}", "variables": {"files": [null, null]}}' ) From c763279710a3b365965b2cbc32cd0fc9856a5e90 Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Mon, 13 Dec 2021 12:58:39 +0100 Subject: [PATCH 06/11] Fix error: AttributeError: 'ParseResultVisitor' object has no attribute 'enter_leave_map' --- gql/utilities/parse_result.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gql/utilities/parse_result.py b/gql/utilities/parse_result.py index ecb73474..5f9dd2a4 100644 --- a/gql/utilities/parse_result.py +++ b/gql/utilities/parse_result.py @@ -102,6 +102,8 @@ def __init__( self.result_stack: List[Any] = [] + super().__init__() + @property def current_result(self): try: From 6d87de82a706491f281c08ba3b9a036c222f0d23 Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Mon, 13 Dec 2021 13:20:00 +0100 Subject: [PATCH 07/11] Rename specifiedByUrl to specifiedByURL See https://github.com/graphql/graphql-js/issues/3156 --- gql/utilities/get_introspection_query_ast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gql/utilities/get_introspection_query_ast.py b/gql/utilities/get_introspection_query_ast.py index bbb07771..d053c1c0 100644 --- a/gql/utilities/get_introspection_query_ast.py +++ b/gql/utilities/get_introspection_query_ast.py @@ -59,7 +59,7 @@ def get_introspection_query_ast( if descriptions: fragment_FullType.select(ds.__Type.description) if specified_by_url: - fragment_FullType.select(ds.__Type.specifiedByUrl) + fragment_FullType.select(ds.__Type.specifiedByURL) fields = ds.__Type.fields(includeDeprecated=True).select(ds.__Field.name) From 517f0f4d212ad5a2651d3019211731e78f117f96 Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Mon, 13 Dec 2021 13:21:43 +0100 Subject: [PATCH 08/11] Bump graphql-core version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7e97f8bc..b36ab8c3 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages install_requires = [ - "graphql-core>=3.1.5,<3.2", + "graphql-core>=3.2.0rc3,<3.3", "yarl>=1.6,<2.0", ] From 9d1433e8cc1bde4f42bc242cbfdecdd6992b9444 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 29 Dec 2021 17:21:20 +0100 Subject: [PATCH 09/11] Update GraphQL-core to v3.2.0rc4 This new version of GraphQL-core replaces the FrozenLists in AST nodes with tuples, so we need to make the appropriate changes in dsl.py. --- gql/dsl.py | 63 ++++++++++++---------------- gql/utilities/build_client_schema.py | 13 +++--- setup.py | 2 +- 3 files changed, 33 insertions(+), 45 deletions(-) diff --git a/gql/dsl.py b/gql/dsl.py index 5d363846..6a2e0718 100644 --- a/gql/dsl.py +++ b/gql/dsl.py @@ -6,7 +6,7 @@ import re from abc import ABC, abstractmethod from math import isfinite -from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple, Union, cast +from typing import Any, Dict, Iterable, Mapping, Optional, Tuple, Union, cast from graphql import ( ArgumentNode, @@ -61,7 +61,7 @@ is_wrapping_type, print_ast, ) -from graphql.pyutils import FrozenList, inspect +from graphql.pyutils import inspect from .utils import to_camel_case @@ -90,17 +90,17 @@ def ast_from_serialized_value_untyped(serialized: Any) -> Optional[ValueNode]: (key, ast_from_serialized_value_untyped(value)) for key, value in serialized.items() ) - field_nodes = ( + field_nodes = tuple( ObjectFieldNode(name=NameNode(value=field_name), value=field_value) for field_name, field_value in field_items if field_value ) - return ObjectValueNode(fields=FrozenList(field_nodes)) + return ObjectValueNode(fields=field_nodes) if isinstance(serialized, Iterable) and not isinstance(serialized, str): maybe_nodes = (ast_from_serialized_value_untyped(item) for item in serialized) - nodes = filter(None, maybe_nodes) - return ListValueNode(values=FrozenList(nodes)) + nodes = tuple(node for node in maybe_nodes if node) + return ListValueNode(values=nodes) if isinstance(serialized, bool): return BooleanValueNode(value=serialized) @@ -158,8 +158,8 @@ def ast_from_value(value: Any, type_: GraphQLInputType) -> Optional[ValueNode]: item_type = type_.of_type if isinstance(value, Iterable) and not isinstance(value, str): maybe_value_nodes = (ast_from_value(item, item_type) for item in value) - value_nodes = filter(None, maybe_value_nodes) - return ListValueNode(values=FrozenList(value_nodes)) + value_nodes = tuple(node for node in maybe_value_nodes if node) + return ListValueNode(values=value_nodes) return ast_from_value(value, item_type) # Populate the fields of the input object by creating ASTs from each value in the @@ -173,12 +173,12 @@ def ast_from_value(value: Any, type_: GraphQLInputType) -> Optional[ValueNode]: for field_name, field in type_.fields.items() if field_name in value ) - field_nodes = ( + field_nodes = tuple( ObjectFieldNode(name=NameNode(value=field_name), value=field_value) for field_name, field_value in field_items if field_value ) - return ObjectValueNode(fields=FrozenList(field_nodes)) + return ObjectValueNode(fields=field_nodes) if is_leaf_type(type_): # Since value is an internally represented value, it must be serialized to an @@ -314,7 +314,7 @@ def __init__( self, *fields: "DSLSelectable", **fields_with_alias: "DSLSelectableWithAlias", ): """:meta private:""" - self.selection_set = SelectionSetNode(selections=FrozenList([])) + self.selection_set = SelectionSetNode(selections=()) if fields or fields_with_alias: self.select(*fields, **fields_with_alias) @@ -355,14 +355,12 @@ def select( raise GraphQLError(f"Invalid field for {self!r}: {field!r}") # Get a list of AST Nodes for each added field - added_selections: List[ - Union[FieldNode, InlineFragmentNode, FragmentSpreadNode] - ] = [field.ast_field for field in added_fields] + added_selections: Tuple[ + Union[FieldNode, InlineFragmentNode, FragmentSpreadNode], ... + ] = tuple(field.ast_field for field in added_fields) # Update the current selection list with new selections - self.selection_set.selections = FrozenList( - self.selection_set.selections + added_selections - ) + self.selection_set.selections = self.selection_set.selections + added_selections log.debug(f"Added fields: {added_fields} in {self!r}") @@ -470,9 +468,7 @@ def executable_ast(self) -> OperationDefinitionNode: return OperationDefinitionNode( operation=OperationType(self.operation_type), selection_set=self.selection_set, - variable_definitions=FrozenList( - self.variable_definitions.get_ast_definitions() - ), + variable_definitions=self.variable_definitions.get_ast_definitions(), **({"name": NameNode(value=self.name)} if self.name else {}), ) @@ -548,19 +544,19 @@ def __getattr__(self, name: str) -> "DSLVariable": self.variables[name] = DSLVariable(name) return self.variables[name] - def get_ast_definitions(self) -> List[VariableDefinitionNode]: + def get_ast_definitions(self) -> Tuple[VariableDefinitionNode, ...]: """ :meta private: Return a list of VariableDefinitionNodes for each variable with a type """ - return [ + return tuple( VariableDefinitionNode( type=var.type, variable=var.ast_variable, default_value=None, ) for var in self.variables.values() if var.type is not None # only variables used - ] + ) class DSLType: @@ -770,7 +766,7 @@ def __init__( """ self.parent_type = parent_type self.field = field - self.ast_field = FieldNode(name=NameNode(value=name), arguments=FrozenList()) + self.ast_field = FieldNode(name=NameNode(value=name), arguments=()) log.debug(f"Creating {self!r}") @@ -803,15 +799,12 @@ def args(self, **kwargs) -> "DSLField": assert self.ast_field.arguments is not None - self.ast_field.arguments = FrozenList( - self.ast_field.arguments - + [ - ArgumentNode( - name=NameNode(value=name), - value=ast_from_value(value, self._get_argument(name).type), - ) - for name, value in kwargs.items() - ] + self.ast_field.arguments = self.ast_field.arguments + tuple( + ArgumentNode( + name=NameNode(value=name), + value=ast_from_value(value, self._get_argument(name).type), + ) + for name, value in kwargs.items() ) log.debug(f"Added arguments {kwargs} in field {self!r})") @@ -1022,9 +1015,7 @@ def executable_ast(self) -> FragmentDefinitionNode: return FragmentDefinitionNode( type_condition=NamedTypeNode(name=NameNode(value=self._type.name)), selection_set=self.selection_set, - variable_definitions=FrozenList( - self.variable_definitions.get_ast_definitions() - ), + variable_definitions=self.variable_definitions.get_ast_definitions(), name=NameNode(value=self.name), ) diff --git a/gql/utilities/build_client_schema.py b/gql/utilities/build_client_schema.py index 78fb7586..2fbff6e1 100644 --- a/gql/utilities/build_client_schema.py +++ b/gql/utilities/build_client_schema.py @@ -1,6 +1,4 @@ -from typing import Dict - -from graphql import GraphQLSchema +from graphql import GraphQLSchema, IntrospectionQuery from graphql import build_client_schema as build_client_schema_orig from graphql.pyutils import inspect @@ -50,7 +48,7 @@ } -def build_client_schema(introspection: Dict) -> GraphQLSchema: +def build_client_schema(introspection: IntrospectionQuery) -> GraphQLSchema: """This is an alternative to the graphql-core function :code:`build_client_schema` but with default include and skip directives added to the schema to fix @@ -77,13 +75,12 @@ def build_client_schema(introspection: Dict) -> GraphQLSchema: directives = schema_introspection.get("directives", None) if directives is None: - directives = [] - schema_introspection["directives"] = directives + schema_introspection["directives"] = directives = [] if not any(directive["name"] == "skip" for directive in directives): - directives.append(SKIP_DIRECTIVE_JSON) + directives.append(SKIP_DIRECTIVE_JSON) # type: ignore if not any(directive["name"] == "include" for directive in directives): - directives.append(INCLUDE_DIRECTIVE_JSON) + directives.append(INCLUDE_DIRECTIVE_JSON) # type: ignore return build_client_schema_orig(introspection, assume_valid=False) diff --git a/setup.py b/setup.py index b36ab8c3..2e32d004 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages install_requires = [ - "graphql-core>=3.2.0rc3,<3.3", + "graphql-core>=3.2.0rc4,<3.3", "yarl>=1.6,<2.0", ] From bc53e730867419982a4684048171e2ee8b0fa716 Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Wed, 29 Dec 2021 18:19:33 +0100 Subject: [PATCH 10/11] Fix Introspection directive typing --- gql/utilities/build_client_schema.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/gql/utilities/build_client_schema.py b/gql/utilities/build_client_schema.py index 2fbff6e1..048ed80d 100644 --- a/gql/utilities/build_client_schema.py +++ b/gql/utilities/build_client_schema.py @@ -1,17 +1,25 @@ from graphql import GraphQLSchema, IntrospectionQuery from graphql import build_client_schema as build_client_schema_orig from graphql.pyutils import inspect +from graphql.utilities.get_introspection_query import ( + DirectiveLocation, + IntrospectionDirective, +) __all__ = ["build_client_schema"] -INCLUDE_DIRECTIVE_JSON = { +INCLUDE_DIRECTIVE_JSON: IntrospectionDirective = { "name": "include", "description": ( "Directs the executor to include this field or fragment " "only when the `if` argument is true." ), - "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], + "locations": [ + DirectiveLocation.FIELD, + DirectiveLocation.FRAGMENT_SPREAD, + DirectiveLocation.INLINE_FRAGMENT, + ], "args": [ { "name": "if", @@ -26,13 +34,17 @@ ], } -SKIP_DIRECTIVE_JSON = { +SKIP_DIRECTIVE_JSON: IntrospectionDirective = { "name": "skip", "description": ( "Directs the executor to skip this field or fragment " "when the `if` argument is true." ), - "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], + "locations": [ + DirectiveLocation.FIELD, + DirectiveLocation.FRAGMENT_SPREAD, + DirectiveLocation.INLINE_FRAGMENT, + ], "args": [ { "name": "if", @@ -78,9 +90,9 @@ def build_client_schema(introspection: IntrospectionQuery) -> GraphQLSchema: schema_introspection["directives"] = directives = [] if not any(directive["name"] == "skip" for directive in directives): - directives.append(SKIP_DIRECTIVE_JSON) # type: ignore + directives.append(SKIP_DIRECTIVE_JSON) if not any(directive["name"] == "include" for directive in directives): - directives.append(INCLUDE_DIRECTIVE_JSON) # type: ignore + directives.append(INCLUDE_DIRECTIVE_JSON) return build_client_schema_orig(introspection, assume_valid=False) From 1de3c310e6948524d23776e498aafab39f588d94 Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Sun, 16 Jan 2022 14:03:32 +0100 Subject: [PATCH 11/11] Bump graphql-core version to stable 3.2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2e32d004..4b1de3e4 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages install_requires = [ - "graphql-core>=3.2.0rc4,<3.3", + "graphql-core>=3.2,<3.3", "yarl>=1.6,<2.0", ]