Skip to content

Commit 9685b8a

Browse files
committed
Improve docs build and Sphinx configuration
Add _set_module_for_docs to fix cross-reference resolution, improve autodoc member filtering, linkcode for dataclass fields, and add backend deps to docs extra.
1 parent 23bbcfe commit 9685b8a

File tree

10 files changed

+78
-10
lines changed

10 files changed

+78
-10
lines changed

.readthedocs.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ build:
55
tools:
66
python: "3.10"
77
commands:
8+
- python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
89
- python -m pip install ".[docs]"
910
- python -m sphinx -E -b html docs $READTHEDOCS_OUTPUT/html
1011

docs/conf.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,32 +139,51 @@
139139

140140

141141
def autodoc_skip_member(app, what, name, obj, skip, options):
142-
"""Skip members (functions, classes, modules) without docstrings."""
142+
"""Skip members without docstrings or from undocumented modules."""
143143
if not getattr(obj, 'docstring', None):
144144
return True
145145
elif what in ('class', 'function', 'attribute'):
146-
# Check if the module of the class has a docstring
147146
module_name = '.'.join(name.split('.')[:-1])
148147
try:
149-
module = importlib.import_module(module_name)
150-
return not getattr(module, '__doc__', None)
148+
mod = importlib.import_module(module_name)
149+
if not getattr(mod, '__doc__', None):
150+
return True # Module has no docstring, skip its members
151151
except ModuleNotFoundError:
152+
# Import failed (e.g. attribute path like Class.attr, or missing dep).
153+
# Defer to AutoAPI default.
152154
return None
155+
# For private names, defer to AutoAPI default (which skips them).
156+
# For public names, force-include (overrides the imported-members default).
157+
short_name = name.split('.')[-1]
158+
if short_name.startswith('_') and not short_name.startswith('__'):
159+
return None
160+
return False
153161
return skip
154162

155163

156164
def linkcode_resolve(domain, info):
157165
if domain != 'py':
158166
return None
159167

168+
fullname = info['fullname']
160169
try:
161-
obj = eval(info['fullname'], module.__dict__)
162-
file, start, end = get_line_numbers(obj)
163-
relpath = os.path.relpath(file, os.path.dirname(module.__file__))
164-
return f'{repo_url}/blob/v{release}/src/{main_module_name}/{relpath}#L{start}-L{end}'
165-
except Exception:
170+
file, start, end = get_line_numbers(eval(fullname))
171+
except AttributeError:
172+
# Instance attribute (dataclass field or self.x = ... in __init__)
173+
parts = fullname.rsplit('.', 1)
174+
if len(parts) != 2:
175+
return None
176+
try:
177+
file, start, end = get_attr_line_numbers(eval(parts[0]), parts[1])
178+
except Exception:
179+
return None
180+
except Exception as e:
181+
print(f'linkcode_resolve failed: {info}{e}')
166182
return None
167183

184+
relpath = os.path.relpath(file, os.path.dirname(module.__file__))
185+
return f'{repo_url}/blob/v{release}/src/{main_module_name}/{relpath}#L{start}-L{end}'
186+
168187

169188
def get_line_numbers(obj):
170189
if isinstance(obj, property):
@@ -196,6 +215,22 @@ def get_enum_member_line_numbers(obj):
196215
raise ValueError(f'Enum member {obj.name} not found in {class_}')
197216

198217

218+
def get_attr_line_numbers(class_, attr_name):
219+
with module_restored(class_):
220+
source_lines, start_line = inspect.getsourcelines(class_)
221+
for i, line in enumerate(source_lines):
222+
stripped = line.strip()
223+
if (
224+
stripped.startswith(f'{attr_name}:')
225+
or stripped.startswith(f'{attr_name} :')
226+
or f'self.{attr_name} =' in stripped
227+
or f'self.{attr_name}=' in stripped
228+
):
229+
return inspect.getsourcefile(class_), start_line + i, start_line + i
230+
else:
231+
raise ValueError(f'Attribute {attr_name} not found in {class_}')
232+
233+
199234
def get_member_line_numbers(obj: types.MemberDescriptorType):
200235
class_ = obj.__objclass__
201236
with module_restored(class_):

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ docs = [
7474
"pydata-sphinx-theme",
7575
"setuptools-scm",
7676
"toml",
77+
"torch",
78+
"tensorflow",
79+
"jax",
80+
"jaxlib",
81+
"numba",
7782
]
7883
dev = ["smplfitter[test,lint,docs,pytorch]"]
7984

