How to generate an OpenAPI document with Flask

OpenAPI is a tool for defining and sharing REST APIs, and Flask can be paired with flask-smorest to build such APIs.

This guide walks you through generating an OpenAPI document from a Flask project and using it to create SDKs with Speakeasy, covering the following steps:

  1. Setting up a simple REST API with Flask
  2. Integrating flask-smorest
  3. Creating the OpenAPI document to describe the API
  4. Customizing the OpenAPI schema
  5. Using the Speakeasy CLI to create an SDK based on the schema
  6. Integrating SDK creation into CI/CD workflows

Requirements

To follow along, you will need:

Example Flask REST API repository

Info Icon

Example repository

The source code for the completed example is available in the Speakeasy Flask example repository (opens in a new tab).

The repository already contains all the code covered throughout the guide. You can clone it and follow along with the tutorial, or use it as a reference to add to your own Flask project.

To better understand the process of generating an OpenAPI document with Flask, let’s start by inspecting some simple CRUD endpoints for an online library, along with a Book class and a serializer for our data.

Models and routes

Open the app.py file, which serves as the main entry point of the program, and inspect the main function:


Here, you will see a method call to create a SQLite database and a function to run the Flask app:


From the root of the repository, open the models.py file to see a Book model containing a few fields with validation:


In the schemas.py file, the BookSchema class can be used to serialize and deserialize book data with the marshmallow package:


The resources.py file contains API endpoints set up to handle all the CRUD operations for the books:

app.py
from flask import Flask
from flask_smorest import Api
from db import db
import models
from resources import blp as BookBlueprint
import yaml
app = Flask(__name__)
app.config["API_TITLE"] = "Library API"
app.config["API_VERSION"] = "v0.0.1"
app.config["OPENAPI_VERSION"] = "3.1.0"
app.config["OPENAPI_DESCRIPTION"] = "A simple library API"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database-file.db"
app.config["API_SPEC_OPTIONS"] = {"x-speakeasy-retries": {
'strategy': 'backoff',
'backoff': {
'initialInterval': 500,
'maxInterval': 60000,
'maxElapsedTime': 3600000,
'exponent': 1.5,
},
'statusCodes': ['5XX'],
'retryConnectionErrors': True,
}
}
db.init_app(app)
api = Api(app)
api.register_blueprint(BookBlueprint)
# Add server information to the OpenAPI spec
api.spec.options["servers"] = [
{
"url": "http://127.0.0.1:5000",
"description": "Local development server"
}
]
# Serve OpenAPI spec document endpoint for download
@app.route("/openapi.yaml")
def openapi_yaml():
spec = api.spec.to_dict()
return app.response_class(
yaml.dump(spec, default_flow_style=False),
mimetype="application/x-yaml"
)
if __name__ == "__main__":
with app.app_context():
db.create_all() # Create database tables
app.run(debug=True)

This code defines a simple Flask REST API with CRUD operations for a Book model. The BookList class provides a way to retrieve all book data and create new books. The BookDetail class handles the retrieval of specific books, updating book data, and deleting books.

Generate the OpenAPI document using flask-smorest

Flask does not support OpenAPI document generation out-of-the-box, so we’ll use the flask-smorest package to generate the OpenAPI document.

If you are following along with the example repository, you can create and activate a virtual environment to install the project dependencies:

Terminal
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt

If you have not already, install flask-smorest with the following command:

Terminal
pip install flask-smorest

The most basic configuration for generating an OpenAPI document with flask-smorest is added in the app.py file:


The new Swagger UI endpoint is also added in the app.py file:


The app.py file contains additional configuration settings for the OpenAPI document. These add a development server:


These additional configuration settings add a route to serve the OpenAPI document:

app.py
from flask import Flask
from flask_smorest import Api
from db import db
import models
from resources import blp as BookBlueprint
import yaml
app = Flask(__name__)
app.config["API_TITLE"] = "Library API"
app.config["API_VERSION"] = "v0.0.1"
app.config["OPENAPI_VERSION"] = "3.1.0"
app.config["OPENAPI_DESCRIPTION"] = "A simple library API"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database-file.db"
app.config["API_SPEC_OPTIONS"] = {"x-speakeasy-retries": {
'strategy': 'backoff',
'backoff': {
'initialInterval': 500,
'maxInterval': 60000,
'maxElapsedTime': 3600000,
'exponent': 1.5,
},
'statusCodes': ['5XX'],
'retryConnectionErrors': True,
}
}
db.init_app(app)
api = Api(app)
api.register_blueprint(BookBlueprint)
# Add server information to the OpenAPI spec
api.spec.options["servers"] = [
{
"url": "http://127.0.0.1:5000",
"description": "Local development server"
}
]
# Serve OpenAPI spec document endpoint for download
@app.route("/openapi.yaml")
def openapi_yaml():
spec = api.spec.to_dict()
return app.response_class(
yaml.dump(spec, default_flow_style=False),
mimetype="application/x-yaml"
)
if __name__ == "__main__":
with app.app_context():
db.create_all() # Create database tables
app.run(debug=True)

