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# Links depends on the actual branch -> release or main 113www = f'https://gitlab.com/petsc/petsc/-/tree/{get_doc_branch()}' 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 149 if 'LOC' in os.environ and os.path.isfile(os.path.join(os.environ['LOC'],'objects.inv')): 150 base_doc_url = os.environ['LOC'] 151 url=f"file://" + os.path.join(base_doc_url,'objects.inv') 152 else: 153 website = intersphinx_mapping['petsc'][0].partition('/release/')[0] 154 branch = get_doc_branch() 155 base_doc_url = f"{website}/{branch}/" 156 url=f"{base_doc_url}objects.inv" 157 print("Using PETSC inventory from "+url) 158 inventory = sphobjinv.Inventory(url=url) 159 print(inventory) 160 161 for obj in inventory.objects: 162 if obj.name.startswith("manualpages"): 163 obj.name = "petsc." + "/".join(obj.name.split("/")[2:]) 164 obj.role = "class" 165 obj.domain = "py" 166 167 new_inventory_filename = "petsc_objects.inv" 168 sphobjinv.writebytes( 169 new_inventory_filename, 170 sphobjinv.compress(inventory.data_file(contract=True)) 171 ) 172 intersphinx_mapping['petsc'] = (base_doc_url, new_inventory_filename) 173 174 175_mangle_petsc_intersphinx() 176 177 178def _setup_mpi4py_typing(): 179 pkg = type(sys)('mpi4py') 180 mod = type(sys)('mpi4py.MPI') 181 mod.__package__ = pkg.__name__ 182 sys.modules[pkg.__name__] = pkg 183 sys.modules[mod.__name__] = mod 184 for clsname in ( 185 'Intracomm', 186 'Datatype', 187 'Op', 188 ): 189 cls = type(clsname, (), {}) 190 cls.__module__ = mod.__name__ 191 setattr(mod, clsname, cls) 192 193 194def _patch_domain_python(): 195 from sphinx.domains.python import PythonDomain 196 PythonDomain.object_types['data'].roles += ('class',) 197 198 199def _setup_autodoc(app): 200 from sphinx.ext import autodoc 201 from sphinx.util import inspect 202 from sphinx.util import typing 203 204 # 205 206 def stringify_annotation(annotation, mode='fully-qualified-except-typing'): 207 qualname = getattr(annotation, '__qualname__', '') 208 module = getattr(annotation, '__module__', '') 209 args = getattr(annotation, '__args__', None) 210 if module == 'builtins' and qualname and args is not None: 211 args = ', '.join(stringify_annotation(a, mode) for a in args) 212 return f'{qualname}[{args}]' 213 return stringify_annotation_orig(annotation, mode) 214 215 try: 216 stringify_annotation_orig = typing.stringify_annotation 217 inspect.stringify_annotation = stringify_annotation 218 typing.stringify_annotation = stringify_annotation 219 autodoc.stringify_annotation = stringify_annotation 220 autodoc.typehints.stringify_annotation = stringify_annotation 221 except AttributeError: 222 stringify_annotation_orig = typing.stringify 223 inspect.stringify_annotation = stringify_annotation 224 typing.stringify = stringify_annotation 225 autodoc.stringify_typehint = stringify_annotation 226 227 # 228 229 class ClassDocumenterMixin: 230 231 def __init__(self, *args, **kwargs): 232 super().__init__(*args, **kwargs) 233 if self.config.autodoc_class_signature == 'separated': 234 members = self.options.members 235 special_members = self.options.special_members 236 if special_members is not None: 237 for name in ('__new__', '__init__'): 238 if name in members: 239 members.remove(name) 240 if name in special_members: 241 special_members.remove(name) 242 243 class ClassDocumenter( 244 ClassDocumenterMixin, 245 autodoc.ClassDocumenter, 246 ): 247 pass 248 249 class ExceptionDocumenter( 250 ClassDocumenterMixin, 251 autodoc.ExceptionDocumenter, 252 ): 253 pass 254 255 app.add_autodocumenter(ClassDocumenter, override=True) 256 app.add_autodocumenter(ExceptionDocumenter, override=True) 257 258 259def _monkey_patch_returns(): 260 """Rewrite the role of names in "Returns" sections. 261 262 This is needed because Napoleon uses ``:class:`` for the return types 263 and this does not work with type aliases like ``ArrayScalar``. To resolve 264 this we swap ``:class:`` for ``:any:``. 265 266 """ 267 _parse_returns_section = \ 268 NumpyDocstring._parse_returns_section 269 270 @functools.wraps(NumpyDocstring._parse_returns_section) 271 def wrapper(*args, **kwargs): 272 out = _parse_returns_section(*args, **kwargs) 273 return [line.replace(":class:", ":any:") for line in out] 274 275 NumpyDocstring._parse_returns_section = wrapper 276 277 278def _monkey_patch_see_also(): 279 """Rewrite the role of names in "see also" sections. 280 281 Napoleon uses :obj: for all names found in "see also" sections but we 282 need :all: so that references to labels work.""" 283 284 _parse_numpydoc_see_also_section = \ 285 NumpyDocstring._parse_numpydoc_see_also_section 286 287 @functools.wraps(NumpyDocstring._parse_numpydoc_see_also_section) 288 def wrapper(*args, **kwargs): 289 out = _parse_numpydoc_see_also_section(*args, **kwargs) 290 return [line.replace(":obj:", ":any:") for line in out] 291 292 NumpyDocstring._parse_numpydoc_see_also_section = wrapper 293 294 295def _apply_monkey_patches(): 296 """Modify Napoleon types after parsing to make references work.""" 297 _monkey_patch_returns() 298 _monkey_patch_see_also() 299 300 301_apply_monkey_patches() 302 303 304def _process_demos(*demos): 305 # Convert demo .py files to rst. Also copy the .py file so it can be 306 # linked from the demo rst file. 307 try: 308 os.mkdir("demo") 309 except FileExistsError: 310 pass 311 for demo in demos: 312 demo_dir = os.path.join("demo", os.path.dirname(demo)) 313 demo_src = os.path.join(os.pardir, os.pardir, "demo", demo) 314 try: 315 os.mkdir(demo_dir) 316 except FileExistsError: 317 pass 318 with open(demo_src, "r") as infile: 319 with open(os.path.join( 320 os.path.join("demo", os.path.splitext(demo)[0] + ".rst")), "w" 321 ) as outfile: 322 converter = pylit.Code2Text(infile) 323 outfile.write(str(converter)) 324 demo_copy_name = os.path.join(demo_dir, os.path.basename(demo)) 325 shutil.copyfile(demo_src, demo_copy_name) 326 html_static_path.append(demo_copy_name) 327 with open(os.path.join("demo", "demo.rst"), "w") as demofile: 328 demofile.write(""" 329petsc4py demos 330============== 331 332.. toctree:: 333 334""") 335 for demo in demos: 336 demofile.write(" " + os.path.splitext(demo)[0] + "\n") 337 demofile.write("\n") 338 339html_static_path=[] 340_process_demos( 341 "poisson2d/poisson2d.py" 342) 343 344 345def setup(app): 346 _setup_mpi4py_typing() 347 _patch_domain_python() 348 _monkey_patch_returns() 349 _monkey_patch_see_also() 350 _setup_autodoc(app) 351 352 try: 353 from petsc4py import PETSc 354 except ImportError: 355 autodoc_mock_imports.append('PETSc') 356 return 357 del PETSc.DA # FIXME 358 359 sys_dwb = sys.dont_write_bytecode 360 sys.dont_write_bytecode = True 361 import apidoc 362 sys.dont_write_bytecode = sys_dwb 363 364 name = PETSc.__name__ 365 here = os.path.abspath(os.path.dirname(__file__)) 366 outdir = os.path.join(here, apidoc.OUTDIR) 367 source = os.path.join(outdir, f'{name}.py') 368 getmtime = os.path.getmtime 369 generate = ( 370 not os.path.exists(source) 371 or getmtime(source) < getmtime(PETSc.__file__) 372 or getmtime(source) < getmtime(apidoc.__file__) 373 ) 374 if generate: 375 apidoc.generate(source) 376 module = apidoc.load_module(source) 377 apidoc.replace_module(module) 378 379 modules = [ 380 'petsc4py', 381 ] 382 typing_overload = typing.overload 383 typing.overload = lambda arg: arg 384 for name in modules: 385 mod = importlib.import_module(name) 386 ann = apidoc.load_module(f'{mod.__file__}i', name) 387 apidoc.annotate(mod, ann) 388 typing.overload = typing_overload 389 390 from petsc4py import typing as tp 391 for attr in tp.__all__: 392 autodoc_type_aliases[attr] = f'~petsc4py.typing.{attr}' 393 394# -- Options for HTML output ------------------------------------------------- 395# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 396 397# The theme to use for HTML and HTML Help pages. See the documentation for 398# a list of builtin themes. 399html_theme = 'pydata_sphinx_theme' 400 401# -- Options for HTMLHelp output ------------------------------------------ 402 403# Output file base name for HTML help builder. 404htmlhelp_basename = f'{package}-man' 405 406 407# -- Options for LaTeX output --------------------------------------------- 408 409# (source start file, target name, title, 410# author, documentclass [howto, manual, or own class]). 411latex_documents = [ 412 ('index', f'{package}.tex', project, author, 'howto'), 413] 414 415latex_elements = { 416 'papersize': 'a4', 417} 418 419 420# -- Options for manual page output --------------------------------------- 421 422# (source start file, name, description, authors, manual section). 423man_pages = [ 424 ('index', package, project, [author], 3) 425] 426 427 428# -- Options for Texinfo output ------------------------------------------- 429 430# (source start file, target name, title, author, 431# dir menu entry, description, category) 432texinfo_documents = [ 433 ('index', package, project, author, 434 package, f'{project}.', 'Miscellaneous'), 435] 436 437 438# -- Options for Epub output ---------------------------------------------- 439 440# Output file base name for ePub builder. 441epub_basename = package 442