示例#1
0
    def match(self, frame_no: int,
              frameinfos: List[inspect.FrameInfo]) -> bool:
        frame = frameinfos[frame_no].frame

        # module is None, check qualname only
        return fnmatch(
            Source.for_frame(frame).code_qualname(frame.f_code), self.qualname)
示例#2
0
 def gen():
     frame = current_frame()
     while frame:
         code = frame.f_code
         filename = code.co_filename
         name = Source.for_frame(frame).code_qualname(code)
         yield (filename, frame.f_lineno, name,
                highlight_stack_frame(frame), include_file(filename))
         frame = frame.f_back
示例#3
0
    def match(self, frame_no: int,
              frameinfos: List[inspect.FrameInfo]) -> bool:
        frame = frameinfos[frame_no].frame

        frame_filename = path.realpath(frame.f_code.co_filename)
        preset_filename = path.realpath(self.filename)
        # return earlier to avoid qualname uniqueness check
        if frame_filename != preset_filename:
            return False

        source = Source.for_frame(frame)
        check_qualname_by_source(source, self.filename, self.qualname)

        return fnmatch(source.code_qualname(frame.f_code), self.qualname)
示例#4
0
    def match(self, frame_no: int,
              frameinfos: List[inspect.FrameInfo]) -> bool:
        frame = frameinfos[frame_no].frame
        module = cached_getmodule(frame.f_code)

        # Return earlier to avoid qualname uniqueness check
        if module and module != self.module:
            return False

        if (not module
                and not frame_matches_module_by_ignore_id(frame, self.module)):
            return False

        source = Source.for_frame(frame)
        check_qualname_by_source(source, self.module.__name__, self.qualname)

        return fnmatch(source.code_qualname(frame.f_code), self.qualname)
示例#5
0
    def check_code(self, code, nodes):
        linestarts = dict(dis.findlinestarts(code))
        instructions = get_instructions(code)
        lineno = None
        for inst in instructions:
            if time.time() - self.start_time > 45 * 60:
                # Avoid travis time limit of 50 minutes
                raise TimeOut

            lineno = linestarts.get(inst.offset, lineno)
            if not inst.opname.startswith((
                    'BINARY_', 'UNARY_', 'LOAD_ATTR', 'LOAD_METHOD', 'LOOKUP_METHOD',
                    'SLICE+', 'COMPARE_OP', 'CALL_', 'IS_OP', 'CONTAINS_OP',
            )):
                continue
            frame = C()
            frame.f_lasti = inst.offset
            frame.f_code = code
            frame.f_globals = globals()
            frame.f_lineno = lineno
            source = Source.for_frame(frame)
            node = None

            try:
                try:
                    node = Source.executing(frame).node
                except Exception:
                    if inst.opname.startswith(('COMPARE_OP', 'CALL_')):
                        continue
                    if isinstance(only(source.statements_at_line(lineno)), (ast.AugAssign, ast.Import)):
                        continue
                    raise
            except Exception:
                print(source.text, lineno, inst, node and ast.dump(node), code, file=sys.stderr, sep='\n')
                raise

            nodes[node].append((inst, frame.__dict__))

            yield [inst.opname, node_string(source, node)]

        for const in code.co_consts:
            if isinstance(const, type(code)):
                for x in self.check_code(const, nodes):
                    yield x
