FastAPI基础入门

FastAPI基础入门

创建第一个 FastAPI 程序

这篇教程主要面向有一定 Python 基础、想快速了解 FastAPI

的读者,不会从零讲解所有涉及的概念。比如同步与异步、服务器、HTTP

协议等内容,我会在用到时做简单说明。如果你对某个概念感兴趣,但还不太了解,可以记下对应的关键词,再搜索相关教程深入学习。

1. 创建项目并启动服务

第一步是创建项目环境。我这里使用 uv 管理 Python 项目。首先执行

uv init 初始化项目,然后执行 uv add fastapi,将 FastAPI

添加到项目依赖中。

接下来创建 main.py,写入下面的代码:

python 复制代码
from fastapi import FastAPI
app=FastAPI()
@app.get("/")
async def root():
    return {"message":"Hello World"}

这是一个最简单的 FastAPI 程序。接下来在终端执行下面的命令,启动项目:

bash 复制代码
uv add uvicorn
uv run uvicorn main:app --reload

这里先执行了 uv add uvicorn,因为 uvicorn 也是一个需要安装的 Python

库。它是一个 ASGI Web 服务器,负责监听请求,并将请求交给 FastAPI

应用处理。ASGI 可以先简单理解为 Python Web 应用与服务器之间的一套异步接口规范,FastAPI

通过它与 Uvicorn 这样的服务器协作,暂时不需要深入了解。

下面把启动命令拆开看:

  • uv run:在当前项目的环境中运行后面的命令。
  • uvicorn:启动 Uvicorn 服务器。
  • main:app:冒号前面的 main 表示 Python 模块,也就是
    main.py;冒号后面的 app 表示该模块中 FastAPI 实例的变量名。
  • --reload:开启热重载。开发过程中修改代码后,Uvicorn
    会自动重启服务,不需要每次都手动停止再启动。

例如,最开始访问根路由时,页面返回的是 Hello World。如果我们把代码中的

World 删除,只保留 Hello,开启热重载后,Uvicorn

会自动重启。重新刷新网页,就能看到修改后的内容。

如果不加

--reload,代码修改后服务器不会自动重启,网页仍然可能显示旧的结果,需要我们手动停止并重新启动服务。需要注意,--reload

主要用于开发调试,正式部署时通常不会开启。

2. 解读 FastAPI 代码

再回到刚才的代码:

python 复制代码
from fastapi import FastAPI
app=FastAPI()
@app.get("/")
async def root():
    return {"message":"Hello World"}

逐行来看:

  • from fastapi import FastAPI:从 fastapi 库中导入 FastAPI 类。
  • app=FastAPI():创建一个 FastAPI 实例,并将它赋值给变量
    app。后续注册路由等操作都围绕这个实例进行。
  • @app.get("/"):注册一个路由,表示当客户端使用 GET 方法访问 /
    路径时,执行下面的函数。
  • async def root():定义一个异步函数
    root。当请求匹配到这个路由时,FastAPI 就会调用它。
  • return {"message":"Hello World"}:返回一个 Python 字典。FastAPI
    会将它处理成 JSON 响应,发送给客户端。

这里的 async 表示函数是异步函数。异步编程常用于处理网络请求、数据库访问等 I/O

操作,能够在等待某项操作完成时处理其他任务。不过,异步的优势并不是只要加上 async

就会自动出现,通常还需要配合 await 调用支持异步的操作。如果在异步函数中直接执行耗时的同步阻塞操作,仍然可能阻塞事件循环。

FastAPI 也支持普通的 def 函数,并不是所有接口都必须写成 async def。如果使用的库提供异步接口,可以考虑使用 async def 和 await;如果使用的是同步阻塞库,普通 def

往往更合适。FastAPI 会在线程池中执行普通 def 路由函数,避免这类同步操作直接阻塞事件循环。

当前这个 Hello World 示例没有执行任何需要等待的异步操作,因此把 root 写成普通的 def

也能正常运行。这里使用 async def,主要是为了展示 FastAPI 常见的异步路由写法。暂时不了解的话,可以搜索"同步与异步""async/await"等关键词。

