621 lines
13 KiB
Markdown
621 lines
13 KiB
Markdown
# Flask 到 FastAPI 迁移指南
|
||
|
||
## 📋 目录
|
||
|
||
1. [迁移概览](#迁移概览)
|
||
2. [核心概念对比](#核心概念对比)
|
||
3. [代码迁移示例](#代码迁移示例)
|
||
4. [项目迁移分析](#项目迁移分析)
|
||
5. [常见迁移问题](#常见迁移问题)
|
||
|
||
---
|
||
|
||
## 迁移概览
|
||
|
||
### 为什么迁移到 FastAPI?
|
||
|
||
1. **性能提升**:FastAPI 性能接近 NodeJS 和 Go
|
||
2. **自动文档**:自动生成 API 文档
|
||
3. **类型安全**:基于 Python 类型提示
|
||
4. **异步支持**:原生支持 async/await
|
||
5. **现代特性**:符合现代 Python 开发标准
|
||
|
||
### 迁移步骤
|
||
|
||
1. ✅ **安装 FastAPI**:`pip install fastapi uvicorn`
|
||
2. ✅ **替换应用实例**:`Flask()` → `FastAPI()`
|
||
3. ✅ **更新路由装饰器**:基本语法相同
|
||
4. ✅ **处理异步**:添加 `async/await`
|
||
5. ✅ **数据验证**:使用 Pydantic 模型
|
||
6. ✅ **更新响应**:使用 FastAPI 响应类
|
||
|
||
---
|
||
|
||
## 核心概念对比
|
||
|
||
### 1. 应用实例
|
||
|
||
#### Flask
|
||
```python
|
||
from flask import Flask
|
||
|
||
app = Flask(__name__)
|
||
```
|
||
|
||
#### FastAPI
|
||
```python
|
||
from fastapi import FastAPI
|
||
|
||
app = FastAPI()
|
||
```
|
||
|
||
### 2. 路由定义
|
||
|
||
#### Flask
|
||
```python
|
||
@app.route('/items', methods=['GET'])
|
||
def get_items():
|
||
return jsonify({'items': []})
|
||
```
|
||
|
||
#### FastAPI
|
||
```python
|
||
@app.get('/items')
|
||
async def get_items():
|
||
return {'items': []}
|
||
```
|
||
|
||
**区别**:
|
||
- FastAPI 使用 `@app.get()` 而不是 `@app.route(methods=['GET'])`
|
||
- FastAPI 函数通常是 `async`
|
||
- FastAPI 直接返回字典,自动转换为 JSON
|
||
|
||
### 3. 获取请求数据
|
||
|
||
#### Flask
|
||
```python
|
||
from flask import request
|
||
|
||
@app.route('/items', methods=['POST'])
|
||
def create_item():
|
||
data = request.get_json()
|
||
name = data.get('name')
|
||
return jsonify({'name': name})
|
||
```
|
||
|
||
#### FastAPI
|
||
```python
|
||
from fastapi import Request
|
||
from pydantic import BaseModel
|
||
|
||
class Item(BaseModel):
|
||
name: str
|
||
|
||
@app.post('/items')
|
||
async def create_item(item: Item):
|
||
return {'name': item.name}
|
||
```
|
||
|
||
**区别**:
|
||
- FastAPI 使用 Pydantic 模型自动验证
|
||
- 不需要手动获取 JSON 数据
|
||
- 类型自动验证和转换
|
||
|
||
### 4. 路径参数
|
||
|
||
#### Flask
|
||
```python
|
||
@app.route('/items/<int:item_id>')
|
||
def get_item(item_id):
|
||
return jsonify({'item_id': item_id})
|
||
```
|
||
|
||
#### FastAPI
|
||
```python
|
||
@app.get('/items/{item_id}')
|
||
async def get_item(item_id: int):
|
||
return {'item_id': item_id}
|
||
```
|
||
|
||
**区别**:
|
||
- FastAPI 使用 `{item_id}` 而不是 `<int:item_id>`
|
||
- 类型在函数参数中指定
|
||
|
||
### 5. 查询参数
|
||
|
||
#### Flask
|
||
```python
|
||
from flask import request
|
||
|
||
@app.route('/items')
|
||
def get_items():
|
||
skip = request.args.get('skip', 0, type=int)
|
||
limit = request.args.get('limit', 10, type=int)
|
||
return jsonify({'skip': skip, 'limit': limit})
|
||
```
|
||
|
||
#### FastAPI
|
||
```python
|
||
@app.get('/items')
|
||
async def get_items(skip: int = 0, limit: int = 10):
|
||
return {'skip': skip, 'limit': limit}
|
||
```
|
||
|
||
**区别**:
|
||
- FastAPI 查询参数直接在函数参数中定义
|
||
- 默认值自动处理
|
||
- 类型自动验证
|
||
|
||
### 6. 请求头
|
||
|
||
#### Flask
|
||
```python
|
||
from flask import request
|
||
|
||
@app.route('/items')
|
||
def get_items():
|
||
user_agent = request.headers.get('User-Agent')
|
||
return jsonify({'user_agent': user_agent})
|
||
```
|
||
|
||
#### FastAPI
|
||
```python
|
||
from fastapi import Request, Header
|
||
|
||
@app.get('/items')
|
||
async def get_items(request: Request):
|
||
user_agent = request.headers.get('user-agent')
|
||
return {'user_agent': user_agent}
|
||
|
||
# 或者使用 Header
|
||
@app.get('/items')
|
||
async def get_items(user_agent: str = Header(None)):
|
||
return {'user_agent': user_agent}
|
||
```
|
||
|
||
### 7. 响应
|
||
|
||
#### Flask
|
||
```python
|
||
from flask import jsonify, Response
|
||
|
||
@app.route('/items')
|
||
def get_items():
|
||
return jsonify({'items': []})
|
||
# 或
|
||
return Response('text', mimetype='text/plain')
|
||
```
|
||
|
||
#### FastAPI
|
||
```python
|
||
from fastapi.responses import JSONResponse, PlainTextResponse
|
||
|
||
@app.get('/items')
|
||
async def get_items():
|
||
return {'items': []} # 自动转换为 JSON
|
||
# 或
|
||
return JSONResponse({'items': []})
|
||
# 或
|
||
return PlainTextResponse('text')
|
||
```
|
||
|
||
### 8. 错误处理
|
||
|
||
#### Flask
|
||
```python
|
||
from flask import abort
|
||
|
||
@app.route('/items/<int:item_id>')
|
||
def get_item(item_id):
|
||
if item_id not in items:
|
||
abort(404, description="Item not found")
|
||
return jsonify({'item_id': item_id})
|
||
```
|
||
|
||
#### FastAPI
|
||
```python
|
||
from fastapi import HTTPException
|
||
|
||
@app.get('/items/{item_id}')
|
||
async def get_item(item_id: int):
|
||
if item_id not in items:
|
||
raise HTTPException(status_code=404, detail="Item not found")
|
||
return {'item_id': item_id}
|
||
```
|
||
|
||
### 9. 应用状态
|
||
|
||
#### Flask
|
||
```python
|
||
from flask import g
|
||
|
||
@app.before_request
|
||
def before_request():
|
||
g.db = get_database()
|
||
|
||
@app.route('/items')
|
||
def get_items():
|
||
db = g.db
|
||
return jsonify({'items': []})
|
||
```
|
||
|
||
#### FastAPI
|
||
```python
|
||
@app.on_event("startup")
|
||
def startup():
|
||
app.state.db = get_database()
|
||
|
||
@app.get('/items')
|
||
async def get_items():
|
||
db = app.state.db
|
||
return {'items': []}
|
||
```
|
||
|
||
### 10. 中间件
|
||
|
||
#### Flask
|
||
```python
|
||
@app.before_request
|
||
def before_request():
|
||
# 请求前处理
|
||
pass
|
||
|
||
@app.after_request
|
||
def after_request(response):
|
||
# 请求后处理
|
||
response.headers['X-Custom'] = 'value'
|
||
return response
|
||
```
|
||
|
||
#### FastAPI
|
||
```python
|
||
@app.middleware("http")
|
||
async def add_custom_header(request: Request, call_next):
|
||
# 请求前处理
|
||
response = await call_next(request)
|
||
# 请求后处理
|
||
response.headers['X-Custom'] = 'value'
|
||
return response
|
||
```
|
||
|
||
---
|
||
|
||
## 代码迁移示例
|
||
|
||
### 示例 1:简单的 API 端点
|
||
|
||
#### Flask 版本
|
||
```python
|
||
from flask import Flask, request, jsonify
|
||
|
||
app = Flask(__name__)
|
||
|
||
@app.route('/api/users', methods=['POST'])
|
||
def create_user():
|
||
data = request.get_json()
|
||
if not data or 'name' not in data:
|
||
return jsonify({'error': '缺少 name 字段'}), 400
|
||
|
||
name = data['name']
|
||
age = data.get('age', 0)
|
||
|
||
return jsonify({'name': name, 'age': age}), 201
|
||
```
|
||
|
||
#### FastAPI 版本
|
||
```python
|
||
from fastapi import FastAPI, HTTPException
|
||
from pydantic import BaseModel
|
||
|
||
app = FastAPI()
|
||
|
||
class User(BaseModel):
|
||
name: str
|
||
age: int = 0
|
||
|
||
@app.post('/api/users', status_code=201)
|
||
async def create_user(user: User):
|
||
return {'name': user.name, 'age': user.age}
|
||
```
|
||
|
||
**改进点**:
|
||
- ✅ 自动数据验证
|
||
- ✅ 类型安全
|
||
- ✅ 更简洁的代码
|
||
- ✅ 自动生成 API 文档
|
||
|
||
### 示例 2:带路径参数的端点
|
||
|
||
#### Flask 版本
|
||
```python
|
||
@app.route('/api/users/<int:user_id>', methods=['GET'])
|
||
def get_user(user_id):
|
||
if user_id not in users:
|
||
return jsonify({'error': '用户不存在'}), 404
|
||
return jsonify(users[user_id])
|
||
```
|
||
|
||
#### FastAPI 版本
|
||
```python
|
||
@app.get('/api/users/{user_id}')
|
||
async def get_user(user_id: int):
|
||
if user_id not in users:
|
||
raise HTTPException(status_code=404, detail='用户不存在')
|
||
return users[user_id]
|
||
```
|
||
|
||
### 示例 3:文件上传
|
||
|
||
#### Flask 版本
|
||
```python
|
||
from flask import request
|
||
|
||
@app.route('/upload', methods=['POST'])
|
||
def upload_file():
|
||
if 'file' not in request.files:
|
||
return jsonify({'error': '没有文件'}), 400
|
||
|
||
file = request.files['file']
|
||
# 处理文件
|
||
return jsonify({'filename': file.filename})
|
||
```
|
||
|
||
#### FastAPI 版本
|
||
```python
|
||
from fastapi import UploadFile, File
|
||
|
||
@app.post('/upload')
|
||
async def upload_file(file: UploadFile = File(...)):
|
||
# 处理文件
|
||
return {'filename': file.filename}
|
||
```
|
||
|
||
---
|
||
|
||
## 项目迁移分析
|
||
|
||
### 当前项目的迁移情况
|
||
|
||
#### 1. 应用初始化(main.py)
|
||
|
||
**Flask 版本(推测)**:
|
||
```python
|
||
from flask import Flask
|
||
|
||
app = Flask(__name__)
|
||
|
||
@app.before_first_request
|
||
def initialize():
|
||
app.config['app_tools'] = AppTools(Config)
|
||
# ...
|
||
```
|
||
|
||
**FastAPI 版本(当前)**:
|
||
```python
|
||
from fastapi import FastAPI
|
||
|
||
app = FastAPI(title="简道云FastAPI服务")
|
||
|
||
@app.on_event("startup")
|
||
def on_startup():
|
||
app.state.app_tools = AppTools(Config)
|
||
app.state.logger = setup_global_logger(Config)
|
||
# ...
|
||
```
|
||
|
||
**迁移要点**:
|
||
- ✅ `Flask()` → `FastAPI()`
|
||
- ✅ `@app.before_first_request` → `@app.on_event("startup")`
|
||
- ✅ `app.config` → `app.state`
|
||
|
||
#### 2. 路由处理(main.py)
|
||
|
||
**Flask 版本(推测)**:
|
||
```python
|
||
@app.route('/webhook', methods=['POST'])
|
||
def webhook():
|
||
data = request.get_json()
|
||
header = request.headers
|
||
# 处理逻辑
|
||
return jsonify(result)
|
||
```
|
||
|
||
**FastAPI 版本(当前)**:
|
||
```python
|
||
@app.post("/webhook")
|
||
async def webhook(request: Request):
|
||
data = await request.json()
|
||
header = request.headers
|
||
# 处理逻辑
|
||
return JSONResponse(result)
|
||
```
|
||
|
||
**迁移要点**:
|
||
- ✅ `@app.route(methods=['POST'])` → `@app.post()`
|
||
- ✅ `def` → `async def`
|
||
- ✅ `request.get_json()` → `await request.json()`
|
||
- ✅ `jsonify()` → `JSONResponse()` 或直接返回字典
|
||
|
||
#### 3. 任务队列处理
|
||
|
||
**项目特点**:
|
||
- 使用自定义任务队列(`Queue` + `threading`)
|
||
- 在异步函数中调用同步函数
|
||
|
||
**FastAPI 处理方式**:
|
||
```python
|
||
# 在线程池中执行同步函数
|
||
result = await anyio.to_thread.run_sync(response_queue.get)
|
||
```
|
||
|
||
**迁移要点**:
|
||
- ✅ 使用 `anyio.to_thread.run_sync()` 在线程池中执行同步代码
|
||
- ✅ 保持原有的任务队列机制
|
||
|
||
---
|
||
|
||
## 常见迁移问题
|
||
|
||
### Q1: 如何处理 Flask 的 `g` 对象?
|
||
|
||
**A:** 使用 `app.state` 或依赖注入:
|
||
|
||
```python
|
||
# Flask
|
||
from flask import g
|
||
g.db = get_db()
|
||
|
||
# FastAPI 方式 1:使用 app.state
|
||
app.state.db = get_db()
|
||
db = app.state.db
|
||
|
||
# FastAPI 方式 2:使用依赖注入(推荐)
|
||
def get_db():
|
||
db = "database"
|
||
yield db
|
||
|
||
@app.get("/items")
|
||
async def get_items(db: str = Depends(get_db)):
|
||
return {"db": db}
|
||
```
|
||
|
||
### Q2: 如何处理 Flask 的 `session`?
|
||
|
||
**A:** FastAPI 不内置 session,需要手动实现或使用第三方库:
|
||
|
||
```python
|
||
from fastapi import FastAPI, Request
|
||
from starlette.middleware.sessions import SessionMiddleware
|
||
|
||
app = FastAPI()
|
||
app.add_middleware(SessionMiddleware, secret_key="secret")
|
||
|
||
@app.post("/login")
|
||
async def login(request: Request):
|
||
request.session['user'] = 'username'
|
||
return {"message": "Logged in"}
|
||
```
|
||
|
||
### Q3: 如何处理 Flask 的 `url_for`?
|
||
|
||
**A:** 使用 `Request` 对象构建 URL:
|
||
|
||
```python
|
||
from fastapi import Request
|
||
|
||
@app.get("/items/{item_id}")
|
||
async def get_item(item_id: int, request: Request):
|
||
url = str(request.url_for('get_item', item_id=item_id))
|
||
return {"url": url}
|
||
```
|
||
|
||
### Q4: 如何迁移 Flask 的蓝图(Blueprint)?
|
||
|
||
**A:** 使用 FastAPI 的 `APIRouter`:
|
||
|
||
```python
|
||
from fastapi import APIRouter
|
||
|
||
router = APIRouter()
|
||
|
||
@router.get("/items")
|
||
async def get_items():
|
||
return {"items": []}
|
||
|
||
# 在主应用中注册
|
||
app.include_router(router, prefix="/api")
|
||
```
|
||
|
||
### Q5: 如何处理 Flask 的 `current_app`?
|
||
|
||
**A:** 使用依赖注入或直接访问 `app`:
|
||
|
||
```python
|
||
# Flask
|
||
from flask import current_app
|
||
config = current_app.config
|
||
|
||
# FastAPI
|
||
from fastapi import Request
|
||
|
||
@app.get("/config")
|
||
async def get_config(request: Request):
|
||
app = request.app
|
||
# 访问应用配置
|
||
return {"config": "value"}
|
||
```
|
||
|
||
### Q6: 如何迁移 Flask 的 `before_request` 和 `after_request`?
|
||
|
||
**A:** 使用中间件:
|
||
|
||
```python
|
||
@app.middleware("http")
|
||
async def middleware(request: Request, call_next):
|
||
# before_request 逻辑
|
||
print("请求前")
|
||
|
||
response = await call_next(request)
|
||
|
||
# after_request 逻辑
|
||
print("请求后")
|
||
response.headers["X-Custom"] = "value"
|
||
|
||
return response
|
||
```
|
||
|
||
---
|
||
|
||
## 迁移检查清单
|
||
|
||
### 基础迁移
|
||
- [ ] 替换 `Flask()` 为 `FastAPI()`
|
||
- [ ] 更新路由装饰器(`@app.route()` → `@app.get()` 等)
|
||
- [ ] 添加 `async` 关键字
|
||
- [ ] 更新请求数据获取方式
|
||
- [ ] 更新响应返回方式
|
||
|
||
### 高级迁移
|
||
- [ ] 使用 Pydantic 模型进行数据验证
|
||
- [ ] 迁移应用状态(`app.config` → `app.state`)
|
||
- [ ] 更新生命周期事件(`before_first_request` → `on_event("startup")`)
|
||
- [ ] 迁移中间件
|
||
- [ ] 更新错误处理
|
||
|
||
### 测试和优化
|
||
- [ ] 测试所有 API 端点
|
||
- [ ] 验证数据验证功能
|
||
- [ ] 检查性能提升
|
||
- [ ] 更新文档
|
||
- [ ] 更新依赖项
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
### 迁移优势
|
||
|
||
1. **性能提升**:FastAPI 性能显著优于 Flask
|
||
2. **开发效率**:自动文档、自动验证减少开发时间
|
||
3. **类型安全**:类型提示提供更好的 IDE 支持
|
||
4. **现代特性**:异步支持、依赖注入等现代特性
|
||
|
||
### 注意事项
|
||
|
||
1. **异步编程**:需要理解 `async/await`
|
||
2. **Pydantic**:需要学习 Pydantic 模型定义
|
||
3. **依赖注入**:FastAPI 的依赖注入系统需要适应
|
||
4. **生态系统**:Flask 的某些扩展可能需要替代方案
|
||
|
||
### 建议
|
||
|
||
1. **逐步迁移**:不要一次性迁移所有代码
|
||
2. **保持兼容**:在迁移过程中保持 API 兼容性
|
||
3. **充分测试**:迁移后充分测试所有功能
|
||
4. **学习资源**:参考 FastAPI 官方文档和示例
|
||
|
||
---
|
||
|
||
**提示**:这个迁移指南帮助你理解从 Flask 到 FastAPI 的迁移过程。结合 `FASTAPI_LEARNING.md` 和 `FASTAPI_QUICK_REFERENCE.md` 一起学习效果更好。
|
||
|