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()

Reply via email to