Skip to content

Commit 6e06038

Browse files
authored
Merge pull request #133 from lsst-sqre/tickets/DM-36137
DM-36137: Support setting extensions, linkcheck and nitpick settings in documenteer.toml
2 parents 77d0a77 + 9da549e commit 6e06038

File tree

15 files changed

+346
-70
lines changed

15 files changed

+346
-70
lines changed

docs/_rst_epilog.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@
3232
.. _breathe: http://breathe.readthedocs.io/en/latest/index.html
3333
.. _conda-forge: https://conda-forge.org
3434
.. _conda: https://conda.io/en/latest/index.html
35-
.. _doxylink: https://pythonhosted.org/sphinxcontrib-doxylink/
3635
.. _isort: https://pycqa.github.io/isort/
3736
.. _numpydoc: https://numpydoc.readthedocs.io/en/latest/index.html
3837
.. _pre-commit: https://pre-commit.com
3938
.. _pytest: https://pytest.org
4039
.. _toctree: http://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-toctree
40+
.. _linkcheck: https://www.sphinx-doc.org/en/master/usage/configuration.html?#options-for-the-linkcheck-builder
4141

4242
.. Internal links
4343

docs/conf.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,5 @@
11
from documenteer.conf.guide import *
22

3-
# Intersphinx
4-
5-
intersphinx_mapping = {
6-
"python": ("https://docs.python.org/3/", None),
7-
"requests": ("https://requests.readthedocs.io/en/latest/", None),
8-
"developer": ("https://developer.lsst.io/", None),
9-
"pybtex": ("https://docs.pybtex.org/", None),
10-
"sphinx": ("https://www.sphinx-doc.org/en/master/", None),
11-
}
12-
13-
# Warnings to ignore
14-
nitpick_ignore = [
15-
# This link to the base pybtex still never resolves because it is not
16-
# in pybtex's intersphinx'd API reference.
17-
("py:class", "pybtex.style.formatting.plain.Style"),
18-
]
19-
203
# Automodapi
214
# https://sphinx-automodapi.readthedocs.io/en/latest/automodapi.html
225
automodapi_toctreedirnm = "dev/api/contents"

docs/documenteer.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,19 @@ package = "documenteer"
99

1010
[sphinx]
1111
rst_epilog_file = "_rst_epilog.rst"
12+
nitpicky = true
13+
nitpick_ignore = [
14+
["py:class", "pybtex.style.formatting.plain.Style"],
15+
["py:obj", "sphinx_automodapi.automodapi"],
16+
]
17+
nitpick_ignore_regex = [
18+
['py:.*', 'docutils.*'],
19+
['py:.*', 'pydantic.*'],
20+
]
21+
22+
[sphinx.intersphinx.projects]
23+
python = "https://docs.python.org/3/"
24+
requests = "https://requests.readthedocs.io/en/latest/"
25+
developer = "https://developer.lsst.io/"
26+
pybtex = "https://docs.pybtex.org/"
27+
sphinx = "https://www.sphinx-doc.org/en/master/"

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Documenteer
99
This documentation is for version |version|. `Find docs for other versions. <https://documenteer.lsst.io/v>`__
1010
Documenteer is developed on GitHub at https://github.com/lsst-sqre/documenteer.
1111

12+
.. _pip-install:
1213
.. _installation:
1314

1415
Installation

docs/project-guides/guides/extend-conf-py.rst

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,29 @@ The basic configurations, through :file:`documenteer.toml` and |documenteer.conf
66
You will likely need to extend that configuration, though.
77
This page describes a few common scenarios.
88

9-
In general, structure your customizations so that they add to the configuration presets from |documenteer.conf.guide|.
10-
If the configuration variable is a list or a dictionary, try to append to that list or dictionary rather than reassigning the whole variable.
9+
Using the [sphinx] table in documenteer.toml
10+
============================================
1111

12-
Adding a package to Intersphinx
13-
-------------------------------
12+
Before editing :file:`conf.py`, check the :doc:`toml-reference` to see if there's support for a particular Sphinx configuration.
13+
For example, the ``[sphinx]`` table includes support for adding Sphinx extensions, adding projects to the Intersphinx_ mapping, among other capabilities.
1414

15-
One scenario is adding additional projects to the Intersphinx_ configuration.
16-
For example, to add the Python standard library so that built-in Python APIs can be referenced:
15+
If the configuration isn't supported by :file:`documenteer.toml`, you can edit the Sphinx :file:`conf.py` configuration file directly.
1716

18-
.. code-block:: python
19-
:caption: conf.py
20-
21-
from documenteer.conf.guide import *
22-
23-
intersphinx_mapping["python"] = ("https://docs.python.org/3", None)
24-
25-
To additionally add the LSST Science Pipelines:
26-
27-
.. code-block:: python
28-
:caption: conf.py
29-
30-
from documenteer.conf.guide import *
31-
32-
intersphinx_mapping["python"] = ("https://pipelines.lsst.io", None)
17+
Editing conf.py
18+
===============
3319