src/smplfitter/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010

1111
from __future__ import annotations
1212

13-
from .common import ModelData, initialize
13+
from .common import ModelData, initialize, _set_module_for_docs
1414

1515
try:
1616
from ._version import version as __version__
1717
except ImportError:
1818
__version__ = '0.0.0'
1919

2020
__all__ = ['ModelData', 'initialize', '__version__']
21+
_set_module_for_docs(__name__, globals(), __all__)

src/smplfitter/common.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,22 @@
1111
import numpy as np
1212

1313

14+
def _set_module_for_docs(module_name, module_globals, all_names):
15+
"""Override __module__ on exported objects so Sphinx resolves package-level names.
16+
17+
sphinx-codeautolink uses __module__ to find the docs page for a name. Without this,
18+
e.g. ``BodyModel`` imported from ``smplfitter.pt.bodymodel`` would not link to
19+
``smplfitter.pt.BodyModel``. The original __module__ is saved as ``_module_original_``
20+
so that ``inspect.getsourcefile`` can still find the real source file (see
21+
``module_restored`` in docs/conf.py).
22+
"""
23+
for name in all_names:
24+
obj = module_globals.get(name)
25+
if obj is not None and callable(obj):
26+
obj._module_original_ = obj.__module__
27+
obj.__module__ = module_name
28+
29+
1430
@dataclass
1531
class ModelData:
1632
"""Data loaded from a SMPL-family body model file.

src/smplfitter/jax/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
from .bodyfitter import BodyFitter
1010
from .bodyconverter import BodyConverter
1111
from . import rotation
12+
from smplfitter.common import _set_module_for_docs
1213

1314
__all__ = ['BodyModel', 'BodyFitter', 'BodyConverter', 'get_cached_body_model', 'rotation']
15+
_set_module_for_docs(__name__, globals(), __all__)
1416

1517

1618
@functools.lru_cache()

src/smplfitter/nb/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
from .bodyfitter import BodyFitter
1010
from .bodyconverter import BodyConverter
1111
from . import rotation
12+
from smplfitter.common import _set_module_for_docs
1213

1314
__all__ = ['BodyModel', 'BodyFitter', 'BodyConverter', 'get_cached_body_model', 'rotation']
15+
_set_module_for_docs(__name__, globals(), __all__)
1416

1517

1618
@functools.lru_cache()

src/smplfitter/np/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
from .bodymodel import BodyModel
66
from .bodyfitter import BodyFitter
77
from .bodyconverter import BodyConverter
8+
from smplfitter.common import _set_module_for_docs
89

910
import functools
1011
import os
1112

1213
__all__ = ['BodyModel', 'BodyFitter', 'BodyConverter', 'get_cached_body_model']
14+
_set_module_for_docs(__name__, globals(), __all__)
1315

1416

1517
@functools.lru_cache()

src/smplfitter/pt/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .bodyfitter import BodyFitter
1313
from .bodyconverter import BodyConverter
1414
from .bodyflipper import BodyFlipper
15+
from smplfitter.common import _set_module_for_docs
1516

1617

1718
__all__ = [
@@ -23,6 +24,7 @@
2324
'get_cached_fit_fn',
2425
'fit',
2526
]
27+
_set_module_for_docs(__name__, globals(), __all__)
2628

2729

2830
@functools.lru_cache()

src/smplfitter/tf/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .bodymodel import BodyModel
1010
from .bodyfitter import BodyFitter
1111
from .bodyconverter import BodyConverter
12+
from smplfitter.common import _set_module_for_docs
1213

1314
__all__ = [
1415
'BodyModel',
@@ -17,6 +18,7 @@
1718
'get_cached_body_model',
1819
'get_cached_fit_fn',
1920
]
21+
_set_module_for_docs(__name__, globals(), __all__)
2022

2123

2224
@functools.lru_cache()

0 commit comments

Comments
 (0)