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