diff --git a/scripts/docs/python_ref_builder.py b/scripts/docs/python_ref_builder.py index f90fc6d65..914b89239 100644 --- a/scripts/docs/python_ref_builder.py +++ b/scripts/docs/python_ref_builder.py @@ -368,12 +368,18 @@ def _pass1(data_root: dict, dir_root: Path, api_ref_root: str, module_import_pat elif name == "functions": for func_name in obj: - if func_name.startswith("_") or func_name in links: + if func_name.startswith("_"): continue - links[func_name] = { - "path": f"{api_ref_root}/{func_name}".lower(), - "kind": "function", - } + # Dedup only the global links dict (used for docstring + # linkification). Page creation must still happen for every + # class, otherwise inherited methods shared across classes + # (e.g. client_class) get a page under only the first class + # while every other class emits a relative link that 404s. + if func_name not in links: + links[func_name] = { + "path": f"{api_ref_root}/{func_name}".lower(), + "kind": "function", + } pages.append( _PageSpec( kind="function", diff --git a/scripts/docs/tests/test_python_ref_builder.py b/scripts/docs/tests/test_python_ref_builder.py index cb4935314..26ba15e22 100644 --- a/scripts/docs/tests/test_python_ref_builder.py +++ b/scripts/docs/tests/test_python_ref_builder.py @@ -398,3 +398,23 @@ def test_duplicate_names_skipped(self, _mod, tmp_path): # Should not raise — second "Shared" is skipped _mod.create_file_structure(data, tmp_path, "/latest/api-reference") assert (tmp_path / "mod1" / "Shared" / "_index.md").exists() + + def test_shared_method_gets_page_under_every_class(self, _mod, tmp_path): + # Methods inherited across many classes (e.g. client_class) share a name. + # Each class lists the method with a relative link, so each class must get + # its own method page — deduping only affects the global links dict. + method = {"kind": "function", "docstring_parsed": None, "signature": {}} + data = { + "mod1": { + "kind": "module", + "ClassA": {"kind": "class", "functions": {"client_class": dict(method)}}, + }, + "mod2": { + "kind": "module", + "ClassB": {"kind": "class", "functions": {"client_class": dict(method)}}, + }, + } + _mod.create_file_structure(data, tmp_path, "/latest/api-reference") + # Both classes must have their own client_class page (neither should 404). + assert (tmp_path / "mod1" / "ClassA" / "client_class.md").exists() + assert (tmp_path / "mod2" / "ClassB" / "client_class.md").exists()