Quantcast
Channel: Planet Python
Viewing all articles
Browse latest Browse all 22462

Albert Hopkins: Packaging static Sphinx-based documentation in a reusable Django app

$
0
0

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

Viewing all articles
Browse latest Browse all 22462

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>