예제 #1
0
def make_dict_comprehension(node, capture, arguments):
    """
    dict([(k, v) for k, v in x])
        --> {k: v for k, v in x}

    PYTHON 2 NOTE:
    Where list comprehensions in python 2 set local-scope variables,
    dict comprehensions do not!
    So this may change the behaviour of your code in subtle ways.
    e.g.

        >>> a = 5
        >>> b = dict([(a, a) for a in (1, 2, 3)])
        >>> print(a)
        3
        >>> a = 5
        >>> b = {a: a for a in (1, 2, 3)}
        >>> print(a)
        5
    """
    kv = capture['kv']
    key = capture['k']
    value = capture['v']
    forloop = capture['forloop'][0]
    ifpart = capture.get('ifpart') or None

    forloop.type = syms.comp_for
    if ifpart:
        ifpart.type = syms.comp_if

    node.replace(
        Node(
            syms.atom,
            [
                Leaf(TOKEN.LBRACE, "{"),
                Node(
                    syms.dictsetmaker,
                    [
                        key.clone(),
                        Leaf(TOKEN.COLON, ":"),
                        value.clone(),
                        forloop.clone(),
                    ],
                    prefix=kv.parent.prefix,
                ),
                Leaf(TOKEN.RBRACE, "}", prefix=kv.parent.get_suffix()),
            ],
            prefix=node.prefix,
        ))
예제 #2
0
def remove_resolve_only_args(node: LN, capture: Capture,
                             filename: Filename) -> Optional[LN]:
    """
    This one doesn't matter to us since there are no occurences of @resolve_only_args.
    XXX: don't use this
    """

    # Remove the decorator
    decorator = capture.get('decorator')
    if decorator is None:
        decorators = capture.get('decorators')
    else:
        decorators = Node(children=[decorator])

    if decorator is not None:
        for d in decorator.children:
            print(f'{d}')
        print(f'Child count: {len(decorator.children)}')
        if Leaf(1, 'resolve_only_args') in decorator.children:
            pos = decorator.children.index(Leaf(1, 'resolve_only_args'))
            print(f'at pos: {pos}')
            print(f'{decorator.children[pos]}')
            decorator.remove()

    # Add 'info' to the parameter list
    print(capture.get('resolver'))
    param = capture.get('param')
    if param is not None:
        print(param)

    return node
예제 #3
0
def pytest_approx(node, capture, filename):
    target_value = listify(capture['target_value'])[0].clone()
    target_value.prefix = ''
    abs_tolerance = capture['abs_tolerance'].clone()
    abs_tolerance.prefix = ''
    op_value = listify(capture['op'])[0].value

    # Adds a 'import pytest' if there wasn't one already
    touch_import(None, "pytest", node)

    if op_value in ('<', '<='):
        # as you'd expect in an assert statement
        operator = Leaf(TOKEN.EQEQUAL, '==', prefix=' ')
    else:
        # probably in an if statement
        operator = Leaf(TOKEN.NOTEQUAL, '!=', prefix=' ')

    node.replace(
        Node(
            syms.comparison,
            [
                capture['lhs'].clone(),
                operator,
                Node(
                    syms.power,
                    [
                        kw('pytest'),
                        Node(
                            syms.trailer,
                            [Leaf(TOKEN.DOT, ".", prefix=''), kw('approx', prefix='')],
                            prefix='',
                        ),
                        ArgList(
                            [
                                target_value,
                                Comma(),
                                KeywordArg(kw('abs'), abs_tolerance),
                            ]
                        ),
                    ],
                ),
            ],
            prefix=node.prefix,
        )
    )
