2022年1月4日火曜日

The story of changing the Python web framework from Flask to FastAPI.

https://note.com/navitime_tech/n/nc0381517d067

I shared it.

NAVITIME_Tech
August 28, 2020 14:11

Hello, this is Kenni. At Navitime Japan, I am in charge of service development and release flow improvement using public transportation timetables.

This time I'm going to talk about introducing FastAPI as a web framework made by Python.

Python web framework

There are two typical web frameworks in Python.

· Django:  Full Stack Framework
· Flask:  Micro Framework

Django is said to be for large-scale development, and Flask is said to be for small-to-medium-scale development, but since the server we developed this time was a small server, we used to develop it with Flask.

However, whichever framework you use, you will need the help of plugins and third parties to use features such as:

・ OpenAPI
・ JSON Schema
・ GraphQL
・ WebSocket
・ Validation using type hints
・ Asynchronous processing
・ CORS settings
・ Support for linking with reverse proxy

Both Django and Flask have a bit weak native support for recent server-side technologies and new features in Python 3.

I've used plugins and third-party libraries to supplement these features, but if it gets stuck due to a catastrophic change due to a version upgrade, it takes a lot of time to find out which library is the cause. It took.

Such an experience makes  me want a modern framework .

Web framework later than Django, Flask

Every year, new Python web frameworks are introduced, and each framework is characterized by a design very similar to Flask.

When considering the introduction of a new framework, I investigated various points such as the following points.

・ Design as simple as Flask
・ Meet the above-mentioned functional requirements
・ Large number of GitHub stars
・ Abundant documentation

Looking at the number of GitHub stars in Python's web framework, it looks like this (as of August 25, 2020).

1. Flask:  51,800
2. Django:  51,500
3. FastAPI:  20,100
4. Tornado:  19,400
5. Django REST Framework:  18,600

We focused on FastAPI, which is in third place.

FastAPI is a framework that appeared in late 2018 and is the latest of the above. Nevertheless, because of this number of stars, it is drawing attention with considerable momentum.

What I found most different about FastAPI from other frameworks is the richness of the documentation and the intuitive design, rather than the functional differences.

Providing equivalent functionality does not mean that developers will immediately understand how to use it. However, FastAPI feels that the time it takes to achieve what you want to do is very low. I think the generous support of this Developer Experience has led to its popularity.

From the above, I decided to rewrite the server implemented in Flask with FastAPI.

FastAPI installation

Install FastAPI as follows.

$ pip install fastapi uvicorn

Uvicorn  is one of the ASGI implementations called the spiritual successor to WSGI (the mechanism that connects web servers and web framework I / Fs) used when developing Python servers.

When developing a server with Flask,  I think that Gunicorn etc. will be used as a WSGI server,  but Uvicorn is okay if you think of it as an asynchronous version.

Now let's see how to use FastAPI while comparing it with Flask. Install the following so that Flask and Gunicorn can also be used.

$ pip install flask gunicorn

Routing definition

If you rewrite the routing definition written in Flask with FastAPI, it will look like this.

Flask

from flask import Flask

app = Flask(__name__)


@app.route("/hello")
def hello():
   return {"Hello": "World!"}

FastAPI

from fastapi import FastAPI

app = FastAPI()


@app.get("/hello")
def hello():
   return {"Hello": "World!"}

It has a fairly similar implementation. Migrating to FastAPI was fairly easy because it was written in a similar way to Flask.

Start the server

Save the Flask and FastAPI source codes under the filenames flask_app.py and fastapi_app.py, respectively. The method for starting the server is as follows.

Flask

$ gunicorn flask_app:app
[2020-08-26 15:21:09 +0900] [27823] [INFO] Starting gunicorn 20.0.4
[2020-08-26 15:21:09 +0900] [27823] [INFO] Listening at: http://127.0.0.1:8000 (27823)
[2020-08-26 15:21:09 +0900] [27823] [INFO] Using worker: sync
[2020-08-26 15:21:09 +0900] [27826] [INFO] Booting worker with pid: 27826

