trace_dependencies.py 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. import sys
  2. from typing import Any, Callable, Iterable, List, Tuple
  3. __all__ = ["trace_dependencies"]
  4. def trace_dependencies(
  5. callable: Callable[[Any], Any], inputs: Iterable[Tuple[Any, ...]]
  6. ) -> List[str]:
  7. """Trace the execution of a callable in order to determine which modules it uses.
  8. Args:
  9. callable: The callable to execute and trace.
  10. inputs: The input to use during tracing. The modules used by 'callable' when invoked by each set of inputs
  11. are union-ed to determine all modules used by the callable for the purpooses of packaging.
  12. Returns: A list of the names of all modules used during callable execution.
  13. """
  14. modules_used = set()
  15. def record_used_modules(frame, event, arg):
  16. # If the event being profiled is not a Python function
  17. # call, there is nothing to do.
  18. if event != "call":
  19. return
  20. # This is the name of the function that was called.
  21. name = frame.f_code.co_name
  22. module = None
  23. # Try to determine the name of the module that the function
  24. # is in:
  25. # 1) Check the global namespace of the frame.
  26. # 2) Check the local namespace of the frame.
  27. # 3) To handle class instance method calls, check
  28. # the attribute named 'name' of the object
  29. # in the local namespace corresponding to "self".
  30. if name in frame.f_globals:
  31. module = frame.f_globals[name].__module__
  32. elif name in frame.f_locals:
  33. module = frame.f_locals[name].__module__
  34. elif "self" in frame.f_locals:
  35. method = getattr(frame.f_locals["self"], name, None)
  36. module = method.__module__ if method else None
  37. # If a module was found, add it to the set of used modules.
  38. if module:
  39. modules_used.add(module)
  40. try:
  41. # Attach record_used_modules as the profiler function.
  42. sys.setprofile(record_used_modules)
  43. # Execute the callable with all inputs.
  44. for inp in inputs:
  45. callable(*inp)
  46. finally:
  47. # Detach the profiler function.
  48. sys.setprofile(None)
  49. return list(modules_used)