This is an automated email from the ASF dual-hosted git repository.
junrushao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new 618c03b doc: Handle builtin and inheritance (#177)
618c03b is described below
commit 618c03bd558a745980d119f1cae9c867128d6a1e
Author: Junru Shao <[email protected]>
AuthorDate: Mon Oct 20 21:12:55 2025 -0700
doc: Handle builtin and inheritance (#177)
This PR does two things to the doc:
- **Change A**. Skip member methods of builtin classes;
- **Change B**. Clearly mark which base class a method inherits from.
**Change A**. To give an example, `tvm_ffi.dtype`, as a subclass of
Python's native `str`, is shown to have the following methods inherited
from `str`:
| Method | Description |
|---|---|
| `capitalize()` | Return a capitalized version of the string. |
| `casefold()` | Return a version of the string suitable for caseless
comparisons. |
| `center(width[, fillchar])` | Return a centered string of length
`width`. |
| `count(sub[, start[, end]])` | Return the number of non-overlapping
occurrences of substring `sub` in `S[start:end]`. |
| `encode([encoding, errors])` | Encode the string using the codec
registered for `encoding`. |
| `endswith(suffix[, start[, end]])` | Return `True` if the string ends
with the specified `suffix`, `False` otherwise. |
| `expandtabs([tabsize])` | Return a copy where all tab characters are
expanded using spaces. |
| `find(sub[, start[, end]])` | Return the lowest index in `S` where
substring `sub` is found, such that `sub` is contained within
`S[start:end]`. |
| `format(*args, **kwargs)` | Return a formatted version of the string,
using substitutions from `args` and `kwargs`. |
| `format_map(mapping, /)` | Return a formatted version of the string,
using substitutions from `mapping`. |
, which adds quite a lot of noise because they are irrelevant to `dtype`
usecases.
**Change B**. For inherited methods, e.g. `Tensor.same_as` which is from
`Object.same_as` - currently it's not clickable and doesn't have a
detailed documentation for it, which is quite inconvenient. This PR
replaces its description to:
```
Defined in
[Object](link/to/reference/python/generated/tvm_ffi.Object.html#tvm_ffi.Object).`
```
---
docs/conf.py | 82 ++++++++++++++++++++++++++++++++++++++++++++--
pyproject.toml | 14 ++++----
python/tvm_ffi/__init__.py | 14 ++++++++
3 files changed, 100 insertions(+), 10 deletions(-)
diff --git a/docs/conf.py b/docs/conf.py
index ddd69b7..befe7c1 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -19,6 +19,8 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
+import importlib
+import inspect
import os
import shutil
import subprocess
@@ -242,15 +244,71 @@ def setup(app: sphinx.application.Sphinx) -> None:
app.add_config_value("build_rust_docs", build_rust_docs, "env")
app.connect("config-inited", _apply_config_overrides)
app.connect("build-finished", _copy_rust_docs_to_output)
- app.connect("autodoc-skip-member", _never_skip_selected_dunders)
+ app.connect("autodoc-skip-member", _filter_inherited_members)
+ app.connect("autodoc-process-docstring", _link_inherited_members)
-def _never_skip_selected_dunders(app, what, name, obj, skip, options): #
noqa: ANN001, ANN202
+def _filter_inherited_members(app, what, name, obj, skip, options): # noqa:
ANN001, ANN202
if name in _autodoc_always_show:
- return False # do not skip
+ return False
+ if "built-in method " in str(obj):
+ # Skip: `str.maketrans`, `EnumType.from_bytes`
+ return True
+ if getattr(obj, "__objclass__", None) in _py_native_classes:
+ return True
return None
+def _link_inherited_members(app, what, name, obj, options, lines) -> None: #
noqa: ANN001
+ # Only act on members (methods/attributes/properties)
+ if what not in {"method", "attribute", "property"}:
+ return
+ cls = _import_cls(name.rsplit(".", 1)[0])
+ if cls is None:
+ return
+
+ member_name = name.rsplit(".", 1)[-1] # just "foo"
+ base = _defining_class(cls, member_name)
+
+ # If we can't find a base or this class defines it, nothing to do
+ if base is None or base is cls:
+ return
+
+ # If it comes from builtins we already hide it; no link needed
+ if base in _py_native_classes or getattr(base, "__module__", "") ==
"builtins":
+ return
+ owner_fq = f"{base.__module__}.{base.__qualname__}"
+ role = "attr" if what in {"attribute", "property"} else "meth"
+ lines.clear()
+ lines.append(
+ f"*Defined in* :class:`~{owner_fq}` *as {what}*
:{role}:`~{owner_fq}.{member_name}`."
+ )
+
+
+def _defining_class(cls: type | None, attr_name: str) -> type | None:
+ """Find the first class in cls.__mro__ that defines attr_name in its
__dict__."""
+ if not isinstance(cls, type):
+ return None
+ method = getattr(cls, attr_name, None)
+ if method is None:
+ return None
+ for base in reversed(inspect.getmro(cls)):
+ d = getattr(base, "__dict__", {})
+ if d.get(attr_name, None) is method:
+ return base
+ return None
+
+
+def _import_cls(cls_name: str) -> type | None:
+ """Import and return the class object given its module and class name."""
+ try:
+ mod, clsname = cls_name.rsplit(".", 1)
+ m = importlib.import_module(mod)
+ return getattr(m, clsname, None)
+ except Exception:
+ return None
+
+
autodoc_mock_imports = ["torch"]
autodoc_default_options = {
"members": True,
@@ -263,9 +321,27 @@ _autodoc_always_show = {
"__dlpack__",
"__dlpack_device__",
"__device_type_name__",
+ "__ffi_init__",
"__from_extern_c__",
"__from_mlir_packed_safe_call__",
}
+# If a member method comes from one of these native types, hide it in the docs
+_py_native_classes: tuple[type, ...] = (
+ str,
+ tuple,
+ list,
+ dict,
+ set,
+ frozenset,
+ bytes,
+ bytearray,
+ memoryview,
+ int,
+ float,
+ complex,
+ bool,
+ object,
+)
autodoc_typehints = "description" # or "none"
always_use_bars_union = True
diff --git a/pyproject.toml b/pyproject.toml
index df528ee..92e1fce 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -77,16 +77,16 @@ docs = [
"sphinx-book-theme",
"sphinx-copybutton",
"sphinx-design",
- "sphinx-reredirects==0.1.2",
- "sphinx-tabs==3.4.1",
- "sphinx-toolbox==3.4.0",
+ "sphinx-reredirects",
+ "sphinx-tabs",
+ "sphinx-toolbox",
"sphinx-autodoc-typehints",
"sphinxcontrib-mermaid",
- "sphinxcontrib-napoleon==0.7",
- "sphinxcontrib_httpdomain==1.8.1",
- "setuptools<81",
+ "sphinxcontrib-napoleon",
+ "sphinxcontrib_httpdomain",
+ "setuptools",
"tomli",
- "urllib3>=2.5.0",
+ "urllib3",
]
[project.scripts]
diff --git a/python/tvm_ffi/__init__.py b/python/tvm_ffi/__init__.py
index 21c28f2..8043524 100644
--- a/python/tvm_ffi/__init__.py
+++ b/python/tvm_ffi/__init__.py
@@ -95,3 +95,17 @@ __all__ = [
"use_raw_stream",
"use_torch_stream",
]
+
+
+def _update_module() -> None:
+ for name in __all__:
+ obj = globals()[name]
+ if not getattr(obj, "__module__", "tvm_ffi").startswith("tvm_ffi"):
+ try:
+ obj.__module__ = "tvm_ffi"
+ except (AttributeError, TypeError):
+ # some types don't allow setting __module__
+ pass
+
+
+_update_module()