Advanced Usage

Including your definition in a package

The goal is to generate a swagger.json file and include it into your source distribution. There are a few reasons for doing this but the most obvious is to serve this file from a endpoint within your application. I do this in the example project by embedding the JSON file in a package data directory as shown in the following tree:

<project-root>/
|-- docs/
|   |-- conf.py
|   `-- index.rst
|-- MANIFEST.in
|-- README.rst
|-- sample/
|   |-- __init__.py
|   |-- app.py
|   |-- simple_handlers.py
|   `-- swagger.json
`-- setup.py

The MANIFEST.in controls which files are included in a source distribution. Since you will be generating the API definition when you build your package, you aren’t required to include the definition in the source distribution but you should. This is pretty simple:

graft docs
recursive-include sample *.json

That takes care of the source distributions. The API definition also needs to be added to binary distributions if you want to serve it from within an application. You need to modify your setup.py for this:

import setuptools

setuptools.setup(
   name='sample',
   # ...
   packages=['sample'],
   package_data={'': ['**/*.json']},
   include_package_data=True,
)

This tells the setuptools machinery to include any JSON files that it finds in a package directory in the binary distribution.

Now for the awful part... there is no easy way to do this using the standard setup.py build_sphinx command. It will always generate the swagger directory and does not let you customize the location of the doctrees. Use the sphinx-build utility instead:

$ sphinx-build -b swagger -d build/tmp docs sample

That will generate the swagger.json directly into the sample package. Alternatively, you can use setup.py build_sphinx and copy the API definition into the package before generating the distribution.

Serving the API definition

The Swagger UI allows you to browse an API by pointing at it’s API definition file. Once the API definition is packaged into your application as described above, it is relatively easy to write a handler to serve the document. The following snippet implements one such handler in the Tornado web framework.

class SwaggerHandler(web.RequestHandler):
    """Tornado request handler for serving a API definition."""

    def initialize(self, swagger_path):
        super(SwaggerHandler, self).initialize()
        self.swagger_path = swagger_path
        self.application.settings.setdefault('swagger_state', {
            'document': None,
            'last-read': None,
        })

    def set_default_headers(self):
        super(SwaggerHandler, self).set_default_headers()
        self.set_header('Access-Control-Allow-Origin', '*')

    def options(self, *args):
        self.set_header('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS')
        self.set_status(204)
        self.finish()

    def head(self):
        """Retrieve API definition metadata."""
        last_modified = datetime.datetime.utcfromtimestamp(
            self.swagger_state['last-modified'])
        self.set_header('Last-Modified',
                        last_modified.strftime('%a, %d %b %Y %H:%M:%S GMT'))
        self.set_header('Content-Type', 'application/json')
        self.set_header('ETag', self.compute_etag())
        self.set_status(204)
        self.finish()

    def get(self):
        """Retrieve the API definition."""
        try:
            if self.request.headers['If-None-Match'] == self.compute_etag():
                self.set_status(304)
                return
        except KeyError:
            pass

        self.swagger_state['document']['host'] = self.request.host
        last_modified = datetime.datetime.utcfromtimestamp(
            self.swagger_state['last-modified'])
        self.set_header('Content-Type', 'application/json')
        self.set_header('Last-Modified',
                        last_modified.strftime('%a, %d %b %Y %H:%M:%S GMT'))
        self.write(self.swagger_state['document'])

    @property
    def swagger_state(self):
        """
        Returns a :class:`dict` containing the cached state.

        :return: :class:`dict` containing the following keys: ``document``,
            ``last-modified``, and ``digest``.
        :rtype: dict
        """
        self.refresh_swagger_document()
        return self.application.settings['swagger_state']

    def compute_etag(self):
        """Return the digest of the document for use as an ETag."""
        return self.swagger_state['digest']

    def refresh_swagger_document(self):
        state = self.application.settings['swagger_state']
        last_modified = os.path.getmtime(self.swagger_path)
        if state['document']:
            if last_modified <= state['last-modified']:
                return

        with open(self.swagger_path, 'rb') as f:
            raw_data = f.read()
            state['document'] = json.loads(raw_data.decode('utf-8'))
        state['last-modified'] = last_modified
        state['digest'] = hashlib.md5(raw_data).hexdigest()