trace_dependencies.py 2.2 KB

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