fastapi-rest-toolkit 0.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_rest_toolkit-0.0.3/.gitignore +135 -0
- fastapi_rest_toolkit-0.0.3/LICENSE +21 -0
- fastapi_rest_toolkit-0.0.3/PKG-INFO +277 -0
- fastapi_rest_toolkit-0.0.3/README.md +246 -0
- fastapi_rest_toolkit-0.0.3/pyproject.toml +57 -0
- fastapi_rest_toolkit-0.0.3/src/fastapi_rest_toolkit/__init__.py +39 -0
- fastapi_rest_toolkit-0.0.3/src/fastapi_rest_toolkit/contextvar.py +6 -0
- fastapi_rest_toolkit-0.0.3/src/fastapi_rest_toolkit/filters.py +66 -0
- fastapi_rest_toolkit-0.0.3/src/fastapi_rest_toolkit/permissions.py +21 -0
- fastapi_rest_toolkit-0.0.3/src/fastapi_rest_toolkit/request.py +27 -0
- fastapi_rest_toolkit-0.0.3/src/fastapi_rest_toolkit/router.py +75 -0
- fastapi_rest_toolkit-0.0.3/src/fastapi_rest_toolkit/service.py +64 -0
- fastapi_rest_toolkit-0.0.3/src/fastapi_rest_toolkit/throttle.py +295 -0
- fastapi_rest_toolkit-0.0.3/src/fastapi_rest_toolkit/utils.py +85 -0
- fastapi_rest_toolkit-0.0.3/src/fastapi_rest_toolkit/viewset.py +177 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
pip-wheel-metadata/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
*.manifest
|
|
32
|
+
*.spec
|
|
33
|
+
|
|
34
|
+
# Installer logs
|
|
35
|
+
pip-log.txt
|
|
36
|
+
pip-delete-this-directory.txt
|
|
37
|
+
|
|
38
|
+
# Unit test / coverage reports
|
|
39
|
+
htmlcov/
|
|
40
|
+
.tox/
|
|
41
|
+
.nox/
|
|
42
|
+
.coverage
|
|
43
|
+
.coverage.*
|
|
44
|
+
.cache
|
|
45
|
+
nosetests.xml
|
|
46
|
+
coverage.xml
|
|
47
|
+
*.cover
|
|
48
|
+
*.py,cover
|
|
49
|
+
.hypothesis/
|
|
50
|
+
.pytest_cache/
|
|
51
|
+
|
|
52
|
+
# Translations
|
|
53
|
+
*.mo
|
|
54
|
+
*.pot
|
|
55
|
+
|
|
56
|
+
# Django stuff:
|
|
57
|
+
*.log
|
|
58
|
+
local_settings.py
|
|
59
|
+
db.sqlite3
|
|
60
|
+
db.sqlite3-journal
|
|
61
|
+
|
|
62
|
+
# Flask stuff:
|
|
63
|
+
instance/
|
|
64
|
+
.webassets-cache
|
|
65
|
+
|
|
66
|
+
# Scrapy stuff:
|
|
67
|
+
.scrapy
|
|
68
|
+
|
|
69
|
+
# Sphinx documentation
|
|
70
|
+
docs/_build/
|
|
71
|
+
|
|
72
|
+
# PyBuilder
|
|
73
|
+
target/
|
|
74
|
+
|
|
75
|
+
# Jupyter Notebook
|
|
76
|
+
.ipynb_checkpoints
|
|
77
|
+
|
|
78
|
+
# IPython
|
|
79
|
+
profile_default/
|
|
80
|
+
ipython_config.py
|
|
81
|
+
|
|
82
|
+
# pyenv
|
|
83
|
+
.python-version
|
|
84
|
+
|
|
85
|
+
# Pipenv
|
|
86
|
+
Pipfile.lock
|
|
87
|
+
|
|
88
|
+
# PEP 582
|
|
89
|
+
__pypackages__/
|
|
90
|
+
|
|
91
|
+
# Celery stuff
|
|
92
|
+
celerybeat-schedule
|
|
93
|
+
celerybeat.pid
|
|
94
|
+
|
|
95
|
+
# SageMath parsed files
|
|
96
|
+
*.sage.py
|
|
97
|
+
|
|
98
|
+
# Environments
|
|
99
|
+
.env
|
|
100
|
+
.venv
|
|
101
|
+
env/
|
|
102
|
+
venv/
|
|
103
|
+
ENV/
|
|
104
|
+
env.bak/
|
|
105
|
+
venv.bak/
|
|
106
|
+
|
|
107
|
+
# Spyder project settings
|
|
108
|
+
.spyderproject
|
|
109
|
+
.spyproject
|
|
110
|
+
|
|
111
|
+
# Rope project settings
|
|
112
|
+
.ropeproject
|
|
113
|
+
|
|
114
|
+
# mkdocs documentation
|
|
115
|
+
/site
|
|
116
|
+
|
|
117
|
+
# mypy
|
|
118
|
+
.mypy_cache/
|
|
119
|
+
.dmypy.json
|
|
120
|
+
dmypy.json
|
|
121
|
+
|
|
122
|
+
# Pyre type checker
|
|
123
|
+
.pyre/
|
|
124
|
+
|
|
125
|
+
# IDE
|
|
126
|
+
.vscode/
|
|
127
|
+
.idea/
|
|
128
|
+
*.swp
|
|
129
|
+
*.swo
|
|
130
|
+
*~
|
|
131
|
+
.DS_Store
|
|
132
|
+
.cursor/
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# demo
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-rest-toolkit
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: 类DRF风格的FastAPI工具包
|
|
5
|
+
Project-URL: Homepage, https://github.com/pppigrui/fastapi-rest-toolkit
|
|
6
|
+
Project-URL: Documentation, https://github.com/pppigrui/fastapi-rest-toolkit#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/pppigrui/fastapi-rest-toolkit
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/pppigrui/fastapi-rest-toolkit/issues
|
|
9
|
+
Author-email: xiaorui <pppigrui@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api,drf,fastapi,rest,toolkit
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Framework :: FastAPI
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Requires-Dist: fastapi>=0.128.0
|
|
22
|
+
Requires-Dist: pydantic>=2.12.0
|
|
23
|
+
Requires-Dist: redis>=7.1.0
|
|
24
|
+
Requires-Dist: sqlalchemy-crud-plus>=1.13.0
|
|
25
|
+
Requires-Dist: sqlalchemy>=2.0.46
|
|
26
|
+
Provides-Extra: all
|
|
27
|
+
Requires-Dist: redis>=5.0.0; extra == 'all'
|
|
28
|
+
Provides-Extra: redis
|
|
29
|
+
Requires-Dist: redis>=5.0.0; extra == 'redis'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# FastAPI REST Toolkit
|
|
33
|
+
|
|
34
|
+
类 Django REST Framework 风格的 FastAPI 工具包,提供简洁优雅的方式来构建 RESTful API。
|
|
35
|
+
|
|
36
|
+
## 特性
|
|
37
|
+
|
|
38
|
+
- **ViewSet**: 类似 DRF 的 ViewSet,支持 CRUD 操作
|
|
39
|
+
- **Router**: 自动路由注册,简化路由配置
|
|
40
|
+
- **权限系统**: 灵活的权限控制(AllowAny、IsAuthenticated、IsAdmin)
|
|
41
|
+
- **过滤器**: 支持搜索、排序、CRUD Plus 过滤
|
|
42
|
+
- **节流**: 内置限流机制,支持 Redis 存储
|
|
43
|
+
- **Schema 工具**: 从 SQLAlchemy 模型自动生成 Pydantic Schema
|
|
44
|
+
|
|
45
|
+
## 安装
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install fastapi-rest-toolkit
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 快速开始
|
|
52
|
+
|
|
53
|
+
### 完整示例
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from fastapi import FastAPI
|
|
57
|
+
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
|
|
58
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
59
|
+
from sqlalchemy import String, DateTime, func
|
|
60
|
+
|
|
61
|
+
from fastapi_rest_toolkit import DefaultRouter, ViewSet, CRUDService
|
|
62
|
+
from fastapi_rest_toolkit.permissions import IsAuthenticated, AllowAny
|
|
63
|
+
from fastapi_rest_toolkit.throttle import AsyncRedisSimpleRateThrottle
|
|
64
|
+
from sqlalchemy_crud_plus import CRUDPlus
|
|
65
|
+
from app.db.redis import redis_client
|
|
66
|
+
|
|
67
|
+
# 1. 定义 SQLAlchemy 模型
|
|
68
|
+
class User(Base):
|
|
69
|
+
__tablename__ = 'users'
|
|
70
|
+
|
|
71
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
72
|
+
name: Mapped[str] = mapped_column(String(50))
|
|
73
|
+
email: Mapped[str] = mapped_column(String(100), unique=True)
|
|
74
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
|
75
|
+
|
|
76
|
+
# 2. 定义 Schema(手动或自动生成)
|
|
77
|
+
from pydantic import BaseModel
|
|
78
|
+
|
|
79
|
+
class UserRead(BaseModel):
|
|
80
|
+
id: int
|
|
81
|
+
email: str
|
|
82
|
+
name: str
|
|
83
|
+
|
|
84
|
+
class UserCreate(BaseModel):
|
|
85
|
+
email: str
|
|
86
|
+
name: str
|
|
87
|
+
|
|
88
|
+
class UserUpdate(BaseModel):
|
|
89
|
+
email: str | None = None
|
|
90
|
+
name: str | None = None
|
|
91
|
+
|
|
92
|
+
# 3. 定义 ViewSet
|
|
93
|
+
class UserViewSet(ViewSet):
|
|
94
|
+
read_schema = UserRead
|
|
95
|
+
create_schema = UserCreate
|
|
96
|
+
update_schema = UserUpdate
|
|
97
|
+
|
|
98
|
+
# 权限配置
|
|
99
|
+
permission_classes = (AllowAny, IsAuthenticated)
|
|
100
|
+
|
|
101
|
+
# 搜索和排序
|
|
102
|
+
search_fields = ("email", "name")
|
|
103
|
+
ordering_fields = ("id", "email", "name", "created_at")
|
|
104
|
+
|
|
105
|
+
# 节流配置
|
|
106
|
+
throttle_classes = (AsyncRedisSimpleRateThrottle(redis=redis_client),)
|
|
107
|
+
|
|
108
|
+
def __init__(self):
|
|
109
|
+
user_crud = CRUDPlus(User)
|
|
110
|
+
self.service = CRUDService(crud=user_crud, model=User)
|
|
111
|
+
|
|
112
|
+
# 4. 创建数据库会话
|
|
113
|
+
DATABASE_URL = "sqlite+aiosqlite:///./app.db"
|
|
114
|
+
engine = create_async_engine(DATABASE_URL, echo=False)
|
|
115
|
+
async_session = async_sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
|
|
116
|
+
|
|
117
|
+
async def get_session():
|
|
118
|
+
async with async_session() as session:
|
|
119
|
+
yield session
|
|
120
|
+
|
|
121
|
+
# 5. 注册路由
|
|
122
|
+
app = FastAPI()
|
|
123
|
+
router = DefaultRouter()
|
|
124
|
+
|
|
125
|
+
router.register(
|
|
126
|
+
"users",
|
|
127
|
+
UserViewSet,
|
|
128
|
+
get_session=get_session,
|
|
129
|
+
get_user=get_current_user, # 可选的认证依赖
|
|
130
|
+
tags=["users"],
|
|
131
|
+
pk_type=int,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
app.include_router(router.router, prefix="/api")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 自动生成 Schema
|
|
138
|
+
|
|
139
|
+
使用工具函数从 SQLAlchemy 模型自动生成 Pydantic Schema:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from fastapi_rest_toolkit.utils import sqlalchemy_model_to_pydantic
|
|
143
|
+
from app.models.user import User
|
|
144
|
+
|
|
145
|
+
# 自动生成 Schema
|
|
146
|
+
UserRead = sqlalchemy_model_to_pydantic(User, name="UserRead")
|
|
147
|
+
UserCreate = sqlalchemy_model_to_pydantic(User, name="UserCreate", exclude={"id"})
|
|
148
|
+
UserUpdate = sqlalchemy_model_to_pydantic(User, name="UserUpdate", optional=True)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 关联数据加载
|
|
152
|
+
|
|
153
|
+
支持加载关联数据(使用 selectinload):
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
class UserViewSet(ViewSet):
|
|
157
|
+
load_strategies = ("posts",) # 自动加载 posts 关联
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 权限控制
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from fastapi_rest_toolkit.permissions import AllowAny, IsAuthenticated, IsAdmin
|
|
164
|
+
|
|
165
|
+
class ProtectedViewSet(ViewSet):
|
|
166
|
+
permission_classes = (IsAuthenticated,) # 需要登录
|
|
167
|
+
|
|
168
|
+
class AdminViewSet(ViewSet):
|
|
169
|
+
permission_classes = (IsAdmin,) # 需要管理员权限
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 搜索和排序
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
class UserViewSet(ViewSet):
|
|
176
|
+
# 支持搜索的字段
|
|
177
|
+
search_fields = ("name", "email")
|
|
178
|
+
|
|
179
|
+
# 支持排序的字段
|
|
180
|
+
ordering_fields = ("id", "name", "created_at")
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**API 使用示例:**
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# 搜索
|
|
187
|
+
GET /api/users?search=john
|
|
188
|
+
|
|
189
|
+
# 排序
|
|
190
|
+
GET /api/users?ordering=-created_at
|
|
191
|
+
|
|
192
|
+
# 组合使用
|
|
193
|
+
GET /api/users?search=john&ordering=name
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 节流配置
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from fastapi_rest_toolkit.throttle import AsyncRedisSimpleRateThrottle
|
|
200
|
+
from app.db.redis import redis_client
|
|
201
|
+
|
|
202
|
+
class UserViewSet(ViewSet):
|
|
203
|
+
throttle_classes = (AsyncRedisSimpleRateThrottle(
|
|
204
|
+
redis=redis_client,
|
|
205
|
+
rate="100/hour" # 可选,默认 100/hour
|
|
206
|
+
),)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 异常处理
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from fastapi import Request
|
|
213
|
+
from fastapi.responses import JSONResponse
|
|
214
|
+
from sqlalchemy.exc import IntegrityError
|
|
215
|
+
|
|
216
|
+
async def integrity_error_handler(request: Request, exc: IntegrityError) -> JSONResponse:
|
|
217
|
+
"""处理数据库完整性约束错误"""
|
|
218
|
+
error_message = str(exc.orig)
|
|
219
|
+
|
|
220
|
+
if "UNIQUE constraint failed" in error_message:
|
|
221
|
+
parts = error_message.split(":")
|
|
222
|
+
if len(parts) > 1:
|
|
223
|
+
constraint_info = parts[1].strip()
|
|
224
|
+
field = constraint_info.split(".")[-1] if "." in constraint_info else constraint_info
|
|
225
|
+
detail = f"{field} 已存在"
|
|
226
|
+
else:
|
|
227
|
+
detail = error_message
|
|
228
|
+
|
|
229
|
+
return JSONResponse(
|
|
230
|
+
status_code=400,
|
|
231
|
+
content={"detail": detail, "error_type": "integrity_error"}
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# 注册异常处理器
|
|
235
|
+
app.add_exception_handler(IntegrityError, integrity_error_handler)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## 组件说明
|
|
239
|
+
|
|
240
|
+
### ViewSet
|
|
241
|
+
|
|
242
|
+
提供标准的 CRUD 操作接口:
|
|
243
|
+
|
|
244
|
+
| 方法 | 路由 | 说明 |
|
|
245
|
+
|------|------|------|
|
|
246
|
+
| `list()` | `GET /api/users` | 获取列表(支持搜索、排序、分页) |
|
|
247
|
+
| `retrieve()` | `GET /api/users/{id}` | 获取单个对象 |
|
|
248
|
+
| `create()` | `POST /api/users` | 创建对象 |
|
|
249
|
+
| `update()` | `PUT/PATCH /api/users/{id}` | 更新对象 |
|
|
250
|
+
| `destroy()` | `DELETE /api/users/{id}` | 删除对象 |
|
|
251
|
+
|
|
252
|
+
### 权限类
|
|
253
|
+
|
|
254
|
+
- `AllowAny` - 允许所有访问
|
|
255
|
+
- `IsAuthenticated` - 需要认证
|
|
256
|
+
- `IsAdmin` - 需要管理员权限
|
|
257
|
+
- `BasePermission` - 自定义权限基类
|
|
258
|
+
|
|
259
|
+
### 过滤器
|
|
260
|
+
|
|
261
|
+
- `SearchFilterBackend` - 搜索过滤(使用 `search` 查询参数)
|
|
262
|
+
- `OrderingFilterBackend` - 排序(使用 `ordering` 查询参数)
|
|
263
|
+
- `CRUDPlusFilterBackend` - CRUD Plus 过滤
|
|
264
|
+
|
|
265
|
+
### 节流类
|
|
266
|
+
|
|
267
|
+
- `SimpleRateThrottle` - 简单限流(内存存储)
|
|
268
|
+
- `AnonRateThrottle` - 匿名用户限流
|
|
269
|
+
- `AsyncRedisSimpleRateThrottle` - 基于 Redis 的异步限流
|
|
270
|
+
|
|
271
|
+
### 工具函数
|
|
272
|
+
|
|
273
|
+
- `sqlalchemy_model_to_pydantic()` - 从 SQLAlchemy 模型生成 Pydantic Schema
|
|
274
|
+
|
|
275
|
+
## License
|
|
276
|
+
|
|
277
|
+
MIT License
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# FastAPI REST Toolkit
|
|
2
|
+
|
|
3
|
+
类 Django REST Framework 风格的 FastAPI 工具包,提供简洁优雅的方式来构建 RESTful API。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **ViewSet**: 类似 DRF 的 ViewSet,支持 CRUD 操作
|
|
8
|
+
- **Router**: 自动路由注册,简化路由配置
|
|
9
|
+
- **权限系统**: 灵活的权限控制(AllowAny、IsAuthenticated、IsAdmin)
|
|
10
|
+
- **过滤器**: 支持搜索、排序、CRUD Plus 过滤
|
|
11
|
+
- **节流**: 内置限流机制,支持 Redis 存储
|
|
12
|
+
- **Schema 工具**: 从 SQLAlchemy 模型自动生成 Pydantic Schema
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install fastapi-rest-toolkit
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 快速开始
|
|
21
|
+
|
|
22
|
+
### 完整示例
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from fastapi import FastAPI
|
|
26
|
+
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
|
|
27
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
28
|
+
from sqlalchemy import String, DateTime, func
|
|
29
|
+
|
|
30
|
+
from fastapi_rest_toolkit import DefaultRouter, ViewSet, CRUDService
|
|
31
|
+
from fastapi_rest_toolkit.permissions import IsAuthenticated, AllowAny
|
|
32
|
+
from fastapi_rest_toolkit.throttle import AsyncRedisSimpleRateThrottle
|
|
33
|
+
from sqlalchemy_crud_plus import CRUDPlus
|
|
34
|
+
from app.db.redis import redis_client
|
|
35
|
+
|
|
36
|
+
# 1. 定义 SQLAlchemy 模型
|
|
37
|
+
class User(Base):
|
|
38
|
+
__tablename__ = 'users'
|
|
39
|
+
|
|
40
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
41
|
+
name: Mapped[str] = mapped_column(String(50))
|
|
42
|
+
email: Mapped[str] = mapped_column(String(100), unique=True)
|
|
43
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
|
44
|
+
|
|
45
|
+
# 2. 定义 Schema(手动或自动生成)
|
|
46
|
+
from pydantic import BaseModel
|
|
47
|
+
|
|
48
|
+
class UserRead(BaseModel):
|
|
49
|
+
id: int
|
|
50
|
+
email: str
|
|
51
|
+
name: str
|
|
52
|
+
|
|
53
|
+
class UserCreate(BaseModel):
|
|
54
|
+
email: str
|
|
55
|
+
name: str
|
|
56
|
+
|
|
57
|
+
class UserUpdate(BaseModel):
|
|
58
|
+
email: str | None = None
|
|
59
|
+
name: str | None = None
|
|
60
|
+
|
|
61
|
+
# 3. 定义 ViewSet
|
|
62
|
+
class UserViewSet(ViewSet):
|
|
63
|
+
read_schema = UserRead
|
|
64
|
+
create_schema = UserCreate
|
|
65
|
+
update_schema = UserUpdate
|
|
66
|
+
|
|
67
|
+
# 权限配置
|
|
68
|
+
permission_classes = (AllowAny, IsAuthenticated)
|
|
69
|
+
|
|
70
|
+
# 搜索和排序
|
|
71
|
+
search_fields = ("email", "name")
|
|
72
|
+
ordering_fields = ("id", "email", "name", "created_at")
|
|
73
|
+
|
|
74
|
+
# 节流配置
|
|
75
|
+
throttle_classes = (AsyncRedisSimpleRateThrottle(redis=redis_client),)
|
|
76
|
+
|
|
77
|
+
def __init__(self):
|
|
78
|
+
user_crud = CRUDPlus(User)
|
|
79
|
+
self.service = CRUDService(crud=user_crud, model=User)
|
|
80
|
+
|
|
81
|
+
# 4. 创建数据库会话
|
|
82
|
+
DATABASE_URL = "sqlite+aiosqlite:///./app.db"
|
|
83
|
+
engine = create_async_engine(DATABASE_URL, echo=False)
|
|
84
|
+
async_session = async_sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
|
|
85
|
+
|
|
86
|
+
async def get_session():
|
|
87
|
+
async with async_session() as session:
|
|
88
|
+
yield session
|
|
89
|
+
|
|
90
|
+
# 5. 注册路由
|
|
91
|
+
app = FastAPI()
|
|
92
|
+
router = DefaultRouter()
|
|
93
|
+
|
|
94
|
+
router.register(
|
|
95
|
+
"users",
|
|
96
|
+
UserViewSet,
|
|
97
|
+
get_session=get_session,
|
|
98
|
+
get_user=get_current_user, # 可选的认证依赖
|
|
99
|
+
tags=["users"],
|
|
100
|
+
pk_type=int,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
app.include_router(router.router, prefix="/api")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 自动生成 Schema
|
|
107
|
+
|
|
108
|
+
使用工具函数从 SQLAlchemy 模型自动生成 Pydantic Schema:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from fastapi_rest_toolkit.utils import sqlalchemy_model_to_pydantic
|
|
112
|
+
from app.models.user import User
|
|
113
|
+
|
|
114
|
+
# 自动生成 Schema
|
|
115
|
+
UserRead = sqlalchemy_model_to_pydantic(User, name="UserRead")
|
|
116
|
+
UserCreate = sqlalchemy_model_to_pydantic(User, name="UserCreate", exclude={"id"})
|
|
117
|
+
UserUpdate = sqlalchemy_model_to_pydantic(User, name="UserUpdate", optional=True)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 关联数据加载
|
|
121
|
+
|
|
122
|
+
支持加载关联数据(使用 selectinload):
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
class UserViewSet(ViewSet):
|
|
126
|
+
load_strategies = ("posts",) # 自动加载 posts 关联
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 权限控制
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from fastapi_rest_toolkit.permissions import AllowAny, IsAuthenticated, IsAdmin
|
|
133
|
+
|
|
134
|
+
class ProtectedViewSet(ViewSet):
|
|
135
|
+
permission_classes = (IsAuthenticated,) # 需要登录
|
|
136
|
+
|
|
137
|
+
class AdminViewSet(ViewSet):
|
|
138
|
+
permission_classes = (IsAdmin,) # 需要管理员权限
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 搜索和排序
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
class UserViewSet(ViewSet):
|
|
145
|
+
# 支持搜索的字段
|
|
146
|
+
search_fields = ("name", "email")
|
|
147
|
+
|
|
148
|
+
# 支持排序的字段
|
|
149
|
+
ordering_fields = ("id", "name", "created_at")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**API 使用示例:**
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# 搜索
|
|
156
|
+
GET /api/users?search=john
|
|
157
|
+
|
|
158
|
+
# 排序
|
|
159
|
+
GET /api/users?ordering=-created_at
|
|
160
|
+
|
|
161
|
+
# 组合使用
|
|
162
|
+
GET /api/users?search=john&ordering=name
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 节流配置
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from fastapi_rest_toolkit.throttle import AsyncRedisSimpleRateThrottle
|
|
169
|
+
from app.db.redis import redis_client
|
|
170
|
+
|
|
171
|
+
class UserViewSet(ViewSet):
|
|
172
|
+
throttle_classes = (AsyncRedisSimpleRateThrottle(
|
|
173
|
+
redis=redis_client,
|
|
174
|
+
rate="100/hour" # 可选,默认 100/hour
|
|
175
|
+
),)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 异常处理
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from fastapi import Request
|
|
182
|
+
from fastapi.responses import JSONResponse
|
|
183
|
+
from sqlalchemy.exc import IntegrityError
|
|
184
|
+
|
|
185
|
+
async def integrity_error_handler(request: Request, exc: IntegrityError) -> JSONResponse:
|
|
186
|
+
"""处理数据库完整性约束错误"""
|
|
187
|
+
error_message = str(exc.orig)
|
|
188
|
+
|
|
189
|
+
if "UNIQUE constraint failed" in error_message:
|
|
190
|
+
parts = error_message.split(":")
|
|
191
|
+
if len(parts) > 1:
|
|
192
|
+
constraint_info = parts[1].strip()
|
|
193
|
+
field = constraint_info.split(".")[-1] if "." in constraint_info else constraint_info
|
|
194
|
+
detail = f"{field} 已存在"
|
|
195
|
+
else:
|
|
196
|
+
detail = error_message
|
|
197
|
+
|
|
198
|
+
return JSONResponse(
|
|
199
|
+
status_code=400,
|
|
200
|
+
content={"detail": detail, "error_type": "integrity_error"}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# 注册异常处理器
|
|
204
|
+
app.add_exception_handler(IntegrityError, integrity_error_handler)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## 组件说明
|
|
208
|
+
|
|
209
|
+
### ViewSet
|
|
210
|
+
|
|
211
|
+
提供标准的 CRUD 操作接口:
|
|
212
|
+
|
|
213
|
+
| 方法 | 路由 | 说明 |
|
|
214
|
+
|------|------|------|
|
|
215
|
+
| `list()` | `GET /api/users` | 获取列表(支持搜索、排序、分页) |
|
|
216
|
+
| `retrieve()` | `GET /api/users/{id}` | 获取单个对象 |
|
|
217
|
+
| `create()` | `POST /api/users` | 创建对象 |
|
|
218
|
+
| `update()` | `PUT/PATCH /api/users/{id}` | 更新对象 |
|
|
219
|
+
| `destroy()` | `DELETE /api/users/{id}` | 删除对象 |
|
|
220
|
+
|
|
221
|
+
### 权限类
|
|
222
|
+
|
|
223
|
+
- `AllowAny` - 允许所有访问
|
|
224
|
+
- `IsAuthenticated` - 需要认证
|
|
225
|
+
- `IsAdmin` - 需要管理员权限
|
|
226
|
+
- `BasePermission` - 自定义权限基类
|
|
227
|
+
|
|
228
|
+
### 过滤器
|
|
229
|
+
|
|
230
|
+
- `SearchFilterBackend` - 搜索过滤(使用 `search` 查询参数)
|
|
231
|
+
- `OrderingFilterBackend` - 排序(使用 `ordering` 查询参数)
|
|
232
|
+
- `CRUDPlusFilterBackend` - CRUD Plus 过滤
|
|
233
|
+
|
|
234
|
+
### 节流类
|
|
235
|
+
|
|
236
|
+
- `SimpleRateThrottle` - 简单限流(内存存储)
|
|
237
|
+
- `AnonRateThrottle` - 匿名用户限流
|
|
238
|
+
- `AsyncRedisSimpleRateThrottle` - 基于 Redis 的异步限流
|
|
239
|
+
|
|
240
|
+
### 工具函数
|
|
241
|
+
|
|
242
|
+
- `sqlalchemy_model_to_pydantic()` - 从 SQLAlchemy 模型生成 Pydantic Schema
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
MIT License
|