一、FastAPI

1、介绍

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

2、安装

pip install fastapi "uvicorn[standard]" -i https://repo.huaweicloud.com/repository/pypi/simple/

3、入门案例

3.1 创建项目

  • 直接 pycharm 创建,默认有一些代码
from fastapi import FastAPI

app = FastAPI()


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


@app.get("/hello/{name}")
async def say_hello(name: str):
    return {"message": f"Hello {name}"}

3.2 启动测试

  • 启动方式一:命令启动
uvicorn main:app --reload

image-20251016171321752

4、交互式 API 文档

image-20251016171823499

5、类型提示

  • 这些"类型提示"是一种新的语法(在 Python 3.6 版本加入)用来声明一个变量的类型。通过声明变量的类型,编辑器和一些工具能给你提供更好的支持
  • 比如:原来我们定义一个函数方式如下
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"))
  • 现在定义一个函数方式如下
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"))
  • 不只是 str类型,你能够声明所有的标准 Python 类型。比如以下普通类型
    • int
    • float
    • bool
    • bytes
def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
    return item_a, item_b, item_c, item_d, item_d, item_e
  • 嵌套类型:有些容器数据结构可以包含其他的值,比如 dict、list、set 和 tuple。它们内部的值也会拥有自己的类型,你可以使用 Python 的 typing 标准库来声明这些类型以及子类型,它专门用来支持这些类型提示
# 列表
from typing import List


def process_items(items: List[str]):
    for item in items:
        print(item)
        

# 元组和集合        
from typing import Set, Tuple


def process_items(items_t: Tuple[int, int, str], items_s: Set[bytes]):
    return items_t, items_s


# 字典        
from typing import Dict


def process_items(prices: Dict[str, float]):
    for item_name, item_price in prices.items():
        print(item_name)
        print(item_price)
       
    
# 类
class Person:
    def __init__(self, name: str):
        self.name = name


def get_person_name(one_person: Person):
    return one_person.name
  • 总结:就是给函数的形式参数一个类型说明,方便调用的时候基于提示

6、Pydantic 模型

  • Pydantic 是一个用来执行数据校验的 Python 库
  • 你可以将数据的"结构"声明为具有属性的类。每个属性都拥有类型。接着你用一些值来创建这个类的实例,这些值会被校验,并被转换为适当的类型(在需要的情况下),返回一个包含所有数据的对象。然后,你将获得这个对象的所有编辑器支持
  • 基本使用案例
from datetime import datetime

from pydantic import BaseModel


class User(BaseModel):
    id: int
    name: str = "John Doe"
    signup_ts: datetime | None = None
    friends: list[int] = []


