Example #1
0
def test_group_expressions():
    x = (1, 2)
    evaluator = Evaluator({'x': x})
    tree = ast.parse('x[0] + x[x[0]]').body[0].value
    expressions = evaluator.find_expressions(tree)
    grouped = set((frozenset(nodes), value)
                  for nodes, value in group_expressions(expressions))
    expected = {
        (frozenset([tree.left, subscript_item(tree.right)]), x[0]),
        (frozenset([
            tree.left.value,
            subscript_item(tree.right).value, tree.right.value
        ]), x),
        (frozenset([
            subscript_item(tree.left),
            subscript_item(subscript_item(tree.right))
        ]), 0),
        (frozenset([tree.right]), x[x[0]]),
        (frozenset([tree]), x[0] + x[x[0]]),
    }
    assert grouped == expected

    grouped = set(
        (frozenset(nodes), value)
        for nodes, value in evaluator.interesting_expressions_grouped(tree))
    expected = set((nodes, value) for nodes, value in expected if value != 0)
    assert grouped == expected
Example #2
0
def check_interesting(source):
    frame = inspect.currentframe().f_back
    evaluator = Evaluator.from_frame(frame)
    root = ast.parse(source)
    node = root.body[0].value
    cannot = value = None
    try:
        value = evaluator[node]
    except CannotEval as e:
        cannot = e

    expr = ast.Expression(body=node)
    ast.copy_location(expr, node)
    code = compile(expr, "<expr>", "eval")
    try:
        expected = eval(code, frame.f_globals, frame.f_locals)
    except Exception:
        if cannot:
            return None
        else:
            raise
    else:
        if cannot:
            raise cannot
        else:
            assert value == expected

    return is_expression_interesting(node, value)
Example #3
0
def calling_env(funtype: str) -> Any:
    """Checking how the function is called:

    1. PIPING_VERB: It is a verb that is piped directed. ie. data >> verb(...)
    2. PIPING: It is a function called as (part of) the argument
        of a piping verb. ie.:

        >>> data >> verb(func(...))

        Note that `func` here could also be a verb. When a function is called
        inside a lambda body, it should not be counted in this situation:

        >>> data >> verb(lambda: func(...))

        In this case, func should be called as normal function.
        This function should return `None`
    3. FUNC_ARG: It is an argument of any function call
    4. None: None of the above situation fits

    This function should be only called inside register_*.wrapper
    """
    if options.assume_all_piping:
        return (CallingEnvs.PIPING_VERB
                if funtype == 'Verb' else CallingEnvs.PIPING)

    # frame 1: register_*.wrapper
    # frame 2: func(...)
    frame = sys._getframe(2)
    my_node = Source.executing(frame).node
    if not my_node and options.warn_astnode_failure:
        warnings.warn(
            "Failed to fetch the node calling the function, "
            "please call the verbs regularly instead of `data >> verb(...)`.")
        return None

    piping_verb_node = _get_piping_verb_node(my_node)
    if piping_verb_node is my_node and piping_verb_node is not None:
        return CallingEnvs.PIPING_VERB

    if _is_piping_verb_argument_node(my_node, piping_verb_node):
        return CallingEnvs.PIPING

    parent_call_node = _argument_node_of(my_node)
    if parent_call_node is None:
        return None

    # check if parent call node is a function registered by
    # register_verb/register_func
    evaluator = Evaluator.from_frame(frame)
    try:
        func = evaluator[parent_call_node.func]
    except CannotEval:  # pragma: no cover
        return None

    if functype(func) != "plain":
        return CallingEnvs.PIPING

    return None
Example #4
0
    def variables(self) -> List[Variable]:
        """
        All Variable objects whose nodes are contained within .scope
        and whose values could be safely evaluated by pure_eval.
        """
        if not self.scope:
            return []

        evaluator = Evaluator.from_frame(self.frame)
        scope = self.scope
        node_values = [
            pair for pair in evaluator.find_expressions(scope)
            if is_expression_interesting(*pair)
        ]  # type: List[Tuple[ast.AST, Any]]

        if isinstance(scope, (ast.FunctionDef, ast.AsyncFunctionDef)):
            for node in ast.walk(scope.args):
                if not isinstance(node, ast.arg):
                    continue
                name = node.arg
                try:
                    value = evaluator.names[name]
                except KeyError:
                    pass
                else:
                    node_values.append((node, value))

        # Group equivalent nodes together
        def get_text(n):
            if isinstance(n, ast.arg):
                return n.arg
            else:
                return self.source.asttokens().get_text(n)

        def normalise_node(n):
            try:
                # Add parens to avoid syntax errors for multiline expressions
                return ast.parse('(' + get_text(n) + ')')
            except Exception:
                return n

        grouped = group_by_key_func(
            node_values,
            lambda nv: ast.dump(normalise_node(nv[0])),
        )

        result = []
        for group in grouped.values():
            nodes, values = zip(*group)
            value = values[0]
            text = get_text(nodes[0])
            if not text:
                continue
            result.append(Variable(text, nodes, value))

        return result
