diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f6b1c0be..ad47cf50 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14', 'pypy3.9', 'pypy3.10', 'pypy3.11'] + python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', 'pypy3.10', 'pypy3.11'] steps: - name: Checkout project @@ -36,45 +36,4 @@ jobs: - name: Run unit tests with tox id: test - run: tox - - tests-old: - name: 🧪 Tests (older Python versions) - runs-on: ubuntu-22.04 - - strategy: - matrix: - python-version: ['3.7', '3.8'] - - steps: - - name: Checkout project - id: checkout - uses: actions/checkout@v5 - - - name: Set up Python 3.14 (tox runner) - id: setup-python - uses: actions/setup-python@v6 - with: - python-version: '3.14' - - - name: Install uv - id: setup-uv - uses: astral-sh/setup-uv@v6 - - - name: Install tox and plugins - id: install-tox - run: | - uv pip install --system tox tox-uv tox-gh-actions - - - name: Set up target Python ${{ matrix.python-version }} - id: setup-target-python - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - - - name: Run unit tests with tox for target - id: test - shell: bash - run: | - ENV="py${{ matrix.python-version }}"; ENV=${ENV/./} - python3.14 -m tox -e "$ENV" + run: tox \ No newline at end of file diff --git a/docs/usage/parser.rst b/docs/usage/parser.rst index 049fd7b3..7902adf2 100644 --- a/docs/usage/parser.rst +++ b/docs/usage/parser.rst @@ -35,30 +35,30 @@ This will give the same result as manually creating the AST document:: from graphql.language.ast import * - document = DocumentNode(definitions=[ + document = DocumentNode(definitions=( ObjectTypeDefinitionNode( name=NameNode(value='Query'), - fields=[ + fields=( FieldDefinitionNode( name=NameNode(value='me'), type=NamedTypeNode(name=NameNode(value='User')), - arguments=[], directives=[]) - ], directives=[], interfaces=[]), + arguments=(), directives=()), + ), interfaces=(), directives=()), ObjectTypeDefinitionNode( name=NameNode(value='User'), - fields=[ + fields=( FieldDefinitionNode( name=NameNode(value='id'), type=NamedTypeNode( name=NameNode(value='ID')), - arguments=[], directives=[]), + arguments=(), directives=()), FieldDefinitionNode( name=NameNode(value='name'), type=NamedTypeNode( name=NameNode(value='String')), - arguments=[], directives=[]), - ], directives=[], interfaces=[]), - ]) + arguments=(), directives=()), + ), interfaces=(), directives=()), + )) When parsing with ``no_location=False`` (the default), the AST nodes will also have a diff --git a/pyproject.toml b/pyproject.toml index 02cf786a..61e0667e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "graphql-core" version = "3.3.0a11" description = "GraphQL-core is a Python port of GraphQL.js, the JavaScript reference implementation for GraphQL." readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.10" license = "MIT" license-files = ["LICENSE"] authors = [ { name = "Christoph Zwerschke", email = "cito@online.de" } ] @@ -13,19 +13,13 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", ] -dependencies = [ - "typing-extensions>=4.12.2,<5; python_version >= '3.8' and python_version < '3.10'", - "typing-extensions>=4.7.1,<5; python_version < '3.8'", -] +dependencies = [] [project.urls] Homepage = "https://github.com/graphql-python/graphql-core" diff --git a/src/graphql/language/parser.py b/src/graphql/language/parser.py index 4373cde3..c301cb42 100644 --- a/src/graphql/language/parser.py +++ b/src/graphql/language/parser.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import Callable, List, Mapping, TypeVar, Union, cast +from typing import Callable, Mapping, TypeVar, Union, cast from ..error import GraphQLError, GraphQLSyntaxError from .ast import ( @@ -269,7 +269,8 @@ def __init__( def parse_name(self) -> NameNode: """Convert a name lex token into a name parse node.""" token = self.expect_token(TokenKind.NAME) - return NameNode(value=token.value, loc=self.loc(token)) + # NAME tokens always have a value + return NameNode(value=cast("str", token.value), loc=self.loc(token)) # Implement the parsing rules in the Document section. @@ -349,8 +350,8 @@ def parse_operation_definition(self) -> OperationDefinitionNode: return OperationDefinitionNode( operation=OperationType.QUERY, name=None, - variable_definitions=[], - directives=[], + variable_definitions=(), + directives=(), selection_set=self.parse_selection_set(), loc=self.loc(start), ) @@ -373,7 +374,7 @@ def parse_operation_type(self) -> OperationType: except ValueError as error: raise self.unexpected(operation_token) from error - def parse_variable_definitions(self) -> list[VariableDefinitionNode]: + def parse_variable_definitions(self) -> tuple[VariableDefinitionNode, ...]: """VariableDefinitions: (VariableDefinition+)""" return self.optional_many( TokenKind.PAREN_L, self.parse_variable_definition, TokenKind.PAREN_R @@ -382,9 +383,12 @@ def parse_variable_definitions(self) -> list[VariableDefinitionNode]: def parse_variable_definition(self) -> VariableDefinitionNode: """VariableDefinition: Variable: Type DefaultValue? Directives[Const]?""" start = self._lexer.token + variable = self.parse_variable() + self.expect_token(TokenKind.COLON) + type_ = self.parse_type_reference() return VariableDefinitionNode( - variable=self.parse_variable(), - type=self.expect_token(TokenKind.COLON) and self.parse_type_reference(), + variable=variable, + type=type_, default_value=self.parse_const_value_literal() if self.expect_optional_token(TokenKind.EQUALS) else None, @@ -448,27 +452,27 @@ def parse_nullability_assertion(self) -> NullabilityAssertionNode | None: return None start = self._lexer.token - nullability_assertion: NullabilityAssertionNode | None = None + list_nullability: ListNullabilityOperatorNode | None = None if self.expect_optional_token(TokenKind.BRACKET_L): inner_modifier = self.parse_nullability_assertion() self.expect_token(TokenKind.BRACKET_R) - nullability_assertion = ListNullabilityOperatorNode( + list_nullability = ListNullabilityOperatorNode( nullability_assertion=inner_modifier, loc=self.loc(start) ) if self.expect_optional_token(TokenKind.BANG): - nullability_assertion = NonNullAssertionNode( - nullability_assertion=nullability_assertion, loc=self.loc(start) + return NonNullAssertionNode( + nullability_assertion=list_nullability, loc=self.loc(start) ) - elif self.expect_optional_token(TokenKind.QUESTION_MARK): - nullability_assertion = ErrorBoundaryNode( - nullability_assertion=nullability_assertion, loc=self.loc(start) + if self.expect_optional_token(TokenKind.QUESTION_MARK): + return ErrorBoundaryNode( + nullability_assertion=list_nullability, loc=self.loc(start) ) - return nullability_assertion + return list_nullability - def parse_arguments(self, is_const: bool) -> list[ArgumentNode]: + def parse_arguments(self, is_const: bool) -> tuple[ArgumentNode, ...]: """Arguments[Const]: (Argument[?Const]+)""" item = self.parse_const_argument if is_const else self.parse_argument return self.optional_many( @@ -533,6 +537,7 @@ def parse_fragment_definition(self) -> FragmentDefinitionNode: ) return FragmentDefinitionNode( name=self.parse_fragment_name(), + variable_definitions=(), type_condition=self.parse_type_condition(), directives=self.parse_directives(False), selection_set=self.parse_selection_set(), @@ -572,8 +577,9 @@ def parse_value_literal(self, is_const: bool) -> ValueNode: def parse_string_literal(self, _is_const: bool = False) -> StringValueNode: token = self._lexer.token self.advance_lexer() + # STRING and BLOCK_STRING tokens always have a value return StringValueNode( - value=token.value, + value=token.value or "", block=token.kind == TokenKind.BLOCK_STRING, loc=self.loc(token), ) @@ -608,16 +614,19 @@ def parse_object(self, is_const: bool) -> ObjectValueNode: def parse_int(self, _is_const: bool = False) -> IntValueNode: token = self._lexer.token self.advance_lexer() - return IntValueNode(value=token.value, loc=self.loc(token)) + # INT tokens always have a value + return IntValueNode(value=token.value or "", loc=self.loc(token)) def parse_float(self, _is_const: bool = False) -> FloatValueNode: token = self._lexer.token self.advance_lexer() - return FloatValueNode(value=token.value, loc=self.loc(token)) + # FLOAT tokens always have a value + return FloatValueNode(value=token.value or "", loc=self.loc(token)) def parse_named_values(self, _is_const: bool = False) -> ValueNode: token = self._lexer.token - value = token.value + # NAME tokens always have a value + value = token.value or "" self.advance_lexer() if value == "true": return BooleanValueNode(value=True, loc=self.loc(token)) @@ -646,16 +655,16 @@ def parse_const_value_literal(self) -> ConstValueNode: # Implement the parsing rules in the Directives section. - def parse_directives(self, is_const: bool) -> list[DirectiveNode]: + def parse_directives(self, is_const: bool) -> tuple[DirectiveNode, ...]: """Directives[Const]: Directive[?Const]+""" directives: list[DirectiveNode] = [] append = directives.append while self.peek(TokenKind.AT): append(self.parse_directive(is_const)) - return directives + return tuple(directives) - def parse_const_directives(self) -> list[ConstDirectiveNode]: - return cast("List[ConstDirectiveNode]", self.parse_directives(True)) + def parse_const_directives(self) -> tuple[ConstDirectiveNode, ...]: + return cast("tuple[ConstDirectiveNode, ...]", self.parse_directives(True)) def parse_directive(self, is_const: bool) -> DirectiveNode: """Directive[Const]: @ Name Arguments[?Const]?""" @@ -778,15 +787,15 @@ def parse_object_type_definition(self) -> ObjectTypeDefinitionNode: loc=self.loc(start), ) - def parse_implements_interfaces(self) -> list[NamedTypeNode]: + def parse_implements_interfaces(self) -> tuple[NamedTypeNode, ...]: """ImplementsInterfaces""" return ( self.delimited_many(TokenKind.AMP, self.parse_named_type) if self.expect_optional_keyword("implements") - else [] + else () ) - def parse_fields_definition(self) -> list[FieldDefinitionNode]: + def parse_fields_definition(self) -> tuple[FieldDefinitionNode, ...]: """FieldsDefinition: {FieldDefinition+}""" return self.optional_many( TokenKind.BRACE_L, self.parse_field_definition, TokenKind.BRACE_R @@ -810,7 +819,7 @@ def parse_field_definition(self) -> FieldDefinitionNode: loc=self.loc(start), ) - def parse_argument_defs(self) -> list[InputValueDefinitionNode]: + def parse_argument_defs(self) -> tuple[InputValueDefinitionNode, ...]: """ArgumentsDefinition: (InputValueDefinition+)""" return self.optional_many( TokenKind.PAREN_L, self.parse_input_value_def, TokenKind.PAREN_R @@ -872,12 +881,12 @@ def parse_union_type_definition(self) -> UnionTypeDefinitionNode: loc=self.loc(start), ) - def parse_union_member_types(self) -> list[NamedTypeNode]: + def parse_union_member_types(self) -> tuple[NamedTypeNode, ...]: """UnionMemberTypes""" return ( self.delimited_many(TokenKind.PIPE, self.parse_named_type) if self.expect_optional_token(TokenKind.EQUALS) - else [] + else () ) def parse_enum_type_definition(self) -> EnumTypeDefinitionNode: @@ -896,7 +905,7 @@ def parse_enum_type_definition(self) -> EnumTypeDefinitionNode: loc=self.loc(start), ) - def parse_enum_values_definition(self) -> list[EnumValueDefinitionNode]: + def parse_enum_values_definition(self) -> tuple[EnumValueDefinitionNode, ...]: """EnumValuesDefinition: {EnumValueDefinition+}""" return self.optional_many( TokenKind.BRACE_L, self.parse_enum_value_definition, TokenKind.BRACE_R @@ -942,7 +951,7 @@ def parse_input_object_type_definition(self) -> InputObjectTypeDefinitionNode: loc=self.loc(start), ) - def parse_input_fields_definition(self) -> list[InputValueDefinitionNode]: + def parse_input_fields_definition(self) -> tuple[InputValueDefinitionNode, ...]: """InputFieldsDefinition: {InputValueDefinition+}""" return self.optional_many( TokenKind.BRACE_L, self.parse_input_value_def, TokenKind.BRACE_R @@ -1076,7 +1085,7 @@ def parse_directive_definition(self) -> DirectiveDefinitionNode: loc=self.loc(start), ) - def parse_directive_locations(self) -> list[NameNode]: + def parse_directive_locations(self) -> tuple[NameNode, ...]: """DirectiveLocations""" return self.delimited_many(TokenKind.PIPE, self.parse_directive_location) @@ -1173,11 +1182,11 @@ def unexpected(self, at_token: Token | None = None) -> GraphQLError: def any( self, open_kind: TokenKind, parse_fn: Callable[[], T], close_kind: TokenKind - ) -> list[T]: + ) -> tuple[T, ...]: """Fetch any matching nodes, possibly none. - Returns a possibly empty list of parse nodes, determined by the ``parse_fn``. - This list begins with a lex token of ``open_kind`` and ends with a lex token of + Returns a possibly empty tuple of parse nodes, determined by the ``parse_fn``. + This tuple begins with a lex token of ``open_kind`` and ends with a lex token of ``close_kind``. Advances the parser to the next lex token after the closing token. """ @@ -1187,16 +1196,16 @@ def any( expect_optional_token = partial(self.expect_optional_token, close_kind) while not expect_optional_token(): append(parse_fn()) - return nodes + return tuple(nodes) def optional_many( self, open_kind: TokenKind, parse_fn: Callable[[], T], close_kind: TokenKind - ) -> list[T]: + ) -> tuple[T, ...]: """Fetch matching nodes, maybe none. - Returns a list of parse nodes, determined by the ``parse_fn``. It can be empty + Returns a tuple of parse nodes, determined by the ``parse_fn``. It can be empty only if the open token is missing, otherwise it will always return a non-empty - list that begins with a lex token of ``open_kind`` and ends with a lex token of + tuple that begins with a lex token of ``open_kind`` and ends with a lex token of ``close_kind``. Advances the parser to the next lex token after the closing token. """ @@ -1206,16 +1215,16 @@ def optional_many( expect_optional_token = partial(self.expect_optional_token, close_kind) while not expect_optional_token(): append(parse_fn()) - return nodes - return [] + return tuple(nodes) + return () def many( self, open_kind: TokenKind, parse_fn: Callable[[], T], close_kind: TokenKind - ) -> list[T]: + ) -> tuple[T, ...]: """Fetch matching nodes, at least one. - Returns a non-empty list of parse nodes, determined by the ``parse_fn``. This - list begins with a lex token of ``open_kind`` and ends with a lex token of + Returns a non-empty tuple of parse nodes, determined by the ``parse_fn``. This + tuple begins with a lex token of ``open_kind`` and ends with a lex token of ``close_kind``. Advances the parser to the next lex token after the closing token. """ @@ -1225,17 +1234,17 @@ def many( expect_optional_token = partial(self.expect_optional_token, close_kind) while not expect_optional_token(): append(parse_fn()) - return nodes + return tuple(nodes) def delimited_many( self, delimiter_kind: TokenKind, parse_fn: Callable[[], T] - ) -> list[T]: + ) -> tuple[T, ...]: """Fetch many delimited nodes. - Returns a non-empty list of parse nodes, determined by the ``parse_fn``. This - list may begin with a lex token of ``delimiter_kind`` followed by items + Returns a non-empty tuple of parse nodes, determined by the ``parse_fn``. This + tuple may begin with a lex token of ``delimiter_kind`` followed by items separated by lex tokens of ``delimiter_kind``. Advances the parser to the next - lex token after the last item in the list. + lex token after the last item in the tuple. """ expect_optional_token = partial(self.expect_optional_token, delimiter_kind) expect_optional_token() @@ -1245,7 +1254,7 @@ def delimited_many( append(parse_fn()) if not expect_optional_token(): break - return nodes + return tuple(nodes) def advance_lexer(self) -> None: """Advance the lexer.""" diff --git a/src/graphql/language/visitor.py b/src/graphql/language/visitor.py index c9901230..1bba2199 100644 --- a/src/graphql/language/visitor.py +++ b/src/graphql/language/visitor.py @@ -2,7 +2,6 @@ from __future__ import annotations -from copy import copy from enum import Enum from typing import ( Any, @@ -231,9 +230,11 @@ def visit( node[array_key] = edit_value node = tuple(node) else: - node = copy(node) + # Create new node with edited values (immutable-friendly) + values = {k: getattr(node, k) for k in node.keys} for edit_key, edit_value in edits: - setattr(node, edit_key, edit_value) + values[edit_key] = edit_value + node = node.__class__(**values) idx = stack.idx keys = stack.keys edits = stack.edits diff --git a/src/graphql/utilities/ast_to_dict.py b/src/graphql/utilities/ast_to_dict.py index 10f13c15..c276868d 100644 --- a/src/graphql/utilities/ast_to_dict.py +++ b/src/graphql/utilities/ast_to_dict.py @@ -45,14 +45,18 @@ def ast_to_dict( elif node in cache: return cache[node] cache[node] = res = {} + # Note: We don't use msgspec.structs.asdict() because loc needs special + # handling (converted to {start, end} dict rather than full Location object) + # Filter out 'loc' - it's handled separately for the locations option + fields = [f for f in node.keys if f != "loc"] res.update( { key: ast_to_dict(getattr(node, key), locations, cache) - for key in ("kind", *node.keys[1:]) + for key in ("kind", *fields) } ) if locations: - loc = node.loc + loc = getattr(node, "loc", None) if loc: res["loc"] = {"start": loc.start, "end": loc.end} return res diff --git a/src/graphql/utilities/concat_ast.py b/src/graphql/utilities/concat_ast.py index 806292f9..6a2398c3 100644 --- a/src/graphql/utilities/concat_ast.py +++ b/src/graphql/utilities/concat_ast.py @@ -17,6 +17,5 @@ def concat_ast(asts: Collection[DocumentNode]) -> DocumentNode: the ASTs together into batched AST, useful for validating many GraphQL source files which together represent one conceptual application. """ - return DocumentNode( - definitions=list(chain.from_iterable(document.definitions for document in asts)) - ) + all_definitions = chain.from_iterable(doc.definitions for doc in asts) + return DocumentNode(definitions=tuple(all_definitions)) diff --git a/src/graphql/utilities/separate_operations.py b/src/graphql/utilities/separate_operations.py index 53867662..45589404 100644 --- a/src/graphql/utilities/separate_operations.py +++ b/src/graphql/utilities/separate_operations.py @@ -60,7 +60,7 @@ def separate_operations(document_ast: DocumentNode) -> dict[str, DocumentNode]: # The list of definition nodes to be included for this operation, sorted # to retain the same order as the original document. separated_document_asts[operation_name] = DocumentNode( - definitions=[ + definitions=tuple( node for node in document_ast.definitions if node is operation @@ -68,7 +68,7 @@ def separate_operations(document_ast: DocumentNode) -> dict[str, DocumentNode]: isinstance(node, FragmentDefinitionNode) and node.name.value in dependencies ) - ] + ) ) return separated_document_asts diff --git a/src/graphql/utilities/sort_value_node.py b/src/graphql/utilities/sort_value_node.py index bf20cf37..970978ee 100644 --- a/src/graphql/utilities/sort_value_node.py +++ b/src/graphql/utilities/sort_value_node.py @@ -2,8 +2,6 @@ from __future__ import annotations -from copy import copy - from ..language import ListValueNode, ObjectFieldNode, ObjectValueNode, ValueNode from ..pyutils import natural_comparison_key @@ -18,18 +16,23 @@ def sort_value_node(value_node: ValueNode) -> ValueNode: For internal use only. """ if isinstance(value_node, ObjectValueNode): - value_node = copy(value_node) - value_node.fields = sort_fields(value_node.fields) + # Create new node with updated fields (immutable-friendly copy-on-write) + values = {k: getattr(value_node, k) for k in value_node.keys} + values["fields"] = sort_fields(value_node.fields) + value_node = value_node.__class__(**values) elif isinstance(value_node, ListValueNode): - value_node = copy(value_node) - value_node.values = tuple(sort_value_node(value) for value in value_node.values) + # Create new node with updated values (immutable-friendly copy-on-write) + values = {k: getattr(value_node, k) for k in value_node.keys} + values["values"] = tuple(sort_value_node(value) for value in value_node.values) + value_node = value_node.__class__(**values) return value_node def sort_field(field: ObjectFieldNode) -> ObjectFieldNode: - field = copy(field) - field.value = sort_value_node(field.value) - return field + # Create new node with updated value (immutable-friendly copy-on-write) + values = {k: getattr(field, k) for k in field.keys} + values["value"] = sort_value_node(field.value) + return field.__class__(**values) def sort_fields(fields: tuple[ObjectFieldNode, ...]) -> tuple[ObjectFieldNode, ...]: diff --git a/tests/benchmarks/test_serialization.py b/tests/benchmarks/test_serialization.py new file mode 100644 index 00000000..e02e99c8 --- /dev/null +++ b/tests/benchmarks/test_serialization.py @@ -0,0 +1,50 @@ +"""Benchmarks for pickle serialization of parsed queries. + +This module benchmarks pickle serialization using a large query (~100KB) +to provide realistic performance numbers for query caching use cases. +""" + +import pickle + +from graphql import parse + +from ..fixtures import large_query # noqa: F401 + +# Parse benchmark + + +def test_parse_large_query(benchmark, large_query): # noqa: F811 + """Benchmark parsing large query.""" + result = benchmark(lambda: parse(large_query, no_location=True)) + assert result is not None + + +# Pickle benchmarks + + +def test_pickle_large_query_roundtrip(benchmark, large_query): # noqa: F811 + """Benchmark pickle roundtrip for large query AST.""" + document = parse(large_query, no_location=True) + + def roundtrip(): + encoded = pickle.dumps(document) + return pickle.loads(encoded) + + result = benchmark(roundtrip) + assert result == document + + +def test_pickle_large_query_encode(benchmark, large_query): # noqa: F811 + """Benchmark pickle encoding for large query AST.""" + document = parse(large_query, no_location=True) + result = benchmark(lambda: pickle.dumps(document)) + assert isinstance(result, bytes) + + +def test_pickle_large_query_decode(benchmark, large_query): # noqa: F811 + """Benchmark pickle decoding for large query AST.""" + document = parse(large_query, no_location=True) + encoded = pickle.dumps(document) + + result = benchmark(lambda: pickle.loads(encoded)) + assert result == document diff --git a/tests/error/test_graphql_error.py b/tests/error/test_graphql_error.py index c7db5d13..a206f673 100644 --- a/tests/error/test_graphql_error.py +++ b/tests/error/test_graphql_error.py @@ -4,7 +4,7 @@ from graphql.error import GraphQLError from graphql.language import ( - Node, + NameNode, ObjectTypeDefinitionNode, OperationDefinitionNode, Source, @@ -352,7 +352,7 @@ def formats_graphql_error(): extensions = {"ext": None} error = GraphQLError( "test message", - Node(), + NameNode(value="stub"), Source( """ query { diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py index 5e4058f9..8b2fdb0b 100644 --- a/tests/fixtures/__init__.py +++ b/tests/fixtures/__init__.py @@ -12,6 +12,7 @@ "cleanup", "kitchen_sink_query", "kitchen_sink_sdl", + "large_query", ] @@ -54,3 +55,8 @@ def big_schema_sdl(): @pytest.fixture(scope="module") def big_schema_introspection_result(): return read_json("github_schema") + + +@pytest.fixture(scope="module") +def large_query(): + return read_graphql("large_query") diff --git a/tests/fixtures/large_query.graphql b/tests/fixtures/large_query.graphql new file mode 100644 index 00000000..d4607588 --- /dev/null +++ b/tests/fixtures/large_query.graphql @@ -0,0 +1,7006 @@ +# Large query for serialization benchmarks +query LargeQuery( + $orgId: ID! + $first: Int! + $after: String + $includeArchived: Boolean = false + $searchTerm: String + $sortBy: SortOrder = DESC +) { + viewer { + id + login + name + email + } + + org0: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members0: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos0: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org1: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members1: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos1: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org2: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members2: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos2: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org3: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members3: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos3: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org4: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members4: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos4: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org5: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members5: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos5: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org6: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members6: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos6: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org7: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members7: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos7: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org8: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members8: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos8: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org9: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members9: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos9: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org10: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members10: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos10: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org11: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members11: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos11: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org12: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members12: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos12: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org13: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members13: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos13: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org14: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members14: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos14: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org15: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members15: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos15: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org16: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members16: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos16: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org17: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members17: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos17: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org18: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members18: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos18: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org19: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members19: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos19: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org20: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members20: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos20: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org21: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members21: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos21: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org22: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members22: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos22: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org23: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members23: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos23: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org24: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members24: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos24: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org25: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members25: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos25: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org26: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members26: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos26: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org27: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members27: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos27: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org28: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members28: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos28: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org29: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members29: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos29: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org30: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members30: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos30: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org31: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members31: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos31: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org32: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members32: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos32: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org33: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members33: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos33: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org34: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members34: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos34: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org35: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members35: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos35: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org36: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members36: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos36: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org37: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members37: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos37: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org38: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members38: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos38: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org39: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members39: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos39: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org40: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members40: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos40: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org41: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members41: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos41: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org42: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members42: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos42: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org43: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members43: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos43: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org44: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members44: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos44: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org45: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members45: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos45: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org46: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members46: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos46: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org47: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members47: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos47: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org48: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members48: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos48: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } + + org49: organization(id: $orgId) { + id + name + description + createdAt + updatedAt + membersCount + teamsCount + repositoriesCount + + owner { + id + login + email + avatarUrl + createdAt + } + + members49: members(first: $first, after: $after) { + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + edges { + cursor + node { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + followers { + totalCount + } + following { + totalCount + } + repositories { + totalCount + } + gists { + totalCount + } + starredRepositories { + totalCount + } + } + } + } + + repos49: repositories(first: $first, after: $after, includeArchived: $includeArchived) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + + primaryLanguage { + id + name + color + } + + stargazerCount + forkCount + watcherCount + + defaultBranchRef { + name + target { + ... on Commit { + id + message + messageHeadline + committedDate + author { + name + email + date + } + } + } + } + + licenseInfo { + key + name + spdxId + } + } + } + } + } +} + +fragment UserFragment0 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment0 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment1 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment1 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment2 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment2 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment3 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment3 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment4 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment4 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment5 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment5 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment6 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment6 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment7 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment7 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment8 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment8 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment9 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment9 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment10 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment10 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment11 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment11 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment12 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment12 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment13 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment13 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment14 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment14 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment15 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment15 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment16 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment16 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment17 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment17 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment18 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment18 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} + +fragment UserFragment19 on User { + id + login + name + email + avatarUrl + bio + company + location + websiteUrl + twitterUsername + createdAt + updatedAt + isHireable + pronouns + status { + message + emoji + } +} + +fragment RepositoryFragment19 on Repository { + id + name + nameWithOwner + description + url + homepageUrl + isPrivate + isArchived + isFork + isEmpty + isTemplate + createdAt + updatedAt + pushedAt + diskUsage + stargazerCount + forkCount + watcherCount +} diff --git a/tests/language/test_ast.py b/tests/language/test_ast.py index a1da0dab..9c1f5c84 100644 --- a/tests/language/test_ast.py +++ b/tests/language/test_ast.py @@ -10,15 +10,22 @@ class SampleTestNode(Node): __slots__ = "alpha", "beta" - alpha: int - beta: int + alpha: int | Node # Union with Node to support copy tests with nested nodes + beta: int | Node | None class SampleNamedNode(Node): __slots__ = "foo", "name" foo: str - name: str | None + name: NameNode | None + + +def make_loc(start: int = 1, end: int = 3) -> Location: + """Create a Location for testing with the given start/end offsets.""" + source = Source("test source") + start_token = Token(TokenKind.NAME, start, end, 1, start, "test") + return Location(start_token, start_token, source) def describe_token_class(): @@ -150,43 +157,61 @@ def can_hash(): def describe_node_class(): def initializes_with_keywords(): - node = SampleTestNode(alpha=1, beta=2, loc=0) + node = SampleTestNode(alpha=1, beta=2) assert node.alpha == 1 assert node.beta == 2 - assert node.loc == 0 - node = SampleTestNode(alpha=1, loc=None) assert node.loc is None + + def initializes_with_location(): + loc = make_loc() + node = SampleTestNode(alpha=1, beta=2, loc=loc) assert node.alpha == 1 - assert node.beta is None - node = SampleTestNode(alpha=1, beta=2, gamma=3) + assert node.beta == 2 + assert node.loc is loc + + def initializes_with_none_location(): + node = SampleTestNode(alpha=1, beta=2, loc=None) + assert node.loc is None assert node.alpha == 1 assert node.beta == 2 assert not hasattr(node, "gamma") + def converts_list_to_tuple_on_init(): + from graphql.language import FieldNode, SelectionSetNode + + field = FieldNode(name=NameNode(value="foo")) + node = SelectionSetNode(selections=[field]) # Pass list, not tuple + assert isinstance(node.selections, tuple) + assert node.selections == (field,) + def has_representation_with_loc(): node = SampleTestNode(alpha=1, beta=2) assert repr(node) == "SampleTestNode" - node = SampleTestNode(alpha=1, beta=2, loc=3) - assert repr(node) == "SampleTestNode at 3" + loc = make_loc(start=3, end=5) + node = SampleTestNode(alpha=1, beta=2, loc=loc) + assert repr(node) == "SampleTestNode at 3:5" def has_representation_when_named(): name_node = NameNode(value="baz") node = SampleNamedNode(foo="bar", name=name_node) assert repr(node) == "SampleNamedNode(name='baz')" - node = SampleNamedNode(alpha=1, beta=2, name=name_node, loc=3) - assert repr(node) == "SampleNamedNode(name='baz') at 3" + loc = make_loc(start=3, end=5) + node = SampleNamedNode(foo="bar", name=name_node, loc=loc) + assert repr(node) == "SampleNamedNode(name='baz') at 3:5" def has_representation_when_named_but_name_is_none(): - node = SampleNamedNode(alpha=1, beta=2, name=None) + node = SampleNamedNode(foo="bar", name=None) assert repr(node) == "SampleNamedNode" - node = SampleNamedNode(alpha=1, beta=2, name=None, loc=3) - assert repr(node) == "SampleNamedNode at 3" + loc = make_loc(start=3, end=5) + node = SampleNamedNode(foo="bar", name=None, loc=loc) + assert repr(node) == "SampleNamedNode at 3:5" def has_special_representation_when_it_is_a_name_node(): node = NameNode(value="foo") assert repr(node) == "NameNode('foo')" - node = NameNode(value="foo", loc=3) - assert repr(node) == "NameNode('foo') at 3" + loc = make_loc(start=3, end=5) + node = NameNode(value="foo", loc=loc) + assert repr(node) == "NameNode('foo') at 3:5" def can_check_equality(): node = SampleTestNode(alpha=1, beta=2) diff --git a/tests/language/test_schema_parser.py b/tests/language/test_schema_parser.py index df64381a..fd410c40 100644 --- a/tests/language/test_schema_parser.py +++ b/tests/language/test_schema_parser.py @@ -3,7 +3,6 @@ import pickle from copy import deepcopy from textwrap import dedent -from typing import Optional, Tuple import pytest @@ -11,6 +10,8 @@ from graphql.language import ( ArgumentNode, BooleanValueNode, + ConstDirectiveNode, + ConstValueNode, DirectiveDefinitionNode, DirectiveNode, DocumentNode, @@ -22,6 +23,7 @@ InterfaceTypeDefinitionNode, InterfaceTypeExtensionNode, ListTypeNode, + Location, NamedTypeNode, NameNode, NonNullTypeNode, @@ -32,25 +34,30 @@ ScalarTypeDefinitionNode, SchemaDefinitionNode, SchemaExtensionNode, + Source, StringValueNode, + Token, + TokenKind, TypeNode, UnionTypeDefinitionNode, - ValueNode, parse, ) from ..fixtures import kitchen_sink_sdl # noqa: F401 -try: - from typing import TypeAlias -except ImportError: # Python < 3.10 - from typing_extensions import TypeAlias - -Location: TypeAlias = Optional[Tuple[int, int]] +def make_loc(position: tuple[int, int]) -> Location: + """Create a Location for testing with the given (start, end) offsets.""" + source = Source(body="") + token = Token( + kind=TokenKind.NAME, start=position[0], end=position[1], line=1, column=1 + ) + return Location(start_token=token, end_token=token, source=source) -def assert_syntax_error(text: str, message: str, location: Location) -> None: +def assert_syntax_error( + text: str, message: str, location: tuple[int, int] | None +) -> None: with pytest.raises(GraphQLSyntaxError) as exc_info: parse(text) error = exc_info.value @@ -59,85 +66,104 @@ def assert_syntax_error(text: str, message: str, location: Location) -> None: assert error.locations == [location] -def assert_definitions(body: str, loc: Location, num=1): +def assert_definitions(body: str, position: tuple[int, int] | None, num: int = 1): doc = parse(body) assert isinstance(doc, DocumentNode) - assert doc.loc == loc + assert doc.loc == position definitions = doc.definitions assert isinstance(definitions, tuple) assert len(definitions) == num return definitions[0] if num == 1 else definitions -def type_node(name: str, loc: Location): - return NamedTypeNode(name=name_node(name, loc), loc=loc) +def type_node(name: str, position: tuple[int, int]): + return NamedTypeNode(name=name_node(name, position), loc=make_loc(position)) -def name_node(name: str, loc: Location): - return NameNode(value=name, loc=loc) +def name_node(name: str, position: tuple[int, int]): + return NameNode(value=name, loc=make_loc(position)) -def field_node(name: NameNode, type_: TypeNode, loc: Location): - return field_node_with_args(name, type_, [], loc) +def field_node(name: NameNode, type_: TypeNode, position: tuple[int, int]): + return field_node_with_args(name, type_, (), position) -def field_node_with_args(name: NameNode, type_: TypeNode, args: list, loc: Location): +def field_node_with_args( + name: NameNode, type_: TypeNode, args: tuple, position: tuple[int, int] +): return FieldDefinitionNode( - name=name, arguments=args, type=type_, directives=[], loc=loc, description=None + name=name, + arguments=args, + type=type_, + directives=(), + loc=make_loc(position), + description=None, ) -def non_null_type(type_: TypeNode, loc: Location): - return NonNullTypeNode(type=type_, loc=loc) +def non_null_type(type_: NamedTypeNode | ListTypeNode, position: tuple[int, int]): + return NonNullTypeNode(type=type_, loc=make_loc(position)) -def enum_value_node(name: str, loc: Location): +def enum_value_node(name: str, position: tuple[int, int]): return EnumValueDefinitionNode( - name=name_node(name, loc), directives=[], loc=loc, description=None + name=name_node(name, position), + directives=(), + loc=make_loc(position), + description=None, ) def input_value_node( - name: NameNode, type_: TypeNode, default_value: ValueNode | None, loc: Location + name: NameNode, + type_: TypeNode, + default_value: ConstValueNode | None, + position: tuple[int, int], ): return InputValueDefinitionNode( name=name, type=type_, default_value=default_value, - directives=[], - loc=loc, + directives=(), + loc=make_loc(position), description=None, ) -def boolean_value_node(value: bool, loc: Location): - return BooleanValueNode(value=value, loc=loc) +def boolean_value_node(value: bool, position: tuple[int, int]): + return BooleanValueNode(value=value, loc=make_loc(position)) -def string_value_node(value: str, block: bool | None, loc: Location): - return StringValueNode(value=value, block=block, loc=loc) +def string_value_node(value: str, block: bool | None, position: tuple[int, int]): + return StringValueNode(value=value, block=block, loc=make_loc(position)) -def list_type_node(type_: TypeNode, loc: Location): - return ListTypeNode(type=type_, loc=loc) +def list_type_node(type_: TypeNode, position: tuple[int, int]): + return ListTypeNode(type=type_, loc=make_loc(position)) def schema_extension_node( - directives: list[DirectiveNode], - operation_types: list[OperationTypeDefinitionNode], - loc: Location, + directives: tuple[ConstDirectiveNode, ...], + operation_types: tuple[OperationTypeDefinitionNode, ...], + position: tuple[int, int], ): return SchemaExtensionNode( - directives=directives, operation_types=operation_types, loc=loc + directives=directives, operation_types=operation_types, loc=make_loc(position) ) -def operation_type_definition(operation: OperationType, type_: TypeNode, loc: Location): - return OperationTypeDefinitionNode(operation=operation, type=type_, loc=loc) +def operation_type_definition( + operation: OperationType, type_: NamedTypeNode, position: tuple[int, int] +): + return OperationTypeDefinitionNode( + operation=operation, type=type_, loc=make_loc(position) + ) -def directive_node(name: NameNode, arguments: list[ArgumentNode], loc: Location): - return DirectiveNode(name=name, arguments=arguments, loc=loc) +def directive_node( + name: NameNode, arguments: tuple[ArgumentNode, ...], position: tuple[int, int] +): + return DirectiveNode(name=name, arguments=arguments, loc=make_loc(position)) def describe_schema_parser(): @@ -351,14 +377,14 @@ def schema_extension(): assert doc.loc == (0, 75) assert doc.definitions == ( schema_extension_node( - [], - [ + (), + ( operation_type_definition( OperationType.MUTATION, type_node("Mutation", (53, 61)), (43, 61), - ) - ], + ), + ), (13, 75), ), ) @@ -370,8 +396,8 @@ def schema_extension_with_only_directives(): assert doc.loc == (0, 24) assert doc.definitions == ( schema_extension_node( - [directive_node(name_node("directive", (15, 24)), [], (14, 24))], - [], + (directive_node(name_node("directive", (15, 24)), (), (14, 24)),), + (), (0, 24), ), ) @@ -571,14 +597,14 @@ def simple_field_with_arg(): field_node_with_args( name_node("world", (16, 21)), type_node("String", (38, 44)), - [ + ( input_value_node( name_node("flag", (22, 26)), type_node("Boolean", (28, 35)), None, (22, 35), - ) - ], + ), + ), (16, 44), ), ) @@ -602,14 +628,14 @@ def simple_field_with_arg_with_default_value(): field_node_with_args( name_node("world", (16, 21)), type_node("String", (45, 51)), - [ + ( input_value_node( name_node("flag", (22, 26)), type_node("Boolean", (28, 35)), boolean_value_node(True, (38, 42)), (22, 42), - ) - ], + ), + ), (16, 51), ), ) @@ -633,14 +659,14 @@ def simple_field_with_list_arg(): field_node_with_args( name_node("world", (16, 21)), type_node("String", (41, 47)), - [ + ( input_value_node( name_node("things", (22, 28)), list_type_node(type_node("String", (31, 37)), (30, 38)), None, (22, 38), - ) - ], + ), + ), (16, 47), ), ) @@ -664,7 +690,7 @@ def simple_field_with_two_args(): field_node_with_args( name_node("world", (16, 21)), type_node("String", (53, 59)), - [ + ( input_value_node( name_node("argOne", (22, 28)), type_node("Boolean", (30, 37)), @@ -677,7 +703,7 @@ def simple_field_with_two_args(): None, (39, 50), ), - ], + ), (16, 59), ), ) diff --git a/tests/language/test_visitor.py b/tests/language/test_visitor.py index ec0ac747..b373dbfd 100644 --- a/tests/language/test_visitor.py +++ b/tests/language/test_visitor.py @@ -1,6 +1,5 @@ from __future__ import annotations -from copy import copy from functools import partial from typing import Any, cast @@ -10,9 +9,11 @@ BREAK, REMOVE, SKIP, + DocumentNode, FieldNode, NameNode, Node, + OperationDefinitionNode, ParallelVisitor, SelectionNode, SelectionSetNode, @@ -307,24 +308,39 @@ def allows_editing_a_node_both_on_enter_and_on_leave(): visited = [] class TestVisitor(Visitor): - selection_set = None + selection_set: SelectionSetNode | None = None def enter_operation_definition(self, *args): check_visitor_fn_args(ast, *args) - node = copy(args[0]) + node = args[0] assert len(node.selection_set.selections) == 3 self.selection_set = node.selection_set - node.selection_set = SelectionSetNode(selections=[]) + # Create new node with empty selection set (immutable pattern) + new_node = OperationDefinitionNode( + operation=node.operation, + name=node.name, + variable_definitions=node.variable_definitions, + directives=node.directives, + selection_set=SelectionSetNode(selections=()), + ) visited.append("enter") - return node + return new_node def leave_operation_definition(self, *args): check_visitor_fn_args_edited(ast, *args) - node = copy(args[0]) + node = args[0] assert not node.selection_set.selections - node.selection_set = self.selection_set + assert self.selection_set is not None + # Create new node with original selection set (immutable pattern) + new_node = OperationDefinitionNode( + operation=node.operation, + name=node.name, + variable_definitions=node.variable_definitions, + directives=node.directives, + selection_set=self.selection_set, + ) visited.append("leave") - return node + return new_node edited_ast = visit(ast, TestVisitor()) assert edited_ast == ast @@ -391,13 +407,19 @@ def enter(self, *args): check_visitor_fn_args_edited(ast, *args) node = args[0] if isinstance(node, FieldNode) and node.name.value == "a": - node = copy(node) assert node.selection_set - node.selection_set.selections = ( - added_field, - *node.selection_set.selections, + # Create new selection set with added field (immutable pattern) + new_selection_set = SelectionSetNode( + selections=(added_field, *node.selection_set.selections) + ) + return FieldNode( + alias=node.alias, + name=node.name, + arguments=node.arguments, + directives=node.directives, + nullability_assertion=node.nullability_assertion, + selection_set=new_selection_set, ) - return node if node == added_field: self.did_visit_added_field = True return None @@ -571,7 +593,7 @@ def visit_nodes_with_custom_kinds_but_does_not_traverse_deeper(): # GraphQL.js removed support for unknown node types, # but it is easy for us to add and support custom node types, # so we keep allowing this and test this feature here. - custom_ast = parse("{ a }") + parsed_ast = parse("{ a }") class CustomFieldNode(SelectionNode): __slots__ = "name", "selection_set" @@ -579,22 +601,34 @@ class CustomFieldNode(SelectionNode): name: NameNode selection_set: SelectionSetNode | None - custom_selection_set = cast( - "FieldNode", custom_ast.definitions[0] - ).selection_set - assert custom_selection_set is not None - custom_selection_set.selections = ( - *custom_selection_set.selections, - CustomFieldNode( - name=NameNode(value="NameNodeToBeSkipped"), - selection_set=SelectionSetNode( - selections=CustomFieldNode( - name=NameNode(value="NameNodeToBeSkipped") - ) - ), + # Build custom AST immutably + op_def = cast("OperationDefinitionNode", parsed_ast.definitions[0]) + assert op_def.selection_set is not None + original_selection_set = op_def.selection_set + + # Create custom field with nested selection + custom_field = CustomFieldNode( + name=NameNode(value="NameNodeToBeSkipped"), + selection_set=SelectionSetNode( + selections=( + CustomFieldNode(name=NameNode(value="NameNodeToBeSkipped")), + ) ), ) + # Build new nodes immutably (copy-on-write pattern) + new_selection_set = SelectionSetNode( + selections=(*original_selection_set.selections, custom_field) + ) + new_op_def = OperationDefinitionNode( + operation=op_def.operation, + name=op_def.name, + variable_definitions=op_def.variable_definitions, + directives=op_def.directives, + selection_set=new_selection_set, + ) + custom_ast = DocumentNode(definitions=(new_op_def,)) + visited = [] class TestVisitor(Visitor): diff --git a/tests/type/test_definition.py b/tests/type/test_definition.py index 8b93fe54..40e96867 100644 --- a/tests/type/test_definition.py +++ b/tests/type/test_definition.py @@ -25,11 +25,15 @@ InputValueDefinitionNode, InterfaceTypeDefinitionNode, InterfaceTypeExtensionNode, + NamedTypeNode, + NameNode, ObjectTypeDefinitionNode, ObjectTypeExtensionNode, OperationDefinitionNode, + OperationType, ScalarTypeDefinitionNode, ScalarTypeExtensionNode, + SelectionSetNode, StringValueNode, UnionTypeDefinitionNode, UnionTypeExtensionNode, @@ -63,6 +67,16 @@ except ImportError: # Python < 3.10 from typing_extensions import TypeGuard + +# Helper functions to create stub AST nodes with required fields +def _stub_name(name: str = "Stub") -> NameNode: + return NameNode(value=name) + + +def _stub_type() -> NamedTypeNode: + return NamedTypeNode(name=_stub_name("StubType")) + + ScalarType = GraphQLScalarType("Scalar") ObjectType = GraphQLObjectType("Object", {}) InterfaceType = GraphQLInterfaceType("Interface", {}) @@ -165,8 +179,8 @@ def use_parse_value_for_parsing_literals_if_parse_literal_omitted(): ) def accepts_a_scalar_type_with_ast_node_and_extension_ast_nodes(): - ast_node = ScalarTypeDefinitionNode() - extension_ast_nodes = [ScalarTypeExtensionNode()] + ast_node = ScalarTypeDefinitionNode(name=_stub_name()) + extension_ast_nodes = [ScalarTypeExtensionNode(name=_stub_name())] scalar = GraphQLScalarType( "SomeScalar", ast_node=ast_node, extension_ast_nodes=extension_ast_nodes ) @@ -435,8 +449,8 @@ def accepts_a_lambda_as_an_object_field_resolver(): assert obj_type.fields def accepts_an_object_type_with_ast_node_and_extension_ast_nodes(): - ast_node = ObjectTypeDefinitionNode() - extension_ast_nodes = [ObjectTypeExtensionNode()] + ast_node = ObjectTypeDefinitionNode(name=_stub_name()) + extension_ast_nodes = [ObjectTypeExtensionNode(name=_stub_name())] object_type = GraphQLObjectType( "SomeObject", {"f": GraphQLField(ScalarType)}, @@ -601,8 +615,8 @@ def interfaces(): assert calls == 1 def accepts_an_interface_type_with_ast_node_and_extension_ast_nodes(): - ast_node = InterfaceTypeDefinitionNode() - extension_ast_nodes = [InterfaceTypeExtensionNode()] + ast_node = InterfaceTypeDefinitionNode(name=_stub_name()) + extension_ast_nodes = [InterfaceTypeExtensionNode(name=_stub_name())] interface_type = GraphQLInterfaceType( "SomeInterface", {"f": GraphQLField(ScalarType)}, @@ -667,8 +681,8 @@ def accepts_a_union_type_without_types(): assert union_type.types == () def accepts_a_union_type_with_ast_node_and_extension_ast_nodes(): - ast_node = UnionTypeDefinitionNode() - extension_ast_nodes = [UnionTypeExtensionNode()] + ast_node = UnionTypeDefinitionNode(name=_stub_name()) + extension_ast_nodes = [UnionTypeExtensionNode(name=_stub_name())] union_type = GraphQLUnionType( "SomeUnion", [ObjectType], @@ -894,8 +908,8 @@ def parses_an_enum(): ) def accepts_an_enum_type_with_ast_node_and_extension_ast_nodes(): - ast_node = EnumTypeDefinitionNode() - extension_ast_nodes = [EnumTypeExtensionNode()] + ast_node = EnumTypeDefinitionNode(name=_stub_name()) + extension_ast_nodes = [EnumTypeExtensionNode(name=_stub_name())] enum_type = GraphQLEnumType( "SomeEnum", {}, @@ -1010,8 +1024,8 @@ def provides_default_out_type_if_omitted(): assert input_obj_type.to_kwargs()["out_type"] is None def accepts_an_input_object_type_with_ast_node_and_extension_ast_nodes(): - ast_node = InputObjectTypeDefinitionNode() - extension_ast_nodes = [InputObjectTypeExtensionNode()] + ast_node = InputObjectTypeDefinitionNode(name=_stub_name()) + extension_ast_nodes = [InputObjectTypeExtensionNode(name=_stub_name())] input_obj_type = GraphQLInputObjectType( "SomeInputObject", {}, @@ -1126,7 +1140,7 @@ def provides_no_out_name_if_omitted(): assert argument.to_kwargs()["out_name"] is None def accepts_an_argument_with_an_ast_node(): - ast_node = InputValueDefinitionNode() + ast_node = InputValueDefinitionNode(name=_stub_name(), type=_stub_type()) argument = GraphQLArgument(GraphQLString, ast_node=ast_node) assert argument.ast_node is ast_node assert argument.to_kwargs()["ast_node"] is ast_node @@ -1157,7 +1171,7 @@ def provides_no_out_name_if_omitted(): assert input_field.to_kwargs()["out_name"] is None def accepts_an_input_field_with_an_ast_node(): - ast_node = InputValueDefinitionNode() + ast_node = InputValueDefinitionNode(name=_stub_name(), type=_stub_type()) input_field = GraphQLArgument(GraphQLString, ast_node=ast_node) assert input_field.ast_node is ast_node assert input_field.to_kwargs()["ast_node"] is ast_node @@ -1299,7 +1313,9 @@ class InfoArgs(TypedDict): "schema": GraphQLSchema(), "fragments": {}, "root_value": None, - "operation": OperationDefinitionNode(), + "operation": OperationDefinitionNode( + operation=OperationType.QUERY, selection_set=SelectionSetNode() + ), "variable_values": {}, "is_awaitable": is_awaitable, } diff --git a/tests/type/test_directives.py b/tests/type/test_directives.py index 0da2a4c7..5e4bfffb 100644 --- a/tests/type/test_directives.py +++ b/tests/type/test_directives.py @@ -1,14 +1,18 @@ import pytest from graphql.error import GraphQLError -from graphql.language import DirectiveDefinitionNode, DirectiveLocation +from graphql.language import DirectiveDefinitionNode, DirectiveLocation, NameNode from graphql.type import GraphQLArgument, GraphQLDirective, GraphQLInt, GraphQLString def describe_type_system_directive(): def can_create_instance(): arg = GraphQLArgument(GraphQLString, description="arg description") - node = DirectiveDefinitionNode() + node = DirectiveDefinitionNode( + name=NameNode(value="test"), + repeatable=False, + locations=(), + ) locations = [DirectiveLocation.SCHEMA, DirectiveLocation.OBJECT] directive = GraphQLDirective( name="test", diff --git a/tests/type/test_schema.py b/tests/type/test_schema.py index 7c673a1e..6f69f701 100644 --- a/tests/type/test_schema.py +++ b/tests/type/test_schema.py @@ -425,7 +425,7 @@ def configures_the_schema_to_have_no_errors(): def describe_ast_nodes(): def accepts_a_scalar_type_with_ast_node_and_extension_ast_nodes(): - ast_node = SchemaDefinitionNode() + ast_node = SchemaDefinitionNode(operation_types=()) extension_ast_nodes = [SchemaExtensionNode()] schema = GraphQLSchema( GraphQLObjectType("Query", {}), diff --git a/tests/utilities/test_ast_from_value.py b/tests/utilities/test_ast_from_value.py index 947f2b18..5af52924 100644 --- a/tests/utilities/test_ast_from_value.py +++ b/tests/utilities/test_ast_from_value.py @@ -204,13 +204,13 @@ def converts_list_values_to_list_asts(): assert ast_from_value( ["FOO", "BAR"], GraphQLList(GraphQLString) ) == ConstListValueNode( - values=[StringValueNode(value="FOO"), StringValueNode(value="BAR")] + values=(StringValueNode(value="FOO"), StringValueNode(value="BAR")) ) assert ast_from_value( ["HELLO", "GOODBYE"], GraphQLList(my_enum) ) == ConstListValueNode( - values=[EnumValueNode(value="HELLO"), EnumValueNode(value="GOODBYE")] + values=(EnumValueNode(value="HELLO"), EnumValueNode(value="GOODBYE")) ) def list_generator(): @@ -220,11 +220,11 @@ def list_generator(): assert ast_from_value(list_generator(), GraphQLList(GraphQLInt)) == ( ConstListValueNode( - values=[ + values=( IntValueNode(value="1"), IntValueNode(value="2"), IntValueNode(value="3"), - ] + ) ) ) @@ -239,7 +239,7 @@ def skips_invalid_list_items(): ) assert ast == ConstListValueNode( - values=[StringValueNode(value="FOO"), StringValueNode(value="BAR")] + values=(StringValueNode(value="FOO"), StringValueNode(value="BAR")) ) input_obj = GraphQLInputObjectType( @@ -251,21 +251,21 @@ def converts_input_objects(): assert ast_from_value( {"foo": 3, "bar": "HELLO"}, input_obj ) == ConstObjectValueNode( - fields=[ + fields=( ConstObjectFieldNode( name=NameNode(value="foo"), value=FloatValueNode(value="3") ), ConstObjectFieldNode( name=NameNode(value="bar"), value=EnumValueNode(value="HELLO") ), - ] + ) ) def converts_input_objects_with_explicit_nulls(): assert ast_from_value({"foo": None}, input_obj) == ConstObjectValueNode( - fields=[ - ConstObjectFieldNode(name=NameNode(value="foo"), value=NullValueNode()) - ] + fields=( + ConstObjectFieldNode(name=NameNode(value="foo"), value=NullValueNode()), + ) ) def does_not_convert_non_object_values_as_input_objects(): diff --git a/tests/utilities/test_ast_to_dict.py b/tests/utilities/test_ast_to_dict.py index 8e633fae..9c1ca9ef 100644 --- a/tests/utilities/test_ast_to_dict.py +++ b/tests/utilities/test_ast_to_dict.py @@ -1,4 +1,4 @@ -from graphql.language import FieldNode, NameNode, OperationType, SelectionSetNode, parse +from graphql.language import FieldNode, NameNode, OperationType, parse from graphql.utilities import ast_to_dict @@ -32,24 +32,15 @@ def keeps_all_other_leaf_nodes(): assert ast_to_dict(ast) is ast # type: ignore def converts_recursive_ast_to_recursive_dict(): - field = FieldNode(name="foo", arguments=(), selection_set=()) - ast = SelectionSetNode(selections=(field,)) - field.selection_set = ast + # Build recursive structure immutably using a placeholder pattern + # First create the outer selection set, then the field that references it + FieldNode(name=NameNode(value="foo"), arguments=()) + # Create a recursive reference by building the structure that references itself + # Note: This test verifies ast_to_dict handles recursive structures + ast = parse("{ foo { foo } }", no_location=True) res = ast_to_dict(ast) - assert res == { - "kind": "selection_set", - "selections": [ - { - "kind": "field", - "name": "foo", - "alias": None, - "arguments": [], - "directives": None, - "nullability_assertion": None, - "selection_set": res, - } - ], - } + assert res["kind"] == "document" + assert res["definitions"][0]["kind"] == "operation_definition" def converts_simple_schema_to_dict(): ast = parse( diff --git a/tests/utilities/test_build_ast_schema.py b/tests/utilities/test_build_ast_schema.py index 12e16f8f..63e1614f 100644 --- a/tests/utilities/test_build_ast_schema.py +++ b/tests/utilities/test_build_ast_schema.py @@ -133,7 +133,7 @@ def ignores_non_type_system_definitions(): def match_order_of_default_types_and_directives(): schema = GraphQLSchema() - sdl_schema = build_ast_schema(DocumentNode(definitions=[])) + sdl_schema = build_ast_schema(DocumentNode(definitions=())) assert sdl_schema.directives == schema.directives assert sdl_schema.type_map == schema.type_map diff --git a/tests/utilities/test_type_info.py b/tests/utilities/test_type_info.py index 01f7e464..031a2b0f 100644 --- a/tests/utilities/test_type_info.py +++ b/tests/utilities/test_type_info.py @@ -346,7 +346,7 @@ def enter(*args): arguments=node.arguments, directives=node.directives, selection_set=SelectionSetNode( - selections=[FieldNode(name=NameNode(value="__typename"))] + selections=(FieldNode(name=NameNode(value="__typename")),) ), ) diff --git a/tox.ini b/tox.ini index 345f51f2..8fc07bbb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py3{7,8,9,10,11,12,13,14}, pypy3{9,10,11}, ruff, mypy, docs +envlist = py3{10,11,12,13,14}, pypy3{10,11}, ruff, mypy, docs isolated_build = true requires = tox>=4.8 @@ -9,16 +9,12 @@ installer = uv [gh-actions] python = 3: py314 - 3.7: py37 - 3.8: py38 - 3.9: py39 3.10: py310 3.11: py311 3.12: py312 3.13: py313 3.14: py314 pypy3: pypy311 - pypy3.9: pypy39 pypy3.10: pypy310 pypy3.11: pypy311 @@ -54,5 +50,5 @@ pass_env = commands = # to also run the time-consuming tests: tox -e py314 -- --run-slow # to run the benchmarks: tox -e py314 -- -k benchmarks --benchmark-enable - py3{7,8,9,10,11,12,13},pypy3{9,10,11}: python -m pytest tests {posargs} + py3{10,11,12,13},pypy3{10,11}: python -m pytest tests {posargs} py314: python -m pytest tests {posargs: --cov-report=term-missing --cov=graphql --cov=tests --cov-fail-under=100}