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:
- Setting up a simple REST API with Flask
- Integrating
flask-smorest
- Creating the OpenAPI document to describe the API
- Customizing the OpenAPI schema
- Using the Speakeasy CLI to create an SDK based on the schema
- Integrating SDK creation into CI/CD workflows
Requirements
To follow along, you will need:
- Python version 3.8 or higher
- An existing Flask project or a copy of the provided example repository (opens in a new tab)
- A basic understanding of Flask project structure and how REST APIs work
Example Flask REST API repository
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:
from flask import Flaskfrom flask_smorest import Apifrom db import dbimport modelsfrom resources import blp as BookBlueprintimport yamlapp = 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 specapi.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 tablesapp.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:
python -m venv venvsource venv/bin/activatepip install -r requirements.txt
If you have not already, install flask-smorest
with the following command:
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:
from flask import Flaskfrom flask_smorest import Apifrom db import dbimport modelsfrom resources import blp as BookBlueprintimport yamlapp = 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 specapi.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 tablesapp.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:
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
:
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.yaml
file to check the same Book
parameters are reflected in the OpenAPI document:
components:responses:DEFAULT_ERROR:content:application/json:schema:$ref: '#/components/schemas/Error'description: Default error responseUNPROCESSABLE_ENTITY:content:application/json:schema:$ref: '#/components/schemas/Error'description: Unprocessable Entityschemas:Book:properties:author:type: stringdescription:type: stringid:readOnly: truetype: integertitle:type: stringrequired:- author- titletype: objectError:properties:code:description: Error codetype: integererrors:additionalProperties: {}description: Errorstype: objectmessage:description: Error messagetype: stringstatus:description: Error nametype: stringtype: objectPaginationMetadata:properties:first_page:type: integerlast_page:type: integernext_page:type: integerpage:type: integerprevious_page:type: integertotal:type: integertotal_pages:type: integertype: objectinfo:title: Library APIversion: v0.0.1openapi: 3.1.0paths:/books/:get:responses:'200':content:application/json:schema:items:$ref: '#/components/schemas/Book'type: arraydescription: OKdefault:$ref: '#/components/responses/DEFAULT_ERROR'summary: List all bookstags:- Bookspost:requestBody:content:application/json:schema:$ref: '#/components/schemas/Book'required: trueresponses:'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 booktags:- Books/books/{book_id}:delete:responses:default:$ref: '#/components/responses/DEFAULT_ERROR'summary: Delete a booktags:- Booksget:responses:'200':content:application/json:schema:$ref: '#/components/schemas/Book'description: OKdefault:$ref: '#/components/responses/DEFAULT_ERROR'summary: Return books based on ID.tags:- Booksparameters:- in: pathname: book_idrequired: trueschema:minimum: 0type: integerput:requestBody:content:application/json:schema:$ref: '#/components/schemas/Book'required: trueresponses:'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 booktags:- Booksservers:- description: Local development serverurl: http://127.0.0.1:5000tags:- description: Operations on booksname: Booksx-speakeasy-retries:backoff:exponent: 1.5initialInterval: 500maxElapsedTime: 3600000maxInterval: 60000retryConnectionErrors: truestatusCodes:- 5XXstrategy: 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:
from flask.views import MethodViewfrom flask_smorest import Blueprint, abortfrom sqlalchemy.exc import IntegrityErrorfrom db import dbfrom models import Bookfrom schemas import BookSchemablp = 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.querypaginated_books = query.paginate(page=pagination_parameters.page,per_page=pagination_parameters.page_size,error_out=False)pagination_parameters.item_count = paginated_books.totalreturn 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 bookdef 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:
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:
"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""from __future__ import annotationsfrom books.types import BaseModelfrom typing import Optionalfrom typing_extensions import NotRequired, TypedDictclass BookTypedDict(TypedDict):author: strtitle: strdescription: NotRequired[str]id: NotRequired[int]class Book(BaseModel):author: strtitle: strdescription: Optional[str] = Noneid: 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.