Skip to content

Commit

Permalink
Fix #1135: Make 'sphinx-apidoc' an extension
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
stephenfin committed Mar 22, 2018
1 parent f26db5b commit 39386c2
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 3 deletions.
51 changes: 51 additions & 0 deletions doc/ext/apidoc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.. highlight:: rest

:mod:`sphinx.ext.apidoc` -- Generate autodoc stub pages
=======================================================

.. module:: sphinx.ext.apidoc
:synopsis: Generate autodoc stub pages

.. versionadded:: 1.7

The functionality of this extension was previously available as part of the
:program:`sphinx-apidoc` tool. This ability to run this automatically by way
of a Sphinx extension was added in 1.7.

This extension generates function, method and attribute stub documentation
pages, similar to that generated by API doc tools like Doxygen or Javadoc.

.. warning::

The apidoc extension generates source files that use
:mod:`sphinx.ext.autodoc` to document all found modules. If any modules
have side effects on import, these will be executed when building
documentation.

If you document scripts (as opposed to library modules), make sure their
main routine is protected by a ``if __name__ == '__main__'`` condition.

Configuration
-------------

The apidoc extension uses the following configuration values:

.. confval:: apidoc_module_dir

The path to the module to document. This must be a path to a Python package.
This path can be a path relative to the documentation source directory or an
absolute path.

.. confval:: apidoc_output_dir

The output directory. If it does not exist, it is created. This path is
relative to the documentation source directory.

Defaults to ``api``.

.. confval:: apidoc_excluded_modules

An optional list of modules to exclude. These should be paths relative to
``api_module_dir``. fnmatch-style wildcarding is supported.

Defaults to ``[]``.
1 change: 1 addition & 0 deletions doc/ext/builtins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ These extensions are built in and can be activated by respective entries in the

.. toctree::

apidoc
autodoc
autosectionlabel
autosummary
Expand Down
48 changes: 45 additions & 3 deletions sphinx/ext/apidoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@
from sphinx import __display_version__, package_dir
from sphinx.cmd.quickstart import EXTENSIONS
from sphinx.locale import __
from sphinx.util import rst
from sphinx.util import logging, rst
from sphinx.util.osutil import FileAvoidWrite, ensuredir, walk

if False:
# For type annotation
from typing import Any, List, Tuple # NOQA
from typing import Any, Dict, List, Tuple # NOQA
from sphinx.application import Sphinx # NOQA

logger = logging.getLogger(__name__)

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


# So program can be started with "python -m sphinx.apidoc ..."
def builder_inited(app):
# type: (Sphinx) -> None
module_dir = app.config.apidoc_module_dir
output_dir = path.join(app.srcdir, app.config.apidoc_output_dir)
excludes = app.config.apidoc_excluded_modules

if not module_dir:
logger.warning("No 'apidoc_module_dir' specified; skipping API doc "
"generation")
return

# if the path is relative, make it relative to the 'conf.py' directory
if not path.isabs(module_dir):
module_dir = path.abspath(path.join(app.srcdir, module_dir))

excludes = [path.abspath(path.join(module_dir, exc)) for exc in excludes]

# refactor this module so that we can call 'recurse_tree' like a sane
# person - at present there is way too much passing around of the
# 'optparse.Value' instance returned by 'optparse.parse_args'
cmd = [module_dir, '--force', '-o', output_dir]
if excludes:
cmd += excludes

main(cmd)


def setup(app):
# type: (Sphinx) -> Dict[unicode, Any]
app.setup_extension('sphinx.ext.autodoc') # We need autodoc to function

app.connect('builder-inited', builder_inited)
app.add_config_value('apidoc_module_dir', None, 'env', [str])
app.add_config_value('apidoc_output_dir', 'api', 'env', [str])
app.add_config_value('apidoc_excluded_modules', [], 'env',
[[str]])

return {'version': __display_version__, 'parallel_read_safe': True}


if __name__ == "__main__":
main()
9 changes: 9 additions & 0 deletions tests/roots/test-ext-apidoc/apidoc_dummy_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from os import *


class Foo:
def __init__(self):
pass

def bar(self):
pass
12 changes: 12 additions & 0 deletions tests/roots/test-ext-apidoc/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-

import os
import sys

sys.path.insert(0, os.path.abspath('./'))

extensions = ['sphinx.ext.apidoc']
master_doc = 'index'

apidoc_module_dir = '.'
apidoc_excluded_modules = ['conf.py']
6 changes: 6 additions & 0 deletions tests/roots/test-ext-apidoc/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apidoc
======

.. toctree::

api/modules
9 changes: 9 additions & 0 deletions tests/test_ext_apidoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,12 @@ def test_subpackage_in_toc(make_app, apidoc):
assert 'parent.child.foo' in parent_child

assert (outdir / 'parent.child.foo.rst').isfile()


@pytest.mark.sphinx('html', testroot='ext-apidoc')
def test_apidoc_extension(app, status, warning):
app.builder.build_all()
assert (app.outdir / 'api').isdir()
assert (app.outdir / 'api' / 'modules.html').exists()
assert (app.outdir / 'api' / 'apidoc_dummy_module.html').exists()
assert not (app.outdir / 'api' / 'conf.html').exists()

0 comments on commit 39386c2

Please sign in to comment.