Skip to content

Commit df371a6

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 <[email protected]>
1 parent d09efcb commit df371a6

File tree

7 files changed

+135
-4
lines changed

7 files changed

+135
-4
lines changed

doc/ext/apidoc.rst

+51
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.7
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.7.
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 ``['setup.py']``.

doc/ext/builtins.rst

+1
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

+47-3
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@
2626

2727
from sphinx import __display_version__
2828
from sphinx.quickstart import EXTENSIONS
29-
from sphinx.util import rst
29+
from sphinx.util import logging, rst
3030
from sphinx.util.osutil import FileAvoidWrite, walk
3131

3232
if False:
3333
# For type annotation
34-
from typing import Any, List, Tuple # NOQA
34+
from typing import Any, Dict, List, Tuple # NOQA
35+
from sphinx.application import Sphinx # NOQA
36+
37+
logger = logging.getLogger(__name__)
3538

3639
# automodule options
3740
if 'SPHINX_APIDOC_OPTIONS' in os.environ:
@@ -382,7 +385,9 @@ def main(argv=sys.argv[1:]):
382385
os.makedirs(opts.destdir)
383386
rootpath = path.abspath(rootpath)
384387
excludes = normalize_excludes(rootpath, excludes)
388+
385389
modules = recurse_tree(rootpath, excludes, opts)
390+
386391
if opts.full:
387392
from sphinx import quickstart as qs
388393
modules.sort()
@@ -435,6 +440,45 @@ def main(argv=sys.argv[1:]):
435440
return 0
436441

437442

438-
# So program can be started with "python -m sphinx.apidoc ..."
443+
def builder_inited(app):
444+
# type: (Sphinx) -> None
445+
module_dir = app.config.apidoc_module_dir
446+
output_dir = path.join(app.srcdir, app.config.apidoc_output_dir)
447+
excludes = app.config.apidoc_excluded_modules
448+
449+
if not module_dir:
450+
logger.warning("No 'apidoc_module_dir' specified; skipping API doc "
451+
"generation")
452+
return
453+
454+
# if the path is relative, make it relative to the 'conf.py' directory
455+
if not path.isabs(module_dir):
456+
module_dir = path.abspath(path.join(app.srcdir, module_dir))
457+
458+
excludes = [path.abspath(path.join(module_dir, exc)) for exc in excludes]
459+
460+
# refactor this module so that we can call 'recurse_tree' like a sane
461+
# person - at present there is way too much passing around of the
462+
# 'optparse.Value' instance returned by 'optparse.parse_args'
463+
cmd = [module_dir, '--force', '-o', output_dir]
464+
if excludes:
465+
cmd += excludes
466+
467+
main(cmd)
468+
469+
470+
def setup(app):
471+
# type: (Sphinx) -> Dict[unicode, Any]
472+
app.setup_extension('sphinx.ext.autodoc') # We need autodoc to function
473+
474+
app.connect('builder-inited', builder_inited)
475+
app.add_config_value('apidoc_module_dir', None, 'env', [str])
476+
app.add_config_value('apidoc_output_dir', 'api', 'env', [str])
477+
app.add_config_value('apidoc_excluded_modules', ['setup.py'], 'env',
478+
[[str]])
479+
480+
return {'version': __display_version__, 'parallel_read_safe': True}
481+
482+
439483
if __name__ == "__main__":
440484
main()
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

+12
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

+6
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

+9-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import pytest
1717

1818
from sphinx.ext.apidoc import main as apidoc_main
19-
2019
from sphinx.testing.util import remove_unicode_literals
2120

2221

@@ -188,3 +187,12 @@ def test_extension_parsed(make_app, apidoc):
188187
with open(outdir / 'conf.py') as f:
189188
rst = f.read()
190189
assert "sphinx.ext.mathjax" in rst
190+
191+
192+
@pytest.mark.sphinx('html', testroot='ext-apidoc')
193+
def test_apidoc_extension(app, status, warning):
194+
app.builder.build_all()
195+
assert (app.outdir / 'api').isdir()
196+
assert (app.outdir / 'api' / 'modules.html').exists()
197+
assert (app.outdir / 'api' / 'apidoc_dummy_module.html').exists()
198+
assert not (app.outdir / 'api' / 'conf.html').exists()

0 commit comments

Comments
 (0)