예제 #1
0
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))
예제 #2
0
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)
예제 #3
0
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"],
    ]
예제 #4
0
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'],
    ]
예제 #5
0
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
예제 #6
0
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']
    ]
예제 #7
0
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],
    ]
예제 #8
0
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],
    ]
예제 #9
0
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))
예제 #10
0
    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
예제 #11
0
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],
    ]
예제 #12
0
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],
    ]
예제 #13
0
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],
    ]
예제 #14
0
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
예제 #16
0
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']]
예제 #17
0
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]
    ]
예제 #18
0
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
예제 #19
0
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
예제 #20
0
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
예제 #21
0
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
예제 #22
0
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
예제 #23
0
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
예제 #24
0
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
예제 #25
0
    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()))
예제 #26
0
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
예제 #27
0
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
예제 #28
0
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],
    ]
예제 #29
0
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"],
    ]
예제 #30
0
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]
    ]
예제 #31
0
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],
    ]
예제 #32
0
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
예제 #33
0
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
예제 #35
0
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
예제 #36
0
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)
예제 #37
0
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)
예제 #38
0
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],
    ]
예제 #39
0
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
예제 #40
0
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
예제 #41
0
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
예제 #42
0
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]
    ]
예제 #43
0
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]
    ]
예제 #44
0
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]
    ]