前言

首先要说明的是,我对于 FastAPI ,自认为技术水平还不够,所以写出来的文章内容或许太过于浅显或者是简陋,不够详细完整

所以在最开始,我已经为各位整理了我认为 FastAPI 项目中可能会涉及到的一些技术的官方文档

祝各位开发一切顺利,永无 BUG !😉😋

一、FastAPI 简介

FastAPI 是一个用于构建 API 的现代、快速的 web 框架。它建立在 Starlette 和 Pydantic 之上,利用类型提示进行数据处理,并自动生成 API 文档

详细开发手册可以通过 官方文档 查看

客套话说完,来说我使用体验,FastAPI 的代码写起来着实 😋 清爽 😋,它用装饰器的方式清清白白的给你表明了 API 的路由地址

它和Flask很像,但是FlaskDjango它们都属于【同步框架】,而FastAPI属于【异步框架】,如果你不理解什么是同步什么是异步,只需要知道,异步是一种释放性能、提高运行效率的手法,而且FastAPI还有很人性化的一点是它是同步异步混合型,如果你不习惯异步编程,那么你完全可以把它当作Flask

事不宜迟,让我们快速开始使用FastAPI,让DjangoSpringBoot它们感受一下快速成型的恐怖

二、FastAPI 正式教学

2.1 安装

pip install fastapi 安装 FastAPI

pip install uvicorn 安装 它的 ASGI 服务器

如果网络环境太差,可以用国内阿里源镜像

pip install fastapi uvicorn -i https://mirrors.aliyun.com/pypi/simple

2.2 第一个 FastAPI 应用

新建一个 main.py

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def hello():
    return "Hello,world!"

if __name__ == "__main__":
    import uvicorn

    uvicorn.run("main:app", reload=True)

当然你可以不写__main__,直接终端执行 uvicorn main:app --reload也行,我比较喜欢用这样的方式,这样可以让uvicorn的执行在代码里面就控制好了

uvicorn 服务器的默认地址是 http://127.0.0.1:8000,也就是本地的8000端口,如果你想改变可以在 uvicorn.run() 里面设置 hostport

代码解释

  • from fastapi import FastAPI 导入 FastAPI 类,它是 FastAPI 的核心
  • app = FastAPI() 定义出 app 为 FastAPI 的具体实例,后面的所有路由地址都将由它衍生出去,可以把它理解为根地址
  • @app.get("/") 这是 FastAPI 的装饰器
@app.get()
@app.post()
@app.put()
@app.patch()
@app.delete()
所有常见的请求方式它都有,里面跟上路由地址即可
  • def hello() 这个函数表示是当请求发送给 http://127.0.0.1:8000/ 时触发这个hello函数
  • return "Hello,world!" 这个是返回值,如果请求成功,那么这个值会被返回给请求方的浏览器或者是 API 客户端

2.3 交互文档

FastAPI有一大顶级功能——内置的 API 交互文档,让你不再需要Postman😋

默认地址是 http://127.0.0.1:8000/docs 和 http://127.0.0.1:8000/redoc

如果你想改变它的路由地址,具体设置在 app 实例里面

app = FastAPI(
    docs_url="/docs",
    redoc_url="/redoc",
)

具体使用你只需要点进去立马会用,就是一个很普通的发送请求查看具体结果,如果发生错误了会在启动的终端里面报出来

主要需要讲一下的是,它会经常性的加载不出来或者加载很不稳定,本质是当前网络连接外网 CDN 比较慢

这里提供一个我常用的解决方法,就是用fastapi_offline,它本质是把所有资源包给你打包好直接下载到本地用,保证一定能加载运行,pip install fastapi-offline即可

from fastapi import FastAPI
app = FastAPI()

改成

from fastapi_offline import FastAPIOffline
app = FastAPIOffline()

2.4 路由操作

app 路由

这个就是简单的直接由 app 发出的路由地址

from fastapi import FastAPI

app = FastAPI()

