Skip to content

Commit 3dda9d2

Browse files
committed
Add Nox autodoc install & build to Nox and Contributing Guide
1 parent 8fc8c54 commit 3dda9d2

File tree

3 files changed

+182
-36
lines changed

3 files changed

+182
-36
lines changed

CONTRIBUTING.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ Regardless of the tool you use, make sure to remember to always activate your en
144144

145145
#### Conda
146146

147-
To create an environment with Conda (recommended), simply execute the following:
147+
To create an environment with Conda (recommended), execute the following:
148148

149149
```shell
150150
conda create -c conda-forge -n spyder-api-docs-env python
@@ -193,6 +193,13 @@ Or if using ``pip``, you can grab them with:
193193
python -m pip install -r requirements.txt
194194
```
195195

196+
If you plan to generate and build the API reference documentation extracted from Spyder's docstrings, you'll also need to install Spyder in development mode as well as its dev dependencies.
197+
To do so, you can run the ``install_dev_repos.py`` script in the ``spyder`` submodule:
198+
199+
```shell
200+
python install_dev_repos.py
201+
```
202+
196203

197204

198205
## Installing and Using the Pre-Commit Hooks
@@ -247,7 +254,13 @@ To build the project using Nox, just run
247254
nox -s build
248255
```
249256

250-
and can then open the rendered output in your default web browser with
257+
or, to also extract, generate and build the API reference from Spyder's docstrings (expensive the first time), pass the ``-t autodoc`` argument to any build command:
258+
259+
```shell
260+
nox -s build -- -t autodoc
261+
```
262+
263+
and then open the rendered output in your default web browser with
251264

252265
```shell
253266
nox -s serve
@@ -256,7 +269,7 @@ nox -s serve
256269
Alternatively, to automatically rebuild the project when changes occur, you can invoke
257270

258271
```shell
259-
nox -s autobuild
272+
nox -s autorebuild
260273
```
261274

262275
You can also pass your own custom [Sphinx build options](https://www.sphinx-doc.org/en/master/man/sphinx-build.html) after a ``--`` separator, which are added to the default set.
@@ -266,6 +279,12 @@ For example, to rebuild just the install guide and FAQ in verbose mode with the
266279
nox -s build -- --verbose --builder dirhtml -- index.rst
267280
```
268281

282+
When changing build options (particularly autodoc), cleaning the generated files avoids spurious errors:
283+
284+
```shell
285+
nox -s clean
286+
```
287+
269288

270289
### Build manually
271290

@@ -275,8 +294,21 @@ For manual installations, you can invoke Sphinx yourself with the appropriate op
275294
python -m sphinx -n -W --keep-going docs docs/_build/html
276295
```
277296

297+
or to also extract, generate and build the API reference from Spyder's docstrings (requires Spyder and its dependencies to be installed in your environment):
298+
299+
```shell
300+
python -I -m sphinx -n --keep-going -t autodoc docs docs/_build/html
301+
```
302+
278303
Then, navigate to the ``_build/html`` directory inside the ``spyder-docs`` repository and open ``index.html`` (the main page of the docs) in your preferred browser.
279304

305+
When changing build options (particularly autodoc), cleaning the generated files first avoids spurious errors:
306+
307+
```shell
308+
rm -r docs/_autosummary/
309+
rm -r docs/_build/
310+
```
311+
280312

281313

282314
## Contributing Changes

docs/conf.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@
145145
# so a file named "default.css" will overwrite the builtin "default.css".
146146
html_static_path = ["_static"]
147147

148+
# Warning suppression
149+
suppress_warnings = []
150+
148151

149152
# -- Options for HTMLHelp output ---------------------------------------
150153

@@ -264,8 +267,13 @@
264267
# "qstylizer",
265268
]
266269

267-
# Generate autodoc stubs with summaries from code
268-
autosummary_generate = True
270+
# Generate autosummaries if the autodoc tag is passed
271+
if "autodoc" in tags:
272+
autosummary_generate = True
273+
else:
274+
autosummary_generate = False
275+
suppress_warnings += ["autodoc", "autosummary", "toc.excluded"]
276+
exclude_patterns += ["reference.rst"]
269277

270278

271279
# -- Additional Directives ---------------------------------------------------

noxfile.py

Lines changed: 137 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44
import contextlib
55
import logging
66
import os
7-
import tempfile
87
import shutil
98
import sys
9+
import tempfile
1010
import webbrowser
1111
from pathlib import Path
1212

1313
# Third party imports
1414
import nox # pylint: disable=import-error
1515
import nox.logger # pylint: disable=import-error
16+
import packaging.requirements
1617

1718

1819
# --- Global constants --- #
@@ -29,7 +30,7 @@
2930
REPO_URL_SSH = "git@github.com:{user}/{repo}.git"
3031