34-
Adding a Sphinx extension
35-
-------------------------
20+
When Sphinx runs, the :file:`conf.py` configuration file is "executed" so that all variables in the global namespace become Sphinx configurations.
21+
The |documenteer.conf.guide| configuration presets populate the global namespace.
22+
Therefore you can add or edit those configurations by setting variables after the import of |documenteer.conf.guide|.
3623

37-
You can add additional `Sphinx extensions`_ to your Sphinx build to make use of custom reStructuredText directives and roles.
38-
To add a new extension, append to the ``extensions`` list:
24+
For example, to change the number of times the linkcheck builder will try a URL:
3925

4026
.. code-block:: python
4127
:caption: conf.py
4228
4329
from documenteer.conf.guide import *
4430
45-
extensions.extend(["sphinx-click"])
31+
linkcheck_retries = 2
4632
47-
Remember that additional packages may need to be added to your project's Python dependencies (such as in a ``requirements.txt`` or ``pyproject.toml`` file).
33+
See the `list of Sphinx configuration in the Sphinx documentation <https://www.sphinx-doc.org/en/master/usage/configuration.html>`__.
34+
Extensions can also declare additional configurations, see their documentation for listings.

docs/project-guides/guides/toml-reference.rst

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,79 @@ If your GitHub repository's URL is associated with a different field label, set
131131

132132
This ``[sphinx]`` table allows you to set a number of Sphinx configurations that you would normally set through the :file:`conf.py` file.
133133

134+
extensions
135+
----------
136+
137+
|optional|
138+
139+
A list of Sphinx extensions to append to the extensions included in the Documenteer configuration preset (see |documenteer.conf.guide|).
140+
Duplicate extensions are ignored.
141+
142+
Remember that additional packages may need to be added to your project's Python dependencies (such as in a ``requirements.txt`` or ``pyproject.toml`` file).
143+
144+
nitpicky
145+
--------
146+
147+
|optional|
148+
149+
Set to ``true`` to escalate Sphinx warnings to errors, which is useful for leveraging CI to notify you of any syntax errors.
150+
The default is ``false``.
151+
152+
.. code-block:: toml
153+
154+
[sphinx]
155+
nitpicky = true
156+
157+
See ``nitpick_ignore`` and ``nitpick_ignore_regex`` for ways to suppress unavoidable errors.
158+
159+
nitpick_ignore
160+
--------------
161+
162+
|optional|
163+
164+
A list of Sphinx warnings to ignore.
165+
Each item is a tuple of two items:
166+
167+
1. ``type``, often the reStructuredText role or directive creating the error/warning.
168+
2. ``target``, often the argument to the reStructuredText role.
169+
170+
.. code-block:: toml
171+
172+
[sphinx]
173+
nitpick_ignore = [
174+
["py:class", "fastapi.applications.FastAPI"],
175+
["py:class", "httpx.AsyncClient"],
176+
["py:class", "pydantic.main.BaseModel"],
177+
]
178+
179+
This configuration extends the Sphinx ``nitpick_ignore`` configuration.
180+
181+
nitpick_ignore_regex
182+
--------------------
183+
184+
|optional|
185+
186+
A list of Sphinx warnings to ignore, formatted as regular expressions.
187+
Each item is a tuple of two items:
188+
189+
1. ``type``, a regular expression of the warning type.
190+
2. ``target``, a regular expression of the warning target.
191+
192+
.. code-block:: toml
193+
194+
[sphinx]
195+
nitpick_ignore_regex = [
196+
['py:.*', 'fastapi.*'],
197+
['py:.*', 'httpx.*'],
198+
['py:.*', 'pydantic*'],
199+
]
200+
201+
.. tip::
202+
203+
Use single quotes for literal strings in TOML.
204+
205+
This configuration extends the Sphinx ``nitpick_ignore_regex`` configuration.
206+
134207
rst_epilog_file
135208
---------------
136209

@@ -153,3 +226,43 @@ If set, the file is also included in the Sphinx source ignore list to prevent it
153226
154227
.. |required| replace:: :bdg-primary-line:`Required`
155228
.. |optional| replace:: :bdg-secondary-line:`Optional`
229+
230+
[sphinx.intersphinx]
231+
====================
232+
233+
|optional|
234+
235+
Configurations related to Intersphinx_ for linking to other Sphinx projects.
236+
237+
[sphinx.intersphinx.projects]
238+
=============================
239+
240+
|optional|
241+
242+
A table of Sphinx projects.
243+
The labels are targets for the :external+sphinx:rst:role:`external` role.
244+
The values are URLs to the root of Sphinx documentation projects.
245+
246+
.. code-block:: toml
247+
248+
[sphinx.intersphinx.projects]
249+
sphinx = "https://www.sphinx-doc.org/en/master/"
250+
documenteer = "https://documenteer.lsst.io"
251+
python = "https://docs.python.org/3/"
252+
253+
See the Intersphinx_ documentation for details on linking to other Sphinx projects.
254+
255+
[sphinx.linkcheck]
256+
==================
257+
258+
|optional|
259+
260+
Configurations related to Sphinx's linkcheck_ builder.
261+
262+
ignore
263+
------
264+
265+
|optional|
266+
267+
List of URL regular expressions patterns to ignore checking.
268+
These are appended to the ``linkcheck_ignore`` configuration.