예제 #4
0
def replace_unicode_methods(node, capture, arguments):

    # remove any existing __str__ method
    b = find_binding("__str__", capture['suite'])
    if b and b.type == syms.funcdef:
        b.remove()

    # rename __unicode__ to __str__
    funcname = capture['funcname'].clone()
    funcname.value = '__str__'
    capture['funcname'].replace(funcname)

    # Add a six import
    touch_import(None, "six", node)

    # Decorate the class with `@six.python_2_unicode_compatible`
    classdef = node.clone()
    classdef.prefix = ''
    decorated = Node(
        syms.decorated,
        [
            Node(
                syms.decorator,
                [
                    Leaf(TOKEN.AT, '@', prefix=node.prefix),
                    Node(
                        syms.dotted_name,
                        [
                            Name('six'),
                            Dot(),
                            Name('python_2_unicode_compatible')
                        ],
                    ),
                    Newline(),
                ],
                prefix=node.prefix,
            ),
            classdef,
        ],
        prefix=node.prefix,
    )
    node.replace(decorated)
예제 #5
0
def simplify_none_operand(node, capture, arguments):
    """
    a != None
        --> a is not None
    """
    op = capture['op'][0]
    print(op)
    if op.type == TOKEN.EQEQUAL:
        op.replace(kw('is'))
    else:
        op.replace(Node(syms.comp_op, [kw('is'), kw('not')]))
예제 #6
0
def make_set_comprehension(node, capture, arguments):
    arg = capture['arg']
    forloop = capture['forloop'][0]
    ifpart = capture.get('ifpart') or None

    forloop.type = syms.comp_for
    if ifpart:
        ifpart.type = syms.comp_if

    node.replace(
        Node(
            syms.atom,
            [
                Leaf(TOKEN.LBRACE, "{"),
                Node(
                    syms.dictsetmaker,
                    [arg.clone(), forloop.clone()],
                    prefix=arg.parent.prefix,
                ),
                Leaf(TOKEN.RBRACE, "}", prefix=arg.parent.get_suffix()),
            ],
            prefix=node.prefix,
        ))
예제 #7
0
def handle_assertraises(node, capture, arguments):
    """
    with self.assertRaises(x):

        --> with pytest.raises(x):

    self.assertRaises(ValueError, func, arg1)

        --> pytest.raises(ValueError, func, arg1)
    """
    capture['attr1'].replace(kw('pytest', prefix=capture['attr1'].prefix))
    capture['attr2'].replace(
        Node(syms.trailer, [Dot(), kw('raises', prefix='')]))

    # Adds a 'import pytest' if there wasn't one already
    touch_import(None, "pytest", node)
예제 #8
0
def Assert(test, message=None, **kwargs):
    """Build an assertion statement"""
    if not isinstance(test, list):
        test = [test]
    test[0].prefix = " "
    if message is not None:
        if not isinstance(message, list):
            message = [message]
        message.insert(0, Comma())
        message[1].prefix = " "

    return Node(
        syms.assert_stmt,
        [Leaf(TOKEN.NAME, "assert")] + test + (message or []),
        **kwargs,
    )
예제 #9
0
def handle_assert_regex(node, capture, arguments):
    """
    self.assertRegex(text, pattern, msg)
    --> assert re.search(pattern, text), msg

    self.assertNotRegex(text, pattern, msg)
    --> assert not re.search(pattern, text), msg

    """
    function_name = capture["function_name"].value
    invert = function_name in INVERT_FUNCTIONS
    function_name = SYNONYMS.get(function_name, function_name)
    num_arguments = ARGUMENTS[function_name]

    if len(arguments) not in (num_arguments, num_arguments + 1):
        # Not sure what this is. Leave it alone.
        return None

    if len(arguments) == num_arguments:
        message = None
    else:
        message = arguments.pop()
        if message.type == syms.argument:
            # keyword argument (e.g. `msg=abc`)
            message = message.children[2].clone()

    arguments[0].prefix = " "
    arguments[1].prefix = ""

    # Adds a 'import re' if there wasn't one already
    touch_import(None, "re", node)

    op_tokens = []
    if invert:
        op_tokens.append(keyword("not"))

    assert_test_nodes = [
        Node(
            syms.power,
            op_tokens + Attr(keyword("re"), keyword("search", prefix="")) +
            [ArgList([arguments[1], Comma(), arguments[0]])],
        )
    ]
    return Assert(assert_test_nodes,
                  message.clone() if message else None,
                  prefix=node.prefix)
