From 30d33fa23a57214a80e6450e5bc74ae8deb137e1 Mon Sep 17 00:00:00 2001 From: Russell Bryant Date: Tue, 26 Nov 2024 08:33:36 -0500 Subject: [PATCH 1/4] [Doc] Add github links for source code references Previously, the use of `sphinx.ext.viewcode` generated new pages that included the raw source code. This change adds the `sphinx.ext.linkcode` extension, which allows defining a custom method for generating source code URLs given a code reference. This now includes links to the appropriate file and line number on github for code references. For now I left the `viewcode` extension in place. The result is that code references now have 2 `[source]` links - one for the copy of the source in the docs, and one to github. If the github links seem to work well enough, we can drop `viewcode` later. Signed-off-by: Russell Bryant --- docs/source/conf.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 96ad9a4c26b09..822b6a8a76bd1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,6 +10,7 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. +import inspect import logging import os import sys @@ -34,6 +35,7 @@ extensions = [ "sphinx.ext.napoleon", "sphinx.ext.viewcode", + "sphinx.ext.linkcode", "sphinx.ext.intersphinx", "sphinx_copybutton", "sphinx.ext.autodoc", @@ -94,6 +96,25 @@ def setup(app): generate_examples() +def linkcode_resolve(domain, info): + if domain != 'py': + return None + if not info['module']: + return None + filename = info['module'].replace('.', '/') + module = info['module'] + try: + obj = sys.modules[module] + for part in info['fullname'].split('.'): + obj = getattr(obj, part) + lineno = inspect.getsourcelines(obj)[1] + filename = (inspect.getsourcefile(obj) + or f"{filename}.py").split("vllm/", 1)[1] + return f"https://github.com/vllm-project/vllm/blob/main/{filename}#L{lineno}" + except Exception: + return f"https://github.com/vllm-project/vllm/blob/main/{filename}.py" + + # Mock out external dependencies here, otherwise the autodoc pages may be blank. autodoc_mock_imports = [ "compressed_tensors", From 99368505bc6b71d8c71fd14edeb0b9aa29878a5f Mon Sep 17 00:00:00 2001 From: Russell Bryant Date: Tue, 26 Nov 2024 09:46:20 -0500 Subject: [PATCH 2/4] [Doc] attempt to handle github PR builds Make github source link generation work for PR doc builds, as well. This also fixes link generation for class members, which was failing before. Signed-off-by: Russell Bryant --- docs/requirements-docs.txt | 3 ++- docs/source/conf.py | 52 +++++++++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index e3e35844405ac..626bb10329ea2 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -16,4 +16,5 @@ mistral_common >= 1.3.4 aiohttp starlette openai # Required by docs/source/serving/openai_compatible_server.md's vllm.entrypoints.openai.cli_args -partial-json-parser # Required by docs/source/serving/openai_compatible_server.md's vllm.entrypoints.openai.cli_args \ No newline at end of file +partial-json-parser # Required by docs/source/serving/openai_compatible_server.md's vllm.entrypoints.openai.cli_args +requests diff --git a/docs/source/conf.py b/docs/source/conf.py index 822b6a8a76bd1..a2a6d03d61fbc 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -16,6 +16,7 @@ import sys from typing import List +import requests from sphinx.ext import autodoc logger = logging.getLogger(__name__) @@ -96,6 +97,27 @@ def setup(app): generate_examples() +_cached_base: str = "" +_cached_branch: str = "" + + +def get_repo_base_and_branch(pr_number): + global _cached_base, _cached_branch + if _cached_base and _cached_branch: + return _cached_base, _cached_branch + + url = f"https://api.github.com/repos/vllm-project/vllm/pulls/{pr_number}" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + _cached_base = data['head']['repo']['full_name'] + _cached_branch = data['head']['ref'] + return _cached_base, _cached_branch + else: + logger.error("Failed to fetch PR details: %s", response) + return None, None + + def linkcode_resolve(domain, info): if domain != 'py': return None @@ -103,16 +125,34 @@ def linkcode_resolve(domain, info): return None filename = info['module'].replace('.', '/') module = info['module'] + + # try to determine the correct file and line number to link to + obj = sys.modules[module] + + # get as specific as we can + lineno: int = 0 + filename: str = "" try: - obj = sys.modules[module] for part in info['fullname'].split('.'): obj = getattr(obj, part) - lineno = inspect.getsourcelines(obj)[1] - filename = (inspect.getsourcefile(obj) - or f"{filename}.py").split("vllm/", 1)[1] - return f"https://github.com/vllm-project/vllm/blob/main/{filename}#L{lineno}" + lineno = inspect.getsourcelines(obj)[1] + filename = (inspect.getsourcefile(obj) + or f"{filename}.py").split("vllm/", 1)[1] except Exception: - return f"https://github.com/vllm-project/vllm/blob/main/{filename}.py" + # For some things, like a class member, won't work, so + # we'll use the line number of the parent (the class) + pass + + if filename.startswith("checkouts/"): + # a PR build on readthedocs + pr_number = filename.split("/")[1] + filename = filename.split("/", 2)[2] + base, branch = get_repo_base_and_branch(pr_number) + if base and branch: + return f"https://github.com/{base}/blob/{branch}/{filename}#L{lineno}" + + # Otherwise, link to the source file on the main branch + return f"https://github.com/vllm-project/vllm/blob/main/{filename}#L{lineno}" # Mock out external dependencies here, otherwise the autodoc pages may be blank. From a14c47578b84d70c52992a439d53cf659e40cd58 Mon Sep 17 00:00:00 2001 From: Russell Bryant Date: Mon, 2 Dec 2024 17:43:41 -0500 Subject: [PATCH 3/4] Try to handle references to instances of a class inspect doesn't support getting the file and line number for a reference to an instance of a class. We can get the class and provide a link to that, instead. That's better than nothing. This improves the result for `vllm.multimodal.MULTIMODAL_REGISTRY`. Signed-off-by: Russell Bryant --- docs/source/conf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index a2a6d03d61fbc..50b1ad717e4b3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -135,6 +135,10 @@ def linkcode_resolve(domain, info): try: for part in info['fullname'].split('.'): obj = getattr(obj, part) + + if not inspect.isclass(obj) and not inspect.isfunction(obj) and not inspect.ismethod(obj): + obj = obj.__class__ # Get the class of the instance + lineno = inspect.getsourcelines(obj)[1] filename = (inspect.getsourcefile(obj) or f"{filename}.py").split("vllm/", 1)[1] From 042e9d3c68f5ebeb4a895a221b3b46d7f3b3820e Mon Sep 17 00:00:00 2001 From: DarkLight1337 Date: Tue, 3 Dec 2024 04:02:41 +0000 Subject: [PATCH 4/4] format Signed-off-by: DarkLight1337 --- docs/source/conf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 50b1ad717e4b3..4a1a5fb455ff3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -136,7 +136,8 @@ def linkcode_resolve(domain, info): for part in info['fullname'].split('.'): obj = getattr(obj, part) - if not inspect.isclass(obj) and not inspect.isfunction(obj) and not inspect.ismethod(obj): + if not (inspect.isclass(obj) or inspect.isfunction(obj) + or inspect.ismethod(obj)): obj = obj.__class__ # Get the class of the instance lineno = inspect.getsourcelines(obj)[1]