ray/ci/lint/check_api_annotations.py

114 lines
3 KiB
Python
Executable file

#!/usr/bin/env python
import inspect
import ray
from ray.util.annotations import _is_annotated
IGNORE_PATHS = {
".impl.",
".backend.",
".experimental.",
".internal.",
".generated.",
".test_utils.",
".annotations.",
".deprecation.",
".protobuf.",
".cloudpickle.",
}
def _fullname(attr):
"""Fully qualified name of an attribute."""
fullname = ""
try:
if hasattr(attr, "__module__"):
fullname += attr.__module__
if hasattr(attr, "__name__"):
if fullname:
fullname += "."
fullname += attr.__name__
if not fullname:
fullname = str(attr)
except Exception as e:
print("Error qualifying", e)
return fullname
def _ignore(attr, extra_ignore):
"""Whether an attr should be ignored from annotation checking."""
attr = _fullname(attr)
if "ray." not in attr or "._" in attr:
return True
for path in IGNORE_PATHS:
if path in attr:
return True
for path in extra_ignore or []:
if path in attr:
return True
return False
def verify(symbol, scanned, ok, output, prefix=None, ignore=None):
"""Recursively verify all child symbols of a given module."""
if not prefix:
prefix = symbol.__name__ + "."
if symbol in scanned:
return
scanned.add(symbol)
for child in dir(symbol):
if child.startswith("_"):
continue
attr = getattr(symbol, child)
if _ignore(attr, ignore):
continue
if (inspect.isclass(attr) or inspect.isfunction(attr)) and prefix in _fullname(
attr
):
print("Scanning class", attr)
if _is_annotated(attr):
if attr not in scanned:
print("OK:", _fullname(attr))
ok.add(attr)
else:
output.add(attr)
scanned.add(attr)
elif inspect.ismodule(attr):
print("Scanning module", attr)
verify(attr, scanned, ok, output, prefix, ignore)
else:
print("Not scanning", attr, type(attr))
if __name__ == "__main__":
import ray.data
import ray.rllib
import ray.serve
import ray.train
import ray.tune
import ray.workflow
output = set()
ok = set()
verify(ray.data, set(), ok, output)
# Sanity check the lint logic.
assert len(ok) >= 60, len(ok)
verify(ray.rllib, set(), ok, output)
verify(ray.air, set(), ok, output)
verify(ray.train, set(), ok, output)
verify(ray, set(), ok, output, ignore=["ray.workflow", "ray.tune", "ray.serve"])
verify(ray.serve, set(), ok, output)
assert len(ok) >= 500, len(ok)
# TODO(ekl) enable it for all modules.
# verify(ray.tune, set(), ok, output)
# verify(ray.workflow, set(), ok, output)
print("Num ok", len(ok))
print("Num bad", len(output))
print("!!! No API stability annotation found for:")
for x in sorted([_fullname(x) for x in output]):
print(x)
if output:
exit(1)