程序启动后,终端会显示一个访问地址。默认情况下,本地开发地址通常是:

http://127.0.0.1:8000/

其中 127.0.0.1 表示本机,8000

是服务监听的端口。直接在浏览器中访问,就能看到接口返回的内容:

可以看到,我们在 Python 中返回的是字典,但浏览器展示的是

JSON。这部分转换由 FastAPI 自动处理,不需要我们手动把字典转换成 JSON

字符串。

3. 交互式接口文档

FastAPI 还有一个很方便的功能:自动生成交互式接口文档。

启动程序后,访问:

http://127.0.0.1:8000/docs

就可以打开交互式文档。默认使用的是 Swagger

UI,它会根据我们注册的路由和参数信息生成接口说明。

点击接口,再点击 Try it
out
,就可以填写参数并尝试调用接口。填写完成后点击 Execute

执行请求,下方的 Response body 就会显示接口返回的内容。

这个功能在开发和调试时非常实用:接口写好之后,不需要另外编写前端页面,也可以直接在文档中测试请求和查看响应。

路由

路由可以理解为:客户端访问某个 URL

时,服务器根据请求方法和路径,找到对应的处理函数。

例如,大家平时可能见过这样的 B 站视频链接:

https://www.bilibili.com/video/BV1Pin26oEYz/

其中 www.bilibili.com 是域名,后面的 /video/BV1Pin26oEYz/ 是 URL

路径。用户访问这个地址时,B

站的服务器会根据请求路径找到对应的处理逻辑,再返回视频相关的数据或页面。

回到 FastAPI:

python 复制代码
@app.get("/")

这里的 app 是 FastAPI 实例,get 表示 HTTP 请求方法,后面的 "/"

则是请求路径。常见的请求方法还有 POST、PUT、PATCH 和

DELETE,它们通常用于不同类型的操作。例如,GET 常用于获取数据,POST

常用于提交数据或创建资源。具体使用哪种方法,要根据接口的用途决定。

路径参数与 Path 参数校验

先看一个例子:

python 复制代码
from fastapi import FastAPI
app=FastAPI()
@app.get("/book/{id}")
async def get_book(id:int):
    return {"id":id,"title":f"这是第{id}本书。"}

假设我们正在开发一个图书管理系统,希望通过接口查询不同书籍的信息。显然,不可能为每一本书都单独定义一个路由。这时就可以使用路径参数,把书籍的

id 放在 URL 路径中,再根据这个 id 查询对应的信息。

例如,访问 /book/1,就可以将 1 传递给函数中的 id 参数。

可以看到,函数成功读取到了路径中的 id,并将它放进返回结果中。这里的

id:int 是 Python 的类型注解,表示 id 应该是一个整数。

FastAPI

会根据类型注解解析和校验请求参数。如果传入的内容能够转换为整数,就继续执行函数;如果传入的是字母

a 这样的内容,就无法通过整数校验,FastAPI

会返回参数校验错误,而不会正常执行接口函数。

使用 Path 约束参数

除了指定参数类型,FastAPI 还可以通过 Path

为路径参数添加更多校验条件和描述信息。

例如,假设图书管理系统中只有 50 本书,那么书籍 id 的取值范围就应该是 1

到 50。我们可以使用 Path 限制参数范围。

下面列出一些比较常用的参数:

参数 说明
... 表示参数必填
gt / ge 大于 / 大于等于
lt / le 小于 / 小于等于
description 参数描述
min_length / max_length 字符串最小长度 / 最大长度

参数不止这些,这里只列出比较常用的一部分。gt、ge、lt、le

主要用于数字约束;min_length 和 max_length 则用于字符串长度限制。

使用方式如下:

python 复制代码
from fastapi import FastAPI,Path
app=FastAPI()
@app.get("/book/{id}")
async def get_book(id:int=Path(...,ge=1,le=50,description="书籍的id,取值范围为1-50")):
    return {"id":id,"title":f"这是第{id}本书。"}

这里的 Path(...) 中,... 表示这个参数是必填的。它不是说调用 Path

