import sys import click from ci.ray_ci.doc.api import API from ci.ray_ci.doc.autodoc import Autodoc from ci.ray_ci.doc.module import Module TEAM_API_CONFIGS = { "data": { "head_modules": {"ray.data", "ray.data.grouped_data"}, "head_doc_file": "doc/source/data/api/api.rst", # List of APIs that are not following our API policy, and we will be fixing, or # we cannot deprecate them although we want to "white_list_apis": { # not sure what to do "ray.data.dataset.MaterializedDataset", # special case where we cannot deprecate although we want to "ray.data.random_access_dataset.RandomAccessDataset", }, }, "serve": { "head_modules": {"ray.serve"}, "head_doc_file": "doc/source/serve/api/index.md", "white_list_apis": { # private versions of request router APIs "ray.serve._private.common.ReplicaID", "ray.serve._private.request_router.common.PendingRequest", "ray.serve._private.request_router.pow_2_router.PowerOfTwoChoicesRequestRouter", "ray.serve._private.request_router.request_router.RequestRouter", "ray.serve._private.request_router.replica_wrapper.RunningReplica", "ray.serve._private.request_router.request_router.FIFOMixin", "ray.serve._private.request_router.request_router.LocalityMixin", "ray.serve._private.request_router.request_router.MultiplexMixin", }, }, "core": { "head_modules": {"ray"}, "head_doc_file": "doc/source/ray-core/api/index.rst", "white_list_apis": { # These APIs will be documented in near future "ray.util.scheduling_strategies.DoesNotExist", "ray.util.scheduling_strategies.Exists", "ray.util.scheduling_strategies.NodeLabelSchedulingStrategy", "ray.util.scheduling_strategies.In", "ray.util.scheduling_strategies.NotIn", # TODO(jjyao): document this API "ray.ObjectRefGenerator", # TODO(jjyao): document or deprecate these APIs "ray.experimental.compiled_dag_ref.CompiledDAGFuture", "ray.experimental.compiled_dag_ref.CompiledDAGRef", "ray.cross_language.cpp_actor_class", "ray.cross_language.cpp_function", "ray.client_builder.ClientContext", "ray.remote_function.RemoteFunction", }, }, "train": { "head_modules": {"ray.train"}, "head_doc_file": "doc/source/train/api/api.rst", "white_list_apis": { # NOTE: These APIs are documented in a separate file (deprecated.rst). # These are deprecated APIs, so just white-listing them here for CI. "ray.train.error.SessionMisuseError", "ray.train.base_trainer.TrainingFailedError", "ray.train.TrainingFailedError", "ray.train.context.TrainContext", "ray.train.context.get_context", }, }, "tune": { "head_modules": {"ray.tune"}, "head_doc_file": "doc/source/tune/api/api.rst", "white_list_apis": { # Already documented as ray.tune.search.ConcurrencyLimiter "ray.tune.search.searcher.ConcurrencyLimiter", # TODO(ml-team): deprecate these APIs "ray.tune.utils.log.Verbosity", }, }, "rllib": { "head_modules": {"ray.rllib"}, "head_doc_file": "doc/source/rllib/package_ref/index.rst", "white_list_apis": {}, }, } def _check_team(ray_checkout_dir: str, team: str) -> bool: # Load all APIs from the codebase api_in_codes = {} for module in TEAM_API_CONFIGS[team]["head_modules"]: module = Module(module) api_in_codes.update( {api.get_canonical_name(): api for api in module.get_apis()} ) # Load all APIs from the documentation autodoc = Autodoc(f"{ray_checkout_dir}/{TEAM_API_CONFIGS[team]['head_doc_file']}") api_in_docs = {api.get_canonical_name() for api in autodoc.get_apis()} # Load the white list APIs white_list_apis = TEAM_API_CONFIGS[team]["white_list_apis"] # Policy 01: all public APIs should be documented print( f"--- Validating that public {team} APIs should be documented...", file=sys.stderr, ) good_apis, bad_apis = API.split_good_and_bad_apis( api_in_codes, api_in_docs, white_list_apis ) if good_apis: print("Public APIs that are documented:", file=sys.stderr) for api in good_apis: print(f"\t{api}", file=sys.stderr) if bad_apis: print("Public APIs that are NOT documented:", file=sys.stderr) for api in bad_apis: print(f"\t{api}", file=sys.stderr) if bad_apis: print( f"Some public {team} APIs are not documented. Please document them.", file=sys.stderr, ) return False return True @click.command() @click.argument("ray_checkout_dir", required=True, type=str) @click.argument( "team", default="ALL", type=click.Choice(list(TEAM_API_CONFIGS.keys()) + ["ALL"]) ) def main(ray_checkout_dir: str, team: str) -> None: """ This script checks for annotated classes and functions in a module, and finds discrepancies between the annotations and the documentation. """ if team != "ALL": if not _check_team(ray_checkout_dir, team): exit(1) return all_pass = True # Needs to do core first, otherwise, the APIs in other teams may be covered by core. # This is due to the side effect of "importlib" and walking through the modules. if not _check_team(ray_checkout_dir, "core"): all_pass = False for team in TEAM_API_CONFIGS: if team == "core": continue if not _check_team(ray_checkout_dir, team): all_pass = False if not all_pass: exit(1) if __name__ == "__main__": main()