本文翻译自 Moving from Flask to FastAPI, 作者:Amal Shaji

刚好笔者这几天上手体会 FastAPI,感受到这个结构易用和便利。之前也运用过 Python 中的 Django 和 Flask 作为项目的结构。Django 说实话上手也便利,可是学习起来有点重量级结构的感觉,FastAPI 带给我的直观体会还是很轻便的,本文翻译的这篇文章就会侧重介绍 FastAPI 和 Flask 的区别。

Python 是最盛行的编程言语之一。从脚本到 API 开发再到机器学习,Python 都有着它自己的脚印。因为 Python 重视开发者的体会和其所能供给的很多东西而大受欢迎。网络结构 Flask 便是这样一个东西,它在机器学习社区中很受欢迎。它也被广泛用于 API开发。可是有一个新的结构正在兴起: FastAPI。与 Flask 不同,FastAPI 是一个 ASGI(Asynchronous Server Gateway Interface 异步服务器网关接口)结构。与 Go 和 NodeJS 相同,FastAPI 是最快的根据 Python 的 Web 结构之一。

本文针对那些有兴趣从 Flask 转移到 FastAPI 的人,比较和对比了 Flask 和 FastAPI 的常见模式。

FastAPI vs Flask

FastAPI 的构建考虑了以下三个首要问题:

  • 速度
  • 开发者经历
  • 开放规范

你能够把 FastAPI 看作是把 Starlette、Pydantic、OpenAPI 和 JSON Schema 粘合在一同的胶水。

  • 实质上说,FastAPI 运用 Pydantic 进行数据验证,并运用 Starlette 作为东西,使其与 Flask 比较快得惊人,具有与 Node 或 Go 中的高速 Web APIs 相同的性能。
  • Starlette + Uvicorn 供给异步恳求才能,这是 Flask 所缺少的。
  • 有了 Pydantic 以及类型提示,你就能够得到一个具有主动完结功用的杰出的编辑体会。你还能够得到数据验证、序列化和反序列化(用于构建一个 API),以及主动化文档(经过 JSON Schema 和 OpenAPI )。

也便是说,Flask 的运用更为广泛,所以它经过了实战查验,而且有更大的社区支撑它。因为这两个结构都是用来扩展的,Flask 显然是赢家,因为它有巨大的插件生态系统。

主张:

  • 如果你对上述三个问题有共识,厌恶了 Flask 扩展时的很多挑选,期望运用异步恳求,或许仅仅想树立一个 RESTful API,请运用 FastAPI。
  • 如果你对 FastAPI 的成熟度不满意,需求用服务器端模板构建一个全栈运用,或许离不开一些社区保护的 Flask 扩展,就能够运用 Flask。

开端

装置

与任何其他 Python 包相同,装置十分简略。

Flask

pip install flask

# or
poetry add flask
pipenv install flask
conda install flask

FastAPI

pip install fastapi uvicorn
# or
poetry add fastapi uvicorn
pipenv install fastapi uvicorn
conda install fastapi uvicorn -c conda-forge

与 Flask 不同,FastAPI 没有内置的开发服务器,因而需求像 Uvicorn 或 Daphne 这样的 ASGI 服务器。

“Hello World” 运用

Flask

# flask_code.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
    return {"Hello": "World"}
if __name__ == "__main__":
    app.run()

FastAPI

# fastapi_code.py
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def home():
    return {"Hello": "World"}
if __name__ == "__main__":
    uvicorn.run("fastapi_code:app")

reload=True 这样的参数能够被传递到 uvicorn.run() 中,以完结开发时的热重载。

或许,您能够直接从终端发动服务器:

uvicorn run fastapi_code:app

热加载模式:

uvicorn run fastapi_code:app --reload

配置

Flask 和 FastAPI 都供给了许多选项来处理不同环境的不同配置。两者都支撑以下模式:

  1. 环境变量
  2. 配置文件
  3. 实例文件夹
  4. 类和继承

有关更多信息,请参阅其各自的文档:

  • Flask -Configuration Handling
  • FastAPI -Settings and Environment Variables

