def _ensure_no_unsupported_suppressions(schema_ast: DocumentNode, renamings: RenamingMapping) -> None: """Confirm renamings contains no enums, interfaces, or interface implementation suppressions.""" visitor = SuppressionNotImplementedVisitor(renamings) visit(schema_ast, visitor) if (not visitor.unsupported_enum_suppressions and not visitor.unsupported_interface_suppressions and not visitor.unsupported_interface_implementation_suppressions): return # Otherwise, attempted to suppress something we shouldn't suppress. error_message_components = [ f"Type renamings {renamings} attempted to suppress parts of the schema for which " f"suppression is not implemented yet." ] if visitor.unsupported_enum_suppressions: error_message_components.append( f"Type renamings mapped these schema enums to None: " f"{visitor.unsupported_enum_suppressions}, attempting to suppress them. However, " f"schema renaming has not implemented enum suppression yet.") if visitor.unsupported_interface_suppressions: error_message_components.append( f"Type renamings mapped these schema interfaces to None: " f"{visitor.unsupported_interface_suppressions}, attempting to suppress them. However, " f"schema renaming has not implemented interface suppression yet.") if visitor.unsupported_interface_implementation_suppressions: error_message_components.append( f"Type renamings mapped these object types to None: " f"{visitor.unsupported_interface_implementation_suppressions}, attempting to suppress " f"them. Normally, this would be fine. However, these types each implement at least one " f"interface and schema renaming has not implemented this particular suppression yet." ) error_message_components.append( "To avoid these suppressions, remove the mappings from the renamings argument." ) raise NotImplementedError("\n".join(error_message_components))
def check_query_is_valid_to_split(schema: GraphQLSchema, query_ast: DocumentNode) -> None: """Check the query is valid for splitting. In particular, ensure that the query validates against the schema, does not contain unsupported directives, and that in each selection, all property fields occur before all vertex fields. Args: schema: schema the query is written against query_ast: query to split Raises: GraphQLValidationError if the query doesn't validate against the schema, contains unsupported directives, or some property field occurs after a vertex field in some selection """ # Check builtin errors built_in_validation_errors = validate(schema, query_ast) if len(built_in_validation_errors) > 0: raise GraphQLValidationError( "AST does not validate: {}".format(built_in_validation_errors)) # Check no bad directives and fields are in order visitor = CheckQueryIsValidToSplitVisitor() visit(query_ast, visitor)
def test_allows_early_exit_while_visiting(): # type: () -> None visited = [] ast = parse("{ a, b { x }, c }") class TestVisitor(Visitor): def enter(self, node, *args): # type: (Any, *Any) -> Optional[Any] visited.append(["enter", type(node).__name__, getattr(node, "value", None)]) if isinstance(node, Name) and node.value == "x": return BREAK def leave(self, node, *args): # type: (Union[Field, Name], *Any) -> None visited.append(["leave", type(node).__name__, getattr(node, "value", None)]) visit(ast, TestVisitor()) assert visited == [ ["enter", "Document", None], ["enter", "OperationDefinition", None], ["enter", "SelectionSet", None], ["enter", "Field", None], ["enter", "Name", "a"], ["leave", "Name", "a"], ["leave", "Field", None], ["enter", "Field", None], ["enter", "Name", "b"], ["leave", "Name", "b"], ["enter", "SelectionSet", None], ["enter", "Field", None], ["enter", "Name", "x"], ]
def test_allows_early_exit_while_visiting(): visited = [] ast = parse('{ a, b { x }, c }') class TestVisitor(Visitor): def enter(self, node, *args): visited.append( ['enter', type(node).__name__, getattr(node, 'value', None)]) if isinstance(node, Name) and node.value == 'x': return BREAK def leave(self, node, *args): visited.append( ['leave', type(node).__name__, getattr(node, 'value', None)]) visit(ast, TestVisitor()) assert visited == [ ['enter', 'Document', None], ['enter', 'OperationDefinition', None], ['enter', 'SelectionSet', None], ['enter', 'Field', None], ['enter', 'Name', 'a'], ['leave', 'Name', 'a'], ['leave', 'Field', None], ['enter', 'Field', None], ['enter', 'Name', 'b'], ['leave', 'Name', 'b'], ['enter', 'SelectionSet', None], ['enter', 'Field', None], ['enter', 'Name', 'x'], ]
def hide_string_and_numeric_literals(ast: Document) -> Document: """ In the same spirit as the similarly named `hideLiterals` function, only hide string and numeric literals. """ visit(ast, _HIDE_ONLY_STRING_AND_NUMERIC_LITERALS_VISITOR) return ast
def test_visits_in_pararell_allows_early_exit_while_visiting(): visited = [] ast = parse('{ a, b { x }, c }') class TestVisitor(Visitor): def enter(self, node, key, parent, *args): visited.append( ['enter', type(node).__name__, getattr(node, 'value', None)]) def leave(self, node, key, parent, *args): visited.append( ['leave', type(node).__name__, getattr(node, 'value', None)]) if type(node).__name__ == 'Name' and node.value == 'x': return BREAK visit(ast, ParallelVisitor([TestVisitor()])) assert visited == [ ['enter', 'Document', None], ['enter', 'OperationDefinition', None], ['enter', 'SelectionSet', None], ['enter', 'Field', None], ['enter', 'Name', 'a'], ['leave', 'Name', 'a'], ['leave', 'Field', None], ['enter', 'Field', None], ['enter', 'Name', 'b'], ['leave', 'Name', 'b'], ['enter', 'SelectionSet', None], ['enter', 'Field', None], ['enter', 'Name', 'x'], ['leave', 'Name', 'x'] ]
def test_allows_a_named_functions_visitor_api(): visited = [] ast = parse('{ a, b { x }, c }') class TestVisitor(Visitor): def enter_Name(self, node, *args): visited.append( ['enter', type(node).__name__, getattr(node, 'value', None)]) def enter_SelectionSet(self, node, *args): visited.append( ['enter', type(node).__name__, getattr(node, 'value', None)]) def leave_SelectionSet(self, node, *args): visited.append( ['leave', type(node).__name__, getattr(node, 'value', None)]) visit(ast, TestVisitor()) assert visited == [ ['enter', 'SelectionSet', None], ['enter', 'Name', 'a'], ['enter', 'Name', 'b'], ['enter', 'SelectionSet', None], ['enter', 'Name', 'x'], ['leave', 'SelectionSet', None], ['enter', 'Name', 'c'], ['leave', 'SelectionSet', None], ]
def test_visits_in_pararell_allows_skipping_a_subtree(): visited = [] ast = parse('{ a, b { x }, c }') class TestVisitor(Visitor): def enter(self, node, key, parent, *args): visited.append( ['enter', type(node).__name__, getattr(node, 'value', None)]) if type(node).__name__ == 'Field' and node.name.value == 'b': return False def leave(self, node, key, parent, *args): visited.append( ['leave', type(node).__name__, getattr(node, 'value', None)]) visit(ast, ParallelVisitor([TestVisitor()])) assert visited == [ ['enter', 'Document', None], ['enter', 'OperationDefinition', None], ['enter', 'SelectionSet', None], ['enter', 'Field', None], ['enter', 'Name', 'a'], ['leave', 'Name', 'a'], ['leave', 'Field', None], ['enter', 'Field', None], ['enter', 'Field', None], ['enter', 'Name', 'c'], ['leave', 'Name', 'c'], ['leave', 'Field', None], ['leave', 'SelectionSet', None], ['leave', 'OperationDefinition', None], ['leave', 'Document', None], ]
def check_ast_schema_is_valid(ast): """Check the schema satisfies structural requirements for rename and merge. In particular, check that the schema contains no mutations, no subscriptions, no InputObjectTypeDefinitions, no TypeExtensionDefinitions, all type names are valid and not reserved (not starting with double underscores), and all query type field names match the types they query. Args: ast: Document, representing a schema Raises: - SchemaStructureError if the AST cannot be built into a valid schema, if the schema contains mutations, subscriptions, InputObjectTypeDefinitions, TypeExtensionsDefinitions, or if any query type field does not match the queried type. - InvalidTypeNameError if a type has a type name that is invalid or reserved """ schema = build_ast_schema(ast) if schema.mutation_type is not None: raise SchemaStructureError( "Renaming schemas that contain mutations is currently not supported." ) if schema.subscription_type is not None: raise SchemaStructureError( "Renaming schemas that contain subscriptions is currently not supported." ) visit(ast, CheckValidTypesAndNamesVisitor()) query_type = get_query_type_name(schema) visit(ast, CheckQueryTypeFieldsNameMatchVisitor(query_type))
def __call__(self, request): response = self.get_response(request) # user is available only after the request was processed try: body = json.loads(request.body.decode("utf-8")) except json.decoder.JSONDecodeError: # pragma: no cover return response vis = AccessLogVisitor() try: doc = parser.parse(body["query"]) visitor.visit(doc, vis) except GraphQLSyntaxError: pass AccessLog.objects.create( username=request.user.username, query=body.get("query"), variables=body.get("variables"), status_code=response.status_code, has_error=response.status_code >= 400, **vis.values, ) return response
def test_allows_a_named_functions_visitor_api(): # type: () -> None visited = [] ast = parse("{ a, b { x }, c }") class TestVisitor(Visitor): def enter_Name(self, node, *args): # type: (Name, *Any) -> None visited.append(["enter", type(node).__name__, getattr(node, "value", None)]) def enter_SelectionSet(self, node, *args): # type: (SelectionSet, *Any) -> None visited.append(["enter", type(node).__name__, getattr(node, "value", None)]) def leave_SelectionSet(self, node, *args): # type: (SelectionSet, *Any) -> None visited.append(["leave", type(node).__name__, getattr(node, "value", None)]) visit(ast, TestVisitor()) assert visited == [ ["enter", "SelectionSet", None], ["enter", "Name", "a"], ["enter", "Name", "b"], ["enter", "SelectionSet", None], ["enter", "Name", "x"], ["leave", "SelectionSet", None], ["enter", "Name", "c"], ["leave", "SelectionSet", None], ]
def test_allows_skipping_a_subtree(): # type: () -> None visited = [] ast = parse("{ a, b { x }, c }") class TestVisitor(Visitor): def enter(self, node, *args): # type: (Any, *Any) -> Optional[Any] visited.append(["enter", type(node).__name__, getattr(node, "value", None)]) if isinstance(node, Field) and node.name.value == "b": return False def leave(self, node, *args): # type: (Union[Field, Name, SelectionSet], *Any) -> None visited.append(["leave", type(node).__name__, getattr(node, "value", None)]) visit(ast, TestVisitor()) assert visited == [ ["enter", "Document", None], ["enter", "OperationDefinition", None], ["enter", "SelectionSet", None], ["enter", "Field", None], ["enter", "Name", "a"], ["leave", "Name", "a"], ["leave", "Field", None], ["enter", "Field", None], ["enter", "Field", None], ["enter", "Name", "c"], ["leave", "Name", "c"], ["leave", "Field", None], ["leave", "SelectionSet", None], ["leave", "OperationDefinition", None], ["leave", "Document", None], ]
def test_visits_edited_node(): # type: () -> None added_field = Field(name=Name(value="__typename")) ast = parse("{ a { x } }") class TestVisitor(Visitor): def __init__(self): # type: () -> None self.did_visit_added_field = False def enter(self, node, *args): # type: (Any, *Any) -> Optional[Field] if isinstance(node, Field) and node.name.value == "a": selection_set = node.selection_set selections = [] if selection_set: selections = selection_set.selections new_selection_set = SelectionSet(selections=[added_field] + selections) return Field(name=None, selection_set=new_selection_set) if node is added_field: self.did_visit_added_field = True visitor = TestVisitor() visit(ast, visitor) assert visitor.did_visit_added_field
def separate_operations(document_ast): """Separate operations in a given AST document. This function accepts a single AST document which may contain many operations and fragments and returns a collection of AST documents each of which contains a single operation as well the fragment definitions it refers to. """ # Populate metadata and build a dependency graph. visitor = SeparateOperations() visit(document_ast, visitor) operations = visitor.operations fragments = visitor.fragments positions = visitor.positions dep_graph = visitor.dep_graph # For each operation, produce a new synthesized AST which includes only what is # necessary for completing that operation. separated_document_asts = {} for operation in operations: operation_name = op_name(operation) dependencies = set() collect_transitive_dependencies(dependencies, dep_graph, operation_name) # The list of definition nodes to be included for this operation, sorted to # retain the same order as the original document. definitions = [operation] for name in dependencies: definitions.append(fragments[name]) definitions.sort(key=lambda n: positions.get(n, 0)) separated_document_asts[operation_name] = Document( definitions=definitions) return separated_document_asts
def test_visits_in_pararell_allows_early_exit_while_visiting(): visited = [] ast = parse('{ a, b { x }, c }') class TestVisitor(Visitor): def enter(self, node, key, parent, *args): visited.append( ['enter', type(node).__name__, getattr(node, 'value', None)]) def leave(self, node, key, parent, *args): visited.append( ['leave', type(node).__name__, getattr(node, 'value', None)]) if type(node).__name__ == 'Name' and node.value == 'x': return BREAK visit(ast, ParallelVisitor([TestVisitor()])) assert visited == [['enter', 'Document', None], ['enter', 'OperationDefinition', None], ['enter', 'SelectionSet', None], ['enter', 'Field', None], ['enter', 'Name', 'a'], ['leave', 'Name', 'a'], ['leave', 'Field', None], ['enter', 'Field', None], ['enter', 'Name', 'b'], ['leave', 'Name', 'b'], ['enter', 'SelectionSet', None], ['enter', 'Field', None], ['enter', 'Name', 'x'], ['leave', 'Name', 'x']]
def test_visits_in_pararell_allows_early_exit_from_different_points(): visited = [] ast = parse('{ a { y }, b { x } }') class TestVisitor(Visitor): def __init__(self, name): self.name = name def enter(self, node, key, parent, *args): visited.append(["break-{}".format(self.name), 'enter', type(node).__name__, getattr(node, 'value', None)]) def leave(self, node, key, parent, *args): visited.append(["break-{}".format(self.name), 'leave', type(node).__name__, getattr(node, 'value', None)]) if type(node).__name__ == 'Field' and node.name.value == self.name: return BREAK visit(ast, ParallelVisitor([TestVisitor('a'), TestVisitor('b')])) assert visited == [ ['break-a', 'enter', 'Document', None], ['break-b', 'enter', 'Document', None], ['break-a', 'enter', 'OperationDefinition', None], ['break-b', 'enter', 'OperationDefinition', None], ['break-a', 'enter', 'SelectionSet', None], ['break-b', 'enter', 'SelectionSet', None], ['break-a', 'enter', 'Field', None], ['break-b', 'enter', 'Field', None], ['break-a', 'enter', 'Name', 'a'], ['break-b', 'enter', 'Name', 'a'], ['break-a', 'leave', 'Name', 'a'], ['break-b', 'leave', 'Name', 'a'], ['break-a', 'enter', 'SelectionSet', None], ['break-b', 'enter', 'SelectionSet', None], ['break-a', 'enter', 'Field', None], ['break-b', 'enter', 'Field', None], ['break-a', 'enter', 'Name', 'y'], ['break-b', 'enter', 'Name', 'y'], ['break-a', 'leave', 'Name', 'y'], ['break-b', 'leave', 'Name', 'y'], ['break-a', 'leave', 'Field', None], ['break-b', 'leave', 'Field', None], ['break-a', 'leave', 'SelectionSet', None], ['break-b', 'leave', 'SelectionSet', None], ['break-a', 'leave', 'Field', None], ['break-b', 'leave', 'Field', None], ['break-b', 'enter', 'Field', None], ['break-b', 'enter', 'Name', 'b'], ['break-b', 'leave', 'Name', 'b'], ['break-b', 'enter', 'SelectionSet', None], ['break-b', 'enter', 'Field', None], ['break-b', 'enter', 'Name', 'x'], ['break-b', 'leave', 'Name', 'x'], ['break-b', 'leave', 'Field', None], ['break-b', 'leave', 'SelectionSet', None], ['break-b', 'leave', 'Field', None] ]
def remove_aliases(ast: Document) -> Document: """ removeAliases gets rid of GraphQL aliases, a feature by which you can tell a server to return a field's data under a different name from the field name. Maybe this is useful if somebody somewhere inserts random aliases into their queries. """ visit(ast, _REMOVE_ALIAS_VISITOR) return ast
def sort_ast(ast: Document) -> Document: """ sortAST sorts most multi-child nodes alphabetically. Using this as part of your signature calculation function may make it easier to tell the difference between queries that are similar to each other, and if for some reason your QraphQL client generates query strings with elements in nondeterministic order, it can make sure the queries are treated as identical. """ visit(ast, _SORTING_VISITOR) return ast
def hide_literals(ast: Document) -> Document: """ Replace numeric, string, list, and object literals with "empty" values. Leaves enums alone (since there's no consistent "zero" enum). This can help combine similar queries if you substitute values directly into queries rather than use GraphQL variables, and can hide sensitive data in your query (say, a hardcoded API key) from Engine servers, but in general avoiding those situations is better than working around them. """ visit(ast, _HIDE_LITERALS_VISITOR) return ast
def print_with_reduced_whitespace(ast: Document) -> str: """ Like the graphql-js print function, but deleting whitespace wherever feasible. Specifically, all whitespace (outside of string literals) is reduced to at most one space, and even that space is removed anywhere except for between two alphanumerics. """ visit(ast, _HEX_CONVERSION_VISITOR) val = re.sub(r'\s+', ' ', print_ast(ast)) val = re.sub(r'([^_a-zA-Z0-9]) ', _replace_with_first_group, val) val = re.sub(r' ([^_a-zA-Z0-9])', _replace_with_first_group, val) val = re.sub(r'"([a-f0-9]+)"', _from_hex, val) return val
def _rename_and_suppress_types( schema_ast: DocumentNode, renamings: Mapping[str, Optional[str]], query_type: str, custom_scalar_names: AbstractSet[str], ) -> Tuple[DocumentNode, Dict[str, str]]: """Rename and suppress types, enums, interfaces using renamings. The query type will not be renamed. The input schema AST will not be modified. Args: schema_ast: schema that we're returning a modified version of renamings: maps original type name to renamed name or None (for type suppression). Any name not in the dict will be unchanged query_type: name of the query type, e.g. 'RootSchemaQuery' custom_scalar_names: set of all user defined scalars used in the schema (excluding builtin scalars) Returns: Tuple containing the modified version of the schema AST, and the renamed type name to original type name map. Map contains all non-suppressed types, including those that were not renamed. Raises: - InvalidTypeNameError if the schema contains an invalid type name, or if the user attempts to rename a type to an invalid name - SchemaNameConflictError if the rename causes name conflicts """ visitor = RenameSchemaTypesVisitor(renamings, query_type, custom_scalar_names) renamed_schema_ast = visit(schema_ast, visitor) return renamed_schema_ast, visitor.reverse_name_map
def _rename_types(ast, renamings, query_type, scalars): """Rename types, enums, interfaces using renamings. The query type will not be renamed. Scalar types, field names, enum values will not be renamed. The input AST will not be modified. Args: ast: Document, the schema that we're returning a modified version of renamings: Dict[str, str], mapping original type/interface/enum name to renamed name. If a name does not appear in the dict, it will be unchanged query_type: str, name of the query type, e.g. 'RootSchemaQuery' scalars: Set[str], the set of all scalars used in the schema, including user defined scalars and and used builtin scalars, excluding unused builtins Returns: Tuple[Document, Dict[str, str]], containing the modified version of the AST, and the renamed type name to original type name map. Map contains all types, including those that were not renamed. Raises: - InvalidTypeNameError if the schema contains an invalid type name, or if the user attempts to rename a type to an invalid name - SchemaNameConflictError if the rename causes name conflicts """ visitor = RenameSchemaTypesVisitor(renamings, query_type, scalars) renamed_ast = visit(ast, visitor) return renamed_ast, visitor.reverse_name_map
def test_allows_editing_the_root_node_on_enter_and_on_leave(): ast = parse('{ a, b, c { a, b, c } }', no_location=True) definitions = ast.definitions class TestVisitor(Visitor): def __init__(self): self.did_enter = False self.did_leave = False def enter(self, node, *args): if isinstance(node, Document): self.did_enter = True return Document(loc=node.loc, definitions=[]) def leave(self, node, *args): if isinstance(node, Document): self.did_leave = True return Document(loc=node.loc, definitions=definitions) visitor = TestVisitor() edited_ast = visit(ast, visitor) assert edited_ast == ast assert visitor.did_enter assert visitor.did_leave
def write_operation(self, source): gql = parse_graphql(source) kind_operations = {} visitor = GraphQLToPython( self.validation, self.schema_name, self.short_names, ) for kind, name, code in visit(gql, visitor): kind_operations.setdefault(kind, []).append((name, code)) # sort so fragments come first (fragment, mutation, query) kinds = [] for kind, code_list in sorted(kind_operations.items()): names = [] for name, code in code_list: self.writer('\n\n') self.writer(code) names.append(name) kinds.append(kind) self.writer('\n\nclass %s:\n' % (kind.title(), )) for name in sorted(names): self.writer(' %s = %s_%s()\n' % (name, kind, name)) self.writer('\n\nclass Operations:\n') for kind in kinds: self.writer(' %s = %s\n' % (kind, kind.title()))
def test_allows_editing_the_root_node_on_enter_and_on_leave(): ast = parse('{ a, b, c { a, b, c } }', no_location=True) definitions = ast.definitions class TestVisitor(Visitor): def __init__(self): self.did_enter = False self.did_leave = False def enter(self, node, *args): if isinstance(node, Document): self.did_enter = True return Document( loc=node.loc, definitions=[]) def leave(self, node, *args): if isinstance(node, Document): self.did_leave = True return Document( loc=node.loc, definitions=definitions) visitor = TestVisitor() edited_ast = visit(ast, visitor) assert edited_ast == ast assert visitor.did_enter assert visitor.did_leave
def test_allows_editing_a_node_both_on_enter_and_on_leave(): # type: () -> None ast = parse("{ a, b, c { a, b, c } }", no_location=True) class TestVisitor(Visitor): def __init__(self): # type: () -> None self.did_enter = False self.did_leave = False def enter( self, node, # type: Union[Document, OperationDefinition, SelectionSet] *args # type: Any ): # type: (...) -> Optional[OperationDefinition] if isinstance(node, OperationDefinition): self.did_enter = True selection_set = node.selection_set self.selections = None if selection_set: self.selections = selection_set.selections new_selection_set = SelectionSet(selections=[]) return OperationDefinition( name=node.name, variable_definitions=node.variable_definitions, directives=node.directives, loc=node.loc, operation=node.operation, selection_set=new_selection_set, ) def leave( self, node, # type: Union[Document, OperationDefinition, SelectionSet] *args # type: Any ): # type: (...) -> Optional[OperationDefinition] if isinstance(node, OperationDefinition): self.did_leave = True new_selection_set = None if self.selections: new_selection_set = SelectionSet( selections=self.selections) return OperationDefinition( name=node.name, variable_definitions=node.variable_definitions, directives=node.directives, loc=node.loc, operation=node.operation, selection_set=new_selection_set, ) visitor = TestVisitor() edited_ast = visit(ast, visitor) assert ast == parse("{ a, b, c { a, b, c } }", no_location=True) assert edited_ast == ast assert visitor.did_enter assert visitor.did_leave
def test_visits_in_pararell_allows_skipping_a_subtree(): # type: () -> None visited = [] ast = parse("{ a, b { x }, c }") class TestVisitor(Visitor): def enter(self, node, key, parent, *args): # type: (Any, Union[None, int, str], Any, *List[Any]) -> Optional[Any] visited.append( ["enter", type(node).__name__, getattr(node, "value", None)]) if type(node).__name__ == "Field" and node.name.value == "b": return False def leave( self, node, # type: Union[Field, Name, SelectionSet] key, # type: Union[int, str] parent, # type: Union[List[Field], Field, OperationDefinition] *args # type: List[Any] ): # type: (...) -> None visited.append( ["leave", type(node).__name__, getattr(node, "value", None)]) visit(ast, ParallelVisitor([TestVisitor()])) assert visited == [ ["enter", "Document", None], ["enter", "OperationDefinition", None], ["enter", "SelectionSet", None], ["enter", "Field", None], ["enter", "Name", "a"], ["leave", "Name", "a"], ["leave", "Field", None], ["enter", "Field", None], ["enter", "Field", None], ["enter", "Name", "c"], ["leave", "Name", "c"], ["leave", "Field", None], ["leave", "SelectionSet", None], ["leave", "OperationDefinition", None], ["leave", "Document", None], ]
def test_visits_in_pararell_allows_early_exit_while_visiting(): # type: () -> None visited = [] ast = parse("{ a, b { x }, c }") class TestVisitor(Visitor): def enter(self, node, key, parent, *args): # type: (Any, Union[None, int, str], Any, *List[Any]) -> None visited.append( ["enter", type(node).__name__, getattr(node, "value", None)]) def leave( self, node, # type: Union[Field, Name] key, # type: Union[int, str] parent, # type: Union[List[Field], Field] *args # type: List[Any] ): # type: (...) -> Optional[object] visited.append( ["leave", type(node).__name__, getattr(node, "value", None)]) if type(node).__name__ == "Name" and node.value == "x": return BREAK visit(ast, ParallelVisitor([TestVisitor()])) assert visited == [ ["enter", "Document", None], ["enter", "OperationDefinition", None], ["enter", "SelectionSet", None], ["enter", "Field", None], ["enter", "Name", "a"], ["leave", "Name", "a"], ["leave", "Field", None], ["enter", "Field", None], ["enter", "Name", "b"], ["leave", "Name", "b"], ["enter", "SelectionSet", None], ["enter", "Field", None], ["enter", "Name", "x"], ["leave", "Name", "x"], ]
def test_visits_in_pararell_allows_for_editing_on_leave(): visited = [] ast = parse('{ a, b, c { a, b, c } }', no_location=True) class TestVisitor1(Visitor): def leave(self, node, key, parent, *args): if type(node).__name__ == 'Field' and node.name.value == 'b': return REMOVE class TestVisitor2(Visitor): def enter(self, node, key, parent, *args): visited.append( ['enter', type(node).__name__, getattr(node, 'value', None)]) def leave(self, node, key, parent, *args): visited.append( ['leave', type(node).__name__, getattr(node, 'value', None)]) edited_ast = visit(ast, ParallelVisitor([TestVisitor1(), TestVisitor2()])) assert ast == parse('{ a, b, c { a, b, c } }', no_location=True) assert edited_ast == parse('{ a, c { a, c } }', no_location=True) assert visited == [ ['enter', 'Document', None], ['enter', 'OperationDefinition', None], ['enter', 'SelectionSet', None], ['enter', 'Field', None], ['enter', 'Name', 'a'], ['leave', 'Name', 'a'], ['leave', 'Field', None], ['enter', 'Field', None], ['enter', 'Name', 'b'], ['leave', 'Name', 'b'], ['enter', 'Field', None], ['enter', 'Name', 'c'], ['leave', 'Name', 'c'], ['enter', 'SelectionSet', None], ['enter', 'Field', None], ['enter', 'Name', 'a'], ['leave', 'Name', 'a'], ['leave', 'Field', None], ['enter', 'Field', None], ['enter', 'Name', 'b'], ['leave', 'Name', 'b'], ['enter', 'Field', None], ['enter', 'Name', 'c'], ['leave', 'Name', 'c'], ['leave', 'Field', None], ['leave', 'SelectionSet', None], ['leave', 'Field', None], ['leave', 'SelectionSet', None], ['leave', 'OperationDefinition', None], ['leave', 'Document', None] ]
def test_visits_in_pararell_allows_for_editing_on_enter(): # type: () -> None visited = [] ast = parse("{ a, b, c { a, b, c } }", no_location=True) class TestVisitor1(Visitor): def enter(self, node, key, parent, *args): # type: (Any, Union[None, int, str], Any, *List[Any]) -> Optional[Any] if type(node).__name__ == "Field" and node.name.value == "b": return REMOVE class TestVisitor2(Visitor): def enter(self, node, key, parent, *args): # type: (Any, Union[None, int, str], Any, *List[Any]) -> None visited.append(["enter", type(node).__name__, getattr(node, "value", None)]) def leave( self, node, # type: Union[Field, Name] key, # type: Union[int, str] parent, # type: Union[List[Field], Field] *args # type: List[Any] ): # type: (...) -> None visited.append(["leave", type(node).__name__, getattr(node, "value", None)]) edited_ast = visit(ast, ParallelVisitor([TestVisitor1(), TestVisitor2()])) assert ast == parse("{ a, b, c { a, b, c } }", no_location=True) assert edited_ast == parse("{ a, c { a, c } }", no_location=True) assert visited == [ ["enter", "Document", None], ["enter", "OperationDefinition", None], ["enter", "SelectionSet", None], ["enter", "Field", None], ["enter", "Name", "a"], ["leave", "Name", "a"], ["leave", "Field", None], ["enter", "Field", None], ["enter", "Name", "c"], ["leave", "Name", "c"], ["enter", "SelectionSet", None], ["enter", "Field", None], ["enter", "Name", "a"], ["leave", "Name", "a"], ["leave", "Field", None], ["enter", "Field", None], ["enter", "Name", "c"], ["leave", "Name", "c"], ["leave", "Field", None], ["leave", "SelectionSet", None], ["leave", "Field", None], ["leave", "SelectionSet", None], ["leave", "OperationDefinition", None], ["leave", "Document", None], ]
def rename_query( ast: DocumentNode, renamed_schema_descriptor: RenamedSchemaDescriptor) -> DocumentNode: """Translate names of types using reverse_name_map of the input RenamedSchemaDescriptor. The direction in which types and fields are renamed is opposite of the process that produced the renamed schema descriptor. If a type X was renamed to Y in the schema, then any occurrences of type Y in the input query ast will be renamed to X. All type names (including ones in type coercions), as well as root vertex fields (fields of the query type) will be renamed. No other field names will be renamed. Args: ast: represents a query renamed_schema_descriptor: namedtuple including the attribute reverse_name_map, which maps the new, renamed names of types to their original names. This function will revert these renamed types in the query ast back to their original names Returns: New AST representing the renamed query Raises: - GraphQLValidationError if the AST does not have the expected form; in particular, if the AST fails GraphQL's builtin validation against the provided schema, if it contains Fragments, or if it contains an InlineFragment at the root level """ built_in_validation_errors = validate(renamed_schema_descriptor.schema, ast) if len(built_in_validation_errors) > 0: raise GraphQLValidationError( "AST does not validate: {}".format(built_in_validation_errors)) if len(ast.definitions ) > 1: # includes either multiple queries, or fragment definitions raise GraphQLValidationError( "Only one query may be included, and fragments are not allowed.") query_definition = ast.definitions[0] if not (isinstance(query_definition, OperationDefinitionNode) and query_definition.operation == OperationType.QUERY): raise AssertionError( f"AST argument for rename_query is not a query. Instead, query definition was of type " f"{type(query_definition)}.") for selection in query_definition.selection_set.selections: if not isinstance(selection, FieldNode): # possibly an InlineFragment raise GraphQLValidationError( 'Each root selection must be of type "Field", not "{}" as in ' 'selection "{}"'.format(type(selection).__name__, selection)) visitor = RenameQueryVisitor(renamed_schema_descriptor.reverse_name_map) renamed_ast = visit(ast, visitor) return renamed_ast
def _ensure_no_cascading_type_suppressions( schema_ast: DocumentNode, renamings: Mapping[str, Optional[str]], query_type: str ) -> None: """Check for fields with suppressed types or unions whose members were all suppressed.""" visitor = CascadingSuppressionCheckVisitor(renamings, query_type) visit(schema_ast, visitor) if visitor.fields_to_suppress or visitor.union_types_to_suppress: error_message_components = [ f"Type renamings {renamings} would require further suppressions to produce a valid " f"renamed schema." ] if visitor.fields_to_suppress: for object_type in visitor.fields_to_suppress: error_message_components.append(f"Object type {object_type} contains: ") error_message_components.extend( ( f"field {field} of suppressed type " f"{visitor.fields_to_suppress[object_type][field]}, " for field in visitor.fields_to_suppress[object_type] ) ) error_message_components.append( "A schema containing a field that is of a nonexistent type is invalid. When field " "suppression is supported, you can fix this problem by suppressing the fields " "shown above." ) if visitor.union_types_to_suppress: for union_type in visitor.union_types_to_suppress: error_message_components.append( f"Union type {union_type} has no non-suppressed members: " ) error_message_components.extend( (union_member.name.value for union_member in union_type.types) ) error_message_components.append( "To fix this, you can suppress the union as well by adding union_type: None to the " "renamings argument when renaming types, for each value of union_type described " "here. Note that adding suppressions may lead to other types, fields, etc. " "requiring suppression so you may need to iterate on this before getting a legal " "schema." ) raise CascadingSuppressionError("\n".join(error_message_components))
def pretty_print_graphql(query, use_four_spaces=True): """Take a GraphQL query, pretty print it, and return it.""" # Use our custom visitor, which fixes directive argument order # to get the canonical representation output = visit(parse(query), CustomPrintingVisitor()) # Using four spaces for indentation makes it easier to edit in # Python source files. if use_four_spaces: return fix_indentation_depth(output) return output
def test_allows_for_editing_on_enter(): ast = parse('{ a, b, c { a, b, c } }', no_location=True) class TestVisitor(Visitor): def enter(self, node, *args): if isinstance(node, Field) and node.name.value == 'b': return REMOVE edited_ast = visit(ast, TestVisitor()) assert ast == parse('{ a, b, c { a, b, c } }', no_location=True) assert edited_ast == parse('{ a, c { a, c } }', no_location=True)
def test_allows_skipping_a_subtree(): visited = [] ast = parse('{ a, b { x }, c }') class TestVisitor(Visitor): def enter(self, node, *args): visited.append( ['enter', type(node).__name__, getattr(node, 'value', None)]) if isinstance(node, Field) and node.name.value == 'b': return False def leave(self, node, *args): visited.append( ['leave', type(node).__name__, getattr(node, 'value', None)]) visit(ast, TestVisitor()) assert visited == [ ['enter', 'Document', None], ['enter', 'OperationDefinition', None], ['enter', 'SelectionSet', None], ['enter', 'Field', None], ['enter', 'Name', 'a'], ['leave', 'Name', 'a'], ['leave', 'Field', None], ['enter', 'Field', None], ['enter', 'Field', None], ['enter', 'Name', 'c'], ['leave', 'Name', 'c'], ['leave', 'Field', None], ['leave', 'SelectionSet', None], ['leave', 'OperationDefinition', None], ['leave', 'Document', None], ]
def test_visits_edited_node(): added_field = Field(name=Name(value='__typename')) ast = parse('{ a { x } }') class TestVisitor(Visitor): def __init__(self): self.did_visit_added_field = False def enter(self, node, *args): if isinstance(node, Field) and node.name.value == 'a': selection_set = node.selection_set selections = [] if selection_set: selections = selection_set.selections new_selection_set = SelectionSet(selections=[added_field] + selections) return Field(name=None, selection_set=new_selection_set) if node is added_field: self.did_visit_added_field = True visitor = TestVisitor() visit(ast, visitor) assert visitor.did_visit_added_field
def test_visits_edited_node(): added_field = Field(name=Name(value='__typename')) ast = parse('{ a { x } }') class TestVisitor(Visitor): def __init__(self): self.did_visit_added_field = False def enter(self, node, *args): if isinstance(node, Field) and node.name.value == 'a': selection_set = node.selection_set selections = [] if selection_set: selections = selection_set.selections new_selection_set = SelectionSet( selections=[added_field] + selections) return Field(name=None, selection_set=new_selection_set) if node is added_field: self.did_visit_added_field = True visitor = TestVisitor() visit(ast, visitor) assert visitor.did_visit_added_field
def test_allows_editing_a_node_both_on_enter_and_on_leave(): ast = parse('{ a, b, c { a, b, c } }', no_location=True) class TestVisitor(Visitor): def __init__(self): self.did_enter = False self.did_leave = False def enter(self, node, *args): if isinstance(node, OperationDefinition): self.did_enter = True selection_set = node.selection_set self.selections = None if selection_set: self.selections = selection_set.selections new_selection_set = SelectionSet( selections=[]) return OperationDefinition( name=node.name, variable_definitions=node.variable_definitions, directives=node.directives, loc=node.loc, operation=node.operation, selection_set=new_selection_set) def leave(self, node, *args): if isinstance(node, OperationDefinition): self.did_leave = True new_selection_set = None if self.selections: new_selection_set = SelectionSet( selections=self.selections) return OperationDefinition( name=node.name, variable_definitions=node.variable_definitions, directives=node.directives, loc=node.loc, operation=node.operation, selection_set=new_selection_set) visitor = TestVisitor() edited_ast = visit(ast, visitor) assert ast == parse('{ a, b, c { a, b, c } }', no_location=True) assert edited_ast == ast assert visitor.did_enter assert visitor.did_leave
def test_visits_kitchen_sink(): visited = [] ast = parse(KITCHEN_SINK) class TestVisitor(Visitor): def enter(self, node, key, parent, *args): kind = parent and type(parent).__name__ if kind == 'list': kind = None visited.append(['enter', type(node).__name__, key, kind]) def leave(self, node, key, parent, *args): kind = parent and type(parent).__name__ if kind == 'list': kind = None visited.append(['leave', type(node).__name__, key, kind]) visit(ast, TestVisitor()) assert visited == [ ['enter', 'Document', None, None], ['enter', 'OperationDefinition', 0, None], ['enter', 'Name', 'name', 'OperationDefinition'], ['leave', 'Name', 'name', 'OperationDefinition'], ['enter', 'VariableDefinition', 0, None], ['enter', 'Variable', 'variable', 'VariableDefinition'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'variable', 'VariableDefinition'], ['enter', 'NamedType', 'type', 'VariableDefinition'], ['enter', 'Name', 'name', 'NamedType'], ['leave', 'Name', 'name', 'NamedType'], ['leave', 'NamedType', 'type', 'VariableDefinition'], ['leave', 'VariableDefinition', 0, None], ['enter', 'VariableDefinition', 1, None], ['enter', 'Variable', 'variable', 'VariableDefinition'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'variable', 'VariableDefinition'], ['enter', 'NamedType', 'type', 'VariableDefinition'], ['enter', 'Name', 'name', 'NamedType'], ['leave', 'Name', 'name', 'NamedType'], ['leave', 'NamedType', 'type', 'VariableDefinition'], ['enter', 'EnumValue', 'default_value', 'VariableDefinition'], ['leave', 'EnumValue', 'default_value', 'VariableDefinition'], ['leave', 'VariableDefinition', 1, None], ['enter', 'SelectionSet', 'selection_set', 'OperationDefinition'], ['enter', 'Field', 0, None], ['enter', 'Name', 'alias', 'Field'], ['leave', 'Name', 'alias', 'Field'], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'Argument', 0, None], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'ListValue', 'value', 'Argument'], ['enter', 'IntValue', 0, None], ['leave', 'IntValue', 0, None], ['enter', 'IntValue', 1, None], ['leave', 'IntValue', 1, None], ['leave', 'ListValue', 'value', 'Argument'], ['leave', 'Argument', 0, None], ['enter', 'SelectionSet', 'selection_set', 'Field'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 0, None], ['enter', 'InlineFragment', 1, None], ['enter', 'NamedType', 'type_condition', 'InlineFragment'], ['enter', 'Name', 'name', 'NamedType'], ['leave', 'Name', 'name', 'NamedType'], ['leave', 'NamedType', 'type_condition', 'InlineFragment'], ['enter', 'Directive', 0, None], ['enter', 'Name', 'name', 'Directive'], ['leave', 'Name', 'name', 'Directive'], ['leave', 'Directive', 0, None], ['enter', 'SelectionSet', 'selection_set', 'InlineFragment'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'SelectionSet', 'selection_set', 'Field'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 0, None], ['enter', 'Field', 1, None], ['enter', 'Name', 'alias', 'Field'], ['leave', 'Name', 'alias', 'Field'], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'Argument', 0, None], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'IntValue', 'value', 'Argument'], ['leave', 'IntValue', 'value', 'Argument'], ['leave', 'Argument', 0, None], ['enter', 'Argument', 1, None], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'Variable', 'value', 'Argument'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'value', 'Argument'], ['leave', 'Argument', 1, None], ['enter', 'Directive', 0, None], ['enter', 'Name', 'name', 'Directive'], ['leave', 'Name', 'name', 'Directive'], ['enter', 'Argument', 0, None], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'Variable', 'value', 'Argument'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'value', 'Argument'], ['leave', 'Argument', 0, None], ['leave', 'Directive', 0, None], ['enter', 'SelectionSet', 'selection_set', 'Field'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 0, None], ['enter', 'FragmentSpread', 1, None], ['enter', 'Name', 'name', 'FragmentSpread'], ['leave', 'Name', 'name', 'FragmentSpread'], ['leave', 'FragmentSpread', 1, None], ['leave', 'SelectionSet', 'selection_set', 'Field'], ['leave', 'Field', 1, None], ['leave', 'SelectionSet', 'selection_set', 'Field'], ['leave', 'Field', 0, None], ['leave', 'SelectionSet', 'selection_set', 'InlineFragment'], ['leave', 'InlineFragment', 1, None], ['enter', 'InlineFragment', 2, None], ['enter', 'Directive', 0, None], ['enter', 'Name', 'name', 'Directive'], ['leave', 'Name', 'name', 'Directive'], ['enter', 'Argument', 0, None], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'Variable', 'value', 'Argument'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'value', 'Argument'], ['leave', 'Argument', 0, None], ['leave', 'Directive', 0, None], ['enter', 'SelectionSet', 'selection_set', 'InlineFragment'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 0, None], ['leave', 'SelectionSet', 'selection_set', 'InlineFragment'], ['leave', 'InlineFragment', 2, None], ['enter', 'InlineFragment', 3, None], ['enter', 'SelectionSet', 'selection_set', 'InlineFragment'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 0, None], ['leave', 'SelectionSet', 'selection_set', 'InlineFragment'], ['leave', 'InlineFragment', 3, None], ['leave', 'SelectionSet', 'selection_set', 'Field'], ['leave', 'Field', 0, None], ['leave', 'SelectionSet', 'selection_set', 'OperationDefinition'], ['leave', 'OperationDefinition', 0, None], ['enter', 'OperationDefinition', 1, None], ['enter', 'Name', 'name', 'OperationDefinition'], ['leave', 'Name', 'name', 'OperationDefinition'], ['enter', 'SelectionSet', 'selection_set', 'OperationDefinition'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'Argument', 0, None], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'IntValue', 'value', 'Argument'], ['leave', 'IntValue', 'value', 'Argument'], ['leave', 'Argument', 0, None], ['enter', 'Directive', 0, None], ['enter', 'Name', 'name', 'Directive'], ['leave', 'Name', 'name', 'Directive'], ['leave', 'Directive', 0, None], ['enter', 'SelectionSet', 'selection_set', 'Field'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'SelectionSet', 'selection_set', 'Field'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 0, None], ['leave', 'SelectionSet', 'selection_set', 'Field'], ['leave', 'Field', 0, None], ['leave', 'SelectionSet', 'selection_set', 'Field'], ['leave', 'Field', 0, None], ['leave', 'SelectionSet', 'selection_set', 'OperationDefinition'], ['leave', 'OperationDefinition', 1, None], ['enter', 'OperationDefinition', 2, None], ['enter', 'Name', 'name', 'OperationDefinition'], ['leave', 'Name', 'name', 'OperationDefinition'], ['enter', 'VariableDefinition', 0, None], ['enter', 'Variable', 'variable', 'VariableDefinition'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'variable', 'VariableDefinition'], ['enter', 'NamedType', 'type', 'VariableDefinition'], ['enter', 'Name', 'name', 'NamedType'], ['leave', 'Name', 'name', 'NamedType'], ['leave', 'NamedType', 'type', 'VariableDefinition'], ['leave', 'VariableDefinition', 0, None], ['enter', 'SelectionSet', 'selection_set', 'OperationDefinition'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'Argument', 0, None], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'Variable', 'value', 'Argument'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'value', 'Argument'], ['leave', 'Argument', 0, None], ['enter', 'SelectionSet', 'selection_set', 'Field'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'SelectionSet', 'selection_set', 'Field'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'SelectionSet', 'selection_set', 'Field'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 0, None], ['leave', 'SelectionSet', 'selection_set', 'Field'], ['leave', 'Field', 0, None], ['enter', 'Field', 1, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'SelectionSet', 'selection_set', 'Field'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 0, None], ['leave', 'SelectionSet', 'selection_set', 'Field'], ['leave', 'Field', 1, None], ['leave', 'SelectionSet', 'selection_set', 'Field'], ['leave', 'Field', 0, None], ['leave', 'SelectionSet', 'selection_set', 'Field'], ['leave', 'Field', 0, None], ['leave', 'SelectionSet', 'selection_set', 'OperationDefinition'], ['leave', 'OperationDefinition', 2, None], ['enter', 'FragmentDefinition', 3, None], ['enter', 'Name', 'name', 'FragmentDefinition'], ['leave', 'Name', 'name', 'FragmentDefinition'], ['enter', 'NamedType', 'type_condition', 'FragmentDefinition'], ['enter', 'Name', 'name', 'NamedType'], ['leave', 'Name', 'name', 'NamedType'], ['leave', 'NamedType', 'type_condition', 'FragmentDefinition'], ['enter', 'SelectionSet', 'selection_set', 'FragmentDefinition'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'Argument', 0, None], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'Variable', 'value', 'Argument'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'value', 'Argument'], ['leave', 'Argument', 0, None], ['enter', 'Argument', 1, None], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'Variable', 'value', 'Argument'], ['enter', 'Name', 'name', 'Variable'], ['leave', 'Name', 'name', 'Variable'], ['leave', 'Variable', 'value', 'Argument'], ['leave', 'Argument', 1, None], ['enter', 'Argument', 2, None], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'ObjectValue', 'value', 'Argument'], ['enter', 'ObjectField', 0, None], ['enter', 'Name', 'name', 'ObjectField'], ['leave', 'Name', 'name', 'ObjectField'], ['enter', 'StringValue', 'value', 'ObjectField'], ['leave', 'StringValue', 'value', 'ObjectField'], ['leave', 'ObjectField', 0, None], ['leave', 'ObjectValue', 'value', 'Argument'], ['leave', 'Argument', 2, None], ['leave', 'Field', 0, None], ['leave', 'SelectionSet', 'selection_set', 'FragmentDefinition'], ['leave', 'FragmentDefinition', 3, None], ['enter', 'OperationDefinition', 4, None], ['enter', 'SelectionSet', 'selection_set', 'OperationDefinition'], ['enter', 'Field', 0, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['enter', 'Argument', 0, None], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'BooleanValue', 'value', 'Argument'], ['leave', 'BooleanValue', 'value', 'Argument'], ['leave', 'Argument', 0, None], ['enter', 'Argument', 1, None], ['enter', 'Name', 'name', 'Argument'], ['leave', 'Name', 'name', 'Argument'], ['enter', 'BooleanValue', 'value', 'Argument'], ['leave', 'BooleanValue', 'value', 'Argument'], ['leave', 'Argument', 1, None], ['leave', 'Field', 0, None], ['enter', 'Field', 1, None], ['enter', 'Name', 'name', 'Field'], ['leave', 'Name', 'name', 'Field'], ['leave', 'Field', 1, None], ['leave', 'SelectionSet', 'selection_set', 'OperationDefinition'], ['leave', 'OperationDefinition', 4, None], ['leave', 'Document', None, None] ]
def test_visits_with_typeinfo_maintains_type_info_during_visit(): visited = [] ast = parse('{ human(id: 4) { name, pets { name }, unknown } }') type_info = TypeInfo(test_schema) class TestVisitor(Visitor): def enter(self, node, key, parent, *args): parent_type = type_info.get_parent_type() _type = type_info.get_type() input_type = type_info.get_input_type() visited.append([ 'enter', type(node).__name__, node.value if type(node).__name__ == "Name" else None, str(parent_type) if parent_type else None, str(_type) if _type else None, str(input_type) if input_type else None ]) def leave(self, node, key, parent, *args): parent_type = type_info.get_parent_type() _type = type_info.get_type() input_type = type_info.get_input_type() visited.append([ 'leave', type(node).__name__, node.value if type(node).__name__ == "Name" else None, str(parent_type) if parent_type else None, str(_type) if _type else None, str(input_type) if input_type else None ]) visit(ast, TypeInfoVisitor(type_info, TestVisitor())) assert visited == [ ['enter', 'Document', None, None, None, None], ['enter', 'OperationDefinition', None, None, 'QueryRoot', None], ['enter', 'SelectionSet', None, 'QueryRoot', 'QueryRoot', None], ['enter', 'Field', None, 'QueryRoot', 'Human', None], ['enter', 'Name', 'human', 'QueryRoot', 'Human', None], ['leave', 'Name', 'human', 'QueryRoot', 'Human', None], ['enter', 'Argument', None, 'QueryRoot', 'Human', 'ID'], ['enter', 'Name', 'id', 'QueryRoot', 'Human', 'ID'], ['leave', 'Name', 'id', 'QueryRoot', 'Human', 'ID'], ['enter', 'IntValue', None, 'QueryRoot', 'Human', 'ID'], ['leave', 'IntValue', None, 'QueryRoot', 'Human', 'ID'], ['leave', 'Argument', None, 'QueryRoot', 'Human', 'ID'], ['enter', 'SelectionSet', None, 'Human', 'Human', None], ['enter', 'Field', None, 'Human', 'String', None], ['enter', 'Name', 'name', 'Human', 'String', None], ['leave', 'Name', 'name', 'Human', 'String', None], ['leave', 'Field', None, 'Human', 'String', None], ['enter', 'Field', None, 'Human', '[Pet]', None], ['enter', 'Name', 'pets', 'Human', '[Pet]', None], ['leave', 'Name', 'pets', 'Human', '[Pet]', None], ['enter', 'SelectionSet', None, 'Pet', '[Pet]', None], ['enter', 'Field', None, 'Pet', 'String', None], ['enter', 'Name', 'name', 'Pet', 'String', None], ['leave', 'Name', 'name', 'Pet', 'String', None], ['leave', 'Field', None, 'Pet', 'String', None], ['leave', 'SelectionSet', None, 'Pet', '[Pet]', None], ['leave', 'Field', None, 'Human', '[Pet]', None], ['enter', 'Field', None, 'Human', None, None], ['enter', 'Name', 'unknown', 'Human', None, None], ['leave', 'Name', 'unknown', 'Human', None, None], ['leave', 'Field', None, 'Human', None, None], ['leave', 'SelectionSet', None, 'Human', 'Human', None], ['leave', 'Field', None, 'QueryRoot', 'Human', None], ['leave', 'SelectionSet', None, 'QueryRoot', 'QueryRoot', None], ['leave', 'OperationDefinition', None, None, 'QueryRoot', None], ['leave', 'Document', None, None, None, None] ]
def test_visits_with_typeinfo_maintains_type_info_during_edit(): visited = [] ast = parse('{ human(id: 4) { name, pets }, alien }') type_info = TypeInfo(test_schema) class TestVisitor(Visitor): def enter(self, node, key, parent, *args): parent_type = type_info.get_parent_type() _type = type_info.get_type() input_type = type_info.get_input_type() visited.append([ 'enter', type(node).__name__, node.value if type(node).__name__ == "Name" else None, str(parent_type) if parent_type else None, str(_type) if _type else None, str(input_type) if input_type else None ]) # Make a query valid by adding missing selection sets. if type(node).__name__ == "Field" and not node.selection_set and is_composite_type(get_named_type(_type)): return Field( alias=node.alias, name=node.name, arguments=node.arguments, directives=node.directives, selection_set=SelectionSet( [Field(name=Name(value='__typename'))] ) ) def leave(self, node, key, parent, *args): parent_type = type_info.get_parent_type() _type = type_info.get_type() input_type = type_info.get_input_type() visited.append([ 'leave', type(node).__name__, node.value if type(node).__name__ == "Name" else None, str(parent_type) if parent_type else None, str(_type) if _type else None, str(input_type) if input_type else None ]) edited_ast = visit(ast, TypeInfoVisitor(type_info, TestVisitor())) # assert print_ast(ast) == print_ast(parse( # '{ human(id: 4) { name, pets }, alien }' # )) assert print_ast(edited_ast) == print_ast(parse( '{ human(id: 4) { name, pets { __typename } }, alien { __typename } }' )) assert visited == [ ['enter', 'Document', None, None, None, None], ['enter', 'OperationDefinition', None, None, 'QueryRoot', None], ['enter', 'SelectionSet', None, 'QueryRoot', 'QueryRoot', None], ['enter', 'Field', None, 'QueryRoot', 'Human', None], ['enter', 'Name', 'human', 'QueryRoot', 'Human', None], ['leave', 'Name', 'human', 'QueryRoot', 'Human', None], ['enter', 'Argument', None, 'QueryRoot', 'Human', 'ID'], ['enter', 'Name', 'id', 'QueryRoot', 'Human', 'ID'], ['leave', 'Name', 'id', 'QueryRoot', 'Human', 'ID'], ['enter', 'IntValue', None, 'QueryRoot', 'Human', 'ID'], ['leave', 'IntValue', None, 'QueryRoot', 'Human', 'ID'], ['leave', 'Argument', None, 'QueryRoot', 'Human', 'ID'], ['enter', 'SelectionSet', None, 'Human', 'Human', None], ['enter', 'Field', None, 'Human', 'String', None], ['enter', 'Name', 'name', 'Human', 'String', None], ['leave', 'Name', 'name', 'Human', 'String', None], ['leave', 'Field', None, 'Human', 'String', None], ['enter', 'Field', None, 'Human', '[Pet]', None], ['enter', 'Name', 'pets', 'Human', '[Pet]', None], ['leave', 'Name', 'pets', 'Human', '[Pet]', None], ['enter', 'SelectionSet', None, 'Pet', '[Pet]', None], ['enter', 'Field', None, 'Pet', 'String!', None], ['enter', 'Name', '__typename', 'Pet', 'String!', None], ['leave', 'Name', '__typename', 'Pet', 'String!', None], ['leave', 'Field', None, 'Pet', 'String!', None], ['leave', 'SelectionSet', None, 'Pet', '[Pet]', None], ['leave', 'Field', None, 'Human', '[Pet]', None], ['leave', 'SelectionSet', None, 'Human', 'Human', None], ['leave', 'Field', None, 'QueryRoot', 'Human', None], ['enter', 'Field', None, 'QueryRoot', 'Alien', None], ['enter', 'Name', 'alien', 'QueryRoot', 'Alien', None], ['leave', 'Name', 'alien', 'QueryRoot', 'Alien', None], ['enter', 'SelectionSet', None, 'Alien', 'Alien', None], ['enter', 'Field', None, 'Alien', 'String!', None], ['enter', 'Name', '__typename', 'Alien', 'String!', None], ['leave', 'Name', '__typename', 'Alien', 'String!', None], ['leave', 'Field', None, 'Alien', 'String!', None], ['leave', 'SelectionSet', None, 'Alien', 'Alien', None], ['leave', 'Field', None, 'QueryRoot', 'Alien', None], ['leave', 'SelectionSet', None, 'QueryRoot', 'QueryRoot', None], ['leave', 'OperationDefinition', None, None, 'QueryRoot', None], ['leave', 'Document', None, None, None, None] ]