check_import_order.py 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. """
  2. This script ensures python files conform to ray's import ordering rules.
  3. In particular, we make sure psutil and setproctitle is imported _after_
  4. importing ray due to our bundling of the two libraries.
  5. Usage:
  6. $ python check_import_order.py SOURCE_DIR -s SKIP_DIR
  7. some/file/path.py:23 import psutil without explicitly import ray before it.
  8. """
  9. import argparse
  10. import glob
  11. import io
  12. import re
  13. import sys
  14. from pathlib import Path
  15. exit_with_error = False
  16. def check_import(file):
  17. check_to_lines = {"import ray": -1, "import psutil": -1, "import setproctitle": -1}
  18. with io.open(file, "r", encoding="utf-8") as f:
  19. for i, line in enumerate(f):
  20. for check in check_to_lines.keys():
  21. # This regex will match the following case
  22. # - the string itself: `import psutil`
  23. # - white space/indentation + the string:` import psutil`
  24. # - the string and arbitrary whitespace: `import psutil `
  25. # - the string and the noqa flag to silent pylint
  26. # `import psutil # noqa F401 import-ordering`
  27. # It will not match the following
  28. # - submodule import: `import ray.constants as ray_constants`
  29. # - submodule import: `from ray import xyz`
  30. if (
  31. re.search(r"^\s*" + check + r"(\s*|\s+# noqa F401.*)$", line)
  32. and check_to_lines[check] == -1
  33. ):
  34. check_to_lines[check] = i
  35. for import_lib in ["import psutil", "import setproctitle"]:
  36. if check_to_lines[import_lib] != -1:
  37. import_psutil_line = check_to_lines[import_lib]
  38. import_ray_line = check_to_lines["import ray"]
  39. if import_ray_line == -1 or import_ray_line > import_psutil_line:
  40. print(
  41. "{}:{}".format(str(file), import_psutil_line + 1),
  42. "{} without explicitly import ray before it.".format(import_lib),
  43. )
  44. global exit_with_error
  45. exit_with_error = True
  46. if __name__ == "__main__":
  47. parser = argparse.ArgumentParser()
  48. parser.add_argument("path", help="File path to check. e.g. '.' or './src'")
  49. # TODO(simon): For the future, consider adding a feature to explicitly
  50. # white-list the path instead of skipping them.
  51. parser.add_argument("-s", "--skip", action="append", help="Skip certian directory")
  52. args = parser.parse_args()
  53. file_path = Path(args.path)
  54. if file_path.is_dir():
  55. all_py_files = glob.glob("*.py", recursive=True)
  56. else:
  57. all_py_files = [file_path]
  58. if args.skip is not None:
  59. filtered_py_files = []
  60. for py_file in all_py_files:
  61. should_skip = False
  62. for skip_dir in args.skip:
  63. if str(py_file).startswith(skip_dir):
  64. should_skip = True
  65. if not should_skip:
  66. filtered_py_files.append(py_file)
  67. all_py_files = filtered_py_files
  68. for py_file in all_py_files:
  69. check_import(py_file)
  70. if exit_with_error:
  71. print("check import ordering failed")
  72. sys.exit(1)