项目6 · Python 学习记录 API

用 FastAPI 把课程记录、笔记和统计做成一个真实可调用的本机服务。

约 10 小时
学习重点
  • 用 FastAPI 设计资源化接口:记录、笔记、统计
  • 用 Pydantic 模型完成请求/响应校验与统一错误结构
  • SQLite 持久化:迁移、唯一约束、事务与分页
  • 用 pytest 覆盖成功、参数错误、缺失资源与重复提交
  • 与前端联调:CORS、数据契约、服务不可达的处理
页面 / 接口清单
  • 01GET /records?page=&size=&course= 分页查询学习记录
  • 02POST /records 新增记录(校验 course/status/duration)
  • 03GET /records/{id} 与 DELETE /records/{id}
  • 04GET /records/{id}/notes 与 POST /records/{id}/notes 笔记
  • 05GET /stats 学习统计(总课时、按状态计数、最近 7 天)
  • 06GET /health 健康检查
验收清单
0 / 8(0%)

uvicorn app.main:app 启动,/docs 可打开,/health 返回 ok

缺字段、空课程名、非法状态、负时长都返回 422 且字段定位清楚

page/size 限制上限,course 筛选与总数正确,排序稳定

不存在的记录返回 404 与统一错误结构

同 id 重复创建返回 409,不产生重复行

笔记挂到记录下,删除记录级联删除笔记

总课时/按状态计数/最近 7 天来自真实记录聚合

pytest 全绿,测试使用临时数据库不碰真实数据

README(项目目录)
# 项目 6:Python 学习记录 API

用 FastAPI 做一个本机可调用的学习记录服务:课程记录、笔记与统计。

## 学习目标

- 资源化接口设计(记录 / 笔记 / 统计)
- Pydantic 请求校验与统一错误结构 `{detail: {category, message}}`
- SQLite 持久化:迁移、唯一约束、级联删除、事务
- pytest 覆盖成功、参数错误、缺失资源、重复提交

## 启动(参考实现)

```bash
cd examples/projects/p6-python-learning-api/solution
python -m pip install -r requirements.txt
uvicorn app.main:app --reload --port 8100
# 打开 http://127.0.0.1:8100/docs 调接口
```

## 测试

```bash
cd examples/projects/p6-python-learning-api/solution
python -m pytest tests -q
# 8 passed
```

测试使用临时数据库(`LEARNING_API_DB` 环境变量指向临时目录),不会碰真实数据。

## 目录

```
starter/   可运行的起点(TODO 缺口在 app/main.py)
solution/  完整参考实现(app/main.py + tests/test_api.py)
tests/     本项目的验收脚本(run-checks.py:复制 solution 到临时目录跑 pytest)
docs/      api.md(接口契约)· data-model.md(表结构)
fixtures/  sample-records.json(示例数据)
```

## 验收清单

见 `content/projects/p6-python-learning-api.json` 的 8 项任务(m1-m8)。逐项对照接口行为验证:

1. 启动 + `/docs` + `/health`
2. 非法请求 422(空课程名/非法状态/负时长)
3. 分页与筛选(size 上限 50,总数正确)
4. 缺失资源 404
5. 重复 id 409
6. 笔记关联与级联删除
7. `/stats` 聚合正确
8. pytest 全绿

## 与前端联调

- 服务地址:`http://127.0.0.1:8100`
- CORS 已允许 `http://localhost:3000`
- 前端调用示例:

```js
const res = await fetch("http://127.0.0.1:8100/records?page=1&size=10");
const data = await res.json(); // { items, total, page, size }
```

服务不可达时前端应显示「服务未启动」并给出启动命令,而不是空白表格。
数据模型
records(id TEXT PK, course TEXT, status TEXT, duration_min INTEGER, note_count INTEGER, created_at TEXT)
notes(id TEXT PK, record_id TEXT FK, content TEXT, created_at TEXT)
迁移用 PRAGMA user_version 管理;records.status ∈ {planned, learning, done};外键级联删除。

Command Palette

Search for a command to run...