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