I need to document an API written in pure Flask 2 and I'm looking for what is a consolidated approach for doing this. I found different viable solutions but being new to Python and Flask I'm not able to choose among them. The solutions I found are:
In order to separate the different API endpoints I use the Flask blueprint. The structure of a MWE is as follows:
I first defined two simple domain objects, Author and Book.
# author.py
class Author:
def __init__(self, id: str, name: str):
self.id = id
self.name = name
# book.py
class Book:
def __init__(self, id: str, name: str):
self.id = id
self.name = name
Next, I created a simple GET endpoint for both of them using two separate blueprints.
# author_apy.py
import json
from flask import Blueprint, Response
from domain.author import Author
author = Blueprint("author", __name__, url_prefix="/authors")
@author.get("/")
def authors():
authors: list[Author] = []
for i in range(10):
author: Author = Author(str(i), "Author " + str(i))
authors.append(author)
authors_dicts = [author.__dict__ for author in authors]
return Response(json.dumps(authors_dicts), mimetype="application/json")
and
# book_api.json
import json
from flask import Blueprint, Response
from domain.book import Book
book = Blueprint("book", __name__, url_prefix="/books")
@book.get("/")
def books():
books: list[Book] = []
for i in range(10):
book: Book = Book(str(i), "Book " + str(i))
books.append(book)
books_dicts = [book.__dict__ for book in books]
return Response(json.dumps(books_dicts), mimetype="application/json")
In the end I simply registered both the blueprints under the Flask app.
# app.py
from flask import Flask
from api.author.author_api import author
from api.book.book_api import book
app = Flask(__name__)
app.register_blueprint(author, url_prefix="/authors")
app.register_blueprint(book, url_prefix="/books")
@app.get('/')
def hello_world():
return 'Flask - OpenAPI'
if __name__ == '__main__':
app.run()
The whole source code is also available on GitHub.
Considering this minimal working example, I'd like to know what is the quickest way to automate the generation of an OpenAPI v3 yaml/JSON file, e.g. exposed on a /api-doc.yaml endpoint.
PS: this is my first API using Python and Flask. I am trying to reproduce what I'm able to do with Spring-Boot and SpringDoc
Flask OpenAPI3 is a web API framework based on Flask. It uses Pydantic to verify data and automatic generation of interaction documentation: Swagger, ReDoc and RapiDoc. Pydantic for the data validation. Here's a simple example, further go to the Example.
flask-rest-api automatically generates an OpenAPI documentation (formerly known as Swagger) for the API. That documentation can be made accessible as a JSON file, along with a nice web interface such as ReDoc or Swagger UI.
That documentation can be made accessible as a JSON file, along with a nice web interface such as ReDoc or Swagger UI. The version of the API and the version of the OpenAPI specification can be specified as Flask application parameters:
It comes in handy if an OpenAPI feature is not supported, but it suffers from a few limitations, and it should be considered a last resort solution until flask-rest-api is improved to fit the need. Known issues and alternatives are discussed in issue #71.
Following the suggestion of migrating from Flask to FastAPI I gave it a try and rewrote the Flask-Example of the question. The source code is also available on GitHub.
The structure of the project is almost identical, with some additional features available(e.g. the CORS Middleware):
The models of the domain are slightly different and extend the BaseModel from Pydantic.
# author.py
from pydantic import BaseModel
class Author(BaseModel):
id: str
name: str
and
# book.py
from pydantic import BaseModel
class Book(BaseModel):
id: str
name: str
With FastAPI the equivalent of the Flask Blueprint is the APIRouter. Below are the two controllers for the authors
# author_api.py
from fastapi import APIRouter
from domain.author import Author
router = APIRouter()
@router.get("/", tags=["Authors"], response_model=list[Author])
def get_authors() -> list[Author]:
authors: list[Author] = []
for i in range(10):
authors.append(Author(id="Author-" + str(i), name="Author-Name-" + str(i)))
return authors
and the books
# book_api.py
from fastapi import APIRouter
from domain.book import Book
router = APIRouter()
@router.get("/", tags=["Books"], response_model=list[Book])
def get_books() -> list[Book]:
books: list[Book] = []
for i in range(10):
books.append(Book(id="Book-" + str(i), name="Book-Name-" + str(i)))
return books
It is important to note that the response model of the API endpoints is defined using Python types thanks to Pydantic. These object types are then converted into JSON schemas for the OpenAPI documentation.
In the end I simply registered/included the APIRouters under the FastAPI object and added a configuration for CORS.
# app.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from domain.info import Info
from api.author.author_api import router as authors_router
from api.book.book_api import router as books_router
app = FastAPI()
app.include_router(authors_router, prefix="/authors")
app.include_router(books_router, prefix="/books")
app.add_middleware(CORSMiddleware,
allow_credentials=True,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/", response_model=Info)
def info() -> Info:
info = Info(info="FastAPI - OpenAPI")
return info
The generated OpenAPI documentation is accessible at the endpoint /openapi.json
while the UI (aka Swagger UI, Redoc) is accessible at /docs
and /redoc
To conclued, this is the automatically generated OpenAPI v3 documentation in JSON format, which can be used to easily generate an API client for other languages (e.g. using the OpenAPI-Generator tools).
{
"openapi": "3.0.2",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/authors/": {
"get": {
"tags": [
"Authors"
],
"summary": "Get Authors",
"operationId": "get_authors_authors__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"title": "Response Get Authors Authors Get",
"type": "array",
"items": {
"$ref": "#/components/schemas/Author"
}
}
}
}
}
}
}
},
"/books/": {
"get": {
"tags": [
"Books"
],
"summary": "Get Books",
"operationId": "get_books_books__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"title": "Response Get Books Books Get",
"type": "array",
"items": {
"$ref": "#/components/schemas/Book"
}
}
}
}
}
}
}
},
"/": {
"get": {
"summary": "Info",
"operationId": "info__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Info"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Author": {
"title": "Author",
"required": [
"id",
"name"
],
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
}
}
},
"Book": {
"title": "Book",
"required": [
"id",
"name"
],
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
}
}
},
"Info": {
"title": "Info",
"required": [
"info"
],
"type": "object",
"properties": {
"info": {
"title": "Info",
"type": "string"
}
}
}
}
}
}
In order to start the application we also need an ASGI server for production, such as Uvicorn or Hypercorn. I used Uvicorn and the app is started using the command below:
uvicorn app:app --reload
It is then available on the port 8000 of your machine.
I encourage you to switch your project to FastAPI, it isn't much different or more difficult than Flask.
FastAPI docs about generating OpenAPI schema
It will not only allow you to generate OpenAPI docs / specification easily. It is also asynchronous, much faster and modern.
See also FastAPI Alternatives, Inspiration and Comparisons to read about differences.
Especially this citation from link above should explain why doing what you try to do may not be the best idea:
Flask REST frameworks
There are several Flask REST frameworks, but after investing the time and work into investigating them, I found that many are discontinued or abandoned, with several standing issues that made them unfit.
If you'd like to stick with Flask, swagger-gen
is a library that can generate full-featured specs with pretty low implementation overhead.
from swagger_gen.lib.wrappers import swagger_metadata
from swagger_gen.lib.security import BearerAuth
from swagger_gen.swagger import Swagger
from flask import Flask, request
app = Flask(__name__)
@app.route('/api/hello/say', methods=['GET'])
@swagger_metadata(
summary='Sample endpoint',
description='This is a sample endpoint')
def test():
return {'message': 'hello world!'}
swagger = Swagger(
app=app,
title='app')
swagger.configure()
if __name__ == '__main__':
app.run(debug=True, port='5000')
Full disclosure: I'm the author.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With