示例#6
0
def argname(arg: Any,  # pylint: disable=unused-argument
            *more_args: Any,
            # *, keyword-only argument, only available with python3.8+
            func: Optional[Callable] = None,
            frame: int = 1,
            vars_only: bool = True,
            pos_only: bool = False) -> Union[str, Tuple[str]]:
    """Get the argument names/sources passed to a function

    Args:
        arg: Parameter of the function, used to map the argument passed to
            the function
        *more_args: Other parameters of the function, used to map more arguments
            passed to the function
        func: The target function. If not provided, the AST node of the
            function call will be used to fetch the function:
            - If a variable (ast.Name) used as function, the `node.id` will
                be used to get the function from `locals()` or `globals()`.
            - If variable (ast.Name), attributes (ast.Attribute),
                subscripts (ast.Subscript), and combinations of those and
                literals used as function, `pure_eval` will be used to evaluate
                the node
            - If `pure_eval` is not installed or failed to evaluate, `eval`
                will be used. A warning will be shown since unwanted side
                effects may happen in this case.
            You are encouraged to always pass the function explicitly.
        frame: The frame where target function is called from this call.
            The intermediate calls will be the wrappers of this function.
            However, keep in mind that the wrappers must have the same
            signature as this function. When `pos_only` is `True`, only the
            positional arguments have to be the same
        vars_only: Require the arguments to be variables only,
        pos_only: Only fetch the names/sources for positional arguments.

    Returns:
        The argument source when no more_args passed, otherwise a tuple of
        argument sources

    Raises:
        NonVariableArgumentError: When vars_only is True, and we are trying
            to retrieve the source of an argument that is not a variable
            (i.e. an expression)
        VarnameRetrievingError: When failed to get the frame or node
        ValueError: When the arguments passed to this function is invalid.
            Only variables and subscripts of variables are allow to be passed
            to this function.
    """
    ignore_list = IgnoreList.create(
        ignore_lambda=False,
        ignore_varname=False
    )
    # where argname(...) is called
    argname_frame = ignore_list.get_frame(frame)
    argname_node = get_node_by_frame(argname_frame)
    # where func(...) is called
    func_frame = ignore_list.get_frame(frame + 1)
    func_node = get_node_by_frame(func_frame)
    # Only do it when both nodes are available
    if not argname_node or not func_node:
        # We can do something at bytecode level, when a single positional
        # argument passed to both functions (argname and the target function)
        # However, it's hard to ensure that there is only a single positional
        # arguments passed to the target function, at bytecode level.
        raise VarnameRetrievingError(
            "The source code of 'argname' calling is not available."
        )

    if not func:
        func = get_function_called_argname(func_frame, func_node)

    # don't pass the target arguments so that we can cache the sources in
    # the same call. For example:
    # >>> def func(a, b):
    # >>>   a_name = argname(a)
    # >>>   b_name = argname(b)
    argument_sources = get_argument_sources(
        Source.for_frame(func_frame),
        func_node,
        func,
        vars_only=vars_only,
        pos_only=pos_only
    )

    ret = []
    for argnode in argname_node.args:
        if not isinstance(argnode, (ast.Name, ast.Subscript, ast.Starred)):
            raise ValueError(
                "Arguments of 'argname' must be "
                "function arguments themselves or subscripts of them."
            )

        if isinstance(argnode, ast.Starred):
            if (
                    not isinstance(argnode.value, ast.Name) or
                    argnode.value.id not in argument_sources or
                    not isinstance(argument_sources[argnode.value.id], tuple)
            ):
                posvar = argnode.value
                posvar = getattr(posvar, 'id', posvar)
                raise ValueError(
                    f"No such variable positional argument {posvar!r}"
                )
            ret.extend(argument_sources[argnode.value.id])

        elif isinstance(argnode, ast.Name):
            if argnode.id not in argument_sources:
                raise ValueError(
                    f"No value passed for argument {argnode.id!r}, "
                    "or it is not an argument at all."
                )
            ret.append(argument_sources[argnode.id])

        else:
            name, subscript = parse_argname_subscript(argnode)
            if name not in argument_sources:
                raise ValueError(f"{name!r} is not an argument.")

            if (isinstance(subscript, int) and
                    not isinstance(argument_sources[name], tuple)):
                raise ValueError(
                    f"{name!r} is not a positional argument "
                    "(*args, for example)."
                )
            if (isinstance(subscript, str) and
                    not isinstance(argument_sources[name], dict)):
                raise ValueError(
                    f"{name!r} is not a keyword argument "
                    "(**kwargs, for example)."
                )
            ret.append(argument_sources[name][subscript])

    if vars_only:
        for source in ret:
            if isinstance(source, ast.AST):
                raise NonVariableArgumentError(
                    f'Argument {ast.dump(source)} is not a variable '
                    'or an attribute.'
                )

    return ret[0] if not more_args else tuple(ret)
