Exemplo n.º 1
0
def _get_minimal_query_ast_from_macro_ast(macro_ast):
    """Get a query that should successfully compile to IR if the macro is valid."""
    ast_without_macro_directives = remove_directives_from_ast(macro_ast, {
        directive.name
        for directive in DIRECTIVES_REQUIRED_IN_MACRO_EDGE_DEFINITION
    })

    # We will add this output directive to make the ast a valid query
    output_directive = Directive(Name('output'), arguments=[
        Argument(Name('out_name'), StringValue('dummy_output_name'))
    ])

    # Shallow copy everything on the path to the first level selection list
    query_ast = copy(ast_without_macro_directives)
    root_level_selection = copy(get_only_selection_from_ast(query_ast, GraphQLInvalidMacroError))
    first_level_selections = copy(root_level_selection.selection_set.selections)

    # Add an output to a new or existing __typename field
    existing_typename_field = None
    for idx, selection in enumerate(first_level_selections):
        if isinstance(selection, Field):
            if selection.name.value == '__typename':
                # We have a copy of the list, but the elements are references to objects
                # in macro_ast that we don't want to mutate. So the following copy is necessary.
                existing_typename_field = copy(selection)
                existing_typename_field.directives = copy(existing_typename_field.directives)
                existing_typename_field.directives.append(output_directive)
                first_level_selections[idx] = existing_typename_field
    if existing_typename_field is None:
        first_level_selections.insert(0, Field(Name('__typename'), directives=[output_directive]))

    # Propagate the changes back to the result_ast
    root_level_selection.selection_set = SelectionSet(first_level_selections)
    query_ast.selection_set = SelectionSet([root_level_selection])
    return Document([query_ast])
Exemplo n.º 2
0
def _get_output_directive(out_name):
    """Return a Directive representing an @output with the input out_name."""
    return Directive(
        name=Name(value=OutputDirective.name),
        arguments=[
            Argument(
                name=Name(value=u'out_name'),
                value=StringValue(value=out_name),
            ),
        ],
    )
Exemplo n.º 3
0
def get_schema_with_macros(macro_registry):
    """Get a new GraphQLSchema with fields where macro edges can be used.

    Preconditions:
    1. No macro in the registry has the same name as a field on the vertex where it applies.
    2. Members of a union type do not have outgoing macros with the same name.

    An easy way to satisfy the preconditions is to create the macro_registry using
    create_macro_registry, and only update it with register_macro_edge, which does all
    the necessary validation.

    Postconditions:
    1. Every GraphQLQuery that uses macros from this registry appropriately should
       successfully type-check against the schema generated from this function.
    2. A GraphQLQuery that uses macros not present in the registry, or uses valid
       macros but on types they are not defined at should fail schema validation with
       the schema generated from this function.
    3. This function is total -- A valid macro registry should not fail to create a
       GraphQL schema with macros.

    Args:
        macro_registry: MacroRegistry object containing a schema and macro descriptors
                        we want to add to the schema.

    Returns:
        GraphQLSchema with additional fields where macroe edges can be used.
    """
    # The easiest way to manipulate the schema is through its AST. The easiest
    # way to get an AST is to print it and parse it.
    schema_ast = parse(print_schema(macro_registry.schema_without_macros))

    definitions_by_name = {}
    for definition in schema_ast.definitions:
        if isinstance(definition,
                      (ObjectTypeDefinition, InterfaceTypeDefinition)):
            definitions_by_name[definition.name.value] = definition

    for class_name, macros_for_class in six.iteritems(
            macro_registry.macro_edges_at_class):
        for macro_edge_name, macro_edge_descriptor in six.iteritems(
                macros_for_class):
            list_type_at_target = ListType(
                NamedType(Name(macro_edge_descriptor.target_class_name)))
            arguments = []
            directives = [Directive(Name(MacroEdgeDirective.name))]
            definitions_by_name[class_name].fields.append(
                FieldDefinition(Name(macro_edge_name),
                                arguments,
                                list_type_at_target,
                                directives=directives))

    return build_ast_schema(schema_ast)