3132
# Build config
32-
BUILD_INVOCATION = ("python", "-m", "sphinx")
33+
BUILD_INVOCATION = ("python", "-I", "-m", "sphinx")
3334
SOURCE_DIR = Path("docs").resolve()
3435
BUILD_DIR = Path("docs/_build").resolve()
3536
BUILD_OPTIONS = ("-n", "-W", "--keep-going")
@@ -56,12 +57,29 @@
5657
BASE_URL = "https://spyder-ide.github.io/spyder-api-docs/"
5758

5859
# Other config
59-
CANARY_COMMAND = ("pre-commit", "--version")
60+
CANARY_COMMANDS = {
61+
"doc": {
62+
"cmd": ("pre-commit", "--version"),
63+
"default": True,
64+
"env": {},
65+
},
66+
"autodoc": {
67+
"cmd": ("python", "-I", "-m", "spyder.app.start", "--help"),
68+
"default": False,
69+
"env": {"HOME": str(Path().home())},
70+
},
71+
}
6072
IGNORE_REVS_FILE = ".git-blame-ignore-revs"
6173
PRE_COMMIT_VERSION_SPEC = ">=2.10.0,<4"
6274

6375
# Custom config
6476
SCRIPT_DIR = Path("scripts").resolve()
77+
AUTOSUMMARY_DIR = SOURCE_DIR / "_autosummary"
78+
SPYDER_PATH = Path("spyder").resolve()
79+
DEPS_PATH = SPYDER_PATH / 'external-deps'
80+
81+
# Post config
82+
DIRS_TO_CLEAN = [BUILD_DIR, AUTOSUMMARY_DIR]
6583

6684