时必须填写一个特殊的配置,而是通过 Python 的 Ellipsis 对象告诉

FastAPI:这个参数没有默认值,必须由请求提供。

启动程序后,可以在浏览器中尝试访问 /book/1、/book/20

等地址,这些都在允许范围内;如果传入 0 或

51,就会因为不符合约束而返回校验错误。

另外,description

主要是给接口使用者和开发者查看的说明,不会自动显示在普通接口响应中。打开

/docs,就能看到参数的必填标记、描述,以及最小值和最大值等约束信息。

查询参数与 Query 参数校验

路径参数通常用于定位某个具体资源,而查询参数更常用于筛选、分页等场景,用来告诉服务器希望查询哪些内容。

例如,新闻列表可能有很多条数据,我们不一定希望一次性返回全部内容,而是希望指定从哪里开始取,以及每次返回多少条。这时就可以使用查询参数。

python 复制代码
from fastapi import FastAPI,Query
app=FastAPI()
@app.get("/news/news_list")
async def get_news_list(
    skip:int=Query(0,description="跳过的记录",lt=100),
    limit:int=Query(10,description="返回的记录数")
):
    return {"skip":skip,"limit":limit}

Query 和前面介绍的 Path

很相似,都可以为参数添加默认值、校验条件和描述信息。

它们的一个区别是:查询参数可以设置默认值。比如这里的 skip 默认值为

0,limit 默认值为 10。如果请求时没有传递这两个参数,FastAPI

就会使用默认值。

查询参数在 URL 中的写法也和路径参数不同。它们通常放在路径后面,以 ?

开始,多个参数之间使用 & 连接。

例如:

http://127.0.0.1:8000/news/news_list?skip=5&limit=10

这里表示跳过前 5 条记录,返回接下来的 10

条记录。具体如何查询数据,还需要在接口函数中编写相应的业务逻辑。

请求体参数与 Field 字段约束

路径参数和查询参数通常放在 URL

中,适合传递少量信息。如果需要创建或更新资源,往往需要提交更多数据,例如用户注册时的用户名和密码。这类数据通常放在

HTTP 请求体(Request Body)中。

以常见的 HTTP/1.1 请求报文为例,可以从三个部分来理解:

  1. 请求行:包含请求方法、URL 和 HTTP 协议版本。
  2. 请求头:包含请求的元数据信息,例如内容类型。
  3. 请求体:包含实际提交的数据,例如 JSON。

我们直接看一个用户注册的例子:

python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel
app=FastAPI()
class User(BaseModel):
    username:str
    password:str
@app.post("/register")
async def register(user:User):
    return user

这里用到了 Pydantic 库中的 BaseModel。我们自己定义一个 User

类,让它继承 BaseModel,并声明 username 和 password 两个字段。

函数参数 user:User 告诉 FastAPI:这个接口需要接收一个符合 User

模型结构的请求体。客户端发送 JSON 后,FastAPI 会借助 Pydantic

解析并校验数据,再生成对应的 User 对象,供函数使用。

在交互式文档中也能看到,FastAPI 已经根据 User 模型自动生成了 JSON

请求体示例。执行接口后,函数将 user 返回,FastAPI 又会把它处理成 JSON

响应。

这里涉及对象和 JSON 之间的转换。通俗地说,把 JSON

数据解析成程序可以使用的对象,可以理解为反序列化;把对象转换成 JSON

数据,则可以理解为序列化。FastAPI 和 Pydantic

会帮我们处理这些工作,不需要手动完成。

使用 Field 添加字段约束

请求体中的字段也可以添加默认值、长度限制等校验条件。这里使用的是 Pydantic

提供的 Field 函数(不是从 FastAPI 导入),用来为模型字段声明默认值、校验规则和描述等元数据。它和前面介绍的

Path、Query 用途相似,但作用对象是 Pydantic 模型中的字段。

例如:

python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel,Field
app=FastAPI()
class User(BaseModel):
    username:str=Field(default="张三",min_length=2,max_length=10)
    password:str=Field(min_length=6,max_length=20)
@app.post("/register")
async def register(user:User):
    return user

