【Python框架】FastAPI快速上手

2,985 阅读9分钟

FastAPI

FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 并基于标准的 Python 类型提示。

关键特性:

  • 快速:可与 NodeJS 和 Go 并肩的极高性能(归功于 Starlette 和 Pydantic)。最快的 Python web 框架之一
  • 高效编码:提高功能开发速度约 200% 至 300%。*
  • 更少 bug:减少约 40% 的人为(开发者)导致错误。*
  • 智能:极佳的编辑器支持。处处皆可自动补全,减少调试时间。
  • 简单:设计的易于使用和学习,阅读文档的时间更短。
  • 简短:使代码重复最小化。通过不同的参数声明实现丰富功能。bug 更少。
  • 健壮:生产可用级别的代码。还有自动生成的交互式文档。
  • 标准化:基于(并完全兼容)API 的相关开放标准:OpenAPI (以前被称为 Swagger) 和 JSON Schema

依赖

Python 及更高版本

FastAPI 站在以下巨人的肩膀之上:


安装

pip install fastapi

你还会需要一个 ASGI 服务器,生产环境可以使用 Uvicorn 或者 Hypercorn

pip install "uvicorn[standard]"

示例

创建

  • 创建一个 main.py 文件并写入以下内容:
from typing import Union

from fastapi import FastAPI

app = FastAPI()


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


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}

或者使用 async def...

如果你的代码里会出现 async / await,请使用 async def

from typing import Union

from fastapi import FastAPI

app = FastAPI()


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


@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}

Note:

如果你不知道是否会用到,可以查看文档的  "In a hurry?"  章节中 关于 async 和 await 的部分

运行

通过以下命令运行服务器:

uvicorn main:app --reload
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [28720]
INFO:     Started server process [28722]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

关于 uvicorn main:app --reload 命令......

uvicorn main:app 命令含义如下:

  • mainmain.py 文件(一个 Python "模块")。
  • app:在 main.py 文件中通过 app = FastAPI() 创建的对象。
  • --reload:让服务器在更新代码后重新启动。仅在开发时使用该选项。

检查

使用浏览器访问 http://127.0.0.1:8000/items/5?q=somequery

你将会看到如下 JSON 响应:

{"item_id": 5, "q": "somequery"}

你已经创建了一个具有以下功能的 API:

  • 通过 路径 / 和 /items/{item_id} 接受 HTTP 请求。
  • 以上 路径 都接受 GET 操作(也被称为 HTTP 方法)。
  • /items/{item_id} 路径 有一个 路径参数 item_id 并且应该为 int 类型。
  • /items/{item_id} 路径 有一个可选的 str 类型的 查询参数 q

交互式 API 文档

现在访问 http://127.0.0.1:8000/docs

你会看到自动生成的交互式 API 文档(由 Swagger UI生成):

Swagger UI

可选的 API 文档

访问 http://127.0.0.1:8000/redoc

你会看到另一个自动生成的文档(由 ReDoc 生成):

ReDoc

示例升级

现在修改 main.py 文件来从 PUT 请求中接收请求体。

我们借助 Pydantic 来使用标准的 Python 类型声明请求体。

from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    price: float
    is_offer: Union[bool, None] = None


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


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}


@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
    return {"item_name": item.name, "item_id": item_id}

服务器将会自动重载(因为在上面的步骤中你向 uvicorn 命令添加了 --reload 选项)。

交互式 API 文档升级

访问 http://127.0.0.1:8000/docs

  • 交互式 API 文档将会自动更新,并加入新的请求体:

Swagger UI

  • 点击「Try it out」按钮,之后你可以填写参数并直接调用 API:

Swagger UI interaction

  • 然后点击「Execute」按钮,用户界面将会和 API 进行通信,发送参数,获取结果并在屏幕上展示:

Swagger UI interaction

可选文档升级

访问 http://127.0.0.1:8000/redoc

  • 可选文档同样会体现新加入的请求参数和请求体:

ReDoc

总结

总的来说,你就像声明函数的参数类型一样只声明了一次请求参数、请求体等的类型。

你使用了标准的现代 Python 类型来完成声明。

你不需要去学习新的语法、了解特定库的方法或类,等等。

只需要使用标准的 Python 及更高版本

举个例子,比如声明 int 类型:

item_id: int

或者一个更复杂的 Item 模型:

item: Item

......在进行一次声明之后,你将获得:

  • 编辑器支持,包括:

    • 自动补全
    • 类型检查
  • 数据校验:

    • 在校验失败时自动生成清晰的错误信息
    • 对多层嵌套的 JSON 对象依然执行校验
  • 转换 来自网络请求的输入数据为 Python 数据类型。包括以下数据:

    • JSON
    • 路径参数
    • 查询参数
    • Cookies
    • 请求头
    • 表单
    • 文件
  • 转换 输出的数据:转换 Python 数据类型为供网络传输的 JSON 数据:

    • 转换 Python 基础类型 (str、 int、 float、 bool、 list 等)
    • datetime 对象
    • UUID 对象
    • 数据库模型
    • ......以及更多其他类型
  • 自动生成的交互式 API 文档,包括两种可选的用户界面:

    • Swagger UI
    • ReDoc