# http://127.0.0.1:8000/hello
@app.get("/hello")
def hello():
    return "Hello!"


# http://127.0.0.1:8000/hello/world
@app.get("/hello/world")
def helloworld():
    return "Hello,world!"

router 路由

这个相当于给 app 总的路由分配子路由,具体使用也很简单

from fastapi import APIRouter

router = APIRouter(prefix="/router")
# prefix是必须设置的,如果不设置则这个router路由无效

@router.get("/hello") # 它的具体请求地址为127.0.0.1:8000/router/hello
def router_hello():
    return "这是Router下面的Hello"

@router.get("/world") # 它的具体请求地址为127.0.0.1:8000/router/world
def router_world():
    return "这是Router下面的World"

# --------------------------------------------

from fastapi import FastAPI

app = FastAPI()
app.include_router(router)
# prefix还可以在app.include_router里面设置,比如有时的批量加入router

@app.get("/hello")
def hello():
    return "这是App下面的Hello"

通常来说 router 路由是分文件编写,一般不和 app 写在一个文件内

例如文件结构为:

project/
| - main.py             # app = FastAPI()
| - user.py             # user = APIRouter(prefix="/user")
| - home.py             # home = APIRouter(prefix="/home")

在 main.py 内 app.include_router(user), app.include_router(home)

2.5 请求响应

请求参数

我们通常会见到一些 URL 地址类似于 https://xxx.xxx.com/?x=xxx&x=xxx

?后面的就是查询参数,FastAPI 正是利用查询参数作为函数的传入参

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def get_args(arg1: int, arg2: str):
    return {
        "arg1": arg1,
        "arg2": arg2,
    }

当我们访问 http://127.0.0.1:8000/?arg1=10&arg2=helloworld 时则会返回 {“arg1”:10,“arg2”:“helloworld”}

我们还可以把请求参直接写在路由里面,比如

@app.get("/{arg1}")
def get_args(arg1: int, arg2: str):
    return {
        "arg1": arg1,
        "arg2": arg2,
    }

此时我们需要访问的路由地址变成了 http://127.0.0.1:8000/10?arg2=helloworld ,arg1 的 10 不再作为查询参数放在?后面,而是直接加入在 URL 地址内,而 arg2 仍然是查询参数

数据响应

  • 基本数据返回: 对应网址请求里面的application/text,也就是各种类型的数据
@app.get("/xxx")
def func():
    ...
  • Json 数据返回: 对应网址请求里面的application/json
from fastapi.responses import JSONResponse

@app.get("/xxx")
def func() -> JSONResponse:
    ...
    return JSONResponse(context="...")
  • Pydantic 模型返回: 自定义的数据模型返回,基于 pydanticBaseModel
from fastapi import FastAPI
from pydantic import BaseModel

class DemoModel(BaseModel):
    username: str
    password: str

app = FastAPI()

@app.post("/")
def Demo(demo: DemoModel):
    return demo

2.6 依赖项

依赖项是指:一个可以多次复用的函数或对象,比如常见的数据库连接,身份验证等等

以 Mysql 数据库为例,我们希望的是

  • 项目启动时连接上数据库
  • 项目终止时关闭数据库
  • 运行过程中可以访问数据库进行增删改查
from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI
from pymysql import connect

# 数据库配置
DB_CONFIG = {
    "host": "localhost",
    "port": 3306,
    "user": "your_username",
    "password": "your_password",
    "db": "your_database",
    "charset": "utf8mb4",
}

db = None

# 连接数据库
def connect_db():
    global db
    db = connect(**DB_CONFIG)
    print("数据库连接成功")

# 断开数据库连接
def disconnect_db():
    global db
    if db:
        db.close()
        print("数据库断开连接")
        db = None

# 获取数据库
def get_db():
    return db

# 这个是Fastapi的生命周期管理,是一个异步函数
# 由于以pymysql为例,数据库的连接和关闭需要使用同步函数
@asynccontextmanager
async def lifespan(app: FastAPI):
    connect_db()
    yield
    disconnect_db()

