fastapi-augment 0.1.4__tar.gz → 0.1.6__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.
Files changed (89) hide show
  1. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/PKG-INFO +1026 -779
  2. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/README.md +989 -742
  3. fastapi_augment-0.1.6/VERSION +1 -0
  4. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/pyproject.toml +125 -90
  5. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/setup.cfg +4 -4
  6. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/__init__.py +24 -24
  7. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/common/__init__.py +93 -61
  8. fastapi_augment-0.1.6/src/fastapi_augment/common/app_discovery.py +182 -0
  9. fastapi_augment-0.1.6/src/fastapi_augment/common/asgi_types.py +89 -0
  10. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/common/constants.py +27 -27
  11. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/common/exception_handlers.py +180 -178
  12. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/common/exceptions.py +165 -162
  13. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/common/utils/__init__.py +33 -32
  14. fastapi_augment-0.1.6/src/fastapi_augment/common/utils/paths.py +82 -0
  15. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/common/utils/strings.py +189 -188
  16. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/config/__init__.py +12 -12
  17. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/config/base_settings.py +262 -253
  18. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/config/database_settings.py +229 -219
  19. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/__init__.py +6 -6
  20. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/__init__.py +36 -32
  21. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/alembic/__init__.py +5 -5
  22. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/alembic/env.py +141 -141
  23. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/alembic/script.py.mako +28 -28
  24. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/base.py +9 -9
  25. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/engine.py +289 -252
  26. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/migrate.py +357 -356
  27. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/mixins/__init__.py +28 -18
  28. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/mixins/audit.py +61 -61
  29. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/mixins/soft_delete.py +85 -80
  30. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/mixins/timestamp.py +48 -48
  31. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/model_base.py +47 -47
  32. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/query_parser.py +456 -447
  33. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/repository_base.py +802 -490
  34. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/session.py +184 -182
  35. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/factory.py +262 -253
  36. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/health/__init__.py +34 -34
  37. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/health/checker.py +105 -105
  38. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/health/checkers.py +127 -126
  39. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/health/router.py +99 -92
  40. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/lifespan.py +436 -452
  41. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/logger/__init__.py +31 -26
  42. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/logger/filters.py +23 -23
  43. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/logger/handlers.py +98 -92
  44. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/logger/record_factory.py +41 -39
  45. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/logger/setup.py +222 -217
  46. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/middlewares/__init__.py +20 -20
  47. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/middlewares/base.py +86 -79
  48. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/middlewares/request_id.py +81 -82
  49. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/openapi.py +94 -94
  50. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/schemas/__init__.py +36 -29
  51. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/schemas/base.py +32 -32
  52. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/schemas/pagination.py +47 -46
  53. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/schemas/request.py +27 -28
  54. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/schemas/response.py +139 -139
  55. fastapi_augment-0.1.6/src/fastapi_augment/schemas/types.py +46 -0
  56. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment.egg-info/PKG-INFO +1026 -779
  57. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment.egg-info/SOURCES.txt +8 -1
  58. fastapi_augment-0.1.6/tests/test_app_discovery.py +207 -0
  59. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_config.py +228 -229
  60. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_constants.py +43 -41
  61. fastapi_augment-0.1.6/tests/test_database_settings.py +238 -0
  62. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_db_engine.py +248 -218
  63. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_db_repository.py +452 -429
  64. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_db_session_models.py +198 -182
  65. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_exception_handlers.py +314 -315
  66. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_exceptions.py +145 -133
  67. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_factory.py +242 -239
  68. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_health.py +211 -211
  69. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_lifespan.py +336 -337
  70. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_log.py +347 -343
  71. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_middlewares.py +208 -211
  72. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_migrate.py +127 -80
  73. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_model_base.py +95 -89
  74. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_openapi.py +101 -100
  75. fastapi_augment-0.1.6/tests/test_paths.py +57 -0
  76. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_query_parser.py +261 -246
  77. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_schemas.py +277 -268
  78. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/tests/test_settings.py +139 -128
  79. fastapi_augment-0.1.6/tests/test_soft_delete.py +353 -0
  80. fastapi_augment-0.1.6/tests/test_strings.py +153 -0
  81. fastapi_augment-0.1.4/VERSION +0 -1
  82. fastapi_augment-0.1.4/src/fastapi_augment/common/utils/paths.py +0 -36
  83. fastapi_augment-0.1.4/src/fastapi_augment/schemas/types.py +0 -11
  84. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/alembic/README +0 -0
  85. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment/py.typed +0 -0
  86. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment.egg-info/dependency_links.txt +0 -0
  87. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment.egg-info/entry_points.txt +0 -0
  88. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment.egg-info/requires.txt +0 -0
  89. {fastapi_augment-0.1.4 → fastapi_augment-0.1.6}/src/fastapi_augment.egg-info/top_level.txt +0 -0
