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