16

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:

Project Structure

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

4
  • what is the main question/problem? Commented Jun 8, 2021 at 12:52
  • 1
    I need to automatically generate an OpenAPI v3 specification for the Flask API endpoints defined in different blueprints. The objects/schemas are not defined using any convention/framework, but a simple json.dumps is used for returning the values in the Response. Commented Jun 8, 2021 at 13:01
  • 1
    I know its a bit late, but can check this one pypi.org/project/flask-toolkits Commented Nov 4, 2022 at 10:43
  • flask_openapi3 and flask-restx exist as well Commented Jan 26, 2024 at 10:38

4 Answers 4

7

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): enter image description here

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

enter image description here

and /redoc

enter image description here

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.

Sign up to request clarification or add additional context in comments.

1 Comment

plus one for generator talk. automating openapi snippets for apidoc code should also be checked out
3
+50

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.

8 Comments

Thanks for the answer. I gave it a try and it looks very interesting. I'll consider migrating the existing project from Flask to FastAPI if, as I fear, there will be no other quick solution provided.
@1Z10 migrating will be easy peasy, you don't need to be afraid of it ;)
Instead of the info about comparisons to other frameworks, what would make this a better answer would be to show an actual example: at least one of the routes from the OP's question ported for FastAPI + the auto-generated FastAPI docs (screenshot of how it would look like). While the comparison to Flask is interesting, that was not the question.
Flask-Smorest (using apispec) can do the same for Flask, and is actively maintained by the Marshmallow team.
@MartijnPieters I'd really appreciate to see a simple example applied to my MWE if you can provide it.
|
3

If you'd like to stick with Flask, swagger-gen is a library that can generate full-featured specs with pretty low implementation overhead (see: https://github.com/danleonard-nj/swagger-gen).

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.

4 Comments

For some reason, your lib was very hard to find. Here it is for anyone searching: github.com/danleonard-nj/swagger-gen
ModuleNotFoundError: No module named 'importlib.metadata' getting this error with swagger-gen
@UpasanaMittal importlib.metadata was introduced in Python 3.8, so you'll need that version or above. I figured it was safe to use since that release is about 3 years old :)
@aleith pip install swagger-gen should do it.
0

You could try Flasgger: https://github.com/flasgger/flasgger)

from flask import Flask
from flasgger import Swagger
...

app = Flask(__name__)
...

swagger = Swagger(
    app,
    config={
        "headers": [],
        "openapi": "3.0.2",
        "specs": [{"endpoint": "api-doc", "route": "/api-doc.json"}],
        "static_url_path": "/flasgger_static",
        "swagger_ui": True,
        "specs_route": "/"
    },
    template={
        "info": {
            "title": "Flask-OpenAPI",
            "description": "Example of Flask REST API with OpenAPI documentation",
            "version": "1.0"
        }
    }
)

@app.get('/')
def hello_world():
    """Sample endpoint.
    ---
    responses:
      200:
        description: A sample endpoint
        content:
          text/plain:
            schema:
              type: string
              example: Flask - OpenAPI
    """
    return 'Flask - OpenAPI'


if __name__ == '__main__':
    app.run()

This should automatically build the OpenAPI definition and expose it at:

  • Raw JSON: http://localhost:5000/api-doc.json
  • Swagger UI: http://localhost:5000/api-doc

And, as far as I can see, it should support Flask blueprint: https://github.com/flasgger/flasgger/blob/master/examples/example_blueprint.py

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.