这里给 username 设置了默认值 "张三",并限制字符串长度为 2 到

10;password 没有设置默认值,因此仍然是必填字段,同时长度需要在 6 到

20 之间。

修改代码后重新打开

/docs,就能看到字段的默认值和约束信息。尝试提交过短或过长的用户名、密码,也会得到对应的校验错误提示。

响应类型

客户端访问某个路由后,FastAPI

会调用对应的函数,并将函数返回的内容作为响应发送给客户端。

默认情况下,如果接口函数直接返回字典、列表等 Python 数据,FastAPI

会先将它们转换为 JSON 兼容格式,再通过 JSON 响应发送给客户端。因此,前面虽然直接返回的是 Python

字典,浏览器收到的却是 JSON 数据。如果声明了 response_model,FastAPI

还会按照响应模型校验和处理返回数据。

如果需要返回的不是 JSON,而是 HTML、纯文本、文件或其他内容,FastAPI

也提供了不同的响应类。

响应类型 用途 示例
JSONResponse 返回 JSON 数据 return JSONResponse(content={"key": "value"})
HTMLResponse 返回 HTML 内容 return HTMLResponse(html_content)
PlainTextResponse 返回纯文本 return PlainTextResponse("text")
FileResponse 返回文件 return FileResponse(path)
StreamingResponse 返回流式内容,例如通过生成器持续提供数据 生成器或异步生成器
RedirectResponse 重定向到其他 URL return RedirectResponse(url)

这里先重点了解 JSON、HTML

和文件响应。其他响应类型可以在实际使用时再深入学习。

JSON 响应

前面最简单的例子就是 JSON 响应。函数返回一个 Python 字典,FastAPI

默认将它处理成 JSON 数据。

python 复制代码
from fastapi import FastAPI
app=FastAPI()
@app.get("/hello")
async def hello():
    return {"message":"hello,world"}

启动程序后访问 /hello,就能看到对应的 JSON 数据。

HTML 响应

默认情况下,FastAPI 返回 JSON。如果希望接口返回 HTML,可以指定响应类型。

一种方式是在路由装饰器中设置

response_class,适合响应类型比较固定的场景。例如,下面的接口会将返回的字符串作为

HTML 内容发送:

python 复制代码
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app=FastAPI()
@app.get("/html",response_class=HTMLResponse)
async def get_html():
    return "<h1>这是一级标题</h1>"

访问 /html,浏览器就会将这段 HTML 渲染成一个加粗、字号较大的一级标题。

另一种方式是直接返回响应对象,例如 HTMLResponse、FileResponse 或

StreamingResponse。这种方式适合需要在函数内部决定具体响应内容或类型的场景。直接返回响应对象时,FastAPI

会直接发送这个对象,不再执行通常的自动 JSON 转换;即使声明了 response_model,也不会再自动对这个响应对象进行模型转换和字段过滤。

文件响应

如果需要返回文件,可以使用 FileResponse。

python 复制代码
from fastapi import FastAPI
from fastapi.responses import FileResponse
app=FastAPI()
@app.get("/file")
async def get_file():
    path="./111.jpg"
    return FileResponse(path)

这里准备一张名为 111.jpg 的图片,并确保程序运行时能够通过 ./111.jpg

找到它。这个相对路径是相对于启动程序时的当前工作目录而言的,并不一定是 main.py

所在的目录。通常可以在项目根目录启动程序,并把图片放在对应位置。

运行程序后访问

/file,接口就会返回这张图片。浏览器会根据响应中的文件类型决定如何处理它,例如直接展示图片。

响应模型:response_model

前面介绍的响应类型主要决定接口以什么形式返回数据,例如 JSON、HTML

或文件。

除此之外,我们还可以进一步约束 JSON

响应中的数据结构。这时就可以使用路由装饰器中的 response_model 参数。

response_model 通常接收一个 Pydantic

模型,用来定义接口应该返回哪些字段,以及这些字段分别是什么类型。FastAPI

会根据这个模型校验和处理返回数据,同时也会将模型信息展示在交互式文档中。

例如,我们定义一个 News 模型,包含 id、title 和 content