예제 #10
0
def handle_assertraises(node, capture, arguments):
    """
    with self.assertRaises(x):

        --> with pytest.raises(x):

    self.assertRaises(ValueError, func, arg1)

        --> pytest.raises(ValueError, func, arg1)
    """
    capture["self_attr"].replace(
        keyword("pytest", prefix=capture["self_attr"].prefix))
    capture["raises_attr"].replace(
        Node(syms.trailer, [Dot(), keyword("raises", prefix="")]))
    # Let's remove the msg= keyword argument if found
    for child in node.children:
        if child.type != syms.trailer:
            continue
        for tchild in child.children:
            if tchild.type != syms.arglist:
                continue
            previous_argument = None
            for argument in tchild.children:
                if isinstance(argument, Leaf):
                    previous_argument = argument
                    continue
                if isinstance(argument, Node):
                    if argument.type != syms.argument:
                        previous_argument = argument
                        continue
                    for leaf in argument.leaves():
                        if leaf.value == "msg":
                            argument.remove()
                            if previous_argument.value == ",":
                                # previous_argument is a comma, remove it.
                                previous_argument.remove()

    # Adds a 'import pytest' if there wasn't one already
    touch_import(None, "pytest", node)
예제 #11
0
def assertalmostequal_to_assert(node, capture, arguments):
    function_name = capture["function_name"].value
    invert = function_name in INVERT_FUNCTIONS
    function_name = SYNONYMS.get(function_name, function_name)

    nargs = len(arguments)
    if nargs < 2 or nargs > 5:
        return None

    def get_kwarg_value(index, name):
        idx = 0
        for arg in arguments:
            if arg.type == syms.argument:
                if arg.children[0].value == name:
                    return arg.children[2].clone()
            else:
                if idx == index:
                    return arg.clone()
                idx += 1
        return None

    first = get_kwarg_value(0, "first")
    second = get_kwarg_value(1, "second")

    if first is None or second is None:
        # Not sure what this is, leave it alone
        return

    places = get_kwarg_value(2, "places")
    msg = get_kwarg_value(3, "msg")
    delta = get_kwarg_value(4, "delta")

    if delta is not None:
        try:
            abs_delta = float(delta.value)
        except ValueError:
            # this should be a number, give up.
            return
    else:
        if places is None:
            places = 7
        else:
            try:
                places = int(places.value)
            except (ValueError, AttributeError):
                # this should be an int, give up.
                return
        abs_delta = "1e-%d" % places

    arguments[1].prefix = ""
    if invert:
        op_token = Leaf(TOKEN.NOTEQUAL, "!=", prefix=" ")
    else:
        op_token = Leaf(TOKEN.EQEQUAL, "==", prefix=" ")
    assert_test_nodes = [
        Node(
            syms.comparison,
            [
                arguments[0],
                op_token,
                Node(
                    syms.power,
                    Attr(keyword("pytest"), keyword("approx", prefix="")) + [
                        ArgList([
                            arguments[1],
                            Comma(),
                            KeywordArg(keyword("abs"),
                                       Leaf(TOKEN.NUMBER, abs_delta)),
                        ])
                    ],
                ),
            ],
        )
    ]
    # Adds a 'import pytest' if there wasn't one already
    touch_import(None, "pytest", node)

    return Assert(assert_test_nodes,
                  msg.clone() if msg else None,
                  prefix=node.prefix)
