1# Configuration file for the Sphinx documentation builder. 2# 3# For the full list of built-in configuration values, see the documentation: 4# https://www.sphinx-doc.org/en/master/usage/configuration.html 5 6# -- Path setup -------------------------------------------------------------- 7 8# If extensions (or modules to document with autodoc) are in another directory, 9# add these directories to sys.path here. If the directory is relative to the 10# documentation root, use os.path.abspath to make it absolute, like shown here. 11 12import re 13import os 14import shutil 15import sys 16import subprocess 17import typing 18import datetime 19import importlib 20import sphobjinv 21import functools 22import pylit 23from sphinx import __version__ as sphinx_version 24from sphinx.ext.napoleon.docstring import NumpyDocstring 25from packaging.version import Version 26 27sys.path.insert(0, os.path.abspath('.')) 28_today = datetime.datetime.now() 29 30# FIXME: allow building from build? 31 32# -- Project information ----------------------------------------------------- 33# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 34 35package = 'petsc4py' 36 37docdir = os.path.abspath(os.path.dirname(__file__)) 38topdir = os.path.abspath(os.path.join(docdir, *[os.path.pardir] * 2)) 39 40 41def pkg_version(): 42 with open(os.path.join(topdir, 'src', package, '__init__.py')) as f: 43 m = re.search(r"__version__\s*=\s*'(.*)'", f.read()) 44 return m.groups()[0] 45 46 47def get_doc_branch(): 48 release = 1 49 if topdir.endswith(os.path.join(os.path.sep, 'src', 'binding', package)): 50 rootdir = os.path.abspath(os.path.join(topdir, *[os.path.pardir] * 3)) 51 rootname = package.replace('4py', '') 52 version_h = os.path.join(rootdir, 'include', f'{rootname}version.h') 53 if os.path.exists(version_h) and os.path.isfile(version_h): 54 release_macro = f'{rootname.upper()}_VERSION_RELEASE' 55 version_re = re.compile(rf'#define\s+{release_macro}\s+([-]*\d+)') 56 with open(version_h, 'r') as f: 57 release = int(version_re.search(f.read()).groups()[0]) 58 return 'release' if release else 'main' 59 60 61__project__ = 'PETSc for Python' 62__author__ = 'Lisandro Dalcin' 63__copyright__ = f'{_today.year}, {__author__}' 64 65release = pkg_version() 66version = release.rsplit('.', 1)[0] 67 68 69# -- General configuration --------------------------------------------------- 70# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 71 72extensions = [ 73 'sphinx.ext.autodoc', 74 'sphinx.ext.autosummary', 75 'sphinx.ext.intersphinx', 76 'sphinx.ext.napoleon', 77 'sphinx.ext.extlinks', 78] 79 80templates_path = ['_templates'] 81exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 82 83default_role = 'any' 84 85pygments_style = 'tango' 86 87nitpicky = True 88nitpick_ignore = [ 89 ('envvar', 'NUMPY_INCLUDE'), 90 ('py:class', 'ndarray'), # FIXME 91 ('py:class', 'typing_extensions.Self'), 92] 93nitpick_ignore_regex = [ 94 (r'c:.*', r'MPI_.*'), 95 (r'c:.*', r'Petsc.*'), 96 (r'envvar', r'(LD_LIBRARY_)?PATH'), 97 (r'envvar', r'(MPICH|OMPI|MPIEXEC)_.*'), 98] 99 100toc_object_entries = False 101toc_object_entries_show_parents = 'hide' 102# python_use_unqualified_type_names = True 103 104autodoc_class_signature = 'separated' 105autodoc_typehints = 'description' 106autodoc_typehints_format = 'short' 107autodoc_mock_imports = [] 108autodoc_type_aliases = {} 109 110autosummary_context = { 111 'synopsis': {}, 112 'autotype': {}, 113} 114 115suppress_warnings = [] 116if Version(sphinx_version) >= Version( 117 '7.4' 118): # https://github.com/sphinx-doc/sphinx/issues/12589 119 suppress_warnings.append('autosummary.import_cycle') 120 121# Links depends on the actual branch -> release or main 122www = f'https://gitlab.com/petsc/petsc/-/tree/{get_doc_branch()}' 123extlinks = {'sources': (f'{www}/src/binding/petsc4py/src/%s', '%s')} 124 125napoleon_preprocess_types = True 126 127try: 128 import sphinx_rtd_theme 129 130 if 'sphinx_rtd_theme' not in extensions: 131 extensions.append('sphinx_rtd_theme') 132except ImportError: 133 sphinx_rtd_theme = None 134 135intersphinx_mapping = { 136 'python': ('https://docs.python.org/3/', None), 137 'numpy': ('https://numpy.org/doc/stable/', None), 138 'numpydoc': ('https://numpydoc.readthedocs.io/en/latest/', None), 139 'mpi4py': ('https://mpi4py.readthedocs.io/en/stable/', None), 140 'pyopencl': ('https://documen.tician.de/pyopencl/', None), 141 'dlpack': ('https://dmlc.github.io/dlpack/latest/', None), 142 'petsc': ('https://petsc.org/release/', None), 143} 144 145 146def _mangle_petsc_intersphinx(): 147 """Preprocess the keys in PETSc's intersphinx inventory. 148 149 PETSc have intersphinx keys of the form: 150 151 manualpages/Vec/VecShift 152 153 instead of: 154 155 petsc.VecShift 156 157 This function downloads their object inventory and strips the leading path 158 elements so that references to PETSc names actually resolve.""" 159 160 website = intersphinx_mapping['petsc'][0].partition('/release/')[0] 161 branch = get_doc_branch() 162 doc_url = f'{website}/{branch}/' 163 if 'LOC' in os.environ and os.path.isfile( 164 os.path.join(os.environ['LOC'], 'objects.inv') 165 ): 166 inventory_url = 'file://' + os.path.join(os.environ['LOC'], 'objects.inv') 167 else: 168 inventory_url = f'{doc_url}objects.inv' 169 print('Using PETSC inventory from ' + inventory_url) 170 inventory = sphobjinv.Inventory(url=inventory_url) 171 print(inventory) 172 173 for obj in inventory.objects: 174 if obj.name.startswith('manualpages'): 175 obj.name = 'petsc.' + '/'.join(obj.name.split('/')[2:]) 176 obj.role = 'class' 177 obj.domain = 'py' 178 179 new_inventory_filename = 'petsc_objects.inv' 180 sphobjinv.writebytes( 181 new_inventory_filename, sphobjinv.compress(inventory.data_file(contract=True)) 182 ) 183 intersphinx_mapping['petsc'] = (doc_url, new_inventory_filename) 184 185 186_mangle_petsc_intersphinx() 187 188 189def _setup_mpi4py_typing(): 190 pkg = type(sys)('mpi4py') 191 mod = type(sys)('mpi4py.MPI') 192 mod.__package__ = pkg.__name__ 193 sys.modules[pkg.__name__] = pkg 194 sys.modules[mod.__name__] = mod 195 for clsname in ( 196 'Intracomm', 197 'Datatype', 198 'Op', 199 ): 200 cls = type(clsname, (), {}) 201 cls.__module__ = mod.__name__ 202 setattr(mod, clsname, cls) 203 204 205def _patch_domain_python(): 206 from sphinx.domains.python import PythonDomain 207 208 PythonDomain.object_types['data'].roles += ('class',) 209 210 211def _setup_autodoc(app): 212 from sphinx.ext import autodoc 213 from sphinx.util import inspect 214 from sphinx.util import typing 215 216 # 217 218 def stringify_annotation(annotation, *p, **kw): 219 qualname = getattr(annotation, '__qualname__', '') 220 module = getattr(annotation, '__module__', '') 221 args = getattr(annotation, '__args__', None) 222 if module == 'builtins' and qualname and args is not None: 223 args = ', '.join(stringify_annotation(a, *p, **kw) for a in args) 224 return f'{qualname}[{args}]' 225 return stringify_annotation_orig(annotation, *p, **kw) 226 227 try: 228 stringify_annotation_orig = typing.stringify_annotation 229 inspect.stringify_annotation = stringify_annotation 230 typing.stringify_annotation = stringify_annotation 231 autodoc.stringify_annotation = stringify_annotation 232 autodoc.typehints.stringify_annotation = stringify_annotation 233 except AttributeError: 234 stringify_annotation_orig = typing.stringify 235 inspect.stringify_annotation = stringify_annotation 236 typing.stringify = stringify_annotation 237 autodoc.stringify_typehint = stringify_annotation 238 239 inspect.TypeAliasForwardRef.__repr__ = lambda self: self.name 240 241 # 242 243 class ClassDocumenterMixin: 244 def __init__(self, *args, **kwargs): 245 super().__init__(*args, **kwargs) 246 if self.config.autodoc_class_signature == 'separated': 247 members = self.options.members 248 special_members = self.options.special_members 249 if special_members is not None: 250 for name in ('__new__', '__init__'): 251 if name in members: 252 members.remove(name) 253 if name in special_members: 254 special_members.remove(name) 255 256 class ClassDocumenter( 257 ClassDocumenterMixin, 258 autodoc.ClassDocumenter, 259 ): 260 pass 261 262 class ExceptionDocumenter( 263 ClassDocumenterMixin, 264 autodoc.ExceptionDocumenter, 265 ): 266 pass 267 268 app.add_autodocumenter(ClassDocumenter, override=True) 269 app.add_autodocumenter(ExceptionDocumenter, override=True) 270 271 272def _monkey_patch_returns(): 273 """Rewrite the role of names in "Returns" sections. 274 275 This is needed because Napoleon uses ``:class:`` for the return types 276 and this does not work with type aliases like ``ArrayScalar``. To resolve 277 this we swap ``:class:`` for ``:any:``. 278 279 """ 280 _parse_returns_section = NumpyDocstring._parse_returns_section 281 282 @functools.wraps(NumpyDocstring._parse_returns_section) 283 def wrapper(*args, **kwargs): 284 out = _parse_returns_section(*args, **kwargs) 285 for role in (':py:class:', ':class:'): 286 out = [line.replace(role, ':any:') for line in out] 287 return out 288 289 NumpyDocstring._parse_returns_section = wrapper 290 291 292def _monkey_patch_see_also(): 293 """Rewrite the role of names in "see also" sections. 294 295 Napoleon uses :obj: for all names found in "see also" sections but we 296 need :all: so that references to labels work.""" 297 298 _parse_numpydoc_see_also_section = NumpyDocstring._parse_numpydoc_see_also_section 299 300 @functools.wraps(NumpyDocstring._parse_numpydoc_see_also_section) 301 def wrapper(*args, **kwargs): 302 out = _parse_numpydoc_see_also_section(*args, **kwargs) 303 for role in (':py:obj:', ':obj:'): 304 out = [line.replace(role, ':any:') for line in out] 305 return out 306 307 NumpyDocstring._parse_numpydoc_see_also_section = wrapper 308 309 310def _apply_monkey_patches(): 311 """Modify Napoleon types after parsing to make references work.""" 312 _monkey_patch_returns() 313 _monkey_patch_see_also() 314 315 316_apply_monkey_patches() 317 318 319def _process_demos(*demos): 320 # Convert demo .py files to rst. Also copy the .py file so it can be 321 # linked from the demo rst file. 322 try: 323 os.mkdir('demo') 324 except FileExistsError: 325 pass 326 for demo in demos: 327 demo_dir = os.path.join('demo', os.path.dirname(demo)) 328 demo_src = os.path.join(os.pardir, os.pardir, 'demo', demo) 329 try: 330 os.mkdir(demo_dir) 331 except FileExistsError: 332 pass 333 with open(demo_src, 'r') as infile: 334 with open( 335 os.path.join(os.path.join('demo', os.path.splitext(demo)[0] + '.rst')), 336 'w', 337 ) as outfile: 338 converter = pylit.Code2Text(infile) 339 outfile.write(str(converter)) 340 demo_copy_name = os.path.join(demo_dir, os.path.basename(demo)) 341 shutil.copyfile(demo_src, demo_copy_name) 342 html_static_path.append(demo_copy_name) 343 with open(os.path.join('demo', 'demo.rst'), 'w') as demofile: 344 demofile.write(""" 345petsc4py demos 346============== 347 348.. toctree:: 349 350""") 351 for demo in demos: 352 demofile.write(' ' + os.path.splitext(demo)[0] + '\n') 353 demofile.write('\n') 354 355 356html_static_path = [] 357_process_demos('poisson2d/poisson2d.py') 358 359 360def setup(app): 361 _setup_mpi4py_typing() 362 _patch_domain_python() 363 _monkey_patch_returns() 364 _monkey_patch_see_also() 365 _setup_autodoc(app) 366 367 try: 368 from petsc4py import PETSc 369 except ImportError: 370 autodoc_mock_imports.append('PETSc') 371 return 372 del PETSc.DA # FIXME 373 374 sys_dwb = sys.dont_write_bytecode 375 sys.dont_write_bytecode = True 376 import apidoc 377 378 sys.dont_write_bytecode = sys_dwb 379 380 name = PETSc.__name__ 381 here = os.path.abspath(os.path.dirname(__file__)) 382 outdir = os.path.join(here, apidoc.OUTDIR) 383 source = os.path.join(outdir, f'{name}.py') 384 getmtime = os.path.getmtime 385 generate = ( 386 not os.path.exists(source) 387 or getmtime(source) < getmtime(PETSc.__file__) 388 or getmtime(source) < getmtime(apidoc.__file__) 389 ) 390 if generate: 391 apidoc.generate(source) 392 module = apidoc.load_module(source) 393 apidoc.replace_module(module) 394 395 modules = [ 396 'petsc4py', 397 ] 398 typing_overload = typing.overload 399 typing.overload = lambda arg: arg 400 for name in modules: 401 mod = importlib.import_module(name) 402 ann = apidoc.load_module(f'{mod.__file__}i', name) 403 apidoc.annotate(mod, ann) 404 typing.overload = typing_overload 405 406 from petsc4py import typing as tp 407 408 for attr in tp.__all__: 409 autodoc_type_aliases[attr] = f'~petsc4py.typing.{attr}' 410 411 412# -- Options for HTML output ------------------------------------------------- 413# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 414 415# The theme to use for HTML and HTML Help pages. See the documentation for 416# a list of builtin themes. 417html_theme = 'pydata_sphinx_theme' 418 419html_theme_options = { 420 'navigation_with_keys': True, 421 'footer_end': ['theme-version', 'last-updated'], 422} 423git_describe_version = ( 424 subprocess.check_output(['git', 'describe', '--always']).strip().decode('utf-8') # noqa: S603, S607 425) 426html_last_updated_fmt = r'%Y-%m-%dT%H:%M:%S%z (' + git_describe_version + ')' 427 428# -- Options for HTMLHelp output ------------------------------------------ 429 430# Output file base name for HTML help builder. 431htmlhelp_basename = f'{package}-man' 432 433 434# -- Options for LaTeX output --------------------------------------------- 435 436# (source start file, target name, title, 437# author, documentclass [howto, manual, or own class]). 438latex_documents = [ 439 ('index', f'{package}.tex', __project__, __author__, 'howto'), 440] 441 442latex_elements = { 443 'papersize': 'a4', 444} 445 446 447# -- Options for manual page output --------------------------------------- 448 449# (source start file, name, description, authors, manual section). 450man_pages = [('index', package, __project__, [__author__], 3)] 451 452 453# -- Options for Texinfo output ------------------------------------------- 454 455# (source start file, target name, title, author, 456# dir menu entry, description, category) 457texinfo_documents = [ 458 ( 459 'index', 460 package, 461 __project__, 462 __author__, 463 package, 464 f'{__project__}.', 465 'Miscellaneous', 466 ), 467] 468 469 470# -- Options for Epub output ---------------------------------------------- 471 472# Output file base name for ePub builder. 473epub_basename = package 474