Exemplo n.º 4
0
def _replace_tag_names_in_tag_directive(name_change_map, tag_directive):
    """Apply tag parameter renaming to the given tag directive.

    Args:
        name_change_map: Dict[str, str] mapping tag names to new names
        tag_directive: GraphQL library tag directive whose name is in the name_change_map.
                       This ast is not mutated.

    Returns:
        GraphQL library directive object, equivalent to the input one, with its name changed
        according to the name_change_map. If no changes were made, this is the same object
        as the input tag directive.
    """
    # Schema validation has ensured this exists
    current_name = tag_directive.arguments[0].value.value
    new_name = name_change_map[current_name]

    if new_name == current_name:
        # No changes are necessary, return the original input object.
        return tag_directive

    renamed_tag_directive = copy(tag_directive)
    renamed_tag_directive.arguments = [
        Argument(Name('tag_name'), StringValue(new_name))
    ]
    return renamed_tag_directive
Exemplo n.º 5
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
Exemplo n.º 6
0
        def enter(self, node, key, parent, *args):
            # type: (Any, Union[None, int, str], Any, *List[Any]) -> Optional[Any]
            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"))]),
                )
Exemplo n.º 7
0
def get_copy_of_node_with_new_name(node, new_name):
    """Return a node with new_name as its name and otherwise identical to the input node.

    Args:
        node: type Node, with a .name attribute. Not modified by this function
        new_name: str, name to give to the output node

    Returns:
        Node, with new_name as its name and otherwise identical to the input node
    """
    node_type = type(node).__name__
    allowed_types = frozenset((
        'EnumTypeDefinition',
        'Field',
        'FieldDefinition',
        'InterfaceTypeDefinition',
        'NamedType',
        'ObjectTypeDefinition',
        'UnionTypeDefinition',
    ))
    if node_type not in allowed_types:
        raise AssertionError(
            u'Input node {} of type {} is not allowed, only {} are allowed.'.
            format(node, node_type, allowed_types))
    node_with_new_name = copy(node)  # shallow copy is enough
    node_with_new_name.name = Name(value=new_name)
    return node_with_new_name
Exemplo n.º 8
0
def _get_in_collection_filter_directive(input_filter_name):
    """Create a @filter directive with in_collecion operation and the desired variable name."""
    return Directive(
        name=Name(value=FilterDirective.name),
        arguments=[
            Argument(
                name=Name(value='op_name'),
                value=StringValue(value='in_collection'),
            ),
            Argument(
                name=Name(value='value'),
                value=ListValue(values=[
                    StringValue(value=u'$' + input_filter_name),
                ], ),
            ),
        ],
    )
Exemplo n.º 9
0
def _replace_tag_names_in_filter_directive(name_change_map, filter_directive):
    """Apply tag parameter renaming to the given filter directive.

    Args:
        name_change_map: Dict[str, str] mapping tag names to new names
        filter_directive: GraphQL library filter directive object that potentially uses
                          tagged parameters. All such tagged parameters should be in
                          the name_change_map. This directive object is not mutated,
                          and if no changes are necessary then it will be returned

    Returns:
        GraphQL library directive object, equivalent to the input one, with any tagged parameters it
        uses replaced according to the name_change_map. If no changes were made, this is the
        same object as the input filter directive.
    """
    made_changes = False

    new_arguments = []
    for argument in filter_directive.arguments:
        if argument.name.value == 'op_name':
            new_arguments.append(argument)
        elif argument.name.value == 'value':
            new_value_list = []
            for value in argument.value.values:
                parameter = value.value
                new_value = value

                # Rewrite tagged parameter names if necessary.
                if is_tagged_parameter(parameter):
                    current_name = get_parameter_name(parameter)
                    new_name = name_change_map[current_name]
                    if new_name != current_name:
                        made_changes = True
                        new_value = StringValue('%' + new_name)

                new_value_list.append(new_value)

            if made_changes:
                new_argument = Argument(Name('value'),
                                        value=ListValue(new_value_list))
            else:
                new_argument = argument
            new_arguments.append(new_argument)
        else:
            raise AssertionError(
                u'Unknown argument name {} in filter directive {}, this should '
                u'have been caught in an earlier validation step.'.format(
                    argument.name.value, filter_directive))

    if not made_changes:
        # No changes were made, return the original input object.
        return filter_directive

    filter_with_renamed_args = copy(filter_directive)
    filter_with_renamed_args.arguments = new_arguments
    return filter_with_renamed_args
