Skip to main content

Flask + marshmallow + OpenAPI

Project description

Overview

PyPI Status license python_versions

Provides OpenAPI documentation generated from code for Flask APIs built around marshmallow schemas.

This hackish and organically grown (TM) package was created because no other similar projects worked exactly the way I wanted them.

You will probably be better served by some other, properly maintained project with similar purpose:

If you still want to use it, welcome aboard :-) and read on!

Installation

pip install flask-marshmallow-openapi

What it does?

Searches your codebase for marshmallow schemas and :medal_military: decorated :medal_military: Flask routes. For example:

from flask_marshmallow_openapi import Securities, open_api

from ..views import NewPasswordSchema, LoginResponseSchema


@blueprint.route("/reset_password/<token>", methods=["POST"])
@open_api.post(
    request_schema=NewPasswordSchema,
    response_schema=LoginResponseSchema,
    operation_id="reset_password_finalize",
    security=Securities.no_token,
    additional_parameters=[
        {
            "name": "token",
            "in": "path",
            "required": True,
            "allowEmptyValue": False,
            "schema": {"type": "string"},
        }
    ],
)
def reset_password_confirm(token):
    ...

Using these, it constructs OpenAPI swagger.json and serves it. It also includes and serves ReDoc and SwaggerUI documentation viewers.

Full example

First we need some data:

from dataclasses import dataclass

@dataclass
class Book:
    id: int
    title: str
    publisher: str
    isbn: str

Then we need some marshmallow schemas:

import marshmallow as ma


class SchemaOpts(ma.SchemaOpts):
    def __init__(self, meta, *args, **kwargs):
        self.tags = getattr(meta, "tags", [])
        self.url_id_field = getattr(meta, "url_id_field", None)
        super().__init__(meta, *args, **kwargs)


class BookSchema(ma.Schema):
    OPTIONS_CLASS = SchemaOpts

    class Meta:
        url_id_field = "id"
        tags = ["Books"]
        description = "Schema for Book model"

    id = ma.fields.Integer(as_string=True)
    title = ma.fields.String(
        allow_none=False, metadata={"description": "book.title description"}
    )
    publisher = ma.fields.String(allow_none=False)
    isbn = ma.fields.String(allow_none=False)


class BookCreateSchema(ma.Schema):
    OPTIONS_CLASS = SchemaOpts

    class Meta(BookSchema.Meta):
        pass

    title = ma.fields.String(allow_none=False, required=True)


class BookUpdateSchema(ma.Schema):
    OPTIONS_CLASS = SchemaOpts

    class Meta(BookSchema.Meta):
        pass

    id = ma.fields.Integer(as_string=True, dump_only=True)
    isbn = ma.fields.String(allow_none=False, dump_only=True)

Then an Flask app and some :medal_military: decorated :medal_military: routes:

import flask
from flask_marshmallow_openapi import open_api

app = flask.Flask(__name__)


@app.route("/books", methods=["GET"])
@open_api.get(BookSchema, "bookList", many=True)
def books_list():
    return "<p>Hello, World!</p>"


@app.route("/books/<int:book_id>", methods=["GET"])
@open_api.get(BookSchema, "bookDetail", many=False)
def books_detail(book_id):
    """
    description: |
        Look I can Markdown!

        | foo | bar | baz |
        | --- | --- | --- |
        | 1   | 2   | 3   |
        | 4   | 5   | 6   |
    """
    return "<p>Hello, World!</p>"


@app.route("/books", methods=["POST"])
@open_api.post(BookCreateSchema, BookSchema, "bookCreate")
def books_create():
    return "<p>Hello, World!</p>"


@app.route("/books/<int:book_id>", methods=["PATCH"])
@open_api.patch(BookUpdateSchema, BookSchema, "bookUpdate")
def books_update(book_id):
    return "<p>Hello, World!</p>"

Finally, we need to initialize OpenAPI middleware for our app:

import importlib.resources
import yaml