Example #5
0
def test_cannot_subscript(expr):
    with pytest.raises(Exception):
        eval(expr)

    evaluator = Evaluator({})
    tree = ast.parse(expr)
    node = tree.body[0].value
    assert isinstance(node, ast.Subscript)
    with pytest.raises(CannotEval):
        str(evaluator[node])
Example #6
0
def check_interesting(source):
    frame = inspect.currentframe().f_back
    evaluator = Evaluator.from_frame(frame)
    root = ast.parse(source)
    node = root.body[0].value
    value = evaluator[node]

    expr = ast.Expression(body=node)
    ast.copy_location(expr, node)
    code = compile(expr, "<expr>", "eval")
    expected = eval(code, frame.f_globals, frame.f_locals)
    assert value == expected

    return is_expression_interesting(node, value)
Example #7
0
def check_eval(source, *expected_values, total=True):
    frame = inspect.currentframe().f_back
    evaluator = Evaluator.from_frame(frame)
    root = ast.parse(source)
    values = []
    for node, value in evaluator.find_expressions(root):
        expr = ast.Expression(body=node)
        ast.copy_location(expr, node)
        code = compile(expr, "<expr>", "eval")
        expected = eval(code, frame.f_globals, frame.f_locals)
        assert value == expected
        values.append(value)
        if total:
            assert value in expected_values

    for expected in expected_values:
        assert expected in values
Example #8
0
def get_function_called_argname(frame: FrameType, node: ast.AST) -> Callable:
    """Get the function who called argname"""
    # We need node to be ast.Call
    if not isinstance(node, ast.Call):
        raise VarnameRetrievingError(
            f"Expect an 'ast.Call' node, but got {type(node)!r}. "
            "Are you using 'argname' inside a function?")

    # variable
    if isinstance(node.func, ast.Name):
        func = frame.f_locals.get(node.func.id,
                                  frame.f_globals.get(node.func.id))
        if func is None:  # pragma: no cover
            # not sure how it would happen but in case
            raise VarnameRetrievingError(
                f"Cannot retrieve the function by {node.func.id!r}.")
        return func

    # use pure_eval
    pure_eval_fail_msg = None
    try:
        from pure_eval import Evaluator, CannotEval
    except ImportError:
        pure_eval_fail_msg = "'pure_eval' is not installed."
    else:
        try:
            evaluator = Evaluator.from_frame(frame)
            return evaluator[node.func]
        except CannotEval:
            pure_eval_fail_msg = (
                f"Cannot evaluate node {ast.dump(node.func)} "
                "using 'pure_eval'.")

    # try eval
    warnings.warn(
        f"{pure_eval_fail_msg} "
        "Using 'eval' to get the function that calls 'argname'. "
        "Try calling it using a variable reference to the function, or "
        "passing the function to 'argname' explicitly.")
    expr = ast.Expression(node.func)
    code = compile(expr, '<ast-call>', 'eval')
    # pylint: disable=eval-used
    return eval(code, frame.f_globals, frame.f_locals)
Example #9
0
    def variables(self):
        if not self.source.tree:
            return []

        evaluator = Evaluator.from_frame(self.frame)
        get_text = self.source.asttokens().get_text
        scope = self.scope
        node_values = evaluator.find_expressions(scope)

        if isinstance(scope, ast.FunctionDef):
            for node in ast.walk(scope.args):
                if not isinstance(node, ast.arg):
                    continue
                name = node.arg
                try:
                    value = evaluator.names[name]
                except KeyError:
                    pass
                else:
                    node_values.append((node, value))

        # TODO use compile(...).co_code instead of ast.dump?
        # Group equivalent nodes together
        grouped = group_by_key_func(
            node_values,
            # Add parens to avoid syntax errors for multiline expressions
            lambda nv: ast.dump(ast.parse('(' + get_text(nv[0]) + ')')),
        )

        result = []
        for group in grouped.values():
            nodes, values = zip(*group)
            if not all(value is values[0] for value in values):
                # Distinct values found for same expression
                # Probably indicates another thread is messing with things
                # Since no single correct value exists, ignore this expression
                continue
            value = values[0]
            text = get_text(nodes[0])
            result.append(Variable(text, nodes, value))

        return result
Example #10
0
def test_evaluator_wrong_getitem():
    evaluator = Evaluator({})
    with pytest.raises(TypeError,
                       match="node should be an ast.expr, not 'str'"):
        # noinspection PyTypeChecker
        str(evaluator["foo"])
