每次新建Python项目,第一件事就是创建虚拟环境,然后等pip下载依赖。这个过程少则几十秒,多则几分钟,尤其网络不稳定时pip的串行下载让人恨不得去泡杯咖啡。后来有了poetry和pipenv,依赖管理好了些,但下载速度依旧没什么质变。
大概从2024年初开始,一个叫uv的工具逐渐在Python圈子里流行起来。它是用Rust写的,由发布过black和mypy的Astral团队开发,功能覆盖了虚拟环境创建、依赖安装和锁定,甚至直接接替了pip和pip-tools的角色。实际使用下来,安装速度比pip快了两个数量级,而且它的依赖解析器比pip聪明得多——解析冲突时报错信息清晰明确,不会陷入无限回溯。
正好最近要给团队内部一个微服务搭建骨架,顺便试了试用uv配合FastAPI和Pydantic V2来做。整个过程出乎意料地顺畅,这篇文章就把从头搭到尾的完整流程记录下来,包括环境初始化、依赖管理、模型校验、路由编写和API测试。读完之后,你可以把uv直接用到自己的下一个Python项目里。
先安装uv
uv的安装非常简单,官方提供了各平台的脚本。在macOS和Linux上,一行命令即可:
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows用户可以用PowerShell安装脚本,或者通过Scoop/Chocolatey获取。安装完毕后重新打开终端,验证一下版本:
uv --version
# uv 0.2.26 (或者其他当时的最新版本)
如果已经安装了Rust工具链,也可以用cargo安装,但直接下载二进制文件更快。
创建项目并初始化虚拟环境
用uv创建一个新项目,它会同时初始化项目结构和虚拟环境,完全不需要手动调用python -m venv:
# 创建一个名为 todo-api 的新项目
uv init todo-api
# 进入项目目录
cd todo-api
这个命令会在当前目录下生成一个pyproject.toml和一个默认的hello.py(可以删掉它),同时创建一个.venv虚拟环境。接下来所有操作都会自动识别这个环境,不需要显式激活——这一点体验很好,跟node_modules的逻辑类似。
添加项目依赖
现在用uv来添加FastAPI及其所需的服务器,以及Pydantic作为数据校验工具:
# 添加FastAPI和它的标准依赖
uv add fastapi
# 添加生产级服务器 uvicorn
uv add uvicorn
# 添加 Pydantic V2(FastAPI已经内置了pydantic,这里强调版本)
uv add "pydantic>=2.0"
每条命令的执行时间通常不到两秒,因为uv会并行下载并利用缓存。安装完成后,pyproject.toml里会自动添加对应的依赖项,同时生成一个uv.lock文件锁定精确版本。这个锁文件相当于package-lock.json,保证团队其他成员或部署环境安装的依赖完全一致。
如果想一次性安装多个包,也可以直接列出名称:
uv add fastapi uvicorn pydantic
编写数据模型
我们做一个简单的TODO任务管理API。先定义两个Pydantic模型:一个用于创建任务的请求体,一个用于响应。在项目根目录新建models.py:
from pydantic import BaseModel, Field
from uuid import UUID, uuid4
from datetime import datetime
class TaskCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=100, description="任务标题")
description: str = Field("", max_length=500, description="详细描述")
is_completed: bool = Field(False)
class TaskResponse(BaseModel):
id: UUID
title: str
description: str
is_completed: bool
created_at: datetime
Pydantic V2的数据校验更快,而且Field中可以附带丰富的元信息,这些信息会自动出现在FastAPI生成的OpenAPI文档里。注意我们没有用ORM,只是用内存字典模拟数据库,目的是聚焦API搭建流程。
创建API路由
新建main.py,编写几个核心端点:创建任务、查询任务列表、查询单个任务、更新任务状态、删除任务。全部使用异步函数,虽然这里的内存操作不是异步IO,但可以方便将来替换为异步数据库驱动。
from fastapi import FastAPI, HTTPException
from models import TaskCreate, TaskResponse
from uuid import UUID, uuid4
from datetime import datetime
app = FastAPI(title="TODO API", version="1.0.0")
# 模拟数据存储
tasks_db: dict[UUID, dict] = {}
@app.post("/tasks", response_model=TaskResponse, status_code=201)
async def create_task(task: TaskCreate):
task_id = uuid4()
now = datetime.utcnow()
task_dict = task.model_dump()
task_dict["id"] = task_id
task_dict["created_at"] = now
tasks_db[task_id] = task_dict
return task_dict
@app.get("/tasks", response_model=list[TaskResponse])
async def list_tasks():
return list(tasks_db.values())
@app.get("/tasks/{task_id}", response_model=TaskResponse)
async def get_task(task_id: UUID):
task = tasks_db.get(task_id)
if not task:
raise HTTPException(status_code=404, detail="任务不存在")
return task
@app.patch("/tasks/{task_id}", response_model=TaskResponse)
async def update_task(task_id: UUID, is_completed: bool):
task = tasks_db.get(task_id)
if not task:
raise HTTPException(status_code=404, detail="任务不存在")
task["is_completed"] = is_completed
return task
@app.delete("/tasks/{task_id}", status_code=204)
async def delete_task(task_id: UUID):
if task_id not in tasks_db:
raise HTTPException(status_code=404, detail="任务不存在")
del tasks_db[task_id]
return None
代码本身很直白。注意我们几乎没有写任何参数校验的冗余逻辑——Pydantic模型已经保证了title不为空且长度合规,如果请求体不符合要求,FastAPI会自动返回422错误并指出具体字段问题。
启动服务并测试
用uv提供的uv run命令来启动服务,它会在项目的虚拟环境中执行命令,不需要手动激活:
uv run uvicorn main:app --reload
终端输出会显示服务地址,通常是http://127.0.0.1:8000。打开浏览器访问/docs路径,就能看到自动生成的Swagger UI交互文档。可以直接在页面里创建任务、查询列表、修改完成状态,所有接口都附带参数说明和示例——这些内容完全来自Pydantic模型的描述。
也可以用httpie或者curl测试几个端点:
# 创建任务
curl -X POST http://localhost:8000/tasks
-H "Content-Type: application/json"
-d '{"title":"学习uv","description":"了解其依赖管理和lock机制"}'
# 获取任务列表
curl http://localhost:8000/tasks
服务端的响应格式严格按照TaskResponse模型输出,字段命名保持Python风格的snake_case(FastAPI默认不会自动转换为camelCase,如果需要可以在模型中使用alias)。
uv的额外优势:锁文件和依赖树
查看当前项目的依赖树非常方便,只需:
uv tree
它会列出所有直接依赖和间接依赖的版本,并标记出是否被多个包依赖,帮助及时发现版本冲突。导出的锁文件可以用uv lock更新,用uv sync确保虚拟环境与锁文件一致。
如果想要生成类似requirements.txt的格式以便在其他工具中使用,也可以:
uv export --format requirements-txt
不过对于新项目,直接使用uv管理是最省心的。
关于异步与FastAPI的性能
上面的例子用async def定义端点,虽然内存字典操作不需要await,但FastAPI会将这些函数包装成协程运行,并且在处理请求时不会阻塞事件循环。如果将来我们把数据存储换成asyncpg(异步PostgreSQL驱动)或aioredis,只要在函数里await对应的异步操作就能无缝对接,不需要改动路由结构。
另外,Pydantic V2核心是用Rust重写的,序列化和验证速度比V1快了5到10倍,对高并发API场景有明显收益。
出现错误时的调试建议
如果在运行uv add时出现版本冲突,uv的错误信息会明确指出哪些包之间有冲突以及原因,通常比pip的报错易读。按照提示调整版本约束或者删除一些不必要的包即可。如果想让uv解析时更宽松,可以编辑pyproject.toml中的依赖限制(如从^2.0改为>=2.0)。
开发模式下,uv还支持直接引用本地路径的包(可编辑安装),这和pip的-e .类似,只需要在pyproject.toml中声明,然后用uv sync即可。
总结
整个搭建过程从敲第一行命令到跑通所有接口,耗时不到十分钟。uv替代了以往必须在Python项目初期手动处理的几个步骤:虚拟环境创建、依赖安装、锁定文件生成。FastAPI和Pydantic V2则配合着让API开发变成一件只需要描述数据结构就能自动拥有校验和文档的事情。
把这三样工具组合在一起,不只是让开发变快了,更重要的是团队协作中每个人的环境都一致,依赖变更可追溯,出问题时定位也更快。对于新启动的Python API项目来说,这个技术栈值得考虑。