FastAPI

$ uvicorn fastapi_app:app
INFO:     Started server process [14366]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)​

The startup method is very similar. By the way, FastAPI can also be connected to WSGI, so it can also be started with Gunicorn.

documentation

Now, from here, FastAPI will show its true value. FastAPI has the  ability  to automatically generate both Swagger UI  and  ReDoc style documents.

After starting the FastAPI server, you can see the beautiful document page by accessing the URL below.

Swagger UI:  http: // localhost: 8000 / docs

Enlarged image 1

ReDoc:  http: // localhost: 8000 / redoc

Enlarged image 2

Flask does not have the ability to automatically generate documents. To be honest, I think it's worth introducing FastAPI just to use this feature.

Request parameters

If you're using Flask, you probably don't know how to get request parameters, and many people are worried. I am also one of them.

Consider the case of creating the following API.

POST /users/{name}?age={age}
{
    "country": {country}
}

Each parameter is specified in a different part of the request.

・ {Name}: Path parameter
・ {age}: Query parameter
・ {country}: Body

Since Flask receives values ​​differently depending on where the parameter is specified, I think that people who are new to Flask will be confused. FastAPI solves that very intuitively.

Now let's see how to write it (error checking is omitted).

Flask

from flask import Flask, request

app = Flask(__name__)


@app.route("/users/<name>", methods=["POST"])
def create_user(name):
   age = request.args.get("age", type=int)
   body = request.json

   return {
       "age": age,
       "name": name,
       "country": body["country"],
   }

FastAPI

from fastapi import FastAPI, Query, Body

app = FastAPI()


@app.post("/users/{name}")
def create_user(name: str, age: int = Query(None), body: dict = Body(None)):
   return {
       "age": age,
       "name": name,
       "country": body["country"],
   }

In the case of FastAPI, all path parameters, query parameters and bodies can be defined as function arguments.

Also, the method of specifying the parameter type is intuitive and easy to understand because FastAPI is realized by using the type hint introduced in Python 3.

I remember spending a lot of time exploring how to receive query parameters and bodies in Flask. I feel that the developer experience is greatly influenced by whether it is designed so that it is easy to find a way to realize the frequently used functions in this area.

Validation by Pydantic

FastAPI ships with  a type validation library called Pydantic  , which can be used to validate the types of request and response parameters.

I will try to validate the response to the API mentioned above. Request validation can be done in the same way.

First, import the following.

from pydantic import BaseModel, Field

Next, define a schema class to validate the JSON returned as a response.

class User(BaseModel):
   age: int = Field(description="年齢", ge=0)
   name: str = Field(description="氏名")
   country: str = Field(description="出身国")

By using this class, you can validate that the JSON of the response has "age", "name", "country" fields and "age" is an integer greater than or equal to 0.

Specify this class in the decorator.

@app.post("/users/{name}", response_model=User)​

This will cause an error to be returned if you try to return a response that does not match the User schema.

The whole source code looks like this:

from fastapi import FastAPI, Query, Body
from pydantic import BaseModel, Field

app = FastAPI()


class User(BaseModel):
   age: int = Field(description="年齢", ge=0)
   name: str = Field(description="氏名")
   country: str = Field(description="出身国")


@app.post("/users/{name}", response_model=User)
def create_user(name: str, age: int = Query(None), body: dict = Body(None)):
   return {
       "age": age,
       "name": name,
       "country": body["country"],
   }

The schema description is automatically reflected in the document.

Enlarged screenshot 2020-08-26 17.17.27

summary

Flask is referred to as a micro-framework, but FastAPI is well-balanced as if it were a redefinition of the micro-framework to meet recent needs.

FastAPI has many more attractions. If you want to know other features, please see the official document.

FastAPI documentation

0 コメント:

コメントを投稿