示例#7
0
    def check_code(self, code, nodes, decorators, check_names):
        linestarts = dict(dis.findlinestarts(code))
        instructions = get_instructions(code)
        lineno = None
        for inst in instructions:
            if time.time() - self.start_time > 45 * 60:
                # Avoid travis time limit of 50 minutes
                raise TimeOut

            lineno = linestarts.get(inst.offset, lineno)
            if not inst.opname.startswith(
                (
                    'BINARY_', 'UNARY_', 'LOAD_ATTR', 'LOAD_METHOD', 'LOOKUP_METHOD',
                    'SLICE+', 'COMPARE_OP', 'CALL_', 'IS_OP', 'CONTAINS_OP',
                )
                + ('LOAD_NAME', 'LOAD_GLOBAL', 'LOAD_FAST', 'LOAD_DEREF', 'LOAD_CLASSDEREF') * check_names
            ):
                continue
            frame = C()
            frame.f_lasti = inst.offset
            frame.f_code = code
            frame.f_globals = globals()
            frame.f_lineno = lineno
            source = Source.for_frame(frame)
            node = None

            try:
                try:
                    ex = Source.executing(frame)
                    node = ex.node
                except Exception:
                    if inst.opname.startswith(('COMPARE_OP', 'IS_OP', 'CALL_', 'LOAD_NAME')):
                        continue
                    if inst.opname == 'LOAD_FAST' and inst.argval == '.0':
                        continue

                    if inst.argval == 'AssertionError':
                        continue

                    if any(
                        isinstance(stmt, (ast.AugAssign, ast.Import))
                        for stmt in source.statements_at_line(lineno)
                    ):
                        continue
                    raise
                # argval isn't set for all relevant instructions in python 2
                if isinstance(node, ast.Name) and (PY3 or inst.argval):
                    self.assertEqual(inst.argval, node.id)
            except Exception:
                print(source.text, lineno, inst, node and ast.dump(node), code, file=sys.stderr, sep='\n')
                raise

            if ex.decorator:
                decorators[(node.lineno, node.name)].append(ex.decorator)
            else:
                nodes[node].append((inst, frame.__dict__))

            yield [inst.opname, node_string(source, ex.decorator or node)]

        for const in code.co_consts:
            if isinstance(const, type(code)):
                for x in self.check_code(const, nodes, decorators, check_names=check_names):
                    yield x
示例#8
0
    def check_code(self, code, nodes):
        linestarts = dict(dis.findlinestarts(code))
        instructions = get_instructions(code)
        lineno = None
        for inst in instructions:
            lineno = linestarts.get(inst.offset, lineno)
            if not inst.opname.startswith((
                    'BINARY_',
                    'UNARY_',
                    'LOAD_ATTR',
                    'LOAD_METHOD',
                    'LOOKUP_METHOD',
                    'SLICE+',
                    'COMPARE_OP',
                    'CALL_',
            )):
                continue
            frame = C()
            frame.f_lasti = inst.offset
            frame.f_code = code
            frame.f_globals = globals()
            frame.f_lineno = lineno
            source = Source.for_frame(frame)
            node = None

            try:
                try:
                    node = Source.executing(frame).node
                except Exception:
                    if inst.opname.startswith(('COMPARE_OP', 'CALL_')):
                        continue
                    if isinstance(only(source.statements_at_line(lineno)),
                                  (ast.AugAssign, ast.Import)):
                        continue
                    raise

                try:
                    self.assertIsNone(nodes[node])
                except KeyError:
                    print(ast.dump(source.tree),
                          list(ast.walk(source.tree)),
                          nodes,
                          node,
                          ast.dump(node),
                          file=sys.stderr,
                          sep='\n')
            except Exception:
                print(source.text,
                      lineno,
                      inst,
                      node and ast.dump(node),
                      code,
                      file=sys.stderr,
                      sep='\n')
                raise

            nodes[node] = (inst, frame.__dict__)

            yield [inst.opname, node_string(source, node)]

        for const in code.co_consts:
            if isinstance(const, type(code)):
                for x in self.check_code(const, nodes):
                    yield x