def load_swagger_json_template(api_name: str, api_version: str):
    text = flask.render_template_string(
        importlib.resources.files(app_resources)
        .joinpath("open_api.template.yaml")
        .read_text(),
        api_name=api_name,
    )

    data = yaml.full_load(text)
    data["info"] = dict()
    data["version"] = api_version
    return data


def load_changelog_md():
    return importlib.resources.files(app_resources).joinpath("CHANGELOG.md").read_text()


conf = OpenAPISettings(
    api_version="v1",
    api_name="Foobar API",
    app_package_name="foobar_api",
    mounted_at="/v1",
    swagger_json_template_loader=load_swagger_json_template,
    swagger_json_template_loader_kwargs={"api_name": "Foobar API", "api_version": "v1"},
    changelog_md_loader=load_changelog_md,
)

docs = OpenAPI(config=conf)
docs.init_app(app)

app_package_name must be importable Python package name. It will be searched for any marshmallow.Schema subclasses. These will be added as OpenAPI components.schemas.

Installed middleware will add some routes to serve ReDoc, SwaggerUI and swagger.json:

If you provide (optional) changelog_md_loader, API docs will include routes:

  • /v1/docs/changelog
  • /v1/docs/static/changelog.md

If you provide (optional) load_swagger_json_template, it will be used as basis for constructing swagger.json. Template could look like this:

---
title: {{ api_name }}
openapi_version: 3.0.2

servers:
  - url: http://127.0.0.1:5000
    description: |
      Flask dev server running locally on developer machine

  - url: https://foo.example.com
    description: Live API server

components:
  securitySchemes:
    access_token:
      scheme: bearer
      type: http
      bearerFormat: JWT
      description: |
        This endpoint requires [JWT](https://jwt.io/) access token.
    refresh_token:
      scheme: bearer
      type: http
      bearerFormat: JWT
      description: |
        This endpoint requires [JWT](https://jwt.io/) refresh token.

tags:
  - name: Books
    description: |
      Common documentation for all book related routes.

Serving static docs via ngnix

Add collect-static command to your app:

import shutil

import click
import flask

@app.cli.command("collect_static")
@click.argument(
    "destination_dir",
    nargs=1,
    type=click.Path(file_okay=False, dir_okay=True, writable=True, resolve_path=True),
    required=True,
)
def collect_static_command(destination_dir):
    docs.collect_static(destination_dir)
    shutil.copytree(
        flask.current_app.static_folder, destination_dir, dirs_exist_ok=True
    )
    click.echo(f"Static files collected into {destination_dir}.")

Configure nginx:

server {
    # ...

    location ^~ /v1/static {
        alias /home/user/static;
        try_files $uri $uri.html =404;
    }

    location ^~ /v1/docs {
        alias /home/user/static/docs;
        try_files $uri $uri.html =404;
    }

    # ...
}

Whenever deploying the app, call:

flask --app foobar_api collect-static /home/user/static

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flask-marshmallow-openapi-0.5.0.tar.gz (984.6 kB view details)

Uploaded Source

Built Distribution

flask_marshmallow_openapi-0.5.0-py3-none-any.whl (992.6 kB view details)

Uploaded Python 3

File details

Details for the file flask-marshmallow-openapi-0.5.0.tar.gz.

File metadata

File hashes

Hashes for flask-marshmallow-openapi-0.5.0.tar.gz
Algorithm Hash digest
SHA256 b2f05d16579fca5f77539836db544e0501cdc54e9881a4000a8669b89bb0da61
MD5 503f0b074f1729e9cbdafc49d140ddc1
BLAKE2b-256 3eb105a579bbe96b05682e9b1e2b97cafd76f8838df73a5e36d105c46ecafb73

See more details on using hashes here.

File details

Details for the file flask_marshmallow_openapi-0.5.0-py3-none-any.whl.

File metadata

File hashes

Hashes for flask_marshmallow_openapi-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 25b8d8384958239ea86c11f2b67e5473a87ea1dfcc6b69ce599cd0dba96fca83
MD5 43e1d9756ea364f3d616211ccf3af68c
BLAKE2b-256 f99ddad5e84860793258903d5b2a13a212d551230cbe9322b0beb084d4074ed7

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page