Flask

import os
from flask import Flask
class Config(object):
    MESSAGE = os.environ.get("MESSAGE")
app = Flask(__name__)
app.config.from_object(Config)
@app.route("/settings")
def get_settings():
    return { "message": app.config["MESSAGE"] }
if __name__ == "__main__":
    app.run()

现在,在你运转服务器之前,设置恰当的环境变量:

export MESSAGE="hello, world"

FastAPI

import uvicorn
from fastapi import FastAPI
from pydantic import BaseSettings
class Settings(BaseSettings):
    message: str
settings = Settings()
app = FastAPI()
@app.get("/settings")
def get_settings():
    return { "message": settings.message }
if __name__ == "__main__":
    uvicorn.run("fastapi_code:app")

相同,在运转服务器之前,设置恰当的环境变量:

export MESSAGE="hello, world"

路由, 模板和视图

HTTP 办法

Flask

from flask import request
@app.route("/", methods=["GET", "POST"])
def home():
    # handle POST
    if request.method == "POST":
        return {"Hello": "POST"}
    # handle GET
    return {"Hello": "GET"}

FastAPI

@app.get("/")
def home():
    return {"Hello": "GET"}
@app.post("/")
def home_post():
    return {"Hello": "POST"}

FastAPI 为每个办法供给单独的装修器:

@app.get("/")
@app.post("/")
@app.delete("/")
@app.patch("/")

URL 参数

经过 URL(如 /employee/1 )传递信息以办理状况:

Flask

@app.route("/employee/<int:id>")
def home():
    return {"id": id}

FastAPI

@app.get("/employee/{id}")
def home(id: int):
    return {"id": id}

URL参数的指定类似于一个 f-string 表达式。此外,你还能够运用类型提示。这里,咱们在运转时告知 Pydantic, idint 类型的。在开发中,这也能够帮助完结更好的代码完结度。

查询参数

与 URL 参数相同,查询参数(如 /employee?department=sales )也可用于办理状况(通常用于过滤或排序):

Flask

from flask import request
@app.route("/employee")
def home():
    department = request.args.get("department")
    return {"department": department}

FastAPI

@app.get("/employee")
def home(department: str):
    return {"department": department}

模板

Flask

from flask import render_template
@app.route("/")
def home():
    return render_template("index.html")

默许情况下,Flask会在 “templates “文件夹中寻找模板。

FastAPI

你需求装置 Jinja:

pip install jinja2

完结:

