這是國外一個專門做框架性能對比的網站(TechEmpower)的數據,這是對 Python 語言的常用 web 框架的測試報告,可見 fastapi 表現優異。

這是詳細的鏈接,有興趣的可以看下:

除了在上邊數據方面的優異表現外,它還具有如下特點:

拋去這些,我覺著僅性能堪比 Golang 語言的 gin 框架這一條,就值得我們學習下。

下面我們來具體看下 FastApi 的使用。

FastApi 知識預備

在看 FastApi 之前,我們先來回憶下 Python 的異步編程和協程。

Python 的異步編程

Python 1.0 發布在 1994 年,當時對編程語言性能的要求并不像現在這么高,所以它在設計之初就更傾向于快速開發和便捷的語法。即便有性能的要求,多進程多線程也完全能夠應付。但是隨著現代互聯網的發展,對編程語言自身的性能要求就越來越高。除了多進程和多線程模型,還逐漸演化出「協程」模型(協程,是一種用戶態的更小力度的任務運行單元?;诰€程,可實現多個任務的并行。)為代表的異步編程思想。

在早期,Python 實現異步編程,需要地方的庫,如 Twisted、Tornado 等。從 Python3 開始,加入了對異步編程的原生支持。

下面這種以async def定義函數,且用await?關鍵字來調用函數的代碼,便是協程相關的代碼。

import asyncio

async def compute(x, y):
print("Compute %s + %s ..." % (x, y))
await asyncio.sleep(1.0)
return x + y

async def print_sum(x, y):
result = await compute(x, y)
print("%s + %s = %s" % (x, y, result))

loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()

協程的運行有 3 種方式,參考[5]

一些其他概念:

CGI(通用網關接口, Common Gateway Interface),簡單來說就是解析瀏覽器等客戶端發送給服務端的請求,并組裝需要返回的 HTTP 請求的一種通用協議,處理這個過程的程序,我們就可以叫 CGI 腳本。互聯網早起的動態網頁都是基于 CGI 標準的。

WSGI?是一種 Python 專用的 Web 服務器網關接口,它分為兩部分”服務器(或網關)“和”應用程序(或應用框架)”。「服務器」,一般獨立于應用框架,為應用程序運行提供環境信息和一個回調函數(Callback Function)。當應用程序完成處理請求后,透過回調函數,將結果回傳給服務器。常用的 WSGI 服務器有: uwsgi、gunicon?!笐贸绦颉?,是各種實現了 WSGI 標準的 Python web 框架了,常用的有 Django、Flask 等。

ASGI(Asynchronous Server Gateway Interface)?是 Django 團隊提出的一種具有異步功能的 Python web 服務器網關接口協議。能夠處理多種通用的協議類型,包括 HTTP,HTTP2 和 WebSocket。WSGI 是基于 HTTP 協議模式的,不支持 WebSocket,而 ASGI 的誕生則是為了解決 Python 常用的 WSGI 不支持當前 Web 開發中的一些新的協議標準(WebSocket、Http2 等)。同時,ASGI 向下兼容 WSGI 標準,可以通過一些方法跑 WSGI 的應用程序。常用的「服務器」有 Daphne、Uvicorn。

FastApi 快速開始

Hello world

FastApi 要求 Python 在 3.6 以上,它應用了很多 Python3 的新特性。下面我們安裝一下:

pip install fastapi uvicorn

我們使用uvicorn來啟動異步的 web server ,同時把uvicorn也安裝上。

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
return {"message": "Hello World"}

uvicorn 有兩種啟動方式:

if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

整體代碼看下來,和 Flask 的 Hellow world 類似,有程序實例 app 用來啟動程序,有路由和路由處理函數。

我們來看下它自帶的 Swagger 類型的文檔:

是不是很方便。

接下來,讓我們來看些復雜點的用法。

日常使用

使用 get 方法來請求數據:

@app.get('/user')
async def user(user_id: int = Query(..., title="The ID of the user to get", gt=0)):
# do somethings
return {'user_id': user_id}

QueryString 的傳遞使用了一個類Query來做參數的校驗,Query 類繼承了相關 pydandic 庫的類,實現了對參數的類型校驗,附默認值等功能。

此處...是表示 user_id 參數時必須的。若此處為 None,則表示 user_id 可選。

使用 put 方法來更新數據:

@app.put('/user/{user_id}')
async def user(user_id: int = Path(..., title="The ID of the user to get", gt=0)):
# do somethings
return {'user_id': user_id}

這里的路徑參數,可以使用類Path來做類型校驗,功能和Query類似。

使用 post 方法來創建數據:

from pydantic import BaseModel

class User(BaseModel):
name: str
age: int

@app.post("/users/")
async def create_user(user: User):
# do somethings
return user

這里定義了一個用戶類,作為函數參數的類型。在請求時,FastApi 會自動的將參數注入到該類型的各對應屬性字段。該類也起到了參數類型校驗的作用。在函數參數類型這塊,User類有點類似 Golang 中結構體定義的函數參數類型。

使用 delete 方法來刪除數據:

@app.delete('/user/{user_id}')
async def user(user_id: int = Path(..., title="The ID of the user to get", gt=0)):
# do somethings
return {'user_id': user_id}

同 put 方法的用法。

看了這些基本的用法之后,我們對 FastApi 的使用有了一個初步的了解。

我們從之前的代碼中可以看到路由和路由函數在一塊的,這種方式在路由少的情況下是非常方便的,但是隨著項目復雜度的提升,路由逐漸增多,如果路由設計不合理,便會非常的不好維護。

針對這種情況,FastApi 提供了 APIRouter類來規劃設計路由,以便適應大型的項目架構。

路由分解和版本管理

APIRouter?類的基本用法如下:

# 頁面路由
page_routes = APIRouter()
page_routes.include_router(views.router, prefix="")

# api 相關路由
api_routes = APIRouter()
api_routes.include_router(api_v1_views.router, prefix='/v1')
api_routes.include_router(api_v2_views.router, prefix='/v2')

可以通過它來拆分管理路由,最后再將所有路由注冊到根路由即可。

app = FastAPI()
app.include_router(page_routes)
app.include_router(api_routes, prefix=config.API_PREFIX)

一個真實的項目實例

接下來,讓我們看一個真實的生產環境中的項目案例:

我們從下邊幾點來詳細說下這個項目案例:

1/ ORM

首先在 ORM 選擇方面,官方推薦了強大的SQLAlchemy,它可以說是 Python web 開發中最好用的第三方 ORM 了。在同步框架 Flask 中,應該說已經成為標配。

SQLAlchemy 目前為止對異步的支持還不夠完成,官方推薦使用 Encode 組織下的異步驅動 databases(目前支持 PostgreSQL/MySQL/SQLite),只所以叫他驅動沒叫他 ORM,是因為它僅提供了和數據庫的異步鏈接的管理,并沒有對象模型。在使用的時候,可以結合SQLAlchemy或直接寫 SQL。

使用SQLAlchemy來處理同步的數據操作,使用SQLAlchemydatabases來實現異步的數據操作,完美實現了我們的需求。

2/ 數據庫遷移

SQLAlchemy 還有一個數據庫遷移的問題,它自己不支持數據庫表的變更遷移,只能刪除重建。在 Flask 中可以使用插件flask_migrate來實現數據庫表的變更遷移。

SQLAlchemy官方推薦使用alembic。一個遷移過程大概步驟如下:

# 1/ 初始化環境
alembic init migrations

# 2/ 修改配置參數

# migrations/env.py
import sys
sys.path = ['', '..'] + sys.path[1:]

from core.config import DATABASE_URL
config.set_main_option("sqlalchemy.url", str(DATABASE_URL))

...
from models.posts import PostsBase
from models.posts2 import PostsBase2
target_metadata = Base.metadata # 一個app model
target_metadata = [PostsBase.metadata, PostBase2.metadata] # 多個app model

# 3/ 生成遷移腳本
alembic revision --autogenerate -m "init"

# 4/ 應用遷移腳本到數據庫
alembic upgrade head

3/ service 拆分

從項目目錄可以看到有個 service 目錄,很顯然,當業務邏輯服務度升高時,我們可以提取很多共用的邏輯作為底層的共用邏輯。這個就比較靈活了,完全有你自己控制。各種設計模式,就可以往上懟了。

4/ Docker 化

最后我們來看下 docker 化,Dockerfile 如下:

# 我們選擇了官方 slim 鏡像,體積相對小
FROM python:3.9-slim-buster

LABEL maintainer="DeanWu <pyli.xm@gmail.com>"

# stdout 無緩沖,直接輸出
ENV PYTHONUNBUFFERED 1

# 復制代碼,調整工作目錄和腳本權限
COPY . /app

WORKDIR /app

RUN chmod +x start.sh prestart.sh start-reload.sh

# 安裝用到的工具和python 包
RUN apt-get update && \
apt-get install -y --no-install-recommends default-libmysqlclient-dev gcc libffi-dev make && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
pip install --no-cache-dir -r requirements.txt && \
rm -rf requirements.txt && \
pip install --no-cache-dir gunicorn

ENV PYTHONPATH=/app

EXPOSE 80

# 啟動命令 可添加也可不添加
#CMD ["sh", "start.sh"]

編譯和啟動,

docker build -t fastapi-mysql:v1.0 .

docker run -p 80:80 -d -e DB_CONNECTION="mysql://root:Root1024@xxxx/fastapi" fastapi-mysql:v1.0 ./start.sh

總結

到這里我們從背景到生產項目案例,已經介紹完FastApi?這個框架了,你有沒有覺著這個框架很酷呢?我今天只是簡單的介紹了框架的部分應用場景,還有很多好玩的功能,大家可以去參考它的官方文檔。也可以關注我公眾號,一起討論學習。

本文章轉載微信公眾號@碼農吳先生

上一篇:

Ocelot:.NET開源API網關提供路由管理、服務發現、鑒權限流等功能

下一篇:

Java 快速開發框架 magic-api
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費