Self-Documenting APIs with Flask

08:03 reading time

RESTful APIs have become commonplace in the world of the web.

Given that “REST” itself is a concept and not a protocol, RESTful APIs must only obey a minimal set of rules that can be abused by developers in any way they like.

While the transport protocol might often be HTTP, the rules for interacting with the API (i.e., the API’s protocol) are largely defined by the API itself. I almost want to label these as “simple HTTP services”, because many APIs that label themselves as “RESTful” really aren’t. Consider a JSON-over-HTTP API that happens to maintain state. It’s not RESTful, but it uses elements commonly found in RESTful APIs.

The simple HTTP service has gained popularity for a few reasons. It uses the same transport protocol as traditional webpages, and if it’s a RESTful service, it’s modeled on how the web works: stateless and resource-oriented. An HTTP service that uses JSON as the payload format enables quick and easy interoperability with a web browser—and nearly every modern programming language. The unstructured nature of JSON vs. the structured nature of XML lends itself better to rapid development and change.

Contrast this to SOAP APIs which must conform to a rigid protocol when invoking functions and exchanging information. It complicates prototyping small, simple services, often requiring a significant amount of “ceremony” to just get up and running. And while it’s possible to invoke SOAP operations through Javascript in a web browser, it’s clunky. Even with the help of third-party SOAP-Javascript libraries, it remains undesirable.

SOAP does have something going for it that simple HTTP services lack: a uniform way of self-documenting the API’s interface. The WSDL format describes a SOAP service so that SOAP clients know how to invoke it. Since a WSDL file is XML, it’s human-readable. WSDLs are often generated automatically in the development process, and thus can be considered a definitive source for explaining what the SOAP service offers and how to use it.

I don’t know of a comparable self-documenting standard for simple HTTP services, RESTful or not. With an HTTP service, you can expect it to provide operations that use HTTP verbs, but that’s about all you can assume. Without corresponding documentation, such a service is completely opaque. Even documentation generators that analyze source code often aren’t enough to communicate to the user how the API should be used. Thus developers are required to keep their separate documentation in sync with their APIs—often a low priority.

Maybe we can change that.

On the command-line we use flags like -h and --help to get usage instructions for ways to invoke a particular command. Popular command-line parsing libraries, like argparse in Python, provide a built-in mechanism for showing options and instructions in a meaningful way, without requiring the developer to build or maintain anything “special”.

What if our simple HTTP APIs provided an analogue?

One of my favorite tools for creating simple HTTP APIs in Python is the Flask framework.

With just a small bit of code, we can make an entire Flask-based HTTP API self-documenting.

Consider the following Flask blueprint:

import flask

dev = flask.Blueprint('dev', __name__, template_folder='templates')

def index():
    """GET to generate a list of endpoints and their docstrings"""
    urls = dict([(r.rule, flask.current_app.view_functions.get(r.endpoint).func_doc)
                 for r in flask.current_app.url_map.iter_rules()
                 if not r.rule.startswith('/static')])
    return flask.render_template('index.html', urls=urls)

The corresponding index.html Jinja2 template resembles:

<!doctype html>
  {% for path, docstr in urls|dictsort(false, 'key') %}
  <dt><a href="{{ path }}">{{ path }}</a></dt>
  <dd><pre>{{ docstr.strip() }}</pre></dd>
  {% endfor %}

With this blueprint mounted at the root level of our application, all registered routes are listed with their docstrings when a user accesses our API.

Consider another Flask blueprint that contains our actual API endpoints. One of them may look like:

@api.route('/get/<thing>', methods=['GET'])
def select(thing):
    """Retrieve a thing.

    GET: Returns thing previously 'put' at given location.
         Returns HTTP 200 on success; body is payload as-is.
         Returns HTTP 404 when data does not exist.
    result = db.get(thing)
    return result if result else flask.abort(404)

Now, when we visit our “development” endpoint, we’ll see a nicely-formatted HTML page listing all routes along with the inline documentation from the function that handles the route.

While this solution isn’t as “foolproof” as an automatically-generated WSDL, placing the intended use of the endpoint alongside the code that does the job provides some confidence that it will actually be kept up-to-date.

Of course, there are better ways to do this, with plenty of solutions already out there. The flask-autodoc plugin produces similar output—more consistent, even, than what might be produced above. If using Sphinx for documenting your project, consider the sphinxcontrib.autohttp.flask extension.

There’s also the Swagger project, which offers a very cool tool that allows you to interact with your API through a webpage. A nice thing about this tool is that, at its core, it’s “language-agnostic”. Theoretically, any framework in any language can make use of it. The flask-restful-swagger plugin for the Flask-RESTful extension allows some integration between Flask-RESTful APIs and Swagger.


Ian Melnick
Senior Software Engineer