app = FastAPI(lifespan=lifespan)

@app.get("/all")
def select_all(db = Depends(get_db)): # 把 get_db 函数作为依赖项注入给了db变量,从而使得函数内部可以以它作为数据库进行查询
    with db.cursor() as cursor:
        cursor.execute("select * from your_table_name")
        result = cursor.fetchall()
        return result

@app.get("/single")
def select_single(db = Depends(get_db)): # 再次复用 get_db 函数作为依赖项
    with db.cursor() as cursor:
        cursor.execute("select * from your_table_name where id=xxx")
        result = cursor.fetchall()
        return result

2.7 阶段性总结

到此,你已经掌握了 70% 的 FastAPI,你可能会对此感到惊讶,明明当初学 Springboot,学 Django 的时候好像不是这样的啊,但是事实就是如此,不要忘了 FastAPI 的核心——轻量级 Web 框架,如果使用不简单,那怎么谈轻量

三、项目开发推荐

3.1 项目文件结构

这是我比较喜欢的一种项目结构方式
类似于 Django 的风格

project/
| - page1/
| | - __init__.py
| | - models.py # 数据库ORM模型 -> sqlacalchemy
| | - schemas.py # 数据验证模型 -> pydantic
| | - service.py # 业务代码
| | - router.py # 路由配置
|
| - page2/
| | - __init__.py
| | - models.py
| | - schemas.py
| | - service.py
| | - router.py
|
| - core/
| | - __init__.py
| | - setting.py # 全局配置
| | - database.py # 数据库连接
| | - logger.py # 日志管理
|
| - __init__.py
| - main.py # 主入口

3.2 ORM 数据库推荐 (任选其一即可)

  • SqlAcalchemy (经典 ORM,可以搭配 Alembic 实现数据库迁移)
  • SqlModel (基于 PydanticSQLAlchemy 实现)
  • Tortoise-ORM (类似 Django-ORM 的实现)

3.3 获取 uvicorn 自带的日志

import logging

# uvicorn 主日志器
# 根据情况使用不同级别日志
uvicorn_logger = logging.getLogger("uvicorn")

# uvicorn 访问日志器
# INFO 级别日志
uvicorn_access_logger = logging.getLogger("uvicorn.access")

# uvicorn 错误日志器
# ERROR 级别日志
uvicorn_error_logger = logging.getLogger("uvicorn.error")

3.4 异步编程

以上所涉及到的代码,都是使用同步的方式来编写的,主要是为了初学者方便理解

但是想要彻底释放 FastAPI 相对于 DjangoFlask 的性能优势,那么就需要使用异步方式来编写每个函数

如果数据表本身数据量小,用户的每秒请求数少硬件性能高网络延迟低,那么其实同步和异步的方式在实际运行过程中的区别并不大,具体编程方式因个人喜好而定,反正至少 FastAPI 是个同步异步混合型框架,你可以自由的开发它

借用上面的数据库依赖项的代码示例,但是这次是纯异步方式

from contextlib import asynccontextmanager
from aiomysql import connect
from fastapi import Depends, FastAPI

DB_CONFIG = {
    "host": "localhost",
    "port": 3306,
    "user": "your_username",
    "password": "your_password",
    "db": "your_database",
    "charset": "utf8mb4",
}

db = None

async def connect_db():
    global db
    db = await connect(**DB_CONFIG)
    print("数据库连接成功")

async def disconnect_db():
    global db
    if db:
        db.close()
        print("数据库断开连接")
        db = None

async def get_db():
    return db

@asynccontextmanager
async def lifespan(app: FastAPI):
    await connect_db()
    yield
    await disconnect_db()

app = FastAPI(lifespan=lifespan)

@app.get("/all")
async def select_all(db=Depends(get_db)):
    async with db.cursor() as cursor:
        await cursor.execute("select * from your_table_name")
        result = await cursor.fetchall()
        return result