xref: /petsc/src/binding/petsc4py/docs/source/conf.py (revision 7cd471e73bbf44b514919ff6364b3d49ed4f6988)
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