@@ -1,779 +1,1026 @@
1
- Metadata-Version: 2.4
2
- Name: fastapi-augment
3
- Version: 0.1.4
4
- Summary: FastAPI 通用代码工具包,跨项目复用
5
- Author-email: zarkhan <hanguangzheng@qq.com>
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/hgz1989/fastapi_augment
8
- Project-URL: Repository, https://github.com/hgz1989/fastapi_augment
9
- Project-URL: Documentation, https://github.com/hgz1989/fastapi_augment#readme
10
- Keywords: fastapi,fastapi-augment,augment,web,framework,async
11
- Classifier: Development Status :: 3 - Alpha
12
- Classifier: Framework :: FastAPI
13
- Classifier: Intended Audience :: Developers
14
- Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.11
16
- Classifier: Programming Language :: Python :: 3.12
17
- Classifier: Programming Language :: Python :: 3.13
18
- Classifier: Programming Language :: Python :: 3.14
19
- Classifier: Topic :: Internet :: WWW/HTTP
20
- Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
21
- Classifier: Typing :: Typed
22
- Requires-Python: >=3.11
23
- Description-Content-Type: text/markdown
24
- Requires-Dist: fastapi>=0.141.1
25
- Provides-Extra: uvicorn
26
- Requires-Dist: uvicorn[standard]>=0.24.0; extra == "uvicorn"
27
- Provides-Extra: sqlalchemy
28
- Requires-Dist: sqlalchemy[asyncio]>=2.0.52; extra == "sqlalchemy"
29
- Requires-Dist: python-ulid>=4.0.1; extra == "sqlalchemy"
30
- Requires-Dist: alembic>=1.19.2; extra == "sqlalchemy"
31
- Provides-Extra: orjson
32
- Requires-Dist: orjson>=3.10.0; extra == "orjson"
33
- Provides-Extra: config
34
- Requires-Dist: pydantic-settings>=2.15.0; extra == "config"
35
- Provides-Extra: standard
36
- Requires-Dist: fastapi-augment[config,orjson,sqlalchemy,uvicorn]; extra == "standard"
37
-
38
- # fastapi-augment
39
-
40
- 跨项目复用的 FastAPI 通用代码工具包,将多个项目中反复用到的应用工厂、生命周期管理、读写分离数据库层、通用 Mixin、统一响应模型与 HTTP 异常体系统一封装,开箱即用。
41
-
42
- ## 特性
43
-
44
- - **应用工厂** — 一行代码创建 FastAPI 实例,自动装配中间件、路由、生命周期与数据库
45
- - **生命周期管理** — 多注册表、优先级、超时控制、异常策略的启动/关闭钩子
46
- - **读写分离** — 单库 / 主从 / 集群拓扑的异步引擎管理,线程安全的 Session 自动路由
47
- - **泛型仓储** — 类型安全的异步 Repository,支持直接实例化与子类继承两种方式
48
- - **查询解析器** — REST 风格 query string 转 SQLAlchemy 表达式,支持 FIQL 条件、关键字搜索、排序
49
- - **可组合 Mixin** — 时间戳、审计、软删除等列混入,自由组合
50
- - **统一响应** — 全局 `APIResponse` 格式,自动追踪 `request_id`
51
- - **HTTP 异常** — 完整的 4xx 异常子类,内置默认文案
52
- - **OpenAPI 优化** — 自动清理 422 响应与验证错误模型
53
- - **日志管理** — request_id 自动注入、uvicorn 接管、多进程安全轮转、幂等初始化、一键配置
54
- - **健康检查** — 可扩展的检查器模式,内置应用状态与数据库连通性检查,一行开关
55
- - **配置管理** — 基于 pydantic-settings,支持 `.env` 文件、环境变量前缀、嵌套配置
56
- - **数据库迁移 CLI** — 一行命令生成/执行迁移,自动发现用户模型
57
-
58
- ## 安装
59
-
60
- ```bash
61
- pip install fastapi-augment
62
-
63
- # 推荐:安装全部可选依赖
64
- pip install fastapi-augment[standard]
65
-
66
- # 或按需单独安装
67
- pip install fastapi-augment[sqlalchemy]
68
- pip install fastapi-augment[uvicorn]
69
- pip install fastapi-augment[orjson]
70
- pip install fastapi-augment[config] # pydantic-settings 配置管理
71
- ```
72
-
73
- **要求:** Python >= 3.11
74
-
75
- ## 快速开始
76
-
77
- ```python
78
- from fastapi import APIRouter
79
- from fastapi_augment import create_app
80
- from fastapi_augment.db.sqlalchemy import (
81
- ClusterTopology, NodeConfig, EngineManager, SessionFactory,
82
- ModelBase, RepositoryBase,
83
- )
84
- from fastapi_augment.db.sqlalchemy.mixins import TimestampMixin
85
- from fastapi_augment.schemas import response_success
86
-
87
- # ── 1. 数据库拓扑 ──────────────────────────────────────
88
- topology = ClusterTopology(
89
- primary=NodeConfig(url='postgresql+asyncpg://user:pass@host/db'),
90
- )
91
- manager = EngineManager(topology).start()
92
- sessions = SessionFactory(manager)
93
-
94
-
95
- # ── 2. 定义模型 ────────────────────────────────────────
96
- class User(TimestampMixin, ModelBase):
97
- __tablename__ = 'users'
98
- name: str
99
-
100
-
101
- # ── 3. 路由 ────────────────────────────────────────────
102
- router = APIRouter()
103
- user_repo = RepositoryBase(User)
104
-
105
-
106
- @router.get('/users')
107
- async def list_users():
108
- async with sessions.read_session() as session:
109
- users = await user_repo.list(session, is_active=True, limit=10)
110
- return response_success(data=users)
111
-
112
-
113
- # ── 4. 创建应用 ────────────────────────────────────────
114
- app = create_app(
115
- title='My Service',
116
- version='1.0.0',
117
- engine_manager=manager,
118
- session_factory=sessions,
119
- routers=[router],
120
- )
121
- ```
122
-
123
- ## 核心模块
124
-
125
- ### 应用工厂 — `create_app()`
126
-
127
- 统一创建 FastAPI 实例,自动装配以下组件:
128
-
129
- | 组件 | 说明 |
130
- | -------- | ---------------------------------------------------------- |
131
- | 生命周期 | 接入 `fastapi_lifespan`,合并用户注册表与 `core_registry` |
132
- | 中间件 | 自动添加 `RequestIdMiddleware`,可选 CORS |
133
- | 路由 | 支持 `APIRouter` 列表或 `(router, kwargs)` 元组 |
134
- | OpenAPI | 自动清理 422 响应与验证错误模型 |
135
- | 数据库 | 可选挂载 `EngineManager` / `SessionFactory` 到 `app.state` |
136
- | 健康检查 | `health_check=True` 一键启用 `/health` 端点 |
137
-
138
- ```python
139
- from fastapi_augment import create_app, HookRegistry
140
-
141
- registry = HookRegistry()
142
-
143
- @registry.on_startup
144
- async def init_cache() -> None:
145
- ...
146
-
147
- app = create_app(
148
- title='My Service',
149
- registries=registry, # 单个或列表均可
150
- cors_allow_origins=['*'],
151
- health_check=True, # 启用健康检查
152
- )
153
- ```
154
-
155
- ### 生命周期 — `HookRegistry`
156
-
157
- 多注册表、优先级驱动的启动/关闭钩子管理:
158
-
159
- ```python
160
- from fastapi_augment import HookRegistry
161
-
162
- registry = HookRegistry()
163
-
164
- # 装饰器语法
165
- @registry.on_startup(priority=100)
166
- async def early_init() -> None: ...
167
-
168
- @registry.on_shutdown
169
- async def cleanup() -> None: ...
170
-
171
- # 直接注册
172
- registry.register_startup(func, priority=50, timeout=10, abort_on_exception=True)
173
- ```
174
-
175
- - **优先级** — 数值越大越先执行(启动降序,关闭升序)
176
- - **超时控制** — 可设置单个钩子的超时秒数
177
- - **异常策略** — `abort_on_exception` 控制异常时是否终止流程
178
-
179
- ### 数据库层 — `db.sqlalchemy`
180
-
181
- #### 引擎管理 — `EngineManager`
182
-
183
- 支持三种部署拓扑:
184
-
185
- ```python
186
- from fastapi_augment.db.sqlalchemy import ClusterTopology, NodeConfig, EngineManager
187
-
188
- # 单库
189
- topology = ClusterTopology(
190
- primary=NodeConfig(url='postgresql+asyncpg://user:pass@host/db'),
191
- )
192
-
193
- # 主从
194
- topology = ClusterTopology(
195
- primary=NodeConfig(url='postgresql+asyncpg://primary/db'),
196
- replicas=[NodeConfig(url='postgresql+asyncpg://replica-1/db')],
197
- )
198
-
199
- # 集群(主从 + 独立只读节点)
200
- topology = ClusterTopology(
201
- primary=NodeConfig(url='postgresql+asyncpg://primary/db'),
202
- replicas=[NodeConfig(url='postgresql+asyncpg://replica-1/db')],
203
- readonly=[NodeConfig(url='postgresql+asyncpg://readonly-1/db')],
204
- )
205
-
206
- manager = EngineManager(topology).start()
207
- # 读引擎轮询(round-robin),线程安全
208
- read_engine = manager.next_read_engine()
209
- ```
210
-
211
- #### 会话工厂 — `SessionFactory`
212
-
213
- 读写分离的异步 Session 工厂,支持 FastAPI `Depends()` 注入:
214
-
215
- ```python
216
- from fastapi_augment.db.sqlalchemy import SessionFactory
217
-
218
- sessions = SessionFactory(manager)
219
-
220
- # 写会话(自动 commit/rollback)
221
- async with sessions.transaction() as session:
222
- session.add(obj)
223
- # 自动 commit
224
-
225
- # 读会话(轮询读引擎)
226
- async with sessions.read_session() as session:
227
- result = await session.execute(select(User))
228
-
229
- # FastAPI 依赖注入
230
- from fastapi import Depends
231
- from sqlalchemy.ext.asyncio import AsyncSession
232
-
233
- @router.get('/users')
234
- async def list_users(session: AsyncSession = Depends(sessions.depends_read)):
235
- ...
236
- ```
237
-
238
- > **缓存说明:** `read_session()` 按 `AsyncEngine` 缓存 `async_sessionmaker`,线程安全,避免重复创建。打印 `SessionFactory` 实例可查看已缓存的引擎信息。
239
-
240
- #### 模型基类 — `ModelBase`
241
-
242
- 基于 ULID 主键的声明式模型基类:
243
-
244
- ```python
245
- from fastapi_augment.db.sqlalchemy import ModelBase, TimestampMixin
246
-
247
- class User(TimestampMixin, ModelBase):
248
- __tablename__ = 'users'
249
- name: str
250
- ```
251
-
252
- #### 泛型仓储 — `RepositoryBase`
253
-
254
- 类型安全的异步仓储基类,Repository 方法只 **flush**,不 commit,事务边界由调用方控制。
255
- 支持两种使用方式:
256
-
257
- ```python
258
- from fastapi_augment.db.sqlalchemy import RepositoryBase
259
-
260
- # 方式 1:直接实例化 — 显式传入模型类
261
- user_repo = RepositoryBase(User)
262
-
263
-
264
- # 方式 2:子类继承 — 通过泛型参数绑定模型,可扩展自定义方法
265
- class UserRepo(RepositoryBase[User]):
266
- async def find_by_email(self, session, email: str) -> User | None:
267
- return await self.get_first(session, email=email)
268
-
269
-
270
- user_repo = UserRepo() # 无需再传 User
271
- ```
272
-
273
- **CRUD 操作:**
274
-
275
- ```python
276
- # Create(静态方法)
277
- async with sessions.transaction() as session:
278
- await user_repo.create(session, User(name='alice'))
279
- await user_repo.create_many(session, [User(name='bob'), User(name='carol')])
280
-
281
- # Read(实例方法)
282
- async with sessions.read_session() as session:
283
- user = await user_repo.get(session, id_='01HXK...')
284
- user = await user_repo.get_first(session, name='alice') # 取第一条,无匹配返回 None
285
- user = await user_repo.get_unique(session, email='a@b.com') # 精确唯一,多条匹配抛 MultipleResultsFound
286
- users = await user_repo.list(session, role='admin', order_by=['-created_at'], limit=10)
287
- total = await user_repo.count(session, is_active=True)
288
- has_admin = await user_repo.exists(session, role='admin')
289
-
290
- # 分页查询(返回 dict:items / page / size / total / pages)
291
- result = await user_repo.paginate(session, page=1, size=10, role='admin', order_by=['-created_at'])
292
- # result = {'items': [...], 'page': 1, 'size': 10, 'total': 100, 'pages': 10}
293
-
294
- # Update
295
- async with sessions.transaction() as session:
296
- await user_repo.update(session, user, name='new_name')
297
- affected = await user_repo.update_by_id(session, id_='01HXK...', name='new_name')
298
-
299
- # Delete
300
- async with sessions.transaction() as session:
301
- await user_repo.delete(session, user)
302
- deleted = await user_repo.delete_by_id(session, id_='01HXK...')
303
- count = await user_repo.delete_where(session, is_active=False)
304
- ```
305
-
306
- **过滤语法:**
307
-
308
- ```python
309
- # 关键字过滤 — 等值匹配
310
- await user_repo.list(session, name='alice')
311
-
312
- # 序列 — 自动转为 IN 查询
313
- await user_repo.list(session, id_=['01HXK...', '01HXL...'])
314
-
315
- # None — 自动转为 IS NULL
316
- await user_repo.list(session, deleted_at=None)
317
-
318
- # 原生 SQLAlchemy 表达式
319
- await user_repo.list(session, expressions=(User.age > 18,))
320
-
321
- # 排序:字段名前缀 - 表示降序
322
- await user_repo.list(session, order_by=['-created_at', 'name'])
323
- ```
324
-
325
- ### 查询解析器 — `query_parser`
326
-
327
- 将 REST 风格的 query string 转为 SQLAlchemy `ColumnElement` 条件表达式,可直接传入 `RepositoryBase` 的 `expressions` / `order_by` 参数。
328
-
329
- #### where 组合条件(FIQL 风格)
330
-
331
- ```
332
- where = and_group ("," and_group)* , 表示 OR
333
- and_group = unit (";" unit)* ; 表示 AND
334
- unit = "(" where ")" | condition
335
- condition = field OP value
336
- ```
337
-
338
- **支持的操作符:**
339
-
340
- | 语法 | 含义 | 示例 |
341
- | ---------------- | -------------------- | ------------------------- |
342
- | `field==value` | 等于 | `status==1` |
343
- | `field!=value` | 不等 | `status!=0` |
344
- | `field~=value` | 模糊包含 (ILIKE) | `name~=张` |
345
- | `field>value` | 大于 | `age>18` |
346
- | `field>=value` | 大于等于 | `age>=18` |
347
- | `field<value` | 小于 | `age<60` |
348
- | `field<=value` | 小于等于 | `age<=60` |
349
- | `field~start~end`| 区间 (BETWEEN) | `age~18~60` |
350
-
351
- ```python
352
- from fastapi_augment.db.sqlalchemy import parse_where
353
-
354
- # 昵称含张 且 状态非禁用
355
- expr = parse_where('nickname~=张;status!=0', User)
356
-
357
- # 括号内 OR,与区间 AND
358
- expr = parse_where('(nickname~=张,username~=王);age~20~30', User)
359
- ```
360
-
361
- #### lookup 精确匹配
362
-
363
- ```python
364
- from fastapi_augment.db.sqlalchemy import parse_lookup
365
-
366
- # 单字段精确匹配
367
- expr = parse_lookup('phone==13800138000', User, fields={'phone', 'email'})
368
-
369
- # 多字段 AND(; 分隔)
370
- expr = parse_lookup('phone==138;status==1', User, fields={'phone', 'status'})
371
- ```
372
-
373
- #### 关键字搜索
374
-
375
- ```python
376
- from fastapi_augment.db.sqlalchemy import parse_keyword
377
-
378
- # 多字段 OR 模糊搜索
379
- expr = parse_keyword('admin', 'username,email,nickname', User)
380
- ```
381
-
382
- #### 排序
383
-
384
- ```python
385
- from fastapi_augment.db.sqlalchemy import parse_sort
386
-
387
- # - 前缀表示降序,无前缀为升序
388
- order_by = parse_sort('-created_at,nickname', User)
389
- ```
390
-
391
- #### 一键组合 — `build_query_expressions`
392
-
393
- ```python
394
- from fastapi_augment.db.sqlalchemy import build_query_expressions
395
-
396
- expressions, order_by = build_query_expressions(
397
- User,
398
- where='status!=0;age>=18',
399
- q='admin',
400
- q_field='username,nickname',
401
- sort='-created_at',
402
- )
403
-
404
- # 直接传给 paginate / list
405
- result = await user_repo.paginate(
406
- session, page=1, size=10,
407
- expressions=expressions, order_by=order_by,
408
- )
409
- ```
410
-
411
- ### 数据库迁移 CLI — `fastapi-augment-migrate`
412
-
413
- 内置 Alembic 迁移工具,提供 `init` / `generate` / `upgrade` 三个子命令,开箱即用。
414
-
415
- #### 快速开始
416
-
417
- ```bash
418
- # 1. 初始化(一次性操作,生成 alembic.ini + migrations/versions/)
419
- fastapi-augment-migrate init --db-url "sqlite:///test.db"
420
-
421
- # 2. 生成迁移
422
- fastapi-augment-migrate generate \
423
- --message "add_user_table" \
424
- --models "models,apps.ai.models"
425
-
426
- # 3. 执行迁移
427
- fastapi-augment-migrate upgrade
428
- ```
429
-
430
- #### 初始化 — `init`
431
-
432
- 在项目根目录生成 `alembic.ini` 和 `migrations/versions/` 目录。
433
- 若 `alembic.ini` 已存在则跳过,不会覆盖。
434
-
435
- ```bash
436
- # 初始化
437
- fastapi-augment-migrate init --db-url "sqlite:///test.db"
438
-
439
- # 指定项目根目录
440
- fastapi-augment-migrate init --db-url "postgresql+asyncpg://user:pass@host/db" --project-dir /path/to/project
441
- ```
442
-
443
- `alembic.ini` 中的 `script_location` 指向库内的 `env.py`,`version_locations` 指向本地 `migrations/versions/`。
444
- `env.py` 通过环境变量 `FASTAPI_AUGMENT_MODELS` 动态导入用户模型,无需手动修改。
445
-
446
- #### 生成迁移 — `generate`
447
-
448
- 通过 `--models` 指定模型模块(逗号分隔),调用 `alembic revision --autogenerate` 生成迁移脚本。
449
- 需要先执行 `init` 初始化。
450
-
451
- ```bash
452
- # 生成迁移
453
- fastapi-augment-migrate generate \
454
- --message "add_user_table" \
455
- --models "models,apps.ai.models"
456
-
457
- # 指定项目根目录
458
- fastapi-augment-migrate generate \
459
- --message "add_item" \
460
- --models "apps.ai.models" \
461
- --project-dir /path/to/project
462
- ```
463
-
464
- 迁移文件生成在 `<项目根>/migrations/versions/` 目录下。
465
-
466
- #### 升级 / 降级 — `upgrade`
467
-
468
- `--db-url` 为可选参数,不传时直接从 `alembic.ini` 读取 `sqlalchemy.url`。
469
-
470
- ```bash
471
- # 升级到最新版本(从 alembic.ini 读取数据库 URL)
472
- fastapi-augment-migrate upgrade
473
-
474
- # 指定数据库 URL(覆盖 alembic.ini 中的配置)
475
- fastapi-augment-migrate upgrade --db-url "sqlite:///test.db"
476
-
477
- # 升级到指定版本
478
- fastapi-augment-migrate upgrade --revision abc123
479
-
480
- # 降级一个版本
481
- fastapi-augment-migrate upgrade --downgrade
482
-
483
- # 降级到指定版本
484
- fastapi-augment-migrate upgrade --downgrade --revision abc123
485
-
486
- # 指定项目根目录
487
- fastapi-augment-migrate upgrade --project-dir /path/to/project
488
- ```
489
-
490
- #### CLI 参数一览
491
-
492
- | 子命令 | 参数 | 说明 |
493
- | ---------- | --------------- | --------------------------------------- |
494
- | `init` | `--db-url` | 数据库 URL(默认 `sqlite:///app.db`) |
495
- | | `--project-dir` | 项目根目录(默认当前目录) |
496
- | `generate` | `--message` | **必填**,迁移描述 |
497
- | | `--models` | **必填**,模型模块路径,逗号分隔 |
498
- | | `--project-dir` | 项目根目录(默认当前目录) |
499
- | `upgrade` | `--db-url` | 数据库 URL(不传则从 alembic.ini 读取) |
500
- | | `--revision` | 目标版本(默认 `head`) |
501
- | | `--downgrade` | 降级模式 |
502
- | | `--project-dir` | 项目根目录(默认当前目录) |
503
-
504
- ### 模型 Mixin — `db.sqlalchemy.mixins`
505
-
506
- 可组合的列混入,按需叠加:
507
-
508
- | Mixin | 提供的列 |
509
- | ---------------------- | ------------------------------------------ |
510
- | `CreatedAtMixin` | `created_at` |
511
- | `TimestampMixin` | `created_at` + `updated_at` |
512
- | `CreatedByMixin` | `created_by` |
513
- | `UpdatedByMixin` | `updated_by` |
514
- | `AuditMixin` | `created_by` + `updated_by` |
515
- | `SoftDeleteMixin` | `is_deleted` + `deleted_at` |
516
- | `SoftDeleteAuditMixin` | `is_deleted` + `deleted_at` + `deleted_by` |
517
-
518
- ```python
519
- from fastapi_augment.db.sqlalchemy import ModelBase
520
- from fastapi_augment.db.sqlalchemy.mixins import TimestampMixin, SoftDeleteMixin
521
-
522
- class User(TimestampMixin, SoftDeleteMixin, ModelBase):
523
- __tablename__ = 'users'
524
- name: str
525
- ```
526
-
527
- ### 统一响应 — `schemas`
528
-
529
- #### `APIResponse` — 全局返回格式
530
-
531
- ```json
532
- {
533
- "request_id": "019xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
534
- "code": 0,
535
- "message": "操作成功",
536
- "data": { ... },
537
- "extra": null
538
- }
539
- ```
540
-
541
- `request_id` 自动从 `ContextVar` 获取,无需手动传递。
542
-
543
- #### 工厂函数
544
-
545
- ```python
546
- from fastapi_augment.schemas import response_success, response_fail
547
-
548
- # 成功响应
549
- return response_success(data=user)
550
- return response_success(data=users, extra={'total': 100})
551
-
552
- # 失败响应
553
- return response_fail(code=40001, message='用户名已存在')
554
- ```
555
-
556
- #### 请求参数模型
557
-
558
- ```python
559
- from fastapi_augment.schemas import PageParams, TimeRangeParams, KeywordParams
560
-
561
- # 分页参数
562
- @router.get('/users')
563
- async def list_users(params: PageParams = Depends()):
564
- ...
565
-
566
- # 时间范围 + 关键词搜索
567
- @router.get('/orders')
568
- async def list_orders(
569
- time_range: TimeRangeParams = Depends(),
570
- keyword: KeywordParams = Depends(),
571
- ):
572
- ...
573
- ```
574
-
575
- ### HTTP 异常 — `common.exceptions`
576
-
577
- 完整的 4xx 异常子类,内置默认文案。子类只需声明 `_status_code` 类变量,无需重写 `__init__`:
578
-
579
- ```python
580
- from fastapi_augment.common.exceptions import (
581
- BadRequestError, # 400
582
- UnauthorizedError, # 401
583
- ForbiddenError, # 403
584
- NotFoundError, # 404
585
- ConflictError, # 409
586
- TooManyRequestsError, # 429(支持 retry_after 参数)
587
- # ... 更多异常
588
- )
589
-
590
- # 使用默认文案
591
- raise NotFoundError()
592
-
593
- # 自定义提示
594
- raise BadRequestError(detail='用户名不能为空')
595
-
596
- # 限流场景
597
- raise TooManyRequestsError(retry_after=60)
598
- ```
599
-
600
- ### 日志管理 — `logger`
601
-
602
- 导入即生效:自动注入 `request_id` 到每条日志、接管 uvicorn/fastapi 日志输出。
603
-
604
- ```python
605
- from fastapi_augment.logger import setup_logger, set_log_level
606
-
607
- # 一键配置:控制台 + 按天轮转文件日志
608
- setup_logger(log_dir='./logs', rotation='day', backup_count=30)
609
-
610
- # 动态调整日志级别
611
- set_log_level('info')
612
- ```
613
-
614
- **支持的轮转粒度:**
615
-
616
- | 粒度 | 说明 |
617
- | ---------------------------------- | -------------------- |
618
- | `'second'` / `'minute'` / `'hour'` | 每整秒/分/点 |
619
- | `'day'`(默认) | 每天 00:00 |
620
- | `'week'` | 每周一 00:00 |
621
- | `'month'` | 每月 1 日 00:00 |
622
- | `'year'` | 每年 1 月 1 日 00:00 |
623
-
624
- **核心能力:**
625
-
626
- - **request_id 注入** — 每条日志自动携带当前请求的 `request_id`,方便链路追踪
627
- - **uvicorn 接管** — 统一 `uvicorn.error` / `uvicorn.access` 的日志名称为 `uvicorn`,屏蔽第三方库 DEBUG 噪声
628
- - **多进程安全** — 日志轮转时捕获 `OSError`(含 `PermissionError`、`FileNotFoundError`),兼容多进程部署(如 `uvicorn --workers N`)
629
- - **幂等初始化** — 重复导入或多次调用 `setup_logger()` 不会叠加处理器或工厂链
630
- - **控制台开关** — `enable_console=False` 可关闭控制台输出,仅保留文件日志
631
-
632
- ### 健康检查 — `health`
633
-
634
- 可扩展的检查器模式,内置应用状态与数据库连通性检查。
635
-
636
- #### 一行启用
637
-
638
- ```python
639
- app = create_app(
640
- title='My Service',
641
- engine_manager=manager,
642
- health_check=True, # 自动注册 /health 端点
643
- )
644
- ```
645
-
646
- `GET /health` 响应示例:
647
-
648
- ```json
649
- {
650
- "status": "healthy",
651
- "checks": [
652
- {
653
- "name": "app",
654
- "status": "healthy",
655
- "latencyMs": 0,
656
- "details": {
657
- "status": "running",
658
- "version": "1.0.0",
659
- "uptimeSeconds": 3600
660
- }
661
- },
662
- { "name": "database", "status": "healthy", "latencyMs": 2.3 }
663
- ]
664
- }
665
- ```
666
-
667
- - 总体状态取所有检查项中**最差**的(healthy < degraded < unhealthy)
668
- - 任一检查项 unhealthy 时 HTTP 返回 **503**,便于负载均衡器/探针识别
669
- - 传入 `engine_manager` 时自动包含数据库检查,否则仅检查应用状态
670
-
671
- #### 自定义检查器
672
-
673
- ```python
674
- from fastapi_augment.health import BaseChecker, CheckResult, create_health_router
675
-
676
- class RedisChecker(BaseChecker):
677
- @property
678
- def name(self) -> str:
679
- return 'redis'
680
-
681
- async def check(self, app) -> CheckResult:
682
- # 检查 Redis 连通性
683
- ...
684
-
685
- # 手动注册(适合需要自定义路径或额外检查器的场景)
686
- app.include_router(create_health_router(
687
- path='/health',
688
- extra_checkers=[RedisChecker()],
689
- ))
690
- ```
691
-
692
- ### 配置管理 — `config`
693
-
694
- 基于 `pydantic-settings`,支持多种配置来源(环境变量、.env、JSON、YAML、TOML),通过不同类方法加载:
695
-
696
- ```python
697
- from fastapi_augment.config import AugmentBaseSettings
698
-
699
- class Settings(AugmentBaseSettings):
700
- database_url: str
701
- redis_url: str = ''
702
- debug: bool = False
703
- secret_key: str = 'change-me'
704
-
705
- # 从环境变量加载
706
- cfg = Settings.from_env(env_prefix='APP_', env_nested_delimiter='__')
707
-
708
- # 从 .env 文件加载
709
- cfg = Settings.from_dotenv('.env', env_prefix='APP_')
710
-
711
- # 从 JSON / YAML / TOML 文件加载
712
- cfg = Settings.from_json('config.json')
713
- cfg = Settings.from_yaml('config.yaml')
714
- cfg = Settings.from_toml('config.toml')
715
- ```
716
-
717
- 支持 `SettingsConfigDict` 的所有参数(`env_prefix`、`secrets_dir`、`yaml_file` 等),
718
- 与模型字段值自动区分,无需关心分类。
719
-
720
- ### 中间件 — `middlewares`
721
-
722
- #### `RequestIdMiddleware`
723
-
724
- 自动为每个请求生成/传递 `request_id`(ULID 格式),通过 `ContextVar` 在全链路中可用:
725
-
726
- ```python
727
- from fastapi_augment.middlewares import get_request_id
728
-
729
- request_id = get_request_id()
730
- ```
731
-
732
- ## 项目结构
733
-
734
- ```
735
- fastapi_augment/
736
- ├── common/
737
- │ ├── constants.py # 全局常量与默认错误文案
738
- │ ├── exceptions.py # 4xx HTTP 异常体系
739
- │ ├── exception_handlers.py # 全局异常处理器
740
- │ └── utils/
741
- │ └── strings.py # 字符串工具 / JSON 序列化
742
- ├── config/
743
- │ ├── base_settings.py # AugmentBaseSettings 配置管理
744
- │ └── database_settings.py # DatabaseSettings 数据库配置
745
- ├── db/
746
- │ └── sqlalchemy/
747
- │ ├── engine.py # EngineManager / NodeConfig / ClusterTopology
748
- │ ├── session.py # SessionFactory(读写分离)
749
- │ ├── model_base.py # ModelBase(ULID 主键)
750
- │ ├── repository_base.py # RepositoryBase(泛型仓储 + paginate)
751
- │ ├── query_parser.py # 查询解析器(where / lookup / keyword / sort)
752
- │ ├── migrate.py # 数据库迁移 CLI
753
- │ ├── migrations/ # Alembic 迁移环境(env.py / script.py.mako)
754
- │ └── mixins/ # Timestamp / Audit / SoftDelete
755
- ├── health/
756
- │ ├── checker.py # BaseChecker / CheckResult / HealthResponse
757
- │ ├── checkers.py # AppChecker / DatabaseChecker
758
- │ └── router.py # create_health_router()
759
- ├── logger/
760
- │ ├── record_factory.py # request_id 注入工厂
761
- │ ├── filters.py # UvicornNameRewriteFilter
762
- │ ├── handlers.py # 多进程安全轮转处理器
763
- │ └── setup.py # setup_logger / set_log_level / set_log_format
764
- ├── middlewares/
765
- │ ├── base.py # BaseASGIMiddleware
766
- │ └── request_id.py # RequestId 中间件
767
- ├── schemas/
768
- │ ├── base.py # SchemaBase / ORMSchemaBase
769
- │ ├── request.py # PageParams / TimeRangeParams / KeywordParams
770
- │ ├── response.py # APIResponse / response_success / response_fail
771
- │ └── pagination.py # PageData 分页模型
772
- ├── factory.py # create_app 应用工厂
773
- ├── lifespan.py # HookRegistry 生命周期管理
774
- └── openapi.py # OpenAPI schema 优化
775
- ```
776
-
777
- ## 许可证
778
-
779
- MIT
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-augment
3
+ Version: 0.1.6
4
+ Summary: FastAPI 通用代码工具包,跨项目复用
5
+ Author-email: zarkhan <hanguangzheng@qq.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/hgz1989/fastapi_augment
8
+ Project-URL: Repository, https://github.com/hgz1989/fastapi_augment
9
+ Project-URL: Documentation, https://github.com/hgz1989/fastapi_augment#readme
10
+ Keywords: fastapi,fastapi-augment,augment,web,framework,async
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Framework :: FastAPI
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Internet :: WWW/HTTP
20
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.11
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: fastapi>=0.141.1
25
+ Provides-Extra: uvicorn
26
+ Requires-Dist: uvicorn[standard]>=0.24.0; extra == "uvicorn"
27
+ Provides-Extra: sqlalchemy
28
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.52; extra == "sqlalchemy"
29
+ Requires-Dist: python-ulid>=4.0.1; extra == "sqlalchemy"
30
+ Requires-Dist: alembic>=1.19.2; extra == "sqlalchemy"
31
+ Provides-Extra: orjson
32
+ Requires-Dist: orjson>=3.10.0; extra == "orjson"
33
+ Provides-Extra: config
34
+ Requires-Dist: pydantic-settings>=2.15.0; extra == "config"
35
+ Provides-Extra: standard
36
+ Requires-Dist: fastapi-augment[config,orjson,sqlalchemy,uvicorn]; extra == "standard"
37
+
38
+ # fastapi-augment
39
+
40
+ 跨项目复用的 FastAPI 通用代码工具包,将多个项目中反复用到的应用工厂、生命周期管理、读写分离数据库层、通用 Mixin、统一响应模型与 HTTP 异常体系统一封装,开箱即用。
41
+
42
+ ## 特性
43
+
44
+ - **应用工厂** — 一行代码创建 FastAPI 实例,自动装配中间件、路由、生命周期与数据库
45
+ - **生命周期管理** — 多注册表、优先级、超时控制、异常策略的启动/关闭钩子
46
+ - **读写分离** — 单库 / 主从 / 集群拓扑的异步引擎管理,线程安全的 Session 自动路由
47
+ - **泛型仓储** — 类型安全的异步 Repository,支持直接实例化与子类继承两种方式
48
+ - **查询解析器** — REST 风格 query string 转 SQLAlchemy 表达式,支持 FIQL 条件、关键字搜索、排序
49
+ - **可组合 Mixin** — 时间戳、审计、软删除等列混入,自由组合
50
+ - **统一响应** — 全局 `APIResponse` 格式,自动追踪 `request_id`
51
+ - **HTTP 异常** — 完整的 4xx 异常子类,内置默认文案
52
+ - **OpenAPI 优化** — 自动清理 422 响应与验证错误模型
53
+ - **日志管理** — request_id 自动注入、uvicorn 接管、多进程安全轮转、幂等初始化、一键配置
54
+ - **健康检查** — 可扩展的检查器模式,内置应用状态与数据库连通性检查,一行开关
55
+ - **配置管理** — 基于 pydantic-settings,支持 `.env` 文件、环境变量前缀、嵌套配置
56
+ - **数据库迁移 CLI** — 一行命令生成/执行迁移,自动发现用户模型
57
+ - **应用发现** — 自动发现子包 `__all__` 导出的 ASGI 应用(不限于 FastAPI),支持排除与导入串校验
58
+
59
+ ## 安装
60
+
61
+ ```bash
62
+ pip install fastapi-augment
63
+
64
+ # 推荐:安装全部可选依赖
65
+ pip install fastapi-augment[standard]
66
+
67
+ # 或按需单独安装
68
+ pip install fastapi-augment[sqlalchemy]
69
+ pip install fastapi-augment[uvicorn]
70
+ pip install fastapi-augment[orjson]
71
+ pip install fastapi-augment[config] # pydantic-settings 配置管理
72
+ ```
73
+
74
+ **要求:** Python >= 3.11
75
+
76
+ ## 快速开始
77
+
78
+ ```python
79
+ from fastapi import APIRouter
80
+ from fastapi_augment import create_app
81
+ from fastapi_augment.db.sqlalchemy import (
82
+ ClusterTopology, NodeConfig, EngineManager, SessionFactory,
83
+ ModelBase, RepositoryBase,
84
+ )
85
+ from fastapi_augment.db.sqlalchemy.mixins import TimestampMixin
86
+ from fastapi_augment.schemas import response_success
87
+
88
+ # ── 1. 数据库拓扑 ──────────────────────────────────────
89
+ topology = ClusterTopology(
90
+ primary=NodeConfig(url='postgresql+asyncpg://user:pass@host/db'),
91
+ )
92
+ manager = EngineManager(topology).start()
93
+ sessions = SessionFactory(manager)
94
+
95
+
96
+ # ── 2. 定义模型 ────────────────────────────────────────
97
+ class User(TimestampMixin, ModelBase):
98
+ __tablename__ = 'users'
99
+ name: str
100
+
101
+
102
+ # ── 3. 路由 ────────────────────────────────────────────
103
+ router = APIRouter()
104
+ user_repo = RepositoryBase(User)
105
+
106
+
107
+ @router.get('/users')
108
+ async def list_users():
109
+ async with sessions.read_session() as session:
110
+ users = await user_repo.list(session, is_active=True, limit=10)
111
+ return response_success(data=users)
112
+
113
+
114
+ # ── 4. 创建应用 ────────────────────────────────────────
115
+ app = create_app(
116
+ title='My Service',
117
+ version='1.0.0',
118
+ engine_manager=manager,
119
+ session_factory=sessions,
120
+ routers=[router],
121
+ )
122
+ ```
123
+
124
+ ### 完整可跑示例 — `examples/quickstart`
125
+
126
+ 上面的最小示例浓缩了核心 API;想看到**完整工程形态**(三段式配置、模块级日志、组合根装配、生命周期建表、软删除与聚合、统一响应、测试),直接运行示例工程:
127
+
128
+ ```bash
129
+ cd examples/quickstart
130
+ uv sync --all-groups # 安装依赖(需 uv,Python >= 3.11)
131
+ uv run python src/main.py # 启动:http://127.0.0.1:8000/docs
132
+ uv run --group dev pytest -q # 跑测试(临时 SQLite,不落盘)
133
+ ```
134
+
135
+ 示例工程沉淀自真实业务项目(browser-proxy)的工程模式,与库文档各章节一一对应(配置组合示例 ↔ `src/config/`,数据库层 ↔ `src/core/database.py`,泛型仓储 ↔ `apps/api/router.py`),详见 `examples/quickstart/README.md`。
136
+
137
+ ## 核心模块
138
+
139
+ ### 应用工厂 — `create_app()`
140
+
141
+ 统一创建 FastAPI 实例,自动装配以下组件:
142
+
143
+ | 组件 | 说明 |
144
+ | -------- | ---------------------------------------------------------- |
145
+ | 生命周期 | 接入 `fastapi_lifespan`,合并用户注册表与 `core_registry` |
146
+ | 中间件 | 自动添加 `RequestIdMiddleware`,可选 CORS |
147
+ | 路由 | 支持 `APIRouter` 列表或 `(router, kwargs)` 元组 |
148
+ | OpenAPI | 自动清理 422 响应与验证错误模型 |
149
+ | 数据库 | 可选挂载 `EngineManager` / `SessionFactory` 到 `app.state` |
150
+ | 健康检查 | `health_check=True` 一键启用 `/health` 端点 |
151
+
152
+ ```python
153
+ from fastapi_augment import create_app, HookRegistry
154
+
155
+ registry = HookRegistry()
156
+
157
+ @registry.on_startup
158
+ async def init_cache() -> None:
159
+ ...
160
+
161
+ app = create_app(
162
+ title='My Service',
163
+ registries=registry, # 单个或列表均可
164
+ cors_allow_origins=['*'],
165
+ health_check=True, # 启用健康检查
166
+ )
167
+ ```
168
+
169
+ ### 生命周期 — `HookRegistry`
170
+
171
+ 多注册表、优先级驱动的启动/关闭钩子管理:
172
+
173
+ ```python
174
+ from fastapi_augment import HookRegistry
175
+
176
+ registry = HookRegistry()
177
+
178
+ # 装饰器语法
179
+ @registry.on_startup(priority=100)
180
+ async def early_init() -> None: ...
181
+
182
+ @registry.on_shutdown
183
+ async def cleanup() -> None: ...
184
+
185
+ # 直接注册
186
+ registry.register_startup(func, priority=50, timeout=10, abort_on_exception=True)
187
+ ```
188
+
189
+ - **优先级** — 数值越大越先执行(启动降序,关闭升序)
190
+ - **超时控制** — 可设置单个钩子的超时秒数
191
+ - **异常策略** — `abort_on_exception` 控制异常时是否终止流程
192
+
193
+ ### 数据库层 — `db.sqlalchemy`
194
+
195
+ #### 引擎管理 — `EngineManager`
196
+
197
+ 支持三种部署拓扑:
198
+
199
+ ```python
200
+ from fastapi_augment.db.sqlalchemy import ClusterTopology, NodeConfig, EngineManager
201
+
202
+ # 单库
203
+ topology = ClusterTopology(
204
+ primary=NodeConfig(url='postgresql+asyncpg://user:pass@host/db'),
205
+ )
206
+
207
+ # 主从
208
+ topology = ClusterTopology(
209
+ primary=NodeConfig(url='postgresql+asyncpg://primary/db'),
210
+ replicas=[NodeConfig(url='postgresql+asyncpg://replica-1/db')],
211
+ )
212
+
213
+ # 集群(主从 + 独立只读节点)
214
+ topology = ClusterTopology(
215
+ primary=NodeConfig(url='postgresql+asyncpg://primary/db'),
216
+ replicas=[NodeConfig(url='postgresql+asyncpg://replica-1/db')],
217
+ readonly=[NodeConfig(url='postgresql+asyncpg://readonly-1/db')],
218
+ )
219
+
220
+ manager = EngineManager(topology).start()
221
+ # 读引擎轮询(round-robin),线程安全
222
+ read_engine = manager.next_read_engine()
223
+ ```
224
+
225
+ #### 会话工厂 — `SessionFactory`
226
+
227
+ 读写分离的异步 Session 工厂,支持 FastAPI `Depends()` 注入:
228
+
229
+ ```python
230
+ from fastapi_augment.db.sqlalchemy import SessionFactory
231
+
232
+ sessions = SessionFactory(manager)
233
+
234
+ # 写会话(自动 commit/rollback)
235
+ async with sessions.transaction() as session:
236
+ session.add(obj)
237
+ # 自动 commit
238
+
239
+ # 读会话(轮询读引擎)
240
+ async with sessions.read_session() as session:
241
+ result = await session.execute(select(User))
242
+
243
+ # FastAPI 依赖注入
244
+ from fastapi import Depends
245
+ from sqlalchemy.ext.asyncio import AsyncSession
246
+
247
+ @router.get('/users')
248
+ async def list_users(session: AsyncSession = Depends(sessions.depends_read)):
249
+ ...
250
+ ```
251
+
252
+ > **缓存说明:** `read_session()` 按 `AsyncEngine` 缓存 `async_sessionmaker`,线程安全,避免重复创建。打印 `SessionFactory` 实例可查看已缓存的引擎信息。
253
+
254
+ #### 模型基类 — `ModelBase`
255
+
256
+ 基于 ULID 主键的声明式模型基类:
257
+
258
+ ```python
259
+ from fastapi_augment.db.sqlalchemy import ModelBase, TimestampMixin
260
+
261
+ class User(TimestampMixin, ModelBase):
262
+ __tablename__ = 'users'
263
+ name: str
264
+ ```
265
+
266
+ #### 泛型仓储 — `RepositoryBase`
267
+
268
+ 类型安全的异步仓储基类,Repository 方法只 **flush**,不 commit,事务边界由调用方控制。
269
+ 支持两种使用方式:
270
+
271
+ ```python
272
+ from fastapi_augment.db.sqlalchemy import RepositoryBase
273
+
274
+ # 方式 1:直接实例化 — 显式传入模型类
275
+ user_repo = RepositoryBase(User)
276
+
277
+
278
+ # 方式 2:子类继承 — 通过泛型参数绑定模型,可扩展自定义方法
279
+ class UserRepo(RepositoryBase[User]):
280
+ async def find_by_email(self, session, email: str) -> User | None:
281
+ return await self.get_first(session, email=email)
282
+
283
+
284
+ user_repo = UserRepo() # 无需再传 User
285
+ ```
286
+
287
+ **CRUD 操作:**
288
+
289
+ ```python
290
+ # Create(静态方法)
291
+ async with sessions.transaction() as session:
292
+ await user_repo.create(session, User(name='alice'))
293
+ await user_repo.create_many(session, [User(name='bob'), User(name='carol')])
294
+
295
+ # Read(实例方法)
296
+ async with sessions.read_session() as session:
297
+ user = await user_repo.get(session, id_='01HXK...')
298
+ user = await user_repo.get_first(session, name='alice') # 取第一条,无匹配返回 None
299
+ user = await user_repo.get_unique(session, email='a@b.com') # 精确唯一,多条匹配抛 MultipleResultsFound
300
+ users = await user_repo.list(session, role='admin', order_by=['-created_at'], limit=10)
301
+ total = await user_repo.count(session, is_active=True)
302
+ has_admin = await user_repo.exists(session, role='admin')
303
+
304
+ # 分页查询(返回 dict:items / page / size / total / pages;size 上限 1000,超限静默截断)
305
+ result = await user_repo.paginate(session, page=1, size=10, role='admin', order_by=['-created_at'])
306
+ # result = {'items': [...], 'page': 1, 'size': 10, 'total': 100, 'pages': 10}
307
+
308
+ # Update
309
+ async with sessions.transaction() as session:
310
+ await user_repo.update(session, user, name='new_name')
311
+ affected = await user_repo.update_by_id(session, id_='01HXK...', name='new_name')
312
+
313
+ # Delete
314
+ async with sessions.transaction() as session:
315
+ await user_repo.delete(session, user)
316
+ deleted = await user_repo.delete_by_id(session, id_='01HXK...')
317
+ count = await user_repo.delete_where(session, is_active=False)
318
+ ```
319
+
320
+ **软删除感知(模型混入 `SoftDeleteMixin` 时自动生效,非软删模型行为不变):**
321
+
322
+ ```python
323
+ # 查询默认排除已软删行;include_deleted=True 可放开
324
+ user = await user_repo.get(session, id_='01HXK...') # 已软删 → None
325
+ users = await user_repo.list(session, include_deleted=True) # 包含已软删行
326
+ total = await user_repo.count(session, include_deleted=True)
327
+
328
+ # 删除自动转软删(写 is_deleted=True + deleted_at);非软删模型仍为物理删除
329
+ await user_repo.delete(session, user)
330
+ deleted = await user_repo.delete_by_id(session, id_='01HXK...')
331
+ count = await user_repo.delete_where(session, is_active=False)
332
+
333
+ # 需要真正物理删除时显式调用 hard_delete_*
334
+ await user_repo.hard_delete_by_id(session, id_='01HXK...')
335
+ count = await user_repo.hard_delete_where(session, is_active=False)
336
+ ```
337
+
338
+ **聚合与批量更新:**
339
+
340
+ ```python
341
+ # 数值列聚合(默认排除已软删行,支持过滤条件;无匹配行返回 None)
342
+ total_amount = await user_repo.sum(session, 'amount', role='admin')
343
+ avg_amount = await user_repo.avg(session, 'amount')
344
+ min_amount = await user_repo.min(session, 'amount')
345
+ max_amount = await user_repo.max(session, 'amount')
346
+
347
+ # 批量更新(单条 UPDATE,返回受影响行数;默认跳过已软删行)
348
+ affected = await user_repo.update_where(session, {'role': 'admin'}, name='alice')
349
+ ```
350
+
351
+ **过滤语法:**
352
+
353
+ ```python
354
+ # 关键字过滤 — 等值匹配
355
+ await user_repo.list(session, name='alice')
356
+
357
+ # 序列 — 自动转为 IN 查询
358
+ await user_repo.list(session, id_=['01HXK...', '01HXL...'])
359
+
360
+ # None — 自动转为 IS NULL
361
+ await user_repo.list(session, deleted_at=None)
362
+
363
+ # 原生 SQLAlchemy 表达式
364
+ await user_repo.list(session, expressions=(User.age > 18,))
365
+
366
+ # 排序:字段名前缀 - 表示降序
367
+ await user_repo.list(session, order_by=['-created_at', 'name'])
368
+ ```
369
+
370
+ ### 查询解析器 — `query_parser`
371
+
372
+ 将 REST 风格的 query string 转为 SQLAlchemy `ColumnElement` 条件表达式,可直接传入 `RepositoryBase` 的 `expressions` / `order_by` 参数。
373
+
374
+ #### where 组合条件(FIQL 风格)
375
+
376
+ ```
377
+ where = and_group ("," and_group)* , 表示 OR
378
+ and_group = unit (";" unit)* ; 表示 AND
379
+ unit = "(" where ")" | condition
380
+ condition = field OP value
381
+ ```
382
+
383
+ **支持的操作符:**
384
+
385
+ | 语法 | 含义 | 示例 |
386
+ | ---------------- | -------------------- | ------------------------- |
387
+ | `field==value` | 等于 | `status==1` |
388
+ | `field!=value` | 不等 | `status!=0` |
389
+ | `field~=value` | 模糊包含 (ILIKE) | `name~=张` |
390
+ | `field>value` | 大于 | `age>18` |
391
+ | `field>=value` | 大于等于 | `age>=18` |
392
+ | `field<value` | 小于 | `age<60` |
393
+ | `field<=value` | 小于等于 | `age<=60` |
394
+ | `field~start~end`| 区间 (BETWEEN) | `age~18~60` |
395
+
396
+ ```python
397
+ from fastapi_augment.db.sqlalchemy import parse_where
398
+
399
+ # 昵称含张 且 状态非禁用
400
+ expr = parse_where('nickname~=张;status!=0', User)
401
+
402
+ # 括号内 OR,与区间 AND
403
+ expr = parse_where('(nickname~=张,username~=王);age~20~30', User)
404
+ ```
405
+
406
+ #### lookup 精确匹配
407
+
408
+ ```python
409
+ from fastapi_augment.db.sqlalchemy import parse_lookup
410
+
411
+ # 单字段精确匹配
412
+ expr = parse_lookup('phone==13800138000', User, fields={'phone', 'email'})
413
+
414
+ # 多字段 AND(; 分隔)
415
+ expr = parse_lookup('phone==138;status==1', User, fields={'phone', 'status'})
416
+ ```
417
+
418
+ #### 关键字搜索
419
+
420
+ ```python
421
+ from fastapi_augment.db.sqlalchemy import parse_keyword
422
+
423
+ # 多字段 OR 模糊搜索
424
+ expr = parse_keyword('admin', 'username,email,nickname', User)
425
+ ```
426
+
427
+ #### 排序
428
+
429
+ ```python
430
+ from fastapi_augment.db.sqlalchemy import parse_sort
431
+
432
+ # - 前缀表示降序,无前缀为升序
433
+ order_by = parse_sort('-created_at,nickname', User)
434
+ ```
435
+
436
+ #### 一键组合 — `build_query_expressions`
437
+
438
+ ```python
439
+ from fastapi_augment.db.sqlalchemy import build_query_expressions
440
+
441
+ expressions, order_by = build_query_expressions(
442
+ User,
443
+ where='status!=0;age>=18',
444
+ q='admin',
445
+ q_field='username,nickname',
446
+ sort='-created_at',
447
+ )
448
+
449
+ # 直接传给 paginate / list
450
+ result = await user_repo.paginate(
451
+ session, page=1, size=10,
452
+ expressions=expressions, order_by=order_by,
453
+ )
454
+ ```
455
+
456
+ ### 数据库迁移 CLI — `fastapi-augment-migrate`
457
+
458
+ 内置 Alembic 迁移工具,提供 `init` / `generate` / `upgrade` 三个子命令,开箱即用。
459
+
460
+ #### 快速开始
461
+
462
+ ```bash
463
+ # 1. 初始化(一次性操作,生成 alembic.ini + migrations/versions/)
464
+ fastapi-augment-migrate init --db-url "sqlite:///test.db"
465
+
466
+ # 2. 生成迁移
467
+ fastapi-augment-migrate generate \
468
+ --message "add_user_table" \
469
+ --models "models,apps.ai.models"
470
+
471
+ # 3. 执行迁移
472
+ fastapi-augment-migrate upgrade
473
+ ```
474
+
475
+ #### 初始化 — `init`
476
+
477
+ 在项目根目录生成 `alembic.ini` 和 `migrations/versions/` 目录。
478
+ 若 `alembic.ini` 已存在则跳过,不会覆盖。
479
+
480
+ ```bash
481
+ # 初始化
482
+ fastapi-augment-migrate init --db-url "sqlite:///test.db"
483
+
484
+ # 指定项目根目录
485
+ fastapi-augment-migrate init --db-url "postgresql+asyncpg://user:pass@host/db" --project-dir /path/to/project
486
+ ```
487
+
488
+ `alembic.ini` 中的 `script_location` 指向库内的 `env.py`,`version_locations` 指向本地 `migrations/versions/`。
489
+ `env.py` 通过环境变量 `FASTAPI_AUGMENT_MODELS` 动态导入用户模型,无需手动修改。
490
+
491
+ #### 生成迁移 — `generate`
492
+
493
+ 通过 `--models` 指定模型模块(逗号分隔),调用 `alembic revision --autogenerate` 生成迁移脚本。
494
+ 需要先执行 `init` 初始化。
495
+
496
+ ```bash
497
+ # 生成迁移
498
+ fastapi-augment-migrate generate \
499
+ --message "add_user_table" \
500
+ --models "models,apps.ai.models"
501
+
502
+ # 指定项目根目录
503
+ fastapi-augment-migrate generate \
504
+ --message "add_item" \
505
+ --models "apps.ai.models" \
506
+ --project-dir /path/to/project
507
+ ```
508
+
509
+ 迁移文件生成在 `<项目根>/migrations/versions/` 目录下。
510
+
511
+ #### 升级 / 降级 — `upgrade`
512
+
513
+ `--db-url` 为可选参数,不传时直接从 `alembic.ini` 读取 `sqlalchemy.url`。
514
+
515
+ ```bash
516
+ # 升级到最新版本(从 alembic.ini 读取数据库 URL)
517
+ fastapi-augment-migrate upgrade
518
+
519
+ # 指定数据库 URL(覆盖 alembic.ini 中的配置)
520
+ fastapi-augment-migrate upgrade --db-url "sqlite:///test.db"
521
+
522
+ # 升级到指定版本
523
+ fastapi-augment-migrate upgrade --revision abc123
524
+
525
+ # 降级一个版本
526
+ fastapi-augment-migrate upgrade --downgrade
527
+
528
+ # 降级到指定版本
529
+ fastapi-augment-migrate upgrade --downgrade --revision abc123
530
+
531
+ # 指定项目根目录
532
+ fastapi-augment-migrate upgrade --project-dir /path/to/project
533
+ ```
534
+
535
+ #### CLI 参数一览
536
+
537
+ | 子命令 | 参数 | 说明 |
538
+ | ---------- | --------------- | --------------------------------------- |
539
+ | `init` | `--db-url` | 数据库 URL(默认 `sqlite:///app.db`) |
540
+ | | `--project-dir` | 项目根目录(默认当前目录) |
541
+ | `generate` | `--message` | **必填**,迁移描述 |
542
+ | | `--models` | **必填**,模型模块路径,逗号分隔 |
543
+ | | `--project-dir` | 项目根目录(默认当前目录) |
544
+ | `upgrade` | `--db-url` | 数据库 URL(不传则从 alembic.ini 读取) |
545
+ | | `--revision` | 目标版本(默认 `head`) |
546
+ | | `--downgrade` | 降级模式 |
547
+ | | `--project-dir` | 项目根目录(默认当前目录) |
548
+
549
+ ### 模型 Mixin — `db.sqlalchemy.mixins`
550
+
551
+ 可组合的列混入,按需叠加:
552
+
553
+ | Mixin | 提供的列 |
554
+ | ---------------------- | ------------------------------------------ |
555
+ | `CreatedAtMixin` | `created_at` |
556
+ | `TimestampMixin` | `created_at` + `updated_at` |
557
+ | `CreatedByMixin` | `created_by` |
558
+ | `UpdatedByMixin` | `updated_by` |
559
+ | `AuditMixin` | `created_by` + `updated_by` |
560
+ | `SoftDeleteMixin` | `is_deleted` + `deleted_at` |
561
+ | `SoftDeleteAuditMixin` | `is_deleted` + `deleted_at` + `deleted_by` |
562
+
563
+ ```python
564
+ from fastapi_augment.db.sqlalchemy import ModelBase
565
+ from fastapi_augment.db.sqlalchemy.mixins import TimestampMixin, SoftDeleteMixin
566
+
567
+ class User(TimestampMixin, SoftDeleteMixin, ModelBase):
568
+ __tablename__ = 'users'
569
+ name: str
570
+ ```
571
+
572
+ ### 统一响应 — `schemas`
573
+
574
+ #### `APIResponse` — 全局返回格式
575
+
576
+ ```json
577
+ {
578
+ "request_id": "019xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
579
+ "code": 0,
580
+ "message": "操作成功",
581
+ "data": { ... },
582
+ "extra": null
583
+ }
584
+ ```
585
+
586
+ `request_id` 自动从 `ContextVar` 获取,无需手动传递。
587
+
588
+ #### 工厂函数
589
+
590
+ ```python
591
+ from fastapi_augment.schemas import response_success, response_fail
592
+
593
+ # 成功响应
594
+ return response_success(data=user)
595
+ return response_success(data=users, extra={'total': 100})
596
+
597
+ # 失败响应
598
+ return response_fail(code=40001, message='用户名已存在')
599
+ ```
600
+
601
+ #### 请求参数模型
602
+
603
+ ```python
604
+ from fastapi_augment.schemas import PageParams, TimeRangeParams, KeywordParams
605
+
606
+ # 分页参数
607
+ @router.get('/users')
608
+ async def list_users(params: PageParams = Depends()):
609
+ ...
610
+
611
+ # 时间范围 + 关键词搜索
612
+ @router.get('/orders')
613
+ async def list_orders(
614
+ time_range: TimeRangeParams = Depends(),
615
+ keyword: KeywordParams = Depends(),
616
+ ):
617
+ ...
618
+ ```
619
+
620
+ ### HTTP 异常 — `common.exceptions`
621
+
622
+ 完整的 4xx 异常子类,内置默认文案。子类只需声明 `_status_code` 类变量,无需重写 `__init__`:
623
+
624
+ ```python
625
+ from fastapi_augment.common.exceptions import (
626
+ BadRequestError, # 400
627
+ UnauthorizedError, # 401
628
+ ForbiddenError, # 403
629
+ NotFoundError, # 404
630
+ ConflictError, # 409
631
+ TooManyRequestsError, # 429(支持 retry_after 参数)
632
+ # ... 更多异常
633
+ )
634
+
635
+ # 使用默认文案
636
+ raise NotFoundError()
637
+
638
+ # 自定义提示
639
+ raise BadRequestError(detail='用户名不能为空')
640
+
641
+ # 限流场景
642
+ raise TooManyRequestsError(retry_after=60)
643
+ ```
644
+
645
+ ### 日志管理 — `logger`
646
+
647
+ 导入即生效:自动注入 `request_id` 到每条日志、接管 uvicorn/fastapi 日志输出。
648
+
649
+ ```python
650
+ from fastapi_augment.logger import setup_logger, set_log_level
651
+
652
+ # 一键配置:控制台 + 按天轮转文件日志
653
+ setup_logger(log_dir='./logs', rotation='day', backup_count=30)
654
+
655
+ # 动态调整日志级别
656
+ set_log_level('info')
657
+ ```
658
+
659
+ **支持的轮转粒度:**
660
+
661
+ | 粒度 | 说明 |
662
+ | ---------------------------------- | -------------------- |
663
+ | `'second'` / `'minute'` / `'hour'` | 每整秒/分/点 |
664
+ | `'day'`(默认) | 每天 00:00 |
665
+ | `'week'` | 每周一 00:00 |
666
+ | `'month'` | 每月 1 日 00:00 |
667
+ | `'year'` | 每年 1 月 1 日 00:00 |
668
+
669
+ **核心能力:**
670
+
671
+ - **request_id 注入** — 每条日志自动携带当前请求的 `request_id`,方便链路追踪
672
+ - **uvicorn 接管** — 统一 `uvicorn.error` / `uvicorn.access` 的日志名称为 `uvicorn`,屏蔽第三方库 DEBUG 噪声
673
+ - **多进程安全** — 日志轮转时捕获 `OSError`(含 `PermissionError`、`FileNotFoundError`),兼容多进程部署(如 `uvicorn --workers N`)
674
+ - **幂等初始化** — 重复导入或多次调用 `setup_logger()` 不会叠加处理器或工厂链
675
+ - **控制台开关** — `enable_console=False` 可关闭控制台输出,仅保留文件日志
676
+
677
+ ### 健康检查 — `health`
678
+
679
+ 可扩展的检查器模式,内置应用状态与数据库连通性检查。
680
+
681
+ #### 一行启用
682
+
683
+ ```python
684
+ app = create_app(
685
+ title='My Service',
686
+ engine_manager=manager,
687
+ health_check=True, # 自动注册 /health 端点
688
+ )
689
+ ```
690
+
691
+ `GET /health` 响应示例:
692
+
693
+ ```json
694
+ {
695
+ "status": "healthy",
696
+ "checks": [
697
+ {
698
+ "name": "app",
699
+ "status": "healthy",
700
+ "latencyMs": 0,
701
+ "details": {
702
+ "status": "running",
703
+ "version": "1.0.0",
704
+ "uptimeSeconds": 3600
705
+ }
706
+ },
707
+ { "name": "database", "status": "healthy", "latencyMs": 2.3 }
708
+ ]
709
+ }
710
+ ```
711
+
712
+ - 总体状态取所有检查项中**最差**的(healthy < degraded < unhealthy)
713
+ - 任一检查项 unhealthy 时 HTTP 返回 **503**,便于负载均衡器/探针识别
714
+ - 传入 `engine_manager` 时自动包含数据库检查,否则仅检查应用状态
715
+
716
+ #### 自定义检查器
717
+
718
+ ```python
719
+ from fastapi_augment.health import BaseChecker, CheckResult, create_health_router
720
+
721
+ class RedisChecker(BaseChecker):
722
+ @property
723
+ def name(self) -> str:
724
+ return 'redis'
725
+
726
+ async def check(self, app) -> CheckResult:
727
+ # 检查 Redis 连通性
728
+ ...
729
+
730
+ # 手动注册(适合需要自定义路径或额外检查器的场景)
731
+ app.include_router(create_health_router(
732
+ path='/health',
733
+ extra_checkers=[RedisChecker()],
734
+ ))
735
+ ```
736
+
737
+ ### 配置管理 — `config`
738
+
739
+ 基于 `pydantic-settings`,支持多种配置来源(环境变量、.env、JSON、YAML、TOML),通过不同类方法加载:
740
+
741
+ ```python
742
+ from fastapi_augment.config import AugmentBaseSettings
743
+
744
+ class Settings(AugmentBaseSettings):
745
+ database_url: str
746
+ redis_url: str = ''
747
+ debug: bool = False
748
+ secret_key: str = 'change-me'
749
+
750
+ # 从环境变量加载
751
+ cfg = Settings.from_env(env_prefix='APP_', env_nested_delimiter='__')
752
+
753
+ # 从 .env 文件加载
754
+ cfg = Settings.from_dotenv('.env', env_prefix='APP_')
755
+
756
+ # 从 JSON / YAML / TOML 文件加载
757
+ cfg = Settings.from_json('config.json')
758
+ cfg = Settings.from_yaml('config.yaml')
759
+ cfg = Settings.from_toml('config.toml')
760
+ ```
761
+
762
+ 支持 `SettingsConfigDict` 的所有参数(`env_prefix`、`secrets_dir`、`yaml_file` 等),
763
+ 与模型字段值自动区分,无需关心分类。
764
+
765
+ #### 项目配置组合示例
766
+
767
+ 实际项目中通常组合多个配置类——全局(日志)、项目信息、Uvicorn 运行参数。以下为推荐模式:
768
+ 只定义字段与加载方式,具体值由 `.env` 与环境变量注入;库仅提供 `AugmentBaseSettings` 基类。
769
+
770
+ ```python
771
+ # src/config/settings.py —— 全局配置(日志 + 根目录)
772
+ from pathlib import Path
773
+
774
+ from fastapi_augment.common.utils import find_project_root, get_root_dir
775
+ from fastapi_augment.config import AugmentBaseSettings
776
+
777
+ __VERSION__ = '0.1.0'
778
+ _ENV_PREFIX = 'MY_SERVICE'
779
+
780
+ try:
781
+ _root_dir = find_project_root()
782
+ except Exception:
783
+ _root_dir = get_root_dir(__file__, 2)
784
+
785
+
786
+ class Settings(AugmentBaseSettings):
787
+ """项目全局配置,字段通过 MY_SERVICE_ 前缀环境变量或 .env 覆盖"""
788
+
789
+ # 日志配置(对应 fastapi_augment.logger.setup_logger 参数)
790
+ logs_dir: str | Path | None = _root_dir / 'logs'
791
+ logs_level: str | None = 'INFO'
792
+ logs_filename: str = 'app.log'
793
+ logs_rotation: str = 'hour'
794
+ logs_backup_count: int = 30
795
+ logs_encoding: str = 'utf-8'
796
+ logs_enable_console: bool = True
797
+
798
+ @property
799
+ def root_dir(self) -> Path:
800
+ return _root_dir
801
+
802
+
803
+ settings = Settings.from_env(
804
+ env_file=_root_dir / '.env',
805
+ env_prefix=f'{_ENV_PREFIX}_',
806
+ env_nested_delimiter='__',
807
+ env_parse_none_str='null',
808
+ )
809
+ ```
810
+
811
+ ```python
812
+ # src/config/project_settings.py —— 项目元信息(debug / title / version)
813
+ from fastapi_augment.config import AugmentBaseSettings
814
+
815
+ from .settings import __VERSION__, _root_dir, _ENV_PREFIX
816
+
817
+
818
+ class ProjectSettings(AugmentBaseSettings):
819
+ """项目基础配置,字段通过 MY_SERVICE_PROJECT_ 前缀覆盖"""
820
+
821
+ debug: bool = False
822
+ title: str = 'my-service'
823
+ summary: str = ''
824
+
825
+ @property
826
+ def version(self) -> str:
827
+ return __VERSION__
828
+
829
+
830
+ project_settings = ProjectSettings.from_env(
831
+ env_file=_root_dir / '.env',
832
+ env_prefix=f'{_ENV_PREFIX}_PROJECT_',
833
+ env_nested_delimiter='__',
834
+ env_parse_none_str='null',
835
+ )
836
+ ```
837
+
838
+ ```python
839
+ # src/config/uvicorn_settings.py —— Uvicorn 运行参数(不使用 uvicorn 时无需定义)
840
+ from fastapi_augment.config import AugmentBaseSettings
841
+
842
+ from .project_settings import project_settings
843
+ from .settings import _root_dir, _ENV_PREFIX
844
+
845
+
846
+ class UvicornSettings(AugmentBaseSettings):
847
+ """Uvicorn 运行配置,字段通过 MY_SERVICE_UVICORN_ 前缀覆盖"""
848
+
849
+ host: str = '0.0.0.0'
850
+ port: int = 8000
851
+ workers: int = 2
852
+ access_log: bool = True
853
+ root_path: str = ''
854
+ asgi_app_ref: str = 'apps.main:app' # 启动入口,可用环境变量覆盖
855
+
856
+ @property
857
+ def reload(self) -> bool:
858
+ """是否热重载,联动项目 debug 开关"""
859
+ return project_settings.debug
860
+
861
+
862
+ uvicorn_settings = UvicornSettings.from_env(
863
+ env_file=_root_dir / '.env',
864
+ env_prefix=f'{_ENV_PREFIX}_UVICORN_',
865
+ env_nested_delimiter='__',
866
+ env_parse_none_str='null',
867
+ )
868
+ ```
869
+
870
+ **要点:**
871
+
872
+ - 配置类、环境变量前缀、默认值均为**使用方策略**,留在项目内,不进库
873
+ - 各配置类前缀独立(`MY_SERVICE_` / `MY_SERVICE_PROJECT_` / `MY_SERVICE_UVICORN_`),互不干扰
874
+ - 不使用 uvicorn 时无需定义 `UvicornSettings`,库不强制
875
+ - 环境变量优先级高于 `.env` 文件
876
+
877
+ ### 中间件 — `middlewares`
878
+
879
+ #### `RequestIdMiddleware`
880
+
881
+ 自动为每个请求生成/传递 `request_id`(ULID 格式),通过 `ContextVar` 在全链路中可用:
882
+
883
+ ```python
884
+ from fastapi_augment.middlewares import get_request_id
885
+
886
+ request_id = get_request_id()
887
+ ```
888
+
889
+ ### 应用发现 — `common.app_discovery`
890
+
891
+ 递归发现 `apps` 包下所有子包通过 `__all__` 导出的 ASGI 应用(不限于 FastAPI),用于多应用聚合部署与启动前校验。
892
+
893
+ **约定:** 每个业务子包(如 `apps.platform`)在 `__init__.py` 的 `__all__` 中导出自己创建的 ASGI 应用(如 `platform_app`,不限于 FastAPI 实例);只有出现在 `__all__` 且通过 `is_asgi_app` 判定的对象才被识别为"应用"(判定规则见下)。
894
+
895
+ ```python
896
+ from fastapi_augment.common import ASGIAppSpec, discover_asgi_apps, validate_asgi_import
897
+
898
+ # 发现全部应用(排除主应用,获取主应用之外的其它应用)
899
+ apps: list[ASGIAppSpec] = discover_asgi_apps(root='apps', exclude='apps.platform:platform_app')
900
+
901
+ # 每个应用可直接启动(import_string 即 uvicorn 导入串)
902
+ for spec in apps:
903
+ uvicorn.run(spec.import_string) # 'apps.platform:platform_app'
904
+
905
+ # 启动前校验主应用导入串是否真实存在且为可调用的 ASGI 应用(uvicorn 可直接启动)
906
+ validate_asgi_import('apps.platform:platform_app')
907
+ ```
908
+
909
+ - **`exclude` 支持三种标识** — 模块名(`apps.platform`)、导出名(`platform_app`)或 `module:name` 导入串(`apps.platform:platform_app`)
910
+ - 结果按模块名排序,顺序稳定
911
+ - **`validate_asgi_import` 不限于 FastAPI** — 只要是可调用的 ASGI 应用(Starlette / Flask 等或自定义 ASGI 函数,判定规则见下)均通过
912
+
913
+ **ASGI 类型与校验** — `common.asgi_types` 提供遵循 ASGI 规范的类型定义(`ASGIApplication` / `ASGI2Application` / `ASGI3Application` / `Scope` 等)与运行时近似判定 `is_asgi_app()`(返回 `TypeGuard[ASGIApplication]`,`if is_asgi_app(x)` 后类型检查器自动将 `x` 收窄为 ASGI 应用):callable 为硬门槛(与 uvicorn `Config.load` 一致),签名可解析时进一步检查 ASGI2(类,scope 单参)或 ASGI3(`scope, receive, send` 三参)结构,`*args` 包装器放行。签名判定为近似,权威判定以 uvicorn 实际启动为准。
914
+
915
+ ## 开发与发布
916
+
917
+ ### 分支策略
918
+
919
+ - **`master`** — 受保护分支,**禁止直接提交代码**,仅可通过其它分支 PR 合并
920
+ - **`develop`**(或功能分支)— 日常开发与版本号更新,完成后通过 PR 合并到 `master`
921
+ - 发布类操作(Release 打 tag / 上传、PyPI 发布)**仅 `master` 分支可执行**,workflow 已做分支校验
922
+
923
+ ### 代码检查与测试
924
+
925
+ ```bash
926
+ # lint(读取 pyproject.toml 的 [tool.ruff] 配置)
927
+ uvx ruff check src tests
928
+
929
+ # 类型检查(读取 pyproject.toml 的 [tool.mypy] 配置)
930
+ uv run mypy src tests
931
+
932
+ # 测试(覆盖率门禁 --cov-fail-under=90 在 pyproject.toml 的 pytest addopts 中配置)
933
+ uv run --frozen pytest -q
934
+ ```
935
+
936
+ CI(`.github/workflows/lint.yml`,即 **CI** workflow)已配置自动检查,PR / develop push 时运行:
937
+
938
+ - **Ruff** — `uvx ruff check src tests`
939
+ - **Mypy** — `uv run mypy src tests`(Python 3.11)
940
+ - **Pytest** — Python 3.11 / 3.12 / 3.13 矩阵并行,覆盖率不低于 90%(`--cov-fail-under=90`)
941
+
942
+ 三个 Job 全部通过才允许合并到 `master`(分支保护所需状态检查为 `Ruff`、`Mypy` 与 `Pytest (Python x.y)`)。PR 阶段只做检查,不打包、不打 tag、不发布。
943
+
944
+ ### 发布 Release
945
+
946
+ 单个 workflow(`.github/workflows/release.yml`)串行完成 GitHub Release 与 PyPI 发布,两个 Job:
947
+
948
+ 1. **release Job** — 版本守卫 → 代码门禁(pytest + ruff + mypy)→ 源码打包(zip / tar.gz)→ 上传 GitHub Release(tag 由 `gh release create` 自动创建,成功时 tag 必然存在)
949
+ 2. **publish Job**(`needs: release`)— **仅当 GitHub Release 成功后才执行**:查 PyPI 是否已有该版本(无则上传)→ build → 版本一致性断言 → `twine check` 校验打包 → 上传 PyPI
950
+
951
+ 触发方式:
952
+
953
+ - **推送到 `master`(自动)** — 版本守卫判定"需要发布"时自动执行;不需要时跳过
954
+ - **master 分支手动触发**(workflow_dispatch)— `release` 发布新版本 / `rebuild` 重新打包指定版本
955
+
956
+ 版本守卫规则(release 与 publish 各自独立判断):
957
+
958
+ | 状态 | push 合并 | 手动触发 |
959
+ | ---- | --------- | -------- |
960
+ | tag 与 Release 均在且与代码版本一致 | 跳过 | 报错引导先更新 `VERSION` |
961
+ | 缺 tag 或缺 Release | 补齐发布 | 补齐发布 |
962
+ | rebuild 模式 | — | 直接放行 |
963
+
964
+ publish Job 以 PyPI 线上版本为准(查询 `pypi.org/pypi/<project>/<version>/json`,404 才上传),PyPI 版本不可覆盖,天然不重复。
965
+
966
+ 发布类操作仅 master 分支可执行,任何一步失败都不会产生半成品:
967
+
968
+ 1. **代码门禁** — 判定需要发布时先跑 pytest + ruff + mypy,任一失败即停止(防止绕过分支保护发布未验证代码)
969
+ 2. **版本一致性校验** — 目标版本与代码版本不一致时终止并引导(master 受保护,需先在 `develop` 更新 `VERSION` 与 `factory.py` 版本,PR 合并后重试)
970
+ 3. **打包** — 源码打包为 `fastapi_augment-<版本>.zip` / `.tar.gz`(排除 `.venv`、缓存、构建产物)
971
+ 4. **Release** — 打包成功后才上传 GitHub Release;失败不留任何 tag/Release,重试不会跳版本
972
+ 5. **PyPI** — Release 成功后才上传,上传前经 `twine check` 校验 sdist/wheel 元数据,使用 `PYPI_API_TOKEN`(GitHub Secrets)认证
973
+
974
+ 建议发布前先在 `develop` 分支完成版本号更新并 PR 合并到 `master`——合并触发自动发布,发布流程将直接复用代码版本。
975
+
976
+ ## 项目结构
977
+
978
+ ```
979
+ fastapi_augment/
980
+ ├── common/
981
+ │ ├── app_discovery.py # ASGI 应用发现(__all__ 约定)
982
+ │ ├── asgi_types.py # ASGI 类型定义与 is_asgi_app 运行时判定(TypeGuard)
983
+ │ ├── constants.py # 全局常量与默认错误文案
984
+ │ ├── exceptions.py # 4xx HTTP 异常体系
985
+ │ ├── exception_handlers.py # 全局异常处理器
986
+ │ └── utils/
987
+ │ ├── paths.py # 路径工具(get_root_dir / find_project_root)
988
+ │ └── strings.py # 字符串工具 / JSON 序列化
989
+ ├── config/
990
+ │ ├── base_settings.py # AugmentBaseSettings 配置管理
991
+ │ └── database_settings.py # DatabaseSettings 数据库配置
992
+ ├── db/
993
+ │ └── sqlalchemy/
994
+ │ ├── engine.py # EngineManager / NodeConfig / ClusterTopology
995
+ │ ├── session.py # SessionFactory(读写分离)
996
+ │ ├── model_base.py # ModelBase(ULID 主键)
997
+ │ ├── repository_base.py # RepositoryBase(泛型仓储 + paginate)
998
+ │ ├── query_parser.py # 查询解析器(where / lookup / keyword / sort)
999
+ │ ├── migrate.py # 数据库迁移 CLI
1000
+ │ ├── migrations/ # Alembic 迁移环境(env.py / script.py.mako)
1001
+ │ └── mixins/ # Timestamp / Audit / SoftDelete
1002
+ ├── health/
1003
+ │ ├── checker.py # BaseChecker / CheckResult / HealthResponse
1004
+ │ ├── checkers.py # AppChecker / DatabaseChecker
1005
+ │ └── router.py # create_health_router()
1006
+ ├── logger/
1007
+ │ ├── record_factory.py # request_id 注入工厂
1008
+ │ ├── filters.py # UvicornNameRewriteFilter
1009
+ │ ├── handlers.py # 多进程安全轮转处理器
1010
+ │ └── setup.py # setup_logger / set_log_level / set_log_format
1011
+ ├── middlewares/
1012
+ │ ├── base.py # BaseASGIMiddleware
1013
+ │ └── request_id.py # RequestId 中间件
1014
+ ├── schemas/
1015
+ │ ├── base.py # SchemaBase / ORMSchemaBase
1016
+ │ ├── request.py # PageParams / TimeRangeParams / KeywordParams
1017
+ │ ├── response.py # APIResponse / response_success / response_fail
1018
+ │ └── pagination.py # PageData 分页模型
1019
+ ├── factory.py # create_app 应用工厂
1020
+ ├── lifespan.py # HookRegistry 生命周期管理
1021
+ └── openapi.py # OpenAPI schema 优化
1022
+ ```
1023
+
1024
+ ## 许可证
1025
+
1026
+ MIT