Exemplo n.º 10
0
def build_default_schema(document: Document) -> SchemaDefinition:
    defined_types = [
        td.name.value for td in document.definitions
        if isinstance(td, ObjectTypeDefinition)
    ]
    operations = []
    if "Query" in defined_types:
        operations.append(
            OperationTypeDefinition("query",
                                    type=NamedType(name=Name("Query"))))
    if "Mutation" in defined_types:
        operations.append(
            OperationTypeDefinition("mutation",
                                    type=NamedType(name=Name("Mutation"))))
    if "Subscription" in defined_types:
        operations.append(
            OperationTypeDefinition("subscription",
                                    type=NamedType(name=Name("Subscription"))))
    return SchemaDefinition(operation_types=operations, directives=[])
Exemplo n.º 11
0
def _get_property_field(existing_field, field_name, directives_from_edge):
    """Return a Field object with field_name, sharing directives with any such existing field.

    Any valid directives in directives_on_edge will be transferred over to the new field.
    If there is an existing Field in selection with field_name, the returned new Field
    will also contain all directives of the existing field with that name.

    Args:
        existing_field: Field or None. If it's not None, it is a field with field_name. The
                        directives of this field will carry output to the output field
        field_name: str, the name of the output field
        directives_from_edge: List[Directive], the directives of a vertex field. The output
                              field will contain all @filter and any @optional directives
                              from this list

    Returns:
        Field object, with field_name as its name, containing directives from any field in the
        input selections with the same name and directives from the input list of directives
    """
    new_field = Field(
        name=Name(value=field_name),
        directives=[],
    )

    # Transfer directives from existing field of the same name
    if existing_field is not None:
        # Existing field, add all its directives
        directives_from_existing_field = existing_field.directives
        if directives_from_existing_field is not None:
            new_field.directives.extend(directives_from_existing_field)
    # Transfer directives from edge
    if directives_from_edge is not None:
        for directive in directives_from_edge:
            if directive.name.value == OutputDirective.name:  # output illegal on vertex field
                raise GraphQLValidationError(
                    u'Directive "{}" is not allowed on a vertex field, as @output directives '
                    u'can only exist on property fields.'.format(directive))
            elif directive.name.value == OptionalDirective.name:
                if try_get_ast_by_name_and_type(new_field.directives,
                                                OptionalDirective.name,
                                                Directive) is None:
                    # New optional directive
                    new_field.directives.append(directive)
            elif directive.name.value == FilterDirective.name:
                new_field.directives.append(directive)
            else:
                raise AssertionError(
                    u'Unreachable code reached. Directive "{}" is of an unsupported type, and '
                    u'was not caught in a prior validation step.'.format(
                        directive))

    return new_field
Exemplo n.º 12
0
def _get_query_document(root_vertex_field_name, root_selections):
    """Return a Document representing a query with the specified name and selections."""
    return Document(definitions=[
        OperationDefinition(operation='query',
                            selection_set=SelectionSet(selections=[
                                Field(
                                    name=Name(value=root_vertex_field_name),
                                    selection_set=SelectionSet(
                                        selections=root_selections, ),
                                    directives=[],
                                )
                            ]))
    ])
Exemplo n.º 13
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
Exemplo n.º 14
0
        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'))]))
Exemplo n.º 15
0
def test_prints_minimal_ast():
    # type: () -> None
    ast = Field(name=Name(loc=None, value="foo"))
    assert print_ast(ast) == "foo"