Skip to content

Commit 782ef62

Browse files
committed
Fix #1135: Make 'sphinx-apidoc' an extension
This is an alternative approach to #1135. Rather than modifying the 'build_sphinx' setuptools extension, we allow 'sphinx-apidoc' to be run as an extension. This is very similar to what we do with 'sphinx-autogen', which can be run automatically using the 'autosummary_generate' configuration option instead. We may wish to remove the 'sphinx-apidoc' binary in the future, but that's a decision for another day. Signed-off-by: Stephen Finucane <stephen@that.guru>
1 parent f26db5b commit 782ef62

File tree

7 files changed

+133
-5
lines changed

7 files changed

+133
-5
lines changed

doc/ext/apidoc.rst

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
.. highlight:: rest
2+
3+
:mod:`sphinx.ext.apidoc` -- Generate autodoc stub pages
4+
=======================================================
5+
6+
.. module:: sphinx.ext.apidoc
7+
:synopsis: Generate autodoc stub pages
8+
9+
.. versionadded:: 1.8
10+
11+
The functionality of this extension was previously available as part of the
12+
:program:`sphinx-apidoc` tool. This ability to run this automatically by way
13+
of a Sphinx extension was added in 1.8.
14+
15+
This extension generates function, method and attribute stub documentation
16+
pages, similar to that generated by API doc tools like Doxygen or Javadoc.
17+
18+
.. warning::
19+
20+
The apidoc extension generates source files that use
21+
:mod:`sphinx.ext.autodoc` to document all found modules. If any modules
22+
have side effects on import, these will be executed when building
23+
documentation.
24+
25+
If you document scripts (as opposed to library modules), make sure their
26+
main routine is protected by a ``if __name__ == '__main__'`` condition.
27+
28+
Configuration
29+
-------------
30+
31+
The apidoc extension uses the following configuration values:
32+
33+
.. confval:: apidoc_module_dir
34+
35+
The path to the module to document. This must be a path to a Python package.
36+
This path can be a path relative to the documentation source directory or an
37+
absolute path.
38+
39+
.. confval:: apidoc_output_dir
40+
41+
The output directory. If it does not exist, it is created. This path is
42+
relative to the documentation source directory.
43+
44+
Defaults to ``api``.
45+
46+
.. confval:: apidoc_excluded_modules
47+
48+
An optional list of modules to exclude. These should be paths relative to
49+
``api_module_dir``. fnmatch-style wildcarding is supported.
50+
51+
Defaults to ``[]``.

doc/ext/builtins.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ These extensions are built in and can be activated by respective entries in the
66

77
.. toctree::
88

9+
apidoc
910
autodoc
1011
autosectionlabel
1112
autosummary

sphinx/ext/apidoc.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,15 @@
3131
from sphinx import __display_version__, package_dir
3232
from sphinx.cmd.quickstart import EXTENSIONS
3333
from sphinx.locale import __
34-
from sphinx.util import rst
34+
from sphinx.util import logging, rst
3535
from sphinx.util.osutil import FileAvoidWrite, ensuredir, walk
3636

3737
if False:
3838
# For type annotation
39-
from typing import Any, List, Tuple # NOQA
39+
from typing import Any, Dict, List, Tuple # NOQA
40+
from sphinx.application import Sphinx # NOQA
41+
42+
logger = logging.getLogger(__name__)
4043

4144
# automodule options
4245
if 'SPHINX_APIDOC_OPTIONS' in os.environ:
@@ -459,6 +462,45 @@ def main(argv=sys.argv[1:]):
459462
return 0
460463

461464

462-
# So program can be started with "python -m sphinx.apidoc ..."
465+
def builder_inited(app):
466+
# type: (Sphinx) -> None
467+
module_dir = app.config.apidoc_module_dir
468+
output_dir = path.join(app.srcdir, app.config.apidoc_output_dir)
469+
excludes = app.config.apidoc_excluded_modules
470+
471+
if not module_dir:
472+
logger.warning("No 'apidoc_module_dir' specified; skipping API doc "
473+
"generation")
474+
return
475+
476+
# if the path is relative, make it relative to the 'conf.py' directory
477+
if not path.isabs(module_dir):
478+
module_dir = path.abspath(path.join(app.srcdir, module_dir))
479+
480+
excludes = [path.abspath(path.join(module_dir, exc)) for exc in excludes]
481+
482+
# refactor this module so that we can call 'recurse_tree' like a sane
483+
# person - at present there is way too much passing around of the
484+
# 'optparse.Value' instance returned by 'optparse.parse_args'
485+
cmd = ['--force', '-o', output_dir, module_dir]
486+
if excludes:
487+
cmd += excludes
488+
489+
main(cmd)
490+
491+
492+
def setup(app):
493+
# type: (Sphinx) -> Dict[unicode, Any]
494+
app.setup_extension('sphinx.ext.autodoc') # We need autodoc to function
495+
496+
app.connect('builder-inited', builder_inited)
497+
app.add_config_value('apidoc_module_dir', None, 'env', [str])
498+
app.add_config_value('apidoc_output_dir', 'api', 'env', [str])
499+
app.add_config_value('apidoc_excluded_modules', [], 'env',
500+
[[str]])
501+
502+
return {'version': __display_version__, 'parallel_read_safe': True}
503+
504+
463505
if __name__ == "__main__":
464506
main()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from os import *
2+
3+
4+
class Foo:
5+
def __init__(self):
6+
pass
7+
8+
def bar(self):
9+
pass

tests/roots/test-ext-apidoc/conf.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import os
4+
import sys
5+
6+
sys.path.insert(0, os.path.abspath('./'))
7+
8+
extensions = ['sphinx.ext.apidoc']
9+
master_doc = 'index'
10+
11+
apidoc_module_dir = '.'
12+
apidoc_excluded_modules = ['conf.py']

tests/roots/test-ext-apidoc/index.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apidoc
2+
======
3+
4+
.. toctree::
5+
6+
api/modules

tests/test_ext_apidoc.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,6 @@ def extract_toc(path):
384384
coderoot='test-apidoc-subpackage-in-toc',
385385
options=['--separate']
386386
)
387-
388-
389387
def test_subpackage_in_toc(make_app, apidoc):
390388
"""Make sure that empty subpackages with non-empty subpackages in them
391389
are not skipped (issue #4520)
@@ -404,3 +402,12 @@ def test_subpackage_in_toc(make_app, apidoc):
404402
assert 'parent.child.foo' in parent_child
405403

406404
assert (outdir / 'parent.child.foo.rst').isfile()
405+
406+
407+
@pytest.mark.sphinx('html', testroot='ext-apidoc')
408+
def test_apidoc_extension(app, status, warning):
409+
app.builder.build_all()
410+
assert (app.outdir / 'api').isdir()
411+
assert (app.outdir / 'api' / 'modules.html').exists()
412+
assert (app.outdir / 'api' / 'apidoc_dummy_module.html').exists()
413+
assert not (app.outdir / 'api' / 'conf.html').exists()

0 commit comments

Comments
 (0)