三个字段,再通过 response_model=News 指定接口的响应结构:

python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel
app=FastAPI()
class News(BaseModel):
    id:int
    title:str
    content:str
@app.get("/news/{id}",response_model=News)
async def get_news(id:int):
    return {
        "id":id,
        "title":"西游记",
        "content":"东方奇幻小说"
    }

启动程序后访问对应路由,就能看到接口正常返回了符合 News 模型的数据。

那么,如果实际返回的数据和我们定义的模型不一致呢?

例如,下面的代码没有返回 content 字段:

python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel
app=FastAPI()
class News(BaseModel):
    id:int
    title:str
    content:str
@app.get("/news/{id}",response_model=News)
async def get_news(id:int):
    return {
        "id":id,
        "title":"西游记",
    }

由于 News 模型要求必须包含 content,而接口实际返回的数据缺少这个字段,因此无法通过响应模型校验。

这类问题属于服务端返回数据不符合约定,通常会产生服务端响应校验错误,并向客户端返回 HTTP 500(Internal Server Error)。它与客户端传入错误参数时的请求校验错误不同:后者在 FastAPI 默认情况下通常返回 HTTP 422,问题出在客户端提交的数据;这里的问题则出在接口函数返回的数据。

因此,response_model

不只是为了让文档展示得更清楚,它还能帮助我们约束接口输出,避免意外返回不应该暴露的字段。

异常处理

接口运行过程中,可能会遇到各种异常情况。例如,客户端请求了一本不存在的书籍,这时就应该返回一个明确的错误信息,而不是继续返回正常数据。

HTTP 状态码用于表示请求的处理结果。简单来说:

  • 2xx 通常表示请求成功。
  • 4xx 通常表示客户端请求存在问题,例如请求的资源不存在。
  • 5xx 通常表示服务器处理请求时发生了错误。

在 FastAPI 中,可以使用 HTTPException 主动抛出 HTTP 错误。它来自

fastapi 库,常用参数包括:

  • status_code:指定返回的 HTTP 状态码。
  • detail:填写具体的错误说明。

例如,通过接口查询一本书,如果书籍 id 不存在,就返回 404 Not Found:

python 复制代码
from fastapi import FastAPI,HTTPException
app=FastAPI()
@app.get("/books/{id}")
async def get_book(id:int):
    id_list={1,2,3,4,5,6}
    if id not in id_list:
        raise HTTPException(status_code=404,detail="你查找的信息不存在")
    return {"id":id}

这里使用 raise

抛出异常。一旦执行到这行代码,当前请求就会中断正常处理流程,FastAPI

会生成对应的错误响应。

如果访问一个不存在的 id,就会收到 404 状态码,响应内容则是一个

JSON:

json 复制代码
{
  "detail": "你查找的信息不存在"
}

在交互式文档中也可以看到对应的状态码和错误信息。

相关推荐
fanruitian1 小时前
python 机器学习 ML LinearRegression KNN KMeans
python·机器学习·kmeans
鱼宵1 小时前
取集合第一个元素,Java 为什么要写三种写法?
java·windows·python·java21
卷无止境1 小时前
给AI装上一双眼睛:快速构建代码验证器的思路与落地方案
后端·python
维克兜率天2 小时前
【维克】指数调整:哪些股票会被“强制买入“?
人工智能·python·算法
come112342 小时前
Codex 下不同场景 模式选择
python
神也畏惧菠萝2 小时前
Day08 Python流程控制从入门到上瘾:if/match/while/for四件套,从B站登录一路打到猜数字游戏
python·算法·数据存储·for·while·if·ifelse
言乐63 小时前
通过 3D 动画直观演示球形瓶内可滑动半圆机构在倾倒时防粘盖、回正时刮膏体的运动学过程
python·3d·django·virtualenv·pygame
quantdash_cc3 小时前
ETF 行情数据清洗实战:字段语义拆解、标的池整理与五个校验清单
python·数据分析·pandas·量化交易·股票数据·quantdash
小熊恋旧3 小时前
Python 面向对象编程:方法、属性、继承与多态详解
开发语言·python