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