跑起第一个接口:安装、运行与自动文档
前面的 Python、网络与 HTTP、项目工程教程已经完成了任务客户端:发请求、收响应、跑测试。本篇开始写服务端:接收请求,返回响应,把网络与 HTTP 篇的 task_server.py 改写为 FastAPI 应用。
本节结束时,你能启动一个 FastAPI 服务,用三种方式调通它的第一个接口。
本篇新建 task-api/ 目录,沿用项目工程篇的独立虚拟环境。需要已安装 Python 3.12+;Windows 示例使用 PowerShell 7.3+,命令中的 py 在 macOS / Linux 上换成 python3。
装三件套
mkdir task-api
cd task-api
py -m venv .venv
.\.venv\Scripts\Activate.ps1fastapi 用来写接口,uvicorn 用来运行服务,httpx2 用于后续接口测试。在 task-api/ 目录新建 requirements.txt,写入这三个直接依赖:
fastapi==0.143.0
uvicorn==0.54.0
httpx2==2.13.1保存后,在 task-api/ 目录的终端执行:
.\.venv\Scripts\python.exe -m pip install -r requirements.txt先写版本清单,再按清单安装,才能让实际安装的直接依赖与正文一致。pydantic、starlette 等传递依赖会自动安装,不必手工逐个安装。
macOS / Linux 的虚拟环境激活命令是 source .venv/bin/activate,安装命令是 .venv/bin/python -m pip install -r requirements.txt。后续 Windows 命令中的 curl.exe 换成 curl。
官方文档也提供 pip install "fastapi[standard]",会安装一组可选工具。本篇只安装当前示例需要的依赖。
本篇固定使用清单中的版本,不混用其他版本教程的依赖。httpx2 到接口测试一节再使用。
第一个接口
在 task-api/ 目录中新建 app/ 文件夹,再在其中新建空文件 __init__.py,使它成为 Python 包。接着新建 app/main.py,手写下面的核心代码:
from fastapi import FastAPI
app = FastAPI()
@app.get("/ping") # 注册路由:收到 GET /ping 时调用下面的函数。
def ping():
"""不接收业务参数,返回用于确认服务启动的 JSON 数据。"""
return {"ping": "pong"}三行各做一件事:FastAPI() 造一个应用对象;@app.get("/ping") 声明"GET 请求打到 /ping 就调下面这个函数";函数返回一个字典,FastAPI 自动转成 JSON。
@app.get(...) 这种写在函数头上的叫装饰器——第一篇第 23 节见过,作用一样:给函数贴一张说明,框架读到说明就知道该做什么。
跑起来
虚拟环境已激活的前提下,直接敲 uvicorn(它装在 .venv\Scripts\ 里):
uvicorn app.main:app --reload --port 8000app.main:app 是"包.文件:对象"——app 包里 main.py 的 app。--port 8000 指定端口(这里先用默认的 8000;下一节搬任务接口时统一换到 9000,和旧 CLI 的默认地址一致)。--reload 让改代码即时生效,不用手动重启——开发时一直带着它(本篇的命令默认都带)。
启动成功的输出:
INFO: Will watch for changes in these directories: ['D:\Code\...\task-api']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [51500] using StatReload
INFO: Started server process [56864]
INFO: Waiting for application startup.
INFO: Application startup complete.前两行是监视器(--reload 带来的):盯着目录里的文件,一改就自动重载。方括号里的进程号、监视的目录路径每个人不一样。看到最后一行,服务就在 8000 端口等着了。这个窗口现在是服务视角,不要关。
三边确认它在工作
浏览器地址栏输入 http://127.0.0.1:8000/ping,页面显示:
{"ping":"pong"}另开一个终端,用 curl 看同样的东西:
curl.exe -s http://127.0.0.1:8000/ping{"ping":"pong"}回到服务窗口,多了一行请求日志:
INFO: 127.0.0.1:2112 - "GET /ping HTTP/1.1" 200 OK冒号后的数字是浏览器那边的临时端口,每次不一样。从左到右:谁来的、发了什么(方法 + 路径 + 协议)、回了什么(200 OK)。第二篇读 http.server 日志的本事,这里照样用得上。
没定义的路径
访问一个没写过的地址:
curl.exe -s http://127.0.0.1:8000/nope{"detail":"Not Found"}这个 404 响应使用 detail 键。后面会遇到的参数校验错误也使用该键,但值是错误列表,不是字符串;未处理的服务器异常则可能返回纯文本,不能一概按这个形状解析。
自动文档
浏览器打开 http://127.0.0.1:8000/docs,能看到一个接口列表页:/ping 已经列在上面,点开展开,点击 Try it out → Execute,不用写任何客户端代码就能调一次,返回 200 和 {"ping": "pong"}。
这是 FastAPI 附赠的:代码即文档。以后每加一个接口,这个页面自动多一行;第 9 节回头看它时,会发现它连数据模型都列得清清楚楚。
停服务按 Ctrl + C(在跑服务的那个窗口按)。
完整示例
- 新建
task-api/目录和虚拟环境,创建上面的requirements.txt,执行pip install -r requirements.txt。 - 建
app/包(空__init__.py),写app/main.py(上面三段式代码)。 uvicorn app.main:app --reload --port 8000启动。- 浏览器打开
http://127.0.0.1:8000/ping,确认看到{"ping":"pong"}。 - 另开终端
curl.exe -s http://127.0.0.1:8000/ping,确认输出一致。 - 浏览器打开
http://127.0.0.1:8000/docs,展开/ping,Try it out → Execute,确认返回200。 - 服务窗口
Ctrl + C停止。
预期结果
启动输出六行(进程号、监视目录每个人不一样):
INFO: Will watch for changes in these directories: ['D:\Code\...\task-api']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [51500] using StatReload
INFO: Started server process [56864]
INFO: Waiting for application startup.
INFO: Application startup complete.一次访问后多一行(客户端端口每次不同):
INFO: 127.0.0.1:2112 - "GET /ping HTTP/1.1" 200 OK常见错误
浏览器打开根路径 http://127.0.0.1:8000/ 显示 {"detail":"Not Found"}:只定义了 /ping,根路径没人管。框架对"没定义的路径"统一回这个形状,不是服务没起来。
第二个终端启动报 ERROR: [Errno 10048] error while attempting to bind on address ('127.0.0.1', 8000):端口被占了——多半是之前那个服务窗口还在跑。后半句中文大意是"每个地址只允许用一次"。关掉旧窗口(Ctrl + C),或换个端口(访问地址里也要跟着改)。
**'uvicorn' 不是内部或外部命令**:这个终端没激活虚拟环境,找不到 .venv\Scripts` 里的 uvicorn。看提示符前有没有 (.venv),没有就先执行 .\.venv\Scripts\Activate.ps1。
改了 app/main.py,刷新没变化:启动命令漏了 --reload。开发时一直带着它;不带就要手动停掉重跑。
小结
- 三个包一次装齐:
fastapi写接口、uvicorn跑服务、httpx2留到测接口时用;requirements.txt只写直接依赖。 app.main:app是"包.文件:对象";@app.get("/ping")把函数挂到路径上;返回字典自动转 JSON。- 跑服务用
uvicorn app.main:app --reload --port 8000,三边确认:浏览器、curl、服务日志。 - 没定义的路径统一回
{"detail": "Not Found"};/docs是自动生成的接口文档。 - 下一节搬真正的任务接口,端口统一换到 9000。
练习
-
操作:给
app/main.py加一个/hello接口,返回{"hello": "fastapi"},用curl调通,并在/docs里看到两个路径。 -
排查:新开一个终端启动服务,报
Errno 10048。判断问题出在哪,下一步敲什么? -
先预测、再验证:先写下访问
http://127.0.0.1:8000/会看到什么,再用浏览器验证。说出原因。
验收:第 1 题两个 curl 输出分别是 {"ping":"pong"} 与 {"hello":"fastapi"},/docs 里有两个路径;第 2 题能说出「旧的服务没关,端口被占」,解法是旧窗口 Ctrl + C 或换端口;第 3 题预测与实际一致({"detail":"Not Found"}),原因是「只定义了 /ping(与 /hello),根路径没人管」。
查看提示
第 1 题:照抄 /ping 的三行结构,只换路径、函数名和返回内容;改完不用重启(--reload 开着)。第 2 题:10048 的后半句中文说得很直白——想想"只允许用一次"指的是什么。第 3 题:先想想服务里一共定义了哪几个路径。
查看参考答案
第 1 题:app/main.py 追加:
@app.get("/hello")
def hello():
"""不接收业务参数,返回问候数据。"""
return {"hello": "fastapi"}curl.exe -s http://127.0.0.1:8000/hello{"hello":"fastapi"}刷新 /docs,路径列表里出现 /ping 与 /hello 两条。--reload 开着,改完保存即生效,不用重启;如果没生效,回头检查启动命令里有没有 --reload。
第 2 题:旧的服务进程还在 8000 端口上,新起的绑不上去。回到旧的服务窗口按 Ctrl + C 停掉,再启动;旧窗口找不到了,就换个端口(--port 8001,访问地址同步改成 :8001)。
第 3 题:看到 {"detail":"Not Found"}。服务里只定义了 /ping(加上第 1 题的 /hello),根路径 / 从没定义过——框架对没定义的路径统一回这个形状,和服务是否正常无关。