OpenAPI 3 &
Python

Agenda

Thanks GraphQL

For making OpenAPI 3 a thing

GraphQL rise made favor for REST APIs

  • Contract between client & server is a must
  • API documentation is a need
  • Visual tools for API are great
  • And sometimes you need something more then just POST /api/graphql

There are many API specification formats

  • OpenAPI 3 (previously known as Swagger 2.0)
  • RAML
  • API Blueprint
  • CoreAPI
  • json:api
  • JSON-RPC 2.0

Why OpenAPI 3?

  • It is popular
  • It is easy to use & understand
  • It has many development tools
openapi: "3.0.2"

info:
  title: "API for my project"
  version: "1.0.0"
  ...

servers:
  ...

paths:
  ...

components:
  ...

Development Tools. Swagger UI

Swagger UI

Development Tools. Swagger UI

docker run --rm \
  -e URL="http://localhost:8080/api/openapi.yaml" \
  -h localhost -p 8081:8080 \
  swaggerapi/swagger-ui:v3.24.3

Development Tools. Swagger Editor

Swagger Editor

Development Tools. openapi-spec-validator

poetry add -D openapi-spec-validator
poetry run python -m openapi_spec_validator path/to/openapi.yaml

Development Tools. schemathesis

Tool for testing your web applications built with Open API / Swagger specifications.

poetry add -D schemathesis
poetry run schemathesis path/to/openapi.yaml

Development Tools. Various

Development Tools. openapi-generator

brew install openapi-generator
openapi-generator generate -i path/to/openapi.yaml -g python-aiohttp -o path/to/project
Egg

Prerequisites

  1. Valid openapi.yaml
  2. Unique operationId for each operation
  3. Mapping between operationId and view handler
  4. Optional: Data structures for request / response / parameters / security schemes / etc

Last version: 2.5.1. Downloads last month: 223,983
As of 2019-12-19

import connexion

app = connexion.FlaskApp(__name__, specification_dir="openapi/")
app.add_api("my_api.yaml")
app.run(port=8080)

Has Swagger UI support

Last version: 0.4.1. Downloads last month: 1,029
As of 2019-12-19

def app():
    with Configurator() as config:
        config.include("pyramid_openapi3")
        config.pyramid_openapi3_spec(
            os.path.join(os.path.dirname(__file__), "openapi.yaml")
        )
        config.pyramid_openapi3_add_explorer()
        config.add_route("todo", "/")
        config.scan(".")

        return config.make_wsgi_app()

Has Swagger UI support

Last version: 2.0.0b2. Downloads last month: 622
As of 2019-12-19

from aiohttp import web
from rororo import setup_openapi

from .views import operations


def create_app(argv: List[str] = None) -> web.Application:
    return setup_openapi(
        web.Application(),
        Path(__file__).parent / "openapi.yaml",
        operations,
        route_prefix="/api"
    )

Has not Swagger UI support

Chicken

Why you might want to generate openapi.yaml?

  • Don't like YAML
  • Don't care about your clients ¯\_(ツ)_/¯
  • Love to spend all day with:
    • DRF Serializers
    • marshmallow
    • pydantic

Latest version: 3.11.0. Downloads last month: 2,106,436
As of 2019-12-19

from django.contrib.auth.models import User
from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        fields = ("username", "email", "first_name", "last_name")
        model = User

Has not Swagger UI support

Last version: 3.1.1. Downloads last month: 526,571
As of 2019-12-19

from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin


spec = APISpec(
    title="Swagger Petstore",
    version="1.0.0",
    openapi_version="3.0.2",
    plugins=[FlaskPlugin(), MarshmallowPlugin()],
)

Has not Swagger UI support

Latest version: 0.45.0. Downloads last month: 129,940
As of 2019-12-19

from fastapi import FastAPI


app = FastAPI()


@app.get("/items/{item_id}")
async def retrieve_item(item_id: int, q: str = None):
    ...

Has Swagger UI support

And? What’s next?

Do not forget about CORS headers!

aiohttp? cors_middleware

from aiohttp_middlewares import cors_middleware

app = web.Application(middlewares=(cors_middleware(allow_all=True), ...))

Django? django-cors-headers

INSTALLED_APPS = [
    ...
    "corsheaders",
    ...
]
MIDDLEWARES = [
    ...
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.common.CommonMiddleware",
    ...
]

Choose wisely!

  • As usual, there is no silver bullet
  • But,
    • I prefer aiohttp stack, so I created rororo
    • rororo missing human exceptions, is still in beta
    • Django REST Framework OpenAPI 3 support is not ideal
    • FastAPI doesn’t work for me cause of mypy setup

Why I love eggs?

API development workflow:

  • ro: Make changes to openapi.yaml
  • ro: Implement operation handlers
  • ro: Test that OpenAPI schema handled well by aiohttp app

Why I am ok with chickens?

  • Many models? Maybe it’s need to use Django and Django REST Framework as result
  • FastAPI looks very promising
  • Maybe you really doesn’t care about API clients (monolith, one developer, etc)

Questions?

Twitter: @playpausenstop
GitHub: @playpauseandstop
Telegram: @playpauseandstop