from fastapi import Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.get("/", response_class=HTMLResponse)
def home(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

关于 FastAPI,你需求明确地界说 “模板 “文件夹。然后关于每个呼应,需求供给恳求上下文。

静态文件

Flask

默许情况下,Flask 从“static”文件夹中供给静态文件。

FastAPI

在 FastAPI 中,需求为静态文件挂载一个文件夹:

from fastapi.staticfiles import StaticFiles
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")

异步使命

Flask

从 Flask 2.0 开端,您能够运用 async/await 创立异步路由处理程序:

@app.route("/")
async def home():
    result = await some_async_task()
    return result

有关 Flask 中异步视图的更多信息,请检查 Flask 2.0 中的异步一文。

Flask 中的异步也能够经过运用线程(并发)或多处理(并行)或 Celery 或 RQ 等东西来完结:

  1. Asynchronous Tasks with Flask and Celery
  2. Asynchronous Tasks with Flask and Redis Queue

FastAPI

因为 FastAPI 对 asyncio 的原生支撑,它极大地简化了异步使命。要运用的话,只需在视图函数中增加 async 关键字:

@app.get("/")
async def home():
    result = await some_async_task()
    return result

FastAPI 还具有后台使命功用,您能够运用它来界说回来呼应后要运转的后台使命。这关于不需求在发送回呼应之前完结的操作很有用。

from fastapi import BackgroundTasks
def process_file(filename: str):
    # process file :: takes minimum 3 secs (just an example)
    pass
@app.post("/upload/{filename}")
async def upload_and_process(filename: str, background_tasks: BackgroundTasks):
    background_tasks.add_task(process_file, filename)
    return {"message": "processing file"}

在这里,呼应将被即时发送,而不会让用户等待文件处理完结。

当你需求进行繁重的后台核算时,或许你需求一个使命队列来办理使命(tasks)和工作者(workers)时,你或许想运用Celery 而不是 BackgroundTasks。更多内容请参考 FastAPI 和 Celery 的异步使命。

依靠注入

Flask

尽管你能够完结自己的依靠注入解决方案,但 Flask 默许没有真实的一流支撑。相反,你需求运用一个外部包,如 flask-injector。

FastAPI

另一方面,FastAPI 具有处理依靠注入的强大解决方案。

例如:

from databases import Database
from fastapi import Depends
from starlette.requests import Request
from db_helpers import get_all_data
def get_db(request: Request):
    return request.app.state._db
@app.get("/data")
def get_data(db: Database = Depends(get_db)):
    return get_all_data(db)

因而,get_db 将获取对在运用程序的发动事件处理程序中创立的数据库连接的引用。 Depends 然后用于向 FastAPI 指示路由“依靠于” get_db。因而,它应该在路由处理程序中的代码之前履行,而且成果应该“注入”到路由自身。

数据校验

Flask

Flask 没有任何内部数据验证支撑。您能够运用功用强大的 Pydantic 包经过 Flask-Pydantic 进行数据验证。

FastAPI

FastAPI 如此强大的原因之一是它支撑 Pydantic。

from pydantic import BaseModel
app = FastAPI()
class Request(BaseModel):
    username: str
    password: str
@app.post("/login")
async def login(req: Request):
    if req.username == "testdriven.io" and req.password == "testdriven.io":
        return {"message": "success"}
    return {"message": "Authentication Failed"}

在这里,咱们接受一个模型 Request 的输入。该 payload 必须包括一个用户名和暗码。

# correct payload format
✗ curl -X POST 'localhost:8000/login' \
    --header 'Content-Type: application/json' \
    --data-raw '{"username": "testdriven.io","password":"testdriven.io"}'
{"message":"success"}
# incorrect payload format
✗ curl -X POST 'localhost:8000/login' \
    --header 'Content-Type: application/json' \
    --data-raw '{"username": "testdriven.io","passwords":"testdriven.io"}'
{"detail":[{"loc":["body","password"],"msg":"field required","type":"value_error.missing"}]}

注意到这个恳求。咱们把暗码 passwords 作为一个键而不是 password 传递进去。Pydantic 模型会主动告知用户,password 字段是缺失的。

序列化和反序列化

Flask

最简略的序列化办法是运用 jsonify:

from flask import jsonify
from data import get_data_as_dict
@app.route("/")
def send_data():
    return jsonify(get_data_as_dict)

关于复杂的对象,Flask 开发者经常运用 Flask-Marshmallow

FastAPI

FastAPI 主动序列化任何回来的字典 dict 。关于更复杂和结构化的数据,运用 Pydantic:

from pydantic import BaseModel
app = FastAPI()
class Request(BaseModel):
    username: str
    email: str
    password: str
class Response(BaseModel):
    username: str
    email: str
@app.post("/login", response_model=Response)
async def login(req: Request):
    if req.username == "testdriven.io" and req.password == "testdriven.io":
        return req
    return {"message": "Authentication Failed"}

在这里,咱们增加了一个包括三个输入的 Request 模型:用户名、电子邮件和暗码。咱们还界说了一个仅包括用户名和电子邮件的 Response 模型。输入 Request 模型处理反序列化,而输出 Response 模型处理对象序列化。然后经过 response_model 参数将呼应模型传递给装修器。

现在,如果咱们将恳求自身作为呼应回来,Pydantic 将省略 password ,因为咱们界说的呼应模型不包括暗码字段。

例如:

# output
✗ curl -X POST 'localhost:8000/login' \
    --header 'Content-Type: application/json' \
    --data-raw '{"username":"testdriven.io","email":"admin@testdriven.io","password":"testdriven.io"}'
{"username":"testdriven.io","email":"admin@testdriven.io"}

中间件

中间件被用来在每个恳求被视图功用处理之前运用逻辑。

Flask

class middleware:
    def __init__(self, app) -> None:
        self.app = app
    def __call__(self, environ, start_response):
        start = time.time()
        response = self.app(environ, start_response)
        end = time.time() - start
        print(f"request processed in {end} s")
        return response
app = Flask(__name__)
app.wsgi_app = middleware(app.wsgi_app)

FastAPI

from fastapi import Request
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    print(f"request processed in {process_time} s")
    return response

@app.middleware("http") 装修器是在 FastAPI 中创立中间件的必备东西。上述中间件核算处理恳求所花费的时间。视图函数处理恳求后,核算总处理时间并将其作为呼应头回来。

# flask output(logs)
request processed in 0.0010077953338623047 s
127.0.0.1 - - [22/Sep/2020 18:56:21] "GET / HTTP/1.1" 200 -
# fastapi output(logs)
request processed in 0.0009925365447998047 s
INFO:     127.0.0.1:51123 - "GET / HTTP/1.1" 200 OK

模块化

随着运用程序的发展,在某些时分你会想把类似的视图、模板、静态文件和模型组合在一同,以帮助把运用程序分解成更小的组件。

Flask

在 Flask 中,蓝图被用来完结模块化:

# blueprints/product/views.py
from flask import Blueprint
product = Blueprint("product", __name__)
@product.route("/product1")
    ...
# main.py
from blueprints.product.views import product
app.register_blueprint(product)

FastAPI

一同,在 FastAPI 中,模块化是经过 APIRouter 完结的:

# routers/product/views.py
from fastapi import APIRouter
product = APIRouter()
@product.get("/product1")
    ...
# main.py
from routers.product.views import product
app.include_router(product)

其他特色

主动文档

Flask

Flask 不会主动创立开箱即用的 API 文档。但是,有几个扩展能够处理这个问题,比如 flask-swagger 和 Flask RESTX,但它们需求额外的设置。

FastAPI

默许情况下,FastAPI 支撑 OpenAPI 以及 Swagger UI 和 ReDoc。这意味着每个端点都主动从与端点关联的元数据中记录下来。

从 Flask 转换到 FastAPI

此处列出了一切已注册的端点

从 Flask 转换到 FastAPI

替代文档

从 Flask 转换到 FastAPI

办理运用

Flask

Flask 有一个广泛运用的第三方办理包,称为 Flask-Admin,用于快速对您的模型履行 CRUD 操作。

FastAPI

截至现在,有两个盛行的 FastAPI 扩展用于此:

  1. FastAPI Admin – 功用性办理面板,供给用于对数据履行 CRUD 操作的用户界面。
  2. SQLAlchemy Admin -FastAPI/Starlette 的办理面板,可与 SQLAlchemy 模型一同运用。

身份认证

Flask

尽管 Flask 没有原生解决方案,但能够运用多个第三方扩展。

FastAPI

FastAPI 经过 fastapi.security 包原生支撑许多安全和身份验证东西。经过几行代码,您能够将根本的 HTTP 身份验证增加到您的运用程序中:

import secrets
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
app = FastAPI()
security = HTTPBasic()
def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
    correct_username = secrets.compare_digest(credentials.username, "stanleyjobson")
    correct_password = secrets.compare_digest(credentials.password, "swordfish")
    if not (correct_username and correct_password):
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
    return credentials.username
@app.get("/whoami")
def who_ami_i(username: str = Depends(get_current_username)):
    return {"username": username}

FastAPI 经过 OpenAPI 规范完结 OAuth2 和 OpenID Connect。

检查官方文档中的以下资源以获取更多信息:

  1. Security Intro
  2. Advanced Security

其他资源

  1. Web Authentication Methods Compared
  2. Adding Social Authentication to Flask
  3. Session-based Auth with Flask for Single Page Apps
  4. Securing FastAPI with JWT Token-based Authentication

CORS

CORS(跨源资源共享)中间件检查恳求是否来自允许的来历。如果是,则将恳求传递给下一个中间件或视图函数。如果不是,它会拒绝恳求,并将错误呼应发送回调用者。

Flask Flask 需求一个名为 Flask-CORS 的外部包来支撑 CORS:

pip install flask-cors

根本完结:

from flask_cors import CORS
app = Flask(__name__)
CORS(app)

FastAPI

FastAPI 原生支撑 CORS:

from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = ["*"]
app.add_middleware(CORSMiddleware, allow_origins=origins)

测试

Flask

import pytest
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
    return {"message": "OK"}
def test_hello():
    res = app.test_client().get("/")
    assert res.status_code == 200
    assert res.data == b'{"message":"OK"}\n'

FastAPI

from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.get("/")
async def home():
    return {"message": "OK"}
client = TestClient(app)
def test_home():
    res = client.get("/")
    assert res.status_code == 200
    assert res.json() == {"message": "OK"}

FastAPI 供给了一个 TestClient。有了它,你能够直接用 FastAPI 运转 pytest。有关更多信息,请检查官方文档中的测试攻略。

布置

出产服务器

Flask

Flask 默许运转开发 WSGI(Web 服务器网关接口)运用程序服务器。关于出产环境,您需求运用出产级 WSGI 运用服务器,例如 Gunicorn、uWSGI 或 mod_wsgi

装置 Gunicorn:

pip install gunicorn

发动服务:

# main.py
# app = Flask(__name__)
gunicorn main:app

FastAPI

因为 FastAPI 没有开发服务器,您将运用 Uvicorn(或 Daphne)进行开发和出产。

装置 Uvicorn:

pip install uvicorn

发动服务:

# main.py
# app = FastAPI()
uvicorn main:app

您或许期望运用 Gunicorn 来办理 Uvicorn,以便一同运用并发性(经过 Uvicorn)和并行性(经过 Gunicorn worker):

# main.py
# app = FastAPI()
gunicorn -w 3 -k uvicorn.workers.UvicornWorker main:app

Docker

Flask

FROM python3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["gunicorn", "main:app"]

这是 Flask 最简略的 Dockerfile 之一。要了解如何针对出产对其进行全面配置,请检查运用 Postgres、Gunicorn 和 Nginx 教程对 Flask 进行 Docker 化。

FastAPI

FROM python3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app"]

相同,这是一个十分简略的配置。 FastAPI 作者供给了几个出产就绪的 Dockerfile。有关更多信息,请检查官方 FastAPI 文档以及 Dockerizing FastAPI with Postgres、Uvicorn 和 Traefik 教程。

总结

退一步讲,Django 和 Flask 是两个最盛行的根据 Python 的网络结构(FastAPI 是第三大盛行结构)。不过它们(Django 和 Flask)的理念十分不同。Flask 比 Django 的优势在于 Flask 是一个微结构。程序结构由程序员自己决定,不强制履行。开发者能够在他们以为适宜的时分增加第三方扩展来改善他们的代码。也便是说,通常情况下,随着代码库的增加,需求一些简直一切网络运用都需求的通用功用。这些功用与结构的紧密结合,使得终端开发者需求自己创立和保护的代码大大减少。

本文中的代码实例也表达了相同的意思。换句话说,FastAPI 包括许多必要的功用。它还遵循严厉的规范,使你的代码能够出产并更简单保护。FastAPI 的文档也十分完善。

尽管 FastAPI 或许不像 Flask 那样久经考验,但越来越多的开发人员正在转向它来供给机器学习模型或开发 RESTful API。切换到 FastAPI 是一个不错的挑选。

官方文档

  • FastAPI
  • Flask

其他资源

  • Porting Flask to FastAPI for ML Model Serving
  • Why we switched from Flask to FastAPI for production machine learning
  • Awesome Flask
  • Awesome FastAPI

本文正在参加「金石计划」