def trace_function(self, func): # type: (FunctionType) -> FunctionType """ Returns a version of the passed function with the AST modified to trigger the tracing hooks. """ if not isinstance(func, FunctionType): raise ValueError('You can only trace user-defined functions. ' 'The birdseye decorator must be applied first, ' 'at the bottom of the list.') try: if inspect.iscoroutinefunction(func) or inspect.isasyncgenfunction( func): raise ValueError('You cannot trace async functions') except AttributeError: pass if is_lambda(func): raise ValueError('You cannot trace lambdas') filename = inspect.getsourcefile(func) # type: str if is_ipython_cell(filename): # noinspection PyPackageRequirements from IPython import get_ipython import linecache flags = get_ipython().compile.flags source = ''.join(linecache.cache[filename][2]) else: source = read_source_file(filename) flags = 0 # We compile the entire file instead of just the function source # because it can contain context which affects the function code, # e.g. enclosing functions and classes or __future__ imports traced_file = self.compile(source, filename, flags) if func.__dict__: raise ValueError('The birdseye decorator must be applied first, ' 'at the bottom of the list.') # Then we have to recursively search through the newly compiled # code to find the code we actually want corresponding to this function code_options = [] # type: List[CodeType] def find_code(root_code): # type: (CodeType) -> None for const in root_code.co_consts: # type: CodeType if not inspect.iscode(const): continue matches = (const.co_firstlineno == func.__code__.co_firstlineno and const.co_name == func.__code__.co_name) if matches: code_options.append(const) find_code(const) find_code(traced_file.code) if len(code_options) > 1: # Currently lambdas aren't allowed anyway, but should be in the future assert is_lambda(func) raise ValueError( "Failed to trace lambda. Convert the function to a def.") new_func_code = code_options[0] # type: CodeType # Give the new function access to the hooks # We have to use the original __globals__ and not a copy # because it's the actual module namespace that may get updated by other code func.__globals__.update(self._trace_methods_dict(traced_file)) # http://stackoverflow.com/a/13503277/2482744 # noinspection PyArgumentList new_func = FunctionType(new_func_code, func.__globals__, func.__name__, func.__defaults__, func.__closure__) update_wrapper(new_func, func) # type: FunctionType if PY3: new_func.__kwdefaults__ = getattr(func, '__kwdefaults__', None) new_func.traced_file = traced_file return new_func
def test_is_lambda(self): self.assertTrue(is_lambda(lambda: 0)) self.assertTrue(is_lambda(lambda x, y: x + y)) self.assertFalse(is_lambda(min)) self.assertFalse(is_lambda(flatten_list)) self.assertFalse(is_lambda(self.test_is_lambda))