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.3.0.tar.gz (984.0 kB view details)

Uploaded Source

Built Distribution

flask_marshmallow_openapi-0.3.0-py3-none-any.whl (992.0 kB view details)

Uploaded Python 3

File details

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

File metadata

File hashes

Hashes for flask-marshmallow-openapi-0.3.0.tar.gz
Algorithm Hash digest
SHA256 9a0950498c337ccbfee3fb2a1fdcaa824c2d0b07ef3002d6713a080c00e17d56
MD5 7a8678414680fbcd3a703172aae30a37
BLAKE2b-256 fa91a0e20194fe77998ba3731e5a1b847b3940d9e1f5a1d6da26e0e4b7aae8f9

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for flask_marshmallow_openapi-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0ed7d59fad7f463d96a8d97eb8889aa1970efb58c0bcf54cc380b2526ea33b95
MD5 03f0cdaac672d9a4b22b7a8051536dd4
BLAKE2b-256 daca539d0b6b78a161777dc01d22363e4f3fa8ff5365fe084bbbb2497ad4aa69

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