check_api_annotations.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. #!/usr/bin/env python
  2. import inspect
  3. import ray
  4. from ray.util.annotations import _is_annotated
  5. IGNORE_PATHS = {
  6. ".impl.",
  7. ".backend.",
  8. ".experimental.",
  9. ".internal.",
  10. ".generated.",
  11. ".test_utils.",
  12. ".annotations.",
  13. ".deprecation.",
  14. ".protobuf.",
  15. ".cloudpickle.",
  16. }
  17. def _fullname(attr):
  18. """Fully qualified name of an attribute."""
  19. fullname = ""
  20. try:
  21. if hasattr(attr, "__module__"):
  22. fullname += attr.__module__
  23. if hasattr(attr, "__name__"):
  24. if fullname:
  25. fullname += "."
  26. fullname += attr.__name__
  27. if not fullname:
  28. fullname = str(attr)
  29. except Exception as e:
  30. print("Error qualifying", e)
  31. return fullname
  32. def _ignore(attr, extra_ignore):
  33. """Whether an attr should be ignored from annotation checking."""
  34. attr = _fullname(attr)
  35. if "ray." not in attr or "._" in attr:
  36. return True
  37. for path in IGNORE_PATHS:
  38. if path in attr:
  39. return True
  40. for path in extra_ignore or []:
  41. if path in attr:
  42. return True
  43. return False
  44. def verify(symbol, scanned, ok, output, prefix=None, ignore=None):
  45. """Recursively verify all child symbols of a given module."""
  46. if not prefix:
  47. prefix = symbol.__name__ + "."
  48. if symbol in scanned:
  49. return
  50. scanned.add(symbol)
  51. for child in dir(symbol):
  52. if child.startswith("_"):
  53. continue
  54. attr = getattr(symbol, child)
  55. if _ignore(attr, ignore):
  56. continue
  57. if (inspect.isclass(attr) or inspect.isfunction(attr)) and prefix in _fullname(
  58. attr
  59. ):
  60. print("Scanning class", attr)
  61. if _is_annotated(attr):
  62. if attr not in scanned:
  63. print("OK:", _fullname(attr))
  64. ok.add(attr)
  65. else:
  66. output.add(attr)
  67. scanned.add(attr)
  68. elif inspect.ismodule(attr):
  69. print("Scanning module", attr)
  70. verify(attr, scanned, ok, output, prefix, ignore)
  71. else:
  72. print("Not scanning", attr, type(attr))
  73. if __name__ == "__main__":
  74. import ray.data
  75. import ray.rllib
  76. import ray.serve
  77. import ray.train
  78. import ray.tune
  79. import ray.workflow
  80. output = set()
  81. ok = set()
  82. verify(ray.data, set(), ok, output)
  83. # Sanity check the lint logic.
  84. assert len(ok) >= 60, len(ok)
  85. verify(ray.rllib, set(), ok, output)
  86. verify(ray.air, set(), ok, output)
  87. verify(ray.train, set(), ok, output)
  88. verify(ray.tune, set(), ok, output)
  89. verify(ray, set(), ok, output, ignore=["ray.workflow", "ray.tune", "ray.serve"])
  90. verify(ray.serve, set(), ok, output)
  91. assert len(ok) >= 500, len(ok)
  92. # TODO(ekl) enable it for all modules.
  93. # verify(ray.tune, set(), ok, output)
  94. # verify(ray.workflow, set(), ok, output)
  95. print("Num ok", len(ok))
  96. print("Num bad", len(output))
  97. print("!!! No API stability annotation found for:")
  98. for x in sorted([_fullname(x) for x in output]):
  99. print(x)
  100. if output:
  101. exit(1)