Run server

To inspect and interact with the OpenAPI document, you need to run the development server, which will create a database file if one does not already exist, and serve the API.

Run the development server:

Terminal
python app.py

You can now access the API and documentation:

  • Visit http://127.0.0.1:5000/swagger-ui to view the Swagger documentation and interact with the API.
  • Visit http://127.0.0.1:5000/openapi.yaml to download the OpenAPI document.

OpenAPI document generation

Now that we understand our Flask REST API, we can run the following command to generate the OpenAPI document using flask-smorest:

Terminal
flask openapi write --format=yaml openapi.yaml

This generates a new file, openapi.yaml, in the root of the project.

Here, you can see an example of the generated OpenAPI document:


Return to the app.py file to see how the app configuration influences the OpenAPI document generation:


Open the openapi.yaml file to see the titles and versions reflected in the generated OpenAPI document:


The server URL is also included in the OpenAPI document:


Open the models.py file to see the Book parameters:


Open the openapi.yamlfile to check the same Book parameters are reflected in the OpenAPI document:

openapi.yaml
components:
responses:
DEFAULT_ERROR:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Default error response
UNPROCESSABLE_ENTITY:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Unprocessable Entity
schemas:
Book:
properties:
author:
type: string
description:
type: string
id:
readOnly: true
type: integer
title:
type: string
required:
- author
- title
type: object
Error:
properties:
code:
description: Error code
type: integer
errors:
additionalProperties: {}
description: Errors
type: object
message:
description: Error message
type: string
status:
description: Error name
type: string
type: object
PaginationMetadata:
properties:
first_page:
type: integer
last_page:
type: integer
next_page:
type: integer
page:
type: integer
previous_page:
type: integer
total:
type: integer
total_pages:
type: integer
type: object
info:
title: Library API
version: v0.0.1
openapi: 3.1.0
paths:
/books/:
get:
responses:
'200':
content:
application/json:
schema:
items:
$ref: '#/components/schemas/Book'
type: array
description: OK
default:
$ref: '#/components/responses/DEFAULT_ERROR'
summary: List all books
tags:
- Books
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
required: true
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
description: Created
'422':
$ref: '#/components/responses/UNPROCESSABLE_ENTITY'
default:
$ref: '#/components/responses/DEFAULT_ERROR'
summary: Create a new book
tags:
- Books
/books/{book_id}:
delete:
responses:
default:
$ref: '#/components/responses/DEFAULT_ERROR'
summary: Delete a book
tags:
- Books
get:
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
description: OK
default:
$ref: '#/components/responses/DEFAULT_ERROR'
summary: Return books based on ID.
tags:
- Books
parameters:
- in: path
name: book_id
required: true
schema:
minimum: 0
type: integer
put:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
description: OK
'422':
$ref: '#/components/responses/UNPROCESSABLE_ENTITY'
default:
$ref: '#/components/responses/DEFAULT_ERROR'
summary: Update an existing book
tags:
- Books
servers:
- description: Local development server
url: http://127.0.0.1:5000
tags:
- description: Operations on books
name: Books
x-speakeasy-retries:
backoff:
exponent: 1.5
initialInterval: 500
maxElapsedTime: 3600000
maxInterval: 60000
retryConnectionErrors: true
statusCodes:
- 5XX
strategy: backoff

OpenAPI document customization

The OpenAPI document generated by flask-smorest may not fit all use cases. The document can be customized further to better serve information about your API endpoints. You can add descriptions, tags, examples, and more to make the documentation more informative and user-friendly.

In the customized (opens in a new tab) branch of the example repository, you can find a customized OpenAPI document that demonstrates the options available for modifying your generated document.

Open the resources.py file and inspect the configured endpoints:


You can indicate the expected response codes and models using @blp.response():


