DEV Community

Cover image for Documenting your Flask-powered API like a boss
Julien Tanay
Julien Tanay

Posted on • Originally published at becominghuman.ai

Documenting your Flask-powered API like a boss

Flask is a very popular and powerful framework for building web applications. Over the last years, people used it to create REST API that work well with decoupled, modern front-end applications.

One challenge that backend development teams often face, is how to make it easy for front-end developers, wethere internal or with a distant community, to create API-compliant clients (web app, mobile app or even CLI tools...)

In the wild, they are many good examples of well-documented APIs... Take the Twitter API : the docs are great, user-friendly and cover all the available endpoint with tips and examples. Any fresh CS student could write a small Python tool using it, just by following the documentation and its examples.

At @Ooreka, we decided to follow the OpenAPI (fka Swagger 2.0) specification to build a solid documentation for our Flask-powered micro-services APIs. Let's dive in.

3..2..1.. Doc!

Thanks to the apispec lib, you can automagically generate a specification file (commonly named swagger.json) form your Flask code. Some other libraries can do a lot of magic for you, but apispec is really simple to use and can sit next to your code without interfering with it.

It supports Marshmallow and Flask, allowing you to re-use your code to generate a proper documentation !

Let's write our generation script, e.g. app/scripts/openapi.py :

from apispec import APISpec

# Create spec
spec = APISpec(
    title='My Awesome API',
    version='1.0.42',
    info=dict(
        description='You know, for devs'
    ),
    plugins=[
        'apispec.ext.flask',
        'apispec.ext.marshmallow'
    ]
)

# Reference your schemas definitions
from app.schemas import FooSchema

spec.definition('Foo', schema=FooSchema)
# ...

# Now, reference your routes.
from app.views import my_route

# We need a working context for apispec introspection.
app = create_app()

with app.test_request_context():
    spec.add_path(view=my_route)
    # ...

# We're good to go! Save this to a file for now.
with open('swagger.json', 'w') as f:
    json.dump(spec.to_dict(), f)
Enter fullscreen mode Exit fullscreen mode

Here, we first create a new APISpec instance with some details about our API.
Then, we add our definitions (here, we are using Marshmallow to define how our API will serialize/deserialize data) with APISpec.definition().
Finally, we add our routes to our API specification using APISpec.add_path(). apispec will parse your route functions docstrings, so make sure your add some OpenAPI YaML stuff here, as in :

@app.route('/foo/<bar_id>')
def my_route(gist_id):
    """ Cool Foo-Bar route.
    - - - # dev.to editor dislike triple hyphen, be sure to remove spaces here.
    get:
        summary: Foo-Bar endpoint.
        description: Get a single foo with the bar ID.
        parameters:
            - name: bar_id
              in: path
              description: Bar ID
              type: integer
              required: true
        responses:
            200:
                description: Foo object to be returned.
                schema: FooSchema
            404:
                description: Foo not found.
    """
    # (...)
    return jsonify(foo)
Enter fullscreen mode Exit fullscreen mode

You will end up with a valid JSON API specification. Now, let's see how to bootstrap an HTML version to show it to the world!

Browerify-ing all this

A really cool tool to do that is the ReDoc Javascript library from the guys at APIs.guru. We'll use it to present the generated JSON specification in a convenient way.

Redoc is basically a single, minified JS file you can include in a bare index.html file and tell it where your swagger.json is located. It uses a really neat 3 columns design : a navigation sidebar, a wide center section with your API endpoints definitions and a third column dedicated to requests or responses samples and examples.

<!DOCTYPE html>
<html>
  <head>
    <title>Cool API Documentation</title>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style> body { margin: 0; padding: 0; } </style>
  </head>
  <body>
    <redoc spec-url='./swagger.json' hide-loading></redoc>
    <script src="https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js"> </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Yep, that was quick. Check out a real-world example here.

Wrapping it up

The OpenAPI offers many options I didn't cover here for brievity and simplification. You can add your server's real endpoints to the doc, add many details about the parameters and responses of your routes, provide example in your routes functions docstring that will be parsed and added to your spec, etc...

As a final tip, head to the Flask CLI documentation to see how easily you can hook your generation script into the command line interface of Flask (this will give you some badass command like FLASK_APP=main.py flask generate_doc). Oh, and be sure to put this into your Continuous Integration routine to keep your API documentation up-to-date with your API!

Cheers!

NOTE: This post was originally posted on Medium.

Top comments (3)

Collapse
 
couchmaster789 profile image
CouchMaster

Mate, I was about to give up on using coherent docstrings for my Flask app as couldn't find a good way of using the mainstream documentations styles (such as Google or Numpy) to work fully with status codes 'n all. You're a life saver. Great article, exactly what I was after. Thnx!

Collapse
 
houmie profile image
Houman

Hi Julien, but this requires your flask api to use Marshmallow correct? If I had flask-restful without Marshmallow, this won't work?

Collapse
 
djiit profile image
Julien Tanay

Hey, you can use it without Marshmallow, but you will have a lot less information.