external_data = {
    "id": "123",
    "signup_ts": "2017-06-01 12:22",
    "friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123

7、读取环境变量

  • 对于有的资源,我们可能需要保护其隐私(比如注册阿里千问模型后的 API KEY),那么在代码中我们就不应该把它暴露出来

  • 这种情况下,我们可以把其放在计算机的系统变量中,然后通过这个变量的名字去访问它,步骤如下

    • 第一步:在系统变量下面,创建一个系统变量,值就是对应的内容

    image-20251017091642161

    • 第二步:打开一个新的 cmd,测试配置是否成功

    image-20251017091836705

    • 第三步:代码中读取系统变量
    import os
    
    name = os.getenv("DASHSCOPE_API_KEY")
    print(name)
    

8、并发 async / awai

  • async / await 是 FastAPI 的核心机制之一,它让 FastAPI 能够高性能地处理并发请求。我们来系统地讲清楚它的原理、用法和在 FastAPI 中的意义
  • Python 默认是同步执行的,也就是一行代码执行完再执行下一行,比如下面这个代码,如果有 100 个请求在等待网络响应,程序就会阻塞,CPU 其实是空闲的
def get_data():
    data = request_api()  # 等待网络返回
    return data

8.1 是什么

  • async 用来定义异步函数(协程)
  • await 用来等待一个异步操作完成(非阻塞)

换句话说:

  • async 表示“我可以异步执行”
  • await 表示“我现在要等待这个异步操作,但不会阻塞别人”

8.2 为什么用

  • FastAPI 基于 ASGI(Asynchronous Server Gateway Interface)
    • 支持异步请求处理
    • 能同时处理成千上万的并发请求

8.3 案例助解

  • 通过一个同步和异步案例输出结果,来助力理解
  • 安装requests和httpx库
pip install -i https://repo.huaweicloud.com/repository/pypi/simple/ requests httpx

8.3.1 同步案例

  • 案例代码
import time
import requests

def fetch(url):
    print(f"开始请求:{url}")
    resp = requests.get(url)
    print(f"结束请求:{url}")
    return resp.status_code

def main():
    start = time.time()
    urls = [
        "https://httpbin.org/delay/1",
        "https://httpbin.org/delay/1",
        "https://httpbin.org/delay/1",
    ]
    for url in urls:
        fetch(url)
    print("总耗时:", time.time() - start)

if __name__ == "__main__":
    main()
  • 输出结果
开始请求:https://httpbin.org/delay/1
结束请求:https://httpbin.org/delay/1
开始请求:https://httpbin.org/delay/1
结束请求:https://httpbin.org/delay/1
开始请求:https://httpbin.org/delay/1
结束请求:https://httpbin.org/delay/1
总耗时: 14.537713527679443

8.3.2 异步案例

  • 案例代码
    • async def:声明函数为异步
    • async with:异步上下文(非阻塞方式创建 client)
    • await client.get():等待网络返回,但不会卡住事件循环
    • response.json():拿到返回数据
import asyncio
import time
import httpx

async def fetch(url):
    print(f"开始请求:{url}")
    async with httpx.AsyncClient() as client:
        resp = await client.get(url)
    print(f"结束请求:{url}")
    return resp.status_code

async def main():
    start = time.time()
    urls = [
        "https://httpbin.org/delay/1",
        "https://httpbin.org/delay/1",
        "https://httpbin.org/delay/1",
    ]
    tasks = [fetch(url) for url in urls]
    await asyncio.gather(*tasks)
    print("总耗时:", time.time() - start)

if __name__ == "__main__":
    asyncio.run(main())
  • 输出结果
开始请求:https://httpbin.org/delay/1
开始请求:https://httpbin.org/delay/1
开始请求:https://httpbin.org/delay/1
结束请求:https://httpbin.org/delay/1
结束请求:https://httpbin.org/delay/1
结束请求:https://httpbin.org/delay/1
总耗时: 3.7947113513946533

8.4 总结对比

特性 同步版本 异步版本
写法 def async def
调用 顺序执行 并发执行
等待IO 阻塞线程 非阻塞
执行时间 约 14.5 秒 约 3.8 秒
使用库 requests httpx(异步支持)

8.5 官网介绍异步和同步

9、路由方案【重点】

9.1 请求方式划分

  • 在开发 API 时,你通常使用特定的 HTTP 方法去执行特定的行为。通常使用:

    • POST:创建数据

    • GET:读取数据

    • PUT:更新数据

    • DELETE:删除数据

  • 在 OpenAPI 中,每一个 HTTP 方法都被称为「操作」

9.2 路径装饰器

  • 在 FastAPI 中,路径装饰器(Path Decorator) 是用来定义 接口的访问路径、请求方法 的。它告诉 FastAPI: “当用户访问某个路径时,用哪个函数来处理这个请求。”
  • 通俗易懂:定义路由,提供给客户端,客户端通过这个路由访问服务器中该路由修饰的方法

9.2.1 常见的装饰器

装饰器 HTTP 方法 说明
@app.get() GET 获取数据(不修改服务器状态)
@app.post() POST 创建资源
@app.put() PUT 更新资源(整体替换)
@app.patch() PATCH 局部更新资源
@app.delete() DELETE 删除资源
@app.options() OPTIONS 获取服务器支持的方法
@app.head() HEAD 获取响应头(无响应体)

9.2.2 路径参数

  • resultful 风格,直接把参数值写在路径里面,没有参数名,请求路径定义变量接收,变量的值就是请求路径中的参数值【顺序】,然后接收到的参数给到函数的形式参数,形式参数名和请求路径定义变名字一样
  • 路径中带变量(如 /users/{user_id})
@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id}
  • {user_id} 是路径变量,FastAPI 会自动提取并转换为 int 类型。如果传入 /users/10,返回 {"user_id": 10}