This results in the following additions, for example, to the /books/ get operation in the OpenAPI document:


Use the @blp.arguments() decorator to enforce a schema for arguments:


An enforced arguments schema results in the following additions to the post operation:


Allow pagination with the @blp.paginate() decorator:


Allowing paginations gives you access to the page and page_size properties, which you can use in your database query:


You can add inline documentation using docstrings:


Docstrings are reflected in the OpenAPI document as follows:


Notice the internal comment that is omitted from the OpenAPI document:


You can add global retries to the OpenAPI document by modifying the app config in the app.py file:


This enables retries when using the document to create an SDK with Speakeasy:

resources.py
from flask.views import MethodView
from flask_smorest import Blueprint, abort
from sqlalchemy.exc import IntegrityError
from db import db
from models import Book
from schemas import BookSchema
blp = Blueprint("Books", "books", url_prefix="/books", description="Operations on books")
@blp.route("/")
class BookList(MethodView):
@blp.response(200, BookSchema(many=True))
@blp.paginate()
def get(self, pagination_parameters):
"""List all books"""
query = Book.query
paginated_books = query.paginate(
page=pagination_parameters.page,
per_page=pagination_parameters.page_size,
error_out=False
)
pagination_parameters.item_count = paginated_books.total
return paginated_books.items
@blp.arguments(BookSchema)
@blp.response(201, BookSchema)
def post(self, new_data):
"""Create a new book"""
book = Book(**new_data)
db.session.add(book)
db.session.commit()
return book
@blp.route("/<int:book_id>")
class BookDetail(MethodView):
@blp.response(200, BookSchema)
def get(self, book_id):
"""Return books based on ID.
---
Internal comment not meant to be exposed.
"""
book = Book.query.get_or_404(book_id)
return book
@blp.arguments(BookSchema)
@blp.response(200, BookSchema)
def put(self, updated_data, book_id):
"""Update an existing book"""
book = Book.query.get_or_404(book_id)
book.title = updated_data["title"]
book.author = updated_data["author"]
book.description = updated_data.get("description")
db.session.commit()
return book
def delete(self, book_id):
"""Delete a book"""
book = Book.query.get_or_404(book_id)
db.session.delete(book)
db.session.commit()
return {"message": "Book deleted"}, 204

Creating SDKs for a Flask REST API

To create a Python SDK for the Flask REST API, run the following command:

Terminal
speakeasy quickstart

Follow the onscreen prompts to provide the configuration details for your new SDK, such as the name, schema location, and output path. When prompted, enter openapi.yaml for the OpenAPI document location, select a language, and generate.

Add SDK generation to your GitHub Actions

The Speakeasy sdk-generation-action (opens in a new tab) repository provides workflows for integrating the Speakeasy CLI into your CI/CD pipeline, so that your SDKs are recreated whenever your OpenAPI document changes.

You can set up Speakeasy to automatically push a new branch to your SDK repositories for your engineers to review before merging the SDK changes.

For an overview of how to set up automation for your SDKs, see the Speakeasy SDK Generation Action and Workflows (opens in a new tab) documentation.

SDK customization

After creating your SDK with Speakeasy, you will find a new directory containing the generated SDK code, which we will now explore further.

These examples assume a Python SDK named books-python was generated from the example Flask project above. Edit any paths to reflect your environment if you want to follow in your own project.

Navigate into the books-python/src/books/models directory and find the book.py file created by Speakeasy. Note how the OpenAPI document was used to create the Book class:


Open the src/books/books_sdk.py file to see the methods that call the web API from an application using the SDK:


Here, you can see how the request to the API endpoint is built:


Finally, note the result of the global retries strategy that we set up in the app.py file:

book.py
"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
from __future__ import annotations
from books.types import BaseModel
from typing import Optional
from typing_extensions import NotRequired, TypedDict
class BookTypedDict(TypedDict):
author: str
title: str
description: NotRequired[str]
id: NotRequired[int]
class Book(BaseModel):
author: str
title: str
description: Optional[str] = None
id: Optional[int] = None

Summary

In this guide, we showed you how to generate an OpenAPI document for a Flask API and use Speakeasy to create an SDK based on the OpenAPI document. The step-by-step instructions included adding relevant tools to the Flask project, generating an OpenAPI document, enhancing it for improved creation, using Speakeasy CLI to create the SDKs, and interpreting the basics of the generated SDK.

We also explored automating SDK generation through CI/CD workflows and improving API operations.