src/documenteer/conf/_toml.py

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import sys
88
from dataclasses import dataclass
99
from email.message import Message
10-
from typing import Optional, cast
10+
from typing import Dict, List, MutableMapping, Optional, Tuple, Union, cast
1111

1212
if sys.version_info < (3, 8):
1313
from importlib_metadata import PackageNotFoundError, metadata
@@ -100,6 +100,23 @@ class ProjectModel(BaseModel):
100100
python: Optional[PythonPackageModel]
101101

102102

103+
class IntersphinxModel(BaseModel):
104+
"""Model for Intersphinx configurations in documenteer.toml."""
105+
106+
projects: Dict[str, HttpUrl] = Field(
107+
description="Mapping of projects and their URLs.", default_factory=dict
108+
)
109+
110+
111+
class LinkCheckModel(BaseModel):
112+
"""Model for linkcheck builder configurations in documenteer.toml."""
113+
114+
ignore: List[str] = Field(
115+
description="Regular expressions of URLs to skip checking links",
116+
default_factory=list,
117+
)
118+
119+
103120
class SphinxModel(BaseModel):
104121
"""Model for Sphinx configurations in documenteer.toml."""
105122

@@ -110,6 +127,35 @@ class SphinxModel(BaseModel):
110127
)
111128
)
112129

130+
extensions: List[str] = Field(
131+
description="Additional Sphinx extension.", default_factory=list
132+
)
133+
134+
nitpicky: bool = Field(
135+
False, description="Escalate warnings to build errors."
136+
)
137+
138+
nitpick_ignore: List[Tuple[str, str]] = Field(
139+
description=(
140+
"Errors to ignore. First item is the type (like a role or "
141+
"directive) and the second is the target (like the argument to "
142+
"the role)."
143+
),
144+
default_factory=list,
145+
)
146+
147+
nitpick_ignore_regex: List[Tuple[str, str]] = Field(
148+
description=(
149+
"Same as ``nitpick_ignore``, but both type and target are "
150+
"interpreted as regular expressions."
151+
),
152+
default_factory=list,
153+
)
154+
155+
intersphinx: Optional[IntersphinxModel]
156+
157+
linkcheck: Optional[LinkCheckModel]
158+
113159

114160
class ConfigRoot(BaseModel):
115161
"""The root model for a documenteer.toml configuration file."""
@@ -157,7 +203,7 @@ def base_url(self) -> str:
157203
158204
1. The ``base_url`` field of the ``[project]`` table in
159205
documenteer.toml.
160-
2. From importlib.metadata if `[project.python]` is set in
206+
2. From importlib.metadata if ``[project.python]`` is set in
161207
documenteer.toml.
162208
3. Default is "".
163209
"""
@@ -260,3 +306,50 @@ def _get_pyproject_url(
260306
if value.startswith(prefix):
261307
return value[len(prefix) :]
262308
return None
309+
310+
def append_extensions(self, extensions: List[str]) -> None:
311+
"""Append user-configured extensions to an existing list."""
312+
if self.conf.sphinx:
313+
for new_ext in self.conf.sphinx.extensions:
314+
if new_ext not in extensions:
315+
extensions.append(new_ext)
316+
317+
def extend_intersphinx_mapping(
318+
self, mapping: MutableMapping[str, Tuple[str, Union[str, None]]]
319+
) -> None:
320+
"""Extend the ``intersphinx_mapping`` dictionary with configured
321+
projects.
322+
"""
323+
if (
324+
self.conf.sphinx
325+
and self.conf.sphinx.intersphinx
326+
and self.conf.sphinx.intersphinx.projects
327+
):
328+
for project, url in self.conf.sphinx.intersphinx.projects.items():
329+
mapping[project] = (str(url), None)
330+
331+
def append_linkcheck_ignore(self, link_patterns: List[str]) -> None:
332+
"""Append URL patterns for sphinx.linkcheck.ignore to existing
333+
patterns.
334+
"""
335+
if self.conf.sphinx and self.conf.sphinx.linkcheck:
336+
link_patterns.extend(self.conf.sphinx.linkcheck.ignore)
337+
338+
def append_nitpick_ignore(
339+
self, nitpick_ignore: List[Tuple[str, str]]
340+
) -> None:
341+
if self.conf.sphinx and self.conf.sphinx.nitpick_ignore:
342+
nitpick_ignore.extend(self.conf.sphinx.nitpick_ignore)
343+
344+
def append_nitpick_ignore_regex(
345+
self, nitpick_ignore_regex: List[Tuple[str, str]]
346+
) -> None:
347+
if self.conf.sphinx and self.conf.sphinx.nitpick_ignore_regex:
348+
nitpick_ignore_regex.extend(self.conf.sphinx.nitpick_ignore_regex)
349+
350+
@property
351+
def nitpicky(self) -> bool:
352+
if self.conf.sphinx:
353+
return self.conf.sphinx.nitpicky
354+
else:
355+
return False

0 commit comments

Comments
 (0)