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