import sys from typing import Any, Callable, Iterable, List, Tuple __all__ = ["trace_dependencies"] def trace_dependencies( callable: Callable[[Any], Any], inputs: Iterable[Tuple[Any, ...]] ) -> List[str]: """Trace the execution of a callable in order to determine which modules it uses. Args: callable: The callable to execute and trace. inputs: The input to use during tracing. The modules used by 'callable' when invoked by each set of inputs are union-ed to determine all modules used by the callable for the purpooses of packaging. Returns: A list of the names of all modules used during callable execution. """ modules_used = set() def record_used_modules(frame, event, arg): # If the event being profiled is not a Python function # call, there is nothing to do. if event != "call": return # This is the name of the function that was called. name = frame.f_code.co_name module = None # Try to determine the name of the module that the function # is in: # 1) Check the global namespace of the frame. # 2) Check the local namespace of the frame. # 3) To handle class instance method calls, check # the attribute named 'name' of the object # in the local namespace corresponding to "self". if name in frame.f_globals: module = frame.f_globals[name].__module__ elif name in frame.f_locals: module = frame.f_locals[name].__module__ elif "self" in frame.f_locals: method = getattr(frame.f_locals["self"], name, None) module = method.__module__ if method else None # If a module was found, add it to the set of used modules. if module: modules_used.add(module) try: # Attach record_used_modules as the profiler function. sys.setprofile(record_used_modules) # Execute the callable with all inputs. for inp in inputs: callable(*inp) finally: # Detach the profiler function. sys.setprofile(None) return list(modules_used)