예제 #12
0
def assertmethod_to_assert(node, capture, arguments):
    """
    self.assertEqual(foo, bar, msg)
    --> assert foo == bar, msg

    self.assertTrue(foo, msg)
    --> assert foo, msg

    self.assertIsNotNone(foo, msg)
    --> assert foo is not None, msg

    .. etc
    """
    function_name = capture["function_name"].value
    invert = function_name in INVERT_FUNCTIONS
    function_name = SYNONYMS.get(function_name, function_name)
    num_arguments = ARGUMENTS[function_name]

    if len(arguments) not in (num_arguments, num_arguments + 1):
        # Not sure what this is. Leave it alone.
        return None

    if len(arguments) == num_arguments:
        message = None
    else:
        message = arguments.pop()
        if message.type == syms.argument:
            # keyword argument (e.g. `msg=abc`)
            message = message.children[2].clone()

    if function_name == "assertIsInstance":
        arguments[0].prefix = ""
        assert_test_nodes = [
            Call(keyword("isinstance"),
                 [arguments[0], Comma(), arguments[1]])
        ]
        if invert:
            assert_test_nodes.insert(0, keyword("not"))
    elif function_name == "assertAlmostEqual":
        arguments[1].prefix = ""
        # TODO: insert the `import pytest` at the top of the file
        if invert:
            op_token = Leaf(TOKEN.NOTEQUAL, "!=", prefix=" ")
        else:
            op_token = Leaf(TOKEN.EQEQUAL, "==", prefix=" ")
        assert_test_nodes = [
            Node(
                syms.comparison,
                [
                    arguments[0],
                    op_token,
                    Node(
                        syms.power,
                        Attr(keyword("pytest"), keyword("approx", prefix="")) +
                        [
                            ArgList([
                                arguments[1],
                                Comma(),
                                KeywordArg(keyword("abs"),
                                           Leaf(TOKEN.NUMBER, "1e-7")),
                            ])
                        ],
                    ),
                ],
            )
        ]
        # Adds a 'import pytest' if there wasn't one already
        touch_import(None, "pytest", node)

    else:
        op_tokens = OPERATORS[function_name]
        if not isinstance(op_tokens, list):
            op_tokens = [op_tokens]
        op_tokens = [o.clone() for o in op_tokens]

        if invert:
            if not op_tokens:
                op_tokens.append(keyword("not"))
            elif op_tokens[0].type == TOKEN.NAME and op_tokens[0].value == "is":
                op_tokens[0] = Node(
                    syms.comp_op,
                    [keyword("is"), keyword("not")], prefix=" ")
            elif op_tokens[0].type == TOKEN.NAME and op_tokens[0].value == "in":
                op_tokens[0] = Node(
                    syms.comp_op,
                    [keyword("not"), keyword("in")], prefix=" ")
            elif op_tokens[0].type == TOKEN.EQEQUAL:
                op_tokens[0] = Leaf(TOKEN.NOTEQUAL, "!=", prefix=" ")

        if num_arguments == 2:
            # a != b, etc.
            assert_test_nodes = [arguments[0]] + op_tokens + [arguments[1]]
        elif function_name == "assertTrue":
            assert_test_nodes = op_tokens + [arguments[0]]
            # not a
        elif function_name == "assertIsNone":
            # a is not None
            assert_test_nodes = [arguments[0]] + op_tokens
    return Assert(assert_test_nodes,
                  message.clone() if message else None,
                  prefix=node.prefix)
예제 #13
0
def kw(name, **kwargs):
    """
    A helper to produce keyword nodes
    """
    kwargs.setdefault('prefix', ' ')
    return Leaf(TOKEN.NAME, name, **kwargs)


OPERATOR_INVERSIONS = {
    '==': Leaf(TOKEN.NOTEQUAL, '!=', prefix=' '),
    '!=': Leaf(TOKEN.EQEQUAL, '==', prefix=' '),
    '<': Leaf(TOKEN.GREATEREQUAL, '>=', prefix=' '),
    '>': Leaf(TOKEN.LESSEQUAL, '<=', prefix=' '),
    '<=': Leaf(TOKEN.GREATER, '>', prefix=' '),
    '>=': Leaf(TOKEN.LESS, '<', prefix=' '),
    'in': Node(syms.comp_op, [kw('not'), kw('in')], prefix=' '),
    'not in': kw('in'),
    'is': Node(syms.comp_op, [kw('is'), kw('not')], prefix=' '),
    'is not': kw('is'),
}