6785
# ---- Helpers ---- #
@@ -145,6 +163,9 @@ def construct_sphinx_invocation(
145163
builder = builders[-1] if builders else builder
146164
build_dir = BUILD_DIR / builder if build_dir is None else build_dir
147165

166+
if "autodoc" in cli_options:
167+
build_options = [item for item in build_options if item != "-W"]
168+
148169
if CI:
149170
build_options = list(build_options) + ["--color"]
150171

@@ -163,6 +184,41 @@ def construct_sphinx_invocation(
163184
return sphinx_invocation
164185

165186

187+
def list_spyder_dev_repos():
188+
"""List the development repos included as subrepos of Spyder."""
189+
repos = []
190+
for p in [SPYDER_PATH] + list(DEPS_PATH.iterdir()):
191+
if (
192+
p.name.startswith('.')
193+
or not p.is_dir()
194+
and not ((p / 'setup.py').exists() or (p / 'pyproject.toml').exists())
195+
):
196+
continue
197+
198+
repos.append(p)
199+
return repos
200+
201+
202+
def get_python_lsp_version():
203+
"""Get current version to pass it to setuptools-scm."""
204+
req_file = SPYDER_PATH / 'requirements' / 'main.yml'
205+
with open(req_file, 'r', encoding='UTF-8') as f:
206+
for line in f:
207+
if 'python-lsp-server' not in line:
208+
continue
209+
line = line.split('-')[-1]
210+
specifiers = packaging.requirements.Requirement(line).specifier
211+
break
212+
else:
213+
return "0.0.0"
214+
215+
for specifier in specifiers:
216+
if "=" in specifier.operator:
217+
return specifier.version
218+
else:
219+
return "0.0.0"
220+
221+
166222
# ---- Dispatch ---- #
167223

168224

@@ -176,16 +232,28 @@ def _execute(session):
176232
"Must pass a list of functions to execute as first posarg"
177233
)
178234

235+
canary_commands = {}
236+
install_tags = set()
237+
for arg, properties in CANARY_COMMANDS.items():
238+
cmd = properties["cmd"]
239+
if properties["default"] or arg in session.posargs:
240+
canary_commands[arg] = cmd
241+
env = properties["env"] if properties["env"] else None
242+
179243
if not session.posargs or session.posargs[0] is not _install:
180-
# pylint: disable=too-many-try-statements
181-
try:
182-
with set_log_level():
183-
session.run(
184-
*CANARY_COMMAND, include_outer_env=False, silent=True
185-
)
186-
except nox.command.CommandFailed:
187-
print("Installing dependencies in isolated environment...")
188-
_install(session, use_posargs=False)
244+
for arg, cmd in canary_commands.items():
245+
# pylint: disable=too-many-try-statements
246+
try:
247+
with set_log_level():
248+
session.run(
249+
*cmd, env=env, include_outer_env=False, silent=True
250+
)
251+
except nox.command.CommandFailed:
252+
install_tags.add(arg)
253+
254+
if install_tags:
255+
print("Installing dependencies in isolated environment...")
256+
_install(session, use_posargs=False, install_tags=install_tags)
189257

190258
if session.posargs:
191259
for task in session.posargs[0]:
@@ -195,13 +263,47 @@ def _execute(session):
195263
# ---- Install ---- #
196264

197265

198-
def _install(session, *, use_posargs=True):
199-
"""Execute the dependency installation."""
200-
posargs = session.posargs[1:] if use_posargs else ()
266+
def _install_doc(session, posargs=()):
267+
"""Install the basic documentation and dev dependencies."""
201268
session.install(f"pre-commit{PRE_COMMIT_VERSION_SPEC}")
202269
session.install("-r", "requirements.txt", *posargs)
203270

204271

272+
def _install_autodoc(session, posargs=()):
273+
"""Install the dependencies to generate API autodocs."""
274+
dev_repos = list_spyder_dev_repos()
275+
for dev_repo in dev_repos:
276+
env = None
277+
if 'python-lsp-server' in str(dev_repo):
278+
env = {**os.environ}
279+
env.update(
280+
{'SETUPTOOLS_SCM_PRETEND_VERSION': get_python_lsp_version()})
281+
session.install("-e", dev_repo, *posargs, env=env)
282+
283+
284+
INSTALL_FUNCTIONS = {
285+
"doc": _install_doc,
286+
"autodoc": _install_autodoc,
287+
}
288+
289+
290+
def _install(session, *, use_posargs=True, install_tags=None):
291+
"""Execute the dependency installation."""
292+
posargs = session.posargs[1:] if use_posargs else ()
293+
294+
install_tags = {"doc"} if install_tags is None else install_tags
295+
for arg in CANARY_COMMANDS.keys():
296+
if f"--{arg}" in session.posargs:
297+
install_tags.add(arg)
298+
if posargs:
299+
posargs.remove(f"--{arg}")
300+
elif arg in session.posargs:
301+
install_tags.add(arg)
302+
303+
for tag in install_tags:
304+
INSTALL_FUNCTIONS[tag](session, posargs)
305+
306+
205307
@nox.session
206308
def install(session):
207309
"""Install the project's dependencies (passes through args to pip)."""
@@ -238,17 +340,21 @@ def run(session):
238340

239341
def _clean(session):
240342
"""Remove the build directory."""
241-
print(f"Removing build directory {BUILD_DIR.as_posix()!r}")
242343
ignore_flag = "--ignore"
243344
should_ignore = ignore_flag in session.posargs
244345

245-
try:
246-
shutil.rmtree(BUILD_DIR, ignore_errors=should_ignore)
247-
except FileNotFoundError:
248-
pass
249-
except Exception:
250-
print(f"\nError removing files; pass {ignore_flag!r} flag to ignore\n")
251-
raise
346+
for dir_to_clean in DIRS_TO_CLEAN:
347+
if not dir_to_clean.exists():
348+
continue
349+
print(f"Removing generated directory {dir_to_clean.as_posix()!r}")
350+
try:
351+
shutil.rmtree(dir_to_clean, ignore_errors=should_ignore)
352+
except FileNotFoundError:
353+
pass
354+
except Exception:
355+
print(f"\nError removing files in {dir_to_clean.as_posix()!r}")
356+
print(f"Pass {ignore_flag!r} flag to ignore\n")
357+
raise
252358

253359

254360
@nox.session
@@ -471,15 +577,15 @@ def build(session):
471577
session.notify("_execute", posargs=([_build], *session.posargs))
472578

473579

474-
def _autobuild(session):
580+
def _autorebuild(session):
475581
"""Use Sphinx-Autobuild to rebuild the project and open in browser."""
476-
_autodocs(session)
582+
_docs_autobuild(session)
477583

478584

479585
@nox.session
480-
def autobuild(session):
586+
def autorebuild(session):
481587
"""Rebuild the project continuously as source files are changed."""
482-
session.notify("_execute", posargs=([_autobuild], *session.posargs))
588+
session.notify("_execute", posargs=([_autorebuild], *session.posargs))
483589

484590

485591
# --- Docs --- #
@@ -499,7 +605,7 @@ def docs(session):
499605
session.notify("_execute", posargs=([_docs], *session.posargs))
500606

501607

502-
def _autodocs(session):
608+
def _docs_autobuild(session):
503609
"""Use Sphinx-Autobuild to rebuild the project and open in browser."""
504610
session.install("sphinx-autobuild")
505611

@@ -518,10 +624,10 @@ def _autodocs(session):
518624
session.run(*sphinx_invocation)
519625

520626

521-
@nox.session
522-
def autodocs(session):
627+
@nox.session(name="docs-autobuild")
628+
def docs_autobuild(session):
523629
"""Rebuild the docs continuously as source files are changed."""
524-
session.notify("_execute", posargs=([_autodocs], *session.posargs))
630+
session.notify("_execute", posargs=([_docs_autobuild], *session.posargs))
525631

526632

527633
def _build_languages(session):

0 commit comments

Comments
 (0)