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)
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
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)
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)
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
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)
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
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
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 )