示例#9
0
def argname(
    arg: str,
    *more_args: str,
    func: Callable = None,
    dispatch: Type = None,
    frame: int = 1,
    ignore: IgnoreType = None,
    vars_only: bool = True,
) -> ArgSourceType:
    """Get the names/sources of arguments passed to a function.

    Instead of passing the argument variables themselves to this function
    (like `argname()` does), you should pass their names instead.

    Args:
        arg: and
        *more_args: The names of the arguments that you want to retrieve
            names/sources of.
            You can also use subscripts to get parts of the results.
            >>> def func(*args, **kwargs):
            >>>     return argname('args[0]', 'kwargs[x]') # no quote needed

            Star argument is also allowed:
            >>> def func(*args, x = 1):
            >>>     return argname('*args', 'x')
            >>> a = b = c = 1
            >>> func(a, b, x=c) # ('a', 'b', 'c')

            Note the difference:
            >>> def func(*args, x = 1):
            >>>     return argname('args', 'x')
            >>> a = b = c = 1
            >>> func(a, b, x=c) # (('a', 'b'), 'c')

        func: The target function. If not provided, the AST node of the
            function call will be used to fetch the function:
            - If a variable (ast.Name) used as function, the `node.id` will
                be used to get the function from `locals()` or `globals()`.
            - If variable (ast.Name), attributes (ast.Attribute),
                subscripts (ast.Subscript), and combinations of those and
                literals used as function, `pure_eval` will be used to evaluate
                the node
            - If `pure_eval` is not installed or failed to evaluate, `eval`
                will be used. A warning will be shown since unwanted side
                effects may happen in this case.
            You are very encouraged to always pass the function explicitly.
        dispatch: If a function is a single-dispatched function, you can
            specify a type for it to dispatch the real function. If this is
            specified, expect `func` to be the generic function if provided.
        frame: The frame where target function is called from this call.
            Calls from python standard libraries are ignored.
        ignore: The intermediate calls to be ignored. See `varname.ignore`
        vars_only: Require the arguments to be variables only.
            If False, `asttokens` is required to retrieve the source.

    Returns:
        The argument source when no more_args passed, otherwise a tuple of
        argument sources
        Note that when an argument is an `ast.Constant`, `repr(arg.value)`
        is returned, so `argname()` return `'a'` for `func("a")`

    Raises:
        VarnameRetrievingError: When the ast node where the function is called
            cannot be retrieved
        ImproperUseError: When frame or func is incorrectly specified.
    """
    ignore_list = IgnoreList.create(
        ignore,
        ignore_lambda=False,
        ignore_varname=False,
    )
    # where func(...) is called, skip the argname() call
    func_frame = ignore_list.get_frame(frame + 1)
    func_node = get_node_by_frame(func_frame)
    # Only do it when func_node are available
    if not func_node:
        # We can do something at bytecode level, when a single positional
        # argument passed to both functions (argname and the target function)
        # However, it's hard to ensure that there is only a single positional
        # arguments passed to the target function, at bytecode level.
        raise VarnameRetrievingError(
            "Cannot retrieve the node where the function is called."
        )

    func_node = reconstruct_func_node(func_node)

    if not func:
        func = get_function_called_argname(func_frame, func_node)

    if dispatch:
        func = func.dispatch(dispatch)

    # don't pass the target arguments so that we can cache the sources in
    # the same call. For example:
    # >>> def func(a, b):
    # >>>   a_name = argname(a)
    # >>>   b_name = argname(b)
    try:
        argument_sources = get_argument_sources(
            Source.for_frame(func_frame),
            func_node,
            func,
            vars_only=vars_only,
        )
    except Exception as err:
        raise ImproperUseError(
            "Have you specified the right `frame` or `func`?"
        ) from err

    out = []  # type: List[ArgSourceType]
    farg_star = False
    for farg in (arg, *more_args):

        farg_name = farg
        farg_subscript = None  # type: str | int
        match = re.match(r"^([\w_]+)\[(.+)\]$", farg)
        if match:
            farg_name = match.group(1)
            farg_subscript = match.group(2)
            if farg_subscript.isdigit():
                farg_subscript = int(farg_subscript)
        else:
            match = re.match(r"^\*([\w_]+)$", farg)
            if match:
                farg_name = match.group(1)
                farg_star = True

        if farg_name not in argument_sources:
            raise ImproperUseError(
                f"{farg_name!r} is not a valid argument "
                f"of {func.__qualname__!r}."
            )

        source = argument_sources[farg_name]
        if isinstance(source, ast.AST):
            raise ImproperUseError(
                f"Argument {ast.dump(source)} is not a variable "
                "or an attribute."
            )

        if isinstance(farg_subscript, int) and not isinstance(source, tuple):
            raise ImproperUseError(
                f"`{farg_name}` is not a positional argument."
            )

        if isinstance(farg_subscript, str) and not isinstance(source, dict):
            raise ImproperUseError(
                f"`{farg_name}` is not a keyword argument."
            )

        if farg_subscript is not None:
            out.append(source[farg_subscript])  # type: ignore
        elif farg_star:
            out.extend(source)
        else:
            out.append(source)

    return (
        out[0]
        if not more_args and not farg_star
        else tuple(out)  # type: ignore
    )