feat: add FastAPI example project with CRUD API
- Add FastAPI application with health check and item CRUD endpoints - Add Pydantic models for request/response validation - Add pytest test suite with 5 passing tests - Add project documentation and run script Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
23e94ada3b
commit
67d8c6bf30
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,6 +17,7 @@ __pycache__/
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
*.egg
|
||||
.venv/
|
||||
|
||||
# 环境变量
|
||||
.env
|
||||
|
||||
40
README.md
Normal file
40
README.md
Normal file
@ -0,0 +1,40 @@
|
||||
# FastAPI Example
|
||||
|
||||
一个最小化的 FastAPI REST API 示例项目。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 创建虚拟环境并安装依赖
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 启动服务
|
||||
./run.sh
|
||||
|
||||
# 或直接运行
|
||||
uvicorn app.main:app --reload
|
||||
```
|
||||
|
||||
## API 端点
|
||||
|
||||
| 方法 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| GET | /health | 健康检查 |
|
||||
| GET | /items | 列出所有项目 |
|
||||
| POST | /items | 创建新项目 |
|
||||
| GET | /items/{id} | 获取单个项目 |
|
||||
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
pytest tests/ -v
|
||||
```
|
||||
|
||||
## API 文档
|
||||
|
||||
启动服务后访问:
|
||||
- Swagger UI: http://localhost:8000/docs
|
||||
- ReDoc: http://localhost:8000/redoc
|
||||
1
app/__init__.py
Normal file
1
app/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# app package
|
||||
35
app/main.py
Normal file
35
app/main.py
Normal file
@ -0,0 +1,35 @@
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from typing import Dict
|
||||
from app.models import Item, ItemCreate
|
||||
|
||||
app = FastAPI(title="FastAPI Example")
|
||||
|
||||
# 内存存储
|
||||
_items: Dict[int, Item] = {}
|
||||
_item_id_counter = 0
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.post("/items", response_model=Item)
|
||||
async def create_item(item: ItemCreate):
|
||||
global _item_id_counter
|
||||
_item_id_counter += 1
|
||||
new_item = Item(id=_item_id_counter, **item.model_dump())
|
||||
_items[_item_id_counter] = new_item
|
||||
return new_item
|
||||
|
||||
|
||||
@app.get("/items", response_model=list[Item])
|
||||
async def list_items():
|
||||
return list(_items.values())
|
||||
|
||||
|
||||
@app.get("/items/{item_id}", response_model=Item)
|
||||
async def get_item(item_id: int):
|
||||
if item_id not in _items:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
return _items[item_id]
|
||||
13
app/models.py
Normal file
13
app/models.py
Normal file
@ -0,0 +1,13 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
id: Optional[int] = None
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class ItemCreate(BaseModel):
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
38
docs/plans/2026-05-07-fastapi-example-plan.md
Normal file
38
docs/plans/2026-05-07-fastapi-example-plan.md
Normal file
@ -0,0 +1,38 @@
|
||||
# FastAPI 示例项目实现计划
|
||||
|
||||
日期:2026-05-07
|
||||
|
||||
## 目标
|
||||
搭建一个最小可运行的 FastAPI REST API 示例项目。
|
||||
|
||||
## 任务清单
|
||||
|
||||
### 批次 1:项目基础
|
||||
| # | 任务 | 文件 | 验证 | 预计时间 |
|
||||
|---|------|------|------|----------|
|
||||
| 1 | 创建 requirements.txt | requirements.txt | 文件存在 | 1min |
|
||||
| 2 | 创建项目目录结构 | app/, tests/ | 目录存在 | 1min |
|
||||
| 3 | 编写数据模型 | app/models.py | 导入成功 | 2min |
|
||||
|
||||
### 批次 2:核心实现(TDD)
|
||||
| # | 任务 | 文件 | 验证 | 预计时间 |
|
||||
|---|------|------|------|----------|
|
||||
| 4 | 写失败测试:健康检查 | tests/test_api.py | pytest FAIL | 2min |
|
||||
| 5 | 实现健康检查端点 | app/main.py | pytest PASS | 2min |
|
||||
| 6 | 写失败测试:CRUD | tests/test_api.py | pytest FAIL | 3min |
|
||||
| 7 | 实现 CRUD 端点 | app/routes.py | pytest PASS | 5min |
|
||||
|
||||
### 批次 3:收尾
|
||||
| # | 任务 | 文件 | 验证 | 预计时间 |
|
||||
|---|------|------|------|----------|
|
||||
| 8 | 添加启动脚本 | run.sh | 脚本可执行 | 1min |
|
||||
| 9 | 更新文档 | README.md | 文件存在 | 2min |
|
||||
|
||||
## 验证命令
|
||||
- 单测:`pytest tests/ -v`
|
||||
- 启动:`uvicorn app.main:app --reload`
|
||||
|
||||
## 完成标准
|
||||
- [ ] 所有测试通过
|
||||
- [ ] API 可启动并响应
|
||||
- [ ] 代码已提交
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
fastapi>=0.100.0
|
||||
uvicorn[standard]>=0.23.0
|
||||
pytest>=7.0.0
|
||||
httpx>=0.24.0
|
||||
3
run.sh
Executable file
3
run.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
source .venv/bin/activate
|
||||
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||||
1
tests/__init__.py
Normal file
1
tests/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# tests package
|
||||
49
tests/test_api.py
Normal file
49
tests/test_api.py
Normal file
@ -0,0 +1,49 @@
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
def test_health_check():
|
||||
"""测试健康检查端点"""
|
||||
from app.main import app
|
||||
client = TestClient(app)
|
||||
response = client.get("/health")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"status": "ok"}
|
||||
|
||||
|
||||
def test_create_item():
|
||||
"""测试创建项目"""
|
||||
from app.main import app
|
||||
client = TestClient(app)
|
||||
response = client.post("/items", json={"name": "Test Item", "description": "A test"})
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["name"] == "Test Item"
|
||||
assert data["id"] == 1
|
||||
|
||||
|
||||
def test_list_items():
|
||||
"""测试列出项目"""
|
||||
from app.main import app
|
||||
client = TestClient(app)
|
||||
client.post("/items", json={"name": "Item 1"})
|
||||
response = client.get("/items")
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()) >= 1
|
||||
|
||||
|
||||
def test_get_item():
|
||||
"""测试获取单个项目"""
|
||||
from app.main import app
|
||||
client = TestClient(app)
|
||||
created = client.post("/items", json={"name": "Item 2"}).json()
|
||||
response = client.get(f"/items/{created['id']}")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["name"] == "Item 2"
|
||||
|
||||
|
||||
def test_get_item_not_found():
|
||||
"""测试获取不存在的项目"""
|
||||
from app.main import app
|
||||
client = TestClient(app)
|
||||
response = client.get("/items/99999")
|
||||
assert response.status_code == 404
|
||||
Loading…
x
Reference in New Issue
Block a user