回到前面的代码示例,FastAPI 将会:

  • 校验 GET 和 PUT 请求的路径中是否含有 item_id

  • 校验 GET 和 PUT 请求中的 item_id 是否为 int 类型。

    • 如果不是,客户端将会收到清晰有用的错误信息。
  • 检查 GET 请求中是否有命名为 q 的可选查询参数(比如 http://127.0.0.1:8000/items/foo?q=somequery)。

    • 因为 q 被声明为 = None,所以它是可选的。
    • 如果没有 None 它将会是必需的 (如 PUT 例子中的请求体)。
  • 对于访问 /items/{item_id} 的 PUT 请求,将请求体读取为 JSON 并:

    • 检查是否有必需属性 name 并且值为 str 类型 。
    • 检查是否有必需属性 price 并且值为 float 类型。
    • 检查是否有可选属性 is_offer, 如果有的话值应该为 bool 类型。
    • 以上过程对于多层嵌套的 JSON 对象同样也会执行
  • 自动对 JSON 进行转换或转换成 JSON。

  • 通过 OpenAPI 文档来记录所有内容,可被用于:

    • 交互式文档系统
    • 许多编程语言的客户端代码自动生成系统
  • 直接提供 2 种交互式文档 web 界面。


虽然我们才刚刚开始,但其实你已经了解了这一切是如何工作的。

尝试更改下面这行代码:

    return {"item_name": item.name, "item_id": item_id}

......从:

        ... "item_name": item.name ...

......改为:

        ... "item_price": item.price ...

......注意观察编辑器是如何自动补全属性并且还知道它们的类型:

editor support


Python 类型提示简介

Python 3.6+ 版本加入了对"类型提示"的支持。

这些 "类型提示" 是一种新的语法(在 Python 3.6 版本加入)用来声明一个变量的类型。

通过声明变量的类型,编辑器和一些工具能给你提供更好的支持。

这只是一个关于 Python 类型提示的快速入门 / 复习。它仅涵盖与 FastAPI 一起使用所需的最少部分...实际上只有很少一点。

整个 FastAPI 都基于这些类型提示构建,它们带来了许多优点和好处。

但即使你不会用到 FastAPI,了解一下类型提示也会让你从中受益。

动机

让我们从一个简单的例子开始:

Python 3.8+

def get_full_name(first_name, last_name):
    full_name = first_name.title() + " " + last_name.title()
    return full_name


print(get_full_name("john", "doe"))

运行这段程序将输出:

John Doe

这个函数做了下面这些事情:

  • 接收 first_name 和 last_name 参数。
  • 通过 title() 将每个参数的第一个字母转换为大写形式。
  • 中间用一个空格来拼接它们。

Python 3.8+

def get_full_name(first_name, last_name):
+    full_name = first_name.title() + " " + last_name.title()
    return full_name


print(get_full_name("john", "doe"))

修改示例

这是一个非常简单的程序。

现在假设你将从头开始编写这段程序。

在某一时刻,你开始定义函数,并且准备好了参数...。

现在你需要调用一个"将第一个字母转换为大写形式的方法"。

等等,那个方法是什么来着?upper?还是 uppercasefirst_uppercasecapitalize

然后你尝试向程序员老手的朋友——编辑器自动补全寻求帮助。

输入函数的第一个参数 first_name,输入点号(.)然后敲下 Ctrl+Space 来触发代码补全。

但遗憾的是并没有起什么作用:

添加类型

让我们来修改上面例子的一行代码。

我们将把下面这段代码中的函数参数从:

    first_name, last_name

改成:

    first_name: str, last_name: str

就是这样。

这些就是"类型提示":

Python 3.8+

+ def get_full_name(first_name: str, last_name: str):
    full_name = first_name.title() + " " + last_name.title()
    return full_name


print(get_full_name("john", "doe"))

这和声明默认值是不同的,例如:

    first_name="john", last_name="doe"

这两者不一样。

我们用的是冒号(:),不是等号(=)。

而且添加类型提示一般不会改变原来的运行结果。

现在假设我们又一次正在创建这个函数,这次添加了类型提示。

在同样的地方,通过 Ctrl+Space 触发自动补全,你会发现:

这样,你可以滚动查看选项,直到你找到看起来眼熟的那个:

更多动机

下面是一个已经有类型提示的函数:

Python 3.8+

+ def get_name_with_age(name: str, age: int):
    name_with_age = name + " is this old: " + age
    return name_with_age

因为编辑器已经知道了这些变量的类型,所以不仅能对代码进行补全,还能检查其中的错误:

现在你知道了必须先修复这个问题,通过 str(age) 把 age 转换成字符串:

Python 3.8+

def get_name_with_age(name: str, age: int):
+    name_with_age = name + " is this old: " + str(age)
    return name_with_age


Starlette介绍

Starlette 是一个轻量级的 ASGI 框架/工具包, 非常适合在 Python 中构建异步 Web 服务。

它已准备好用于生产,并为您提供以下功能:

  • 一个轻量级、低复杂度的 HTTP Web 框架。
  • WebSocket 支持。
  • 进程内后台任务。
  • 启动和关闭事件。
  • 基于 构建的测试客户端。httpx
  • CORS、GZip、静态文件、流式响应。
  • 会话和 Cookie 支持。
  • 100% 的测试覆盖率。
  • 100% 类型注释的代码库。
  • 很少的硬依赖项。
  • 兼容 和 后端。asyncio``trio
  • 与独立基准测试相比,整体性能出色。

安装

pip install starlette

您还需要安装 ASGI 服务器,例如 uvicorndaphne 或 hypercorn

pip install uvicorn

main.py

from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route


async def homepage(request):
    return JSONResponse({'hello': 'world'})


app = Starlette(debug=True, routes=[
    Route('/', homepage),
])

然后运行应用程序...

uvicorn main:app

参考链接