At my job we have a few Django apps which are packaged as reusable Django apps. Some of those apps have documentation generated by Sphinx that need to be served along with the served app (as opposed to at another instance like ReadTheDocs). Ideally the Sphinx documentation would be built (as HTML) and copied to the static/
directory of the app. We used to include a custom django-admin command to do so, but you have to remember to run it and you have to remember to do so before calling collectstatic
. One alternative would be to include the built files with the source, but again one must remember to build the docs every they change and I'm not a big fan of "building" things in the source (that's what the build phase is for).
There are some third-party apps that allow you to serve Sphinx-based
documentation, but they either still require you to manually perform the build step or require a database or index server.
If all you want to do is serve the static files and not have to remember the build step, there is another alternative. You could have setup.py
build the Sphinx docs into your static/
directory at build time. Here's how you could do it.
A typical reusable Django app's structure might look like this:
.
├── hello_world
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── static
│ ├── templates
│ ├── tests.py
│ └── views.py
├── MANIFEST.in
├── README.rst
└── setup.py
For this to work, we create our Sphinx documentation in the, e.g., hello_world/docs/
directory.
$ sphinx-quickstart hello_world/docs
Now for the fun part. The trick to getting setuptools to build and install the Sphinx docs at build time, we must interject a step in the build process. setuptools uses the build_py
step to install the Python files. build_py
is a class that comes with setuptools. We can subclass it and tell setuptools to use our subclass instead. So in our setup.py
we define our class as such:
# setup.py
from os import path
from subprocess import check_call
from setuptools import find_packages, setup
from setuptools.command.build_py import build_py
name = 'hello_world'
version = '0.0'
description = 'World greeter'
class build_py_with_docs(build_py):
def run(self):
build_py.run(self)
docs_source = path.join(self.get_package_dir(name), 'docs')
docs_target = path.join(self.build_lib, name, 'static', 'docs', name)
check_call(('sphinx-build', '-b', 'html', docs_source, docs_target))
setup(
name=name,
version=version,
description=description,
author='Albert Hopkins',
author_email='marduk@python.net',
url='http://starship.python.net/crew/marduk/',
packages=find_packages(),
include_package_data=True,
cmdclass={'build_py': build_py_with_docs},
)
The only thing that is unordinary about this setup.py
is the build_py_with_docs
class definition and its accompanying cmdclass
argument to setup()
. The custom class simply overrides build_py
's .run()
method and adds a step to build the Sphinx documentation in the app's static/
directory. The cmdclass
informs setuptools to use our custom class instead of the built-in build_py
class for the "build_py" step.
One caveat: Sphinx must be installed on the system at build time. Likely this is already the case if you are already manually building the docs. If you instead installing from Python wheel files, Sphinx need only be installed on the host(s) generating the wheels.
The take-away is that by simply pip installing hello_world
gives us the app and the HTML-formatted Sphinx docs in the app's static directory. Once installed (and collected) by your Django app, one can access the documentation as simple static files, e.g.:
http://statichost/static/docs/hello_world/index.html