9.2.3 查询参数

  • 参数在请求路径后面(如http://127.0.0.1:8000/search?q=fastapi&limit=5)
  • 方法的形式参数的名字和请求路径中的变量的 key 相同
@app.get("/search")
def search(q: str = None, limit: int = 10):
    return {"query": q, "limit": limit}

9.2.4 请求体

  • POST、PUT 请求用得比较多,DELETE、GET 不使用
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float

@app.post("/items")
def create_item(item: Item):
    return {"received": item}

9.2.5 文件

  • FastAPI 的文件上传、表单上传底层是依赖 python-multipart 包来解析请求体的
  • 安装python-multipart库
pip install python-multipart -i https://repo.huaweicloud.com/repository/pypi/simple/
  • 接收上传的文件内容
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
    contents = await file.read()
    return {
        "filename": file.filename,
        "content_type": file.content_type,
        "size": len(contents)
    }
  • 如果既有文件又有其他字符串数据

@app.post("/upload")
async def upload_file(file: UploadFile = File(...), username: str = Form(...)):
    print(usernmae)
    contents = await file.read()
    return {
        "filename": file.filename,
        "content_type": file.content_type,
        "size": len(contents)
    }

9.2.6 完整案例

  • 先把入门案例代码删掉

  • 编写接口方法,然后使用 APIPOST 进行接口测试、用 FastAPI 提供的 Swagger 也可以

  • get 请求测试【注意参数的2种不同的写法】

  • post 请求测试
from pydantic import BaseModel


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

image-20251017103435283

  • post 上传文件测试

image-20251017103907350

9.2.7 总结

  • FastAPI 换了种操作方式,和 Django 相关操作没啥区别

10、跨域配置

from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# 允许访问的资源列表,* 表示任意
origins = [
    "*",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

11、数据库

  • FastAPI 对 数据库访问方式并不限制,你可以直接用 PyMySQL(同步) 或 aiomysql(异步) 来操作 MySQL
  • 不用去学习花里胡哨的 API
特点 同步 异步
I/O 阻塞 会阻塞事件循环 不阻塞事件循环,可与 async/await 配合
使用场景 小型项目、简单 API 高并发、大型项目、实时系统
ORM 支持 SQLAlchemy ORM(同步模式)、Django ORM SQLAlchemy 1.4+ 异步模式、Tortoise ORM、Gino
复杂性 简单,上手快 略复杂,需要 async/await 语法
  • 同步:
import pymysql
from fastapi import FastAPI

app = FastAPI()

def get_connection():
    return pymysql.connect(
        host="localhost",
        user="root",
        password="root",
        database="test",
        cursorclass=pymysql.cursors.DictCursor
    )

@app.get("/users")
def get_users():
    conn = get_connection()
    try:
        with conn.cursor() as cursor:
            cursor.execute("SELECT * FROM user")
            result = cursor.fetchall()
        return result
    finally:
        conn.close()
  • 异步:每次请求都创建连接池不高效,实际项目通常在 启动时创建全局连接池
import aiomysql
from fastapi import FastAPI

app = FastAPI()


# 连接池
@app.on_event("startup")
async def startup_event():
    app.state.db_pool = await aiomysql.create_pool(
        host="localhost",
        user="root",
        password="123456",
        db="test",
        autocommit=True
    )

@app.get("/users")
async def get_users():
    pool = app.state.db_pool
    async with pool.acquire() as conn:
        async with conn.cursor(aiomysql.DictCursor) as cursor:
            await cursor.execute("SELECT * FROM user")
            result = await cursor.fetchall()
    return result
  • 总结:
    • 小型项目:PyMySQL 够用
    • 中大型或高并发项目:aiomysql + 全局连接池

12、静态资源

  • 可以使用 StaticFiles从目录中自动提供静态文件
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/upload", StaticFiles(directory="upload"), name="upload")
  • 以static为例介绍,这个 "子应用" 会被 "挂载" 到第一个 "/static" 指向的子路径。因此,任何以"/static"开头的路径都会被它处理
  • directory="static" 指向包含你的静态文件的目录名字
  • name="static" 提供了一个能被FastAPI内部使用的名字

13、jinja2 模板

  • Jinja2 是 Python 的一个现代模板引擎(Template Engine),它用于将 Python 数据动态渲染成 HTML、XML、JSON 等文本格式
  • 特点:
    1. 语法类似 Django 模板,但更灵活
    2. 支持模板继承(Template Inheritance)
    3. 支持变量替换、循环、条件判断、宏(macro)等
    4. 安全:默认会转义 HTML,防止 XSS 攻击
  • 基本概念
概念 说明 示例
变量 用 {{ variable }} 输出变量值 {{ name }}
表达式 支持运算或函数 {{ 1 + 2 }} → 3
条件 if / elif / else {% if user %}Hello {{ user }}{% endif %}
循环 for 循环列表或字典 {% for item in items %}{{ item }}{% endfor %}
注释 {# 注释内容 #} 不会输出到页面
模板继承 extends 和 block {% extends "base.html" %}

13.1 使用案例

  • FastAPI 提供了 fastapi.templating.Jinja2Templates 封装,步骤如下
  • 安装jinja2库
pip install -i https://repo.huaweicloud.com/repository/pypi/simple/ jinja2  
  • 目录结构
stu_fastapi/
├── main.py
└── static/
└── templates/
    └── index.html
  • 案例代码
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
    data = {"title": "首页", "user": {"name": "Tom"}, "items": ["苹果", "香蕉", "橘子"]}
    return templates.TemplateResponse("index.html", {"request": request, **data})
  • 页面代码:无需关心取值方式,反正不用
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
</head>
<body>
    <h1>Welcome {{ user.name }}!</h1>
    <ul>
    {% for item in items %}
        <li>{{ item }}</li>
    {% endfor %}
    </ul>
</body>
</html>

14、流式输出

  • 流式输出(Streaming Response)在 FastAPI 中主要用于逐步发送数据到客户端,而不是等整个响应完成才发送。常用于:
    • 大文件下载
    • 实时日志、聊天消息推送
    • 数据生成流(如 AI 模型输出)
  • FastAPI 提供了 StreamingResponse 类,可以接受一个 可迭代对象 或 异步生成器
  • 案例代码:SSE 可以在浏览器端实时接收事件流
import asyncio

async def event_generator():
    for i in range(20):
        yield f"data: message {i}\n\n"
        await asyncio.sleep(1)

@app.get("/sse")
async def sse():
    return StreamingResponse(event_generator(), media_type="text/event-stream")

15、父子路由

  • 可以把路由定义在其他文件中,在 main.py 中引入进来
  • 比如,在根目录下的app/api/user.py中定义子路由
from fastapi import APIRouter

router = APIRouter()


@router.get("/users")
async def get_users():
    return [{"id": 1, "name": "Tom"}, {"id": 2, "name": "Alice"}]


@router.get("/items")
async def get_items():
    return [{"id": 1, "name": "Item1"}, {"id": 2, "name": "Item2"}]
  • main.py 挂载
# 挂载子模块路由
app.include_router(users.router, prefix="/api/v1", tags=["users"])
app.include_router(users.router, prefix="/api/v1", tags=["items"])
  • 挂载说明:
  • prefix:统一前缀 /api/v1
  • tags:用于 Swagger 文档分组
  • 访问:
GET http://127.0.0.1:8000/api/v1/users
GET http://127.0.0.1:8000/api/v1/items

16、配置启动

if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host="127.0.0.1",
        port=8081,
        reload=True
    )