def invert_operator(op):
    return OPERATOR_INVERSIONS[str(op).strip()].clone()


def simplify_not_operators(node, capture, arguments):
    """
    not a == b
        --> a != b
예제 #14
0
def handle_assertraises_regex(node, capture, arguments):
    """
    with self.assertRaisesRegex(x, "regex match"):

        --> with pytest.raises(x, match="regex match"):

    self.assertRaises(ValueError, "regex match", func, arg1)

        --> pytest.raises(ValueError, func, arg1, match="regex match")
    """
    capture["self_attr"].replace(
        keyword("pytest", prefix=capture["self_attr"].prefix))
    capture["raises_attr"].replace(
        Node(syms.trailer, [Dot(), keyword("raises", prefix="")]))
    # Let's remove the msg= keyword argument if found and replace the second argument
    # with a match keyword argument
    regex_match = None
    for child in node.children:
        if child.type != syms.trailer:
            continue
        for tchild in child.children:
            if tchild.type != syms.arglist:
                continue
            previous_argument = None
            argnum = 0
            for argument in list(tchild.children):
                if isinstance(argument, Leaf):
                    if argument.value != ",":
                        argnum += 1
                    else:
                        previous_argument = argument
                        continue

                    if argnum != 2:
                        previous_argument = argument
                        continue

                    if argnum == 2:
                        regex_match = Node(
                            syms.argument,
                            [
                                Leaf(TOKEN.NAME, "match"),
                                Leaf(TOKEN.EQUAL, "="),
                                Leaf(TOKEN.STRING, argument.value),
                            ],
                            prefix=" ",
                        )
                        argument.remove()
                        if previous_argument and previous_argument.value == ",":
                            previous_argument.remove()
                        previous_argument = None
                        continue
                if isinstance(argument, Node):
                    if argument.type != syms.argument:
                        previous_argument = argument
                        continue
                    for leaf in argument.leaves():
                        if leaf.value == "msg":
                            argument.remove()
                            if previous_argument and previous_argument.value == ",":
                                # previous_argument is a comma, remove it.
                                previous_argument.remove()

    if regex_match:
        regex_match_added = False
        for child in node.children:
            if regex_match_added:
                break
            if child.type != syms.trailer:
                continue
            for tchild in child.children:
                if tchild.type != syms.arglist:
                    continue
                tchild.children.append(Leaf(TOKEN.COMMA, ","))
                tchild.children.append(regex_match)
                regex_match_added = True
                break

    # Adds a 'import pytest' if there wasn't one already
    touch_import(None, "pytest", node)
예제 #15
0
def make_pytest_raises_blocks(node, capture, filename):
    """
    Turns this:

        try:
            ...
            pytest.fail(...)
        except:
            pass

    Into:
        with pytest.raises(Exception):
            ...

    Not only is this prettier, but the former is a bug since
    pytest.fail() raises an exception.
    """

    exc_class = capture.get('exc_class', None)

    if exc_class:
        exc_class = exc_class.clone()
        exc_class.prefix = ''
        raises_args = [exc_class]
    else:
        raises_args = [kw('Exception', prefix='')]

    reason = capture.get('reason')
    if reason:
        assert len(reason) == 1
        reason = KeywordArg(kw('message'), reason[0].clone())
        raises_args = [Node(syms.arglist, raises_args + [Comma(), reason])]

    raises_args = [LParen()] + raises_args + [RParen()]

    capture['fail_stmt'].remove()

    try_suite = capture['try_suite'].clone()

    with_stmt = Node(
        syms.with_stmt,
        [
            kw('with', prefix=''),
            Node(
                syms.power,
                [
                    kw('pytest'),
                    Node(syms.trailer, [Dot(), kw('raises', prefix='')]),
                    Node(syms.trailer, raises_args),
                ],
            ),
            Leaf(TOKEN.COLON, ':'),
            try_suite,
        ],
        prefix=node.prefix,
    )

    # Trailing whitespace and any comments after the if statement are captured
    # in the prefix for the dedent node. Copy it to the following node.
    dedent = capture["dedent"]
    next_node = node.next_sibling

    # This extra newline avoids syntax errors in some cases (where the try
    # statement is at the end of another suite)
    # I don't really know why those occur.
    # Should clean this stuff up with `black` later.
    node.replace([with_stmt, Newline()])
    next_node.prefix = dedent.prefix
예제 #16
0
def invert_condition(condition, nested=False):
    """
    Inverts a boolean expression, e.g.:
        a == b
        --> a != b

        a > b
        --> a <= b

        a or b
        --> not (a or b)

        (a or b)
        --> not (a or b)

        (a and b)
        --> not (a and b)

        (a == b and c != d)
        --> (a != b or c == d)

        a if b else c
        --> not (a if b else c)
    """
    if condition.type == syms.comparison:
        a, op, b = condition.children
        op = condition.children[1]
        if op.type == syms.comp_op:
            if (op.children[0].value, op.children[1].value) == ("is", "not"):
                return Node(syms.comparison, [a.clone(), kw("is"), b.clone()])
            elif (op.children[0].value, op.children[1].value) == ("not", "in"):
                return Node(syms.comparison, [a.clone(), kw("in"), b.clone()])
            else:
                raise NotImplementedError(f"unknown comp_op: {op!r}")
        else:
            inversions = {
                "is": Node(syms.comp_op, [kw("is"), kw("not")], prefix=" "),
                "in": Node(syms.comp_op, [kw("not"), kw("in")], prefix=" "),
                "==": Leaf(TOKEN.NOTEQUAL, "!=", prefix=" "),
                "!=": Leaf(TOKEN.EQEQUAL, "==", prefix=" "),
                ">": Leaf(TOKEN.LESSEQUAL, "<=", prefix=" "),
                "<": Leaf(TOKEN.GREATEREQUAL, ">=", prefix=" "),
                "<=": Leaf(TOKEN.GREATER, ">", prefix=" "),
                ">=": Leaf(TOKEN.LESS, "<", prefix=" "),
            }
            return Node(syms.comparison,
                        [a.clone(), inversions[op.value],
                         b.clone()])
    elif condition.type == syms.not_test:
        # `not x` --> just remove the `not`
        return condition.children[1].clone()
    elif condition.type in (syms.and_test, syms.or_test):
        # Tricky one.
        # (a != b and c != d)
        # which should this become?
        #    --> (a == b or c == d)
        #    --> not (a != b and c != d)
        # Seems somewhat context dependent. Basically we compute both, and then
        # decide based on which has the least negations.

        simply_inverted = Node(
            syms.not_test,
            [kw("not"),
             parenthesize_if_not_already(condition.clone())])
        if condition.type == syms.and_test:
            children = [invert_condition(condition.children[0], nested=True)]
            for child in condition.children[2::2]:
                children.extend(
                    [kw('or'), invert_condition(child, nested=True)])

            complex_inverted = Node(syms.or_test, children)
            if nested:
                # We're inside an outer 'and' test, and 'or' has lower precedence.
                # so we need to parenthesize to ensure the expression is correct
                complex_inverted = _parenthesize(complex_inverted)
        else:
            children = [invert_condition(condition.children[0], nested=True)]
            for child in condition.children[2::2]:
                children.extend(
                    [kw('and'),
                     invert_condition(child, nested=True)])

            complex_inverted = Node(syms.and_test, children)

        return min([simply_inverted, complex_inverted], key=_num_negations)
        if len(str(simply_inverted)) < len(str(complex_inverted)):
            return simply_inverted
        else:
            return complex_inverted
    else:
        return Node(
            syms.not_test,
            [kw("not"),
             parenthesize_if_not_already(condition.clone())])