Example #11
0
def get_all_objects(line, frame):
    """Given a (partial) line of code and a frame,
    obtains a dict containing all the relevant information about objects
    found on that line so that they can be formatted as part of the
    answer to "where()" or they can be used during the analysis
    of the cause of the exception.

    The dict returned has five keys.
    The first three, 'locals', 'globals', 'builtins',
    each containing a list of tuples, each tuple being of the form
    (name, repr(obj), obj) where name --> obj.

    The fourth key, 'expressions', contains a list of tuples of the form
    ('name', obj). It is only occasionally used in helping to make
    suggestions regarding the cause of some exception.
    """
    objects = {
        "locals": [],
        "globals": [],
        "builtins": [],
        "expressions": [],
        "name, obj": [],
    }

    scopes = (
        ("locals", frame.f_locals),  # always have locals before globals
        ("globals", frame.f_globals),
    )

    names = set()

    tokens = token_utils.get_significant_tokens(line)
    for tok in tokens:
        if tok.is_identifier():
            name = tok.string
            if name in names:
                continue
            for scope, scope_dict in scopes:
                if name in scope_dict:
                    names.add(name)
                    obj = scope_dict[name]
                    objects[scope].append((name, repr(obj), obj))
                    objects["name, obj"].append((name, obj))
                    break
            else:
                if name in dir(builtins):
                    names.add(name)
                    obj = getattr(builtins, name)
                    objects["builtins"].append((name, repr(obj), obj))
                    objects["name, obj"].append((name, obj))

    try:
        atok = ASTTokens(line, parse=True)
    except SyntaxError:  # this should not happen
        return objects

    if atok is not None:
        evaluator = Evaluator.from_frame(frame)
        for nodes, obj in group_expressions(
                pair for pair in evaluator.find_expressions(atok.tree)):
            name = atok.get_text(nodes[0])
            if name in names:
                continue
            names.add(name)
            objects["name, obj"].append((name, obj))
            try:
                # We're not interested in showing literals in the list of variables
                ast.literal_eval(name)
            except Exception:  # noqa
                objects["expressions"].append((name, obj))

    return objects
def get_all_objects(line, frame):
    """Given a (partial) line of code and a frame,
    obtains a dict containing all the relevant information about objects
    found on that line so that they can be formatted as part of the
    answer to "where()" or they can be used during the analysis
    of the cause of the exception.

    The dict returned has four keys.
    The first three, 'locals', 'globals', 'nonlocals',
    each containing a list of tuples, each tuple being of the form
    (name, repr(obj), obj) where name --> obj.

    The fourth key, 'literals', contains a list of tuples of the form
    ('name', obj). It is only occasionally used in helping to make
    suggestions regarding the cause of some exception.
    """
    objects = {
        "locals": [],
        "globals": [],
        "literals": [],
        "builtins": [],
        "name, obj": [],
    }

    scopes = (
        ("locals", frame.f_locals),  # always have locals before globals
        ("globals", frame.f_globals),
    )

    names = set([])
    try:
        atok = ASTTokens(line, parse=True)
    except SyntaxError:  # this should not happen
        atok = None

    if atok is not None:
        for scope, scope_dict in scopes:
            for nodes, obj in Evaluator(
                    scope_dict).interesting_expressions_grouped(atok.tree):
                name = atok.get_text(nodes[0])
                if name in names:
                    continue
                names.add(name)
                objects[scope].append((name, repr(obj), obj))
                objects["name, obj"].append((name, obj))

        Evaluator.literal_expressions_grouped = literal_expressions_grouped
        for nodes, obj in Evaluator({}).literal_expressions_grouped(
                atok.tree):  # noqa
            name = atok.get_text(nodes[0])
            objects["literals"].append((name, obj))
            objects["name, obj"].append((name, obj))

    tokens = token_utils.get_significant_tokens(line)
    for tok in tokens:
        if tok.is_identifier():
            name = tok.string
            if name in names:
                continue
            for scope, scope_dict in scopes:
                if name in scope_dict:
                    names.add(name)
                    obj = scope_dict[name]
                    objects[scope].append((name, repr(obj), obj))
                    objects["name, obj"].append((name, obj))
                    break
            else:
                if name in dir(builtins):
                    obj = getattr(builtins, name)
                    objects["builtins"].append((name, repr(obj), obj))
                    objects["name, obj"].append((name, obj))

    dotted_names = get_dotted_names(line)
    for name in dotted_names:
        for scope, scope_dict in scopes:
            if name not in scope_dict:
                continue
            obj = scope_dict[name]
            if (name, obj) not in objects["name, obj"]:
                objects[scope].append((name, repr(obj), obj))
                objects["name, obj"].append((name, obj))

    # TODO: check to see if this is still needed
    objects["nonlocals"] = get_nonlocal_objects(frame)
    return objects