fastapi-augment 0.1.0__tar.gz → 0.1.2__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 (80) hide show
  1. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/PKG-INFO +103 -73
  2. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/README.md +99 -72
  3. fastapi_augment-0.1.2/VERSION +1 -0
  4. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/pyproject.toml +8 -3
  5. fastapi_augment-0.1.2/src/fastapi_augment/common/utils/__init__.py +32 -0
  6. fastapi_augment-0.1.2/src/fastapi_augment/common/utils/paths.py +36 -0
  7. fastapi_augment-0.1.2/src/fastapi_augment/db/__init__.py +6 -0
  8. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/db/sqlalchemy/__init__.py +5 -5
  9. fastapi_augment-0.1.2/src/fastapi_augment/db/sqlalchemy/alembic/README +1 -0
  10. fastapi_augment-0.1.2/src/fastapi_augment/db/sqlalchemy/alembic/script.py.mako +28 -0
  11. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/db/sqlalchemy/engine.py +13 -6
  12. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/db/sqlalchemy/mixins/audit.py +2 -2
  13. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/db/sqlalchemy/mixins/soft_delete.py +4 -4
  14. fastapi_augment-0.1.0/src/fastapi_augment/db/sqlalchemy/crud_base.py → fastapi_augment-0.1.2/src/fastapi_augment/db/sqlalchemy/repository_base.py +49 -16
  15. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/db/sqlalchemy/session.py +34 -21
  16. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/factory.py +12 -5
  17. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/log/config.py +9 -1
  18. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/log/factory.py +8 -1
  19. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/log/handlers.py +17 -5
  20. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment.egg-info/PKG-INFO +103 -73
  21. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment.egg-info/SOURCES.txt +9 -3
  22. fastapi_augment-0.1.2/tests/test_db_repository.py +412 -0
  23. fastapi_augment-0.1.2/tests/test_migrate.py +80 -0
  24. fastapi_augment-0.1.2/tests/test_model_base.py +89 -0
  25. fastapi_augment-0.1.2/tests/test_settings.py +78 -0
  26. fastapi_augment-0.1.0/VERSION +0 -1
  27. fastapi_augment-0.1.0/src/fastapi_augment/common/utils/__init__.py +0 -5
  28. fastapi_augment-0.1.0/src/fastapi_augment/db/__init__.py +0 -5
  29. fastapi_augment-0.1.0/tests/test_db_crud.py +0 -348
  30. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/setup.cfg +0 -0
  31. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/__init__.py +0 -0
  32. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/common/__init__.py +0 -0
  33. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/common/constants.py +0 -0
  34. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/common/exception_handlers.py +0 -0
  35. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/common/exceptions.py +0 -0
  36. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/common/utils/strings.py +0 -0
  37. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/config/__init__.py +0 -0
  38. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/config/settings.py +0 -0
  39. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/db/sqlalchemy/alembic/__init__.py +0 -0
  40. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/db/sqlalchemy/alembic/env.py +0 -0
  41. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/db/sqlalchemy/base.py +0 -0
  42. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/db/sqlalchemy/migrate.py +0 -0
  43. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/db/sqlalchemy/mixins/__init__.py +0 -0
  44. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/db/sqlalchemy/mixins/timestamp.py +0 -0
  45. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/db/sqlalchemy/model_base.py +0 -0
  46. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/health/__init__.py +0 -0
  47. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/health/checker.py +0 -0
  48. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/health/checkers.py +0 -0
  49. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/health/router.py +0 -0
  50. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/lifespan.py +0 -0
  51. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/log/__init__.py +0 -0
  52. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/log/filters.py +0 -0
  53. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/middlewares/__init__.py +0 -0
  54. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/middlewares/base.py +0 -0
  55. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/middlewares/request_id.py +0 -0
  56. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/openapi.py +0 -0
  57. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/py.typed +0 -0
  58. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/schemas/__init__.py +0 -0
  59. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/schemas/base.py +0 -0
  60. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/schemas/pagination.py +0 -0
  61. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/schemas/request.py +0 -0
  62. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/schemas/response.py +0 -0
  63. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment/schemas/types.py +0 -0
  64. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment.egg-info/dependency_links.txt +0 -0
  65. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment.egg-info/entry_points.txt +0 -0
  66. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment.egg-info/requires.txt +0 -0
  67. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/src/fastapi_augment.egg-info/top_level.txt +0 -0
  68. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/tests/test_config.py +0 -0
  69. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/tests/test_constants.py +0 -0
  70. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/tests/test_db_engine.py +0 -0
  71. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/tests/test_db_session_models.py +0 -0
  72. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/tests/test_exception_handlers.py +0 -0
  73. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/tests/test_exceptions.py +0 -0
  74. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/tests/test_factory.py +0 -0
  75. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/tests/test_health.py +0 -0
  76. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/tests/test_lifespan.py +0 -0
  77. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/tests/test_log.py +0 -0
  78. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/tests/test_middlewares.py +0 -0
  79. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/tests/test_openapi.py +0 -0
  80. {fastapi_augment-0.1.0 → fastapi_augment-0.1.2}/tests/test_schemas.py +0 -0
@@ -1,9 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-augment
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: FastAPI 通用代码工具包,跨项目复用
5
5
  Author-email: zarkhan <hanguangzheng@qq.com>
6
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
7
10
  Keywords: fastapi,fastapi-augment,augment,web,framework,async
8
11
  Classifier: Development Status :: 3 - Alpha
9
12
  Classifier: Framework :: FastAPI
@@ -40,13 +43,13 @@ Requires-Dist: fastapi-augment[config,orjson,sqlalchemy,uvicorn]; extra == "stan
40
43
 
41
44
  - **应用工厂** — 一行代码创建 FastAPI 实例,自动装配中间件、路由、生命周期与数据库
42
45
  - **生命周期管理** — 多注册表、优先级、超时控制、异常策略的启动/关闭钩子
43
- - **读写分离** — 单库 / 主从 / 集群拓扑的异步引擎管理,Session 自动路由
44
- - **泛型 CRUD** — 类型安全的异步 CRUD 仓库,支持关键字过滤与原生表达式
46
+ - **读写分离** — 单库 / 主从 / 集群拓扑的异步引擎管理,线程安全的 Session 自动路由
47
+ - **泛型仓储** — 类型安全的异步 Repository,支持直接实例化与子类继承两种方式
45
48
  - **可组合 Mixin** — 时间戳、审计、软删除等列混入,自由组合
46
49
  - **统一响应** — 全局 `APIResponse` 格式,自动追踪 `request_id`
47
50
  - **HTTP 异常** — 完整的 4xx 异常子类,内置默认文案
48
51
  - **OpenAPI 优化** — 自动清理 422 响应、可选 Bearer 认证
49
- - **日志管理** — request_id 自动注入、uvicorn 接管、多进程安全轮转、一键配置
52
+ - **日志管理** — request_id 自动注入、uvicorn 接管、多进程安全轮转、幂等初始化、一键配置
50
53
  - **健康检查** — 可扩展的检查器模式,内置应用状态与数据库连通性检查,一行开关
51
54
  - **配置管理** — 基于 pydantic-settings,支持 `.env` 文件、环境变量前缀、嵌套配置
52
55
  - **数据库迁移 CLI** — 一行命令生成/执行迁移,自动发现用户模型
@@ -75,8 +78,9 @@ from fastapi import APIRouter
75
78
  from fastapi_augment import create_app
76
79
  from fastapi_augment.db.sqlalchemy import (
77
80
  ClusterTopology, NodeConfig, EngineManager, SessionFactory,
78
- ModelBase, CrudBase, TimestampMixin,
81
+ ModelBase, RepositoryBase,
79
82
  )
83
+ from fastapi_augment.db.sqlalchemy.mixins import TimestampMixin
80
84
  from fastapi_augment.schemas import response_success
81
85
 
82
86
  # ── 1. 数据库拓扑 ──────────────────────────────────────
@@ -95,13 +99,13 @@ class User(TimestampMixin, ModelBase):
95
99
 
96
100
  # ── 3. 路由 ────────────────────────────────────────────
97
101
  router = APIRouter()
98
- user_crud = CrudBase(User)
102
+ user_repo = RepositoryBase(User)
99
103
 
100
104
 
101
105
  @router.get('/users')
102
106
  async def list_users():
103
107
  async with sessions.read_session() as session:
104
- users = await user_crud.list(session, is_active=True, limit=10)
108
+ users = await user_repo.list(session, is_active=True, limit=10)
105
109
  return response_success(data=users)
106
110
 
107
111
 
@@ -121,14 +125,14 @@ app = create_app(
121
125
 
122
126
  统一创建 FastAPI 实例,自动装配以下组件:
123
127
 
124
- | 组件 | 说明 |
125
- |---|---|
126
- | 生命周期 | 接入 `fastapi_lifespan`,合并用户注册表与 `core_registry` |
127
- | 中间件 | 自动添加 `RequestIdMiddleware`,可选 CORS |
128
- | 路由 | 支持 `APIRouter` 列表或 `(router, kwargs)` 元组 |
129
- | OpenAPI | 自动清理 422 响应、可选 Bearer 认证 |
130
- | 数据库 | 可选挂载 `EngineManager` / `SessionFactory` 到 `app.state` |
131
- | 健康检查 | `health_check=True` 一键启用 `/health` 端点 |
128
+ | 组件 | 说明 |
129
+ | -------- | ---------------------------------------------------------- |
130
+ | 生命周期 | 接入 `fastapi_lifespan`,合并用户注册表与 `core_registry` |
131
+ | 中间件 | 自动添加 `RequestIdMiddleware`,可选 CORS |
132
+ | 路由 | 支持 `APIRouter` 列表或 `(router, kwargs)` 元组 |
133
+ | OpenAPI | 自动清理 422 响应、可选 Bearer 认证 |
134
+ | 数据库 | 可选挂载 `EngineManager` / `SessionFactory` 到 `app.state` |
135
+ | 健康检查 | `health_check=True` 一键启用 `/health` 端点 |
132
136
 
133
137
  ```python
134
138
  from fastapi_augment import create_app, HookRegistry
@@ -141,7 +145,7 @@ async def init_cache() -> None:
141
145
 
142
146
  app = create_app(
143
147
  title='My Service',
144
- registries=[registry],
148
+ registries=registry, # 单个或列表均可
145
149
  cors_allow_origins=['*'],
146
150
  openapi_enable_bearer_auth=True,
147
151
  health_check=True, # 启用健康检查
@@ -200,7 +204,7 @@ topology = ClusterTopology(
200
204
  )
201
205
 
202
206
  manager = EngineManager(topology).start()
203
- # 读引擎轮询(round-robin)
207
+ # 读引擎轮询(round-robin),线程安全
204
208
  read_engine = manager.next_read_engine()
205
209
  ```
206
210
 
@@ -231,6 +235,8 @@ async def list_users(session: AsyncSession = Depends(sessions.depends_read)):
231
235
  ...
232
236
  ```
233
237
 
238
+ > **缓存说明:** `read_session()` 按 `AsyncEngine` 缓存 `async_sessionmaker`,线程安全,避免重复创建。打印 `SessionFactory` 实例可查看已缓存的引擎信息。
239
+
234
240
  #### 模型基类 — `ModelBase`
235
241
 
236
242
  基于 ULID 主键的声明式模型基类:
@@ -243,61 +249,74 @@ class User(TimestampMixin, ModelBase):
243
249
  name: str
244
250
  ```
245
251
 
246
- #### 泛型 CRUD — `CrudBase`
252
+ #### 泛型仓储 — `RepositoryBase`
247
253
 
248
- 类型安全的异步 CRUD 仓库,CRUD 方法只 **flush**,不 commit,事务边界由调用方控制:
254
+ 类型安全的异步仓储基类,Repository 方法只 **flush**,不 commit,事务边界由调用方控制。
255
+ 支持两种使用方式:
249
256
 
250
257
  ```python
251
- from fastapi_augment.db.sqlalchemy import CrudBase
258
+ from fastapi_augment.db.sqlalchemy import RepositoryBase
259
+
260
+ # 方式 1:直接实例化 — 显式传入模型类
261
+ user_repo = RepositoryBase(User)
262
+
263
+ # 方式 2:子类继承 — 通过泛型参数绑定模型,可扩展自定义方法
264
+ class UserRepo(RepositoryBase[User]):
265
+ async def find_by_email(self, session, email: str) -> User | None:
266
+ return await self.get_one(session, email=email)
252
267
 
253
- user_crud = CrudBase(User)
268
+ user_repo = UserRepo() # 无需再传 User
269
+ ```
270
+
271
+ **CRUD 操作:**
254
272
 
273
+ ```python
255
274
  # Create(静态方法)
256
275
  async with sessions.transaction() as session:
257
- await user_crud.create(session, User(name='alice'))
258
- await user_crud.create_many(session, [User(name='bob'), User(name='carol')])
276
+ await user_repo.create(session, User(name='alice'))
277
+ await user_repo.create_many(session, [User(name='bob'), User(name='carol')])
259
278
 
260
279
  # Read(实例方法)
261
280
  async with sessions.read_session() as session:
262
- user = await user_crud.get(session, id_='01HXK...')
263
- user = await user_crud.get_one(session, name='alice')
264
- users = await user_crud.list(session, role='admin', order_by=['-created_at'], limit=10)
265
- total = await user_crud.count(session, is_active=True)
266
- has_admin = await user_crud.exists(session, role='admin')
281
+ user = await user_repo.get(session, id_='01HXK...')
282
+ user = await user_repo.get_one(session, name='alice')
283
+ users = await user_repo.list(session, role='admin', order_by=['-created_at'], limit=10)
284
+ total = await user_repo.count(session, is_active=True)
285
+ has_admin = await user_repo.exists(session, role='admin')
267
286
 
268
287
  # 分页查询(返回 dict:items / page / size / total / pages)
269
- result = await user_crud.paginate(session, page=1, size=10, role='admin', order_by=['-created_at'])
288
+ result = await user_repo.paginate(session, page=1, size=10, role='admin', order_by=['-created_at'])
270
289
  # result = {'items': [...], 'page': 1, 'size': 10, 'total': 100, 'pages': 10}
271
290
 
272
291
  # Update
273
292
  async with sessions.transaction() as session:
274
- await user_crud.update(session, user, name='new_name')
275
- affected = await user_crud.update_by_id(session, id_='01HXK...', name='new_name')
293
+ await user_repo.update(session, user, name='new_name')
294
+ affected = await user_repo.update_by_id(session, id_='01HXK...', name='new_name')
276
295
 
277
296
  # Delete
278
297
  async with sessions.transaction() as session:
279
- await user_crud.delete(session, user)
280
- deleted = await user_crud.delete_by_id(session, id_='01HXK...')
281
- count = await user_crud.delete_where(session, is_active=False)
298
+ await user_repo.delete(session, user)
299
+ deleted = await user_repo.delete_by_id(session, id_='01HXK...')
300
+ count = await user_repo.delete_where(session, is_active=False)
282
301
  ```
283
302
 
284
303
  **过滤语法:**
285
304
 
286
305
  ```python
287
306
  # 关键字过滤 — 等值匹配
288
- await user_crud.list(session, name='alice')
307
+ await user_repo.list(session, name='alice')
289
308
 
290
309
  # 序列 — 自动转为 IN 查询
291
- await user_crud.list(session, id_=['01HXK...', '01HXL...'])
310
+ await user_repo.list(session, id_=['01HXK...', '01HXL...'])
292
311
 
293
312
  # None — 自动转为 IS NULL
294
- await user_crud.list(session, deleted_at=None)
313
+ await user_repo.list(session, deleted_at=None)
295
314
 
296
315
  # 原生 SQLAlchemy 表达式
297
- await user_crud.list(session, expressions=(User.age > 18,))
316
+ await user_repo.list(session, expressions=(User.age > 18,))
298
317
 
299
318
  # 排序:字段名前缀 - 表示降序
300
- await user_crud.list(session, order_by=['-created_at', 'name'])
319
+ await user_repo.list(session, order_by=['-created_at', 'name'])
301
320
  ```
302
321
 
303
322
  ### 数据库迁移 CLI — `fastapi-augment-migrate`
@@ -381,34 +400,35 @@ fastapi-augment-migrate upgrade --project-dir /path/to/project
381
400
 
382
401
  #### CLI 参数一览
383
402
 
384
- | 子命令 | 参数 | 说明 |
385
- |---|---|---|
386
- | `init` | `--db-url` | 数据库 URL(默认 `sqlite:///app.db`) |
387
- | | `--project-dir` | 项目根目录(默认当前目录) |
388
- | `generate` | `--message` | **必填**,迁移描述 |
389
- | | `--models` | **必填**,模型模块路径,逗号分隔 |
390
- | | `--project-dir` | 项目根目录(默认当前目录) |
391
- | `upgrade` | `--db-url` | 数据库 URL(不传则从 alembic.ini 读取) |
392
- | | `--revision` | 目标版本(默认 `head`) |
393
- | | `--downgrade` | 降级模式 |
394
- | | `--project-dir` | 项目根目录(默认当前目录) |
403
+ | 子命令 | 参数 | 说明 |
404
+ | ---------- | --------------- | --------------------------------------- |
405
+ | `init` | `--db-url` | 数据库 URL(默认 `sqlite:///app.db`) |
406
+ | | `--project-dir` | 项目根目录(默认当前目录) |
407
+ | `generate` | `--message` | **必填**,迁移描述 |
408
+ | | `--models` | **必填**,模型模块路径,逗号分隔 |
409
+ | | `--project-dir` | 项目根目录(默认当前目录) |
410
+ | `upgrade` | `--db-url` | 数据库 URL(不传则从 alembic.ini 读取) |
411
+ | | `--revision` | 目标版本(默认 `head`) |
412
+ | | `--downgrade` | 降级模式 |
413
+ | | `--project-dir` | 项目根目录(默认当前目录) |
395
414
 
396
415
  ### 模型 Mixin — `db.sqlalchemy.mixins`
397
416
 
398
417
  可组合的列混入,按需叠加:
399
418
 
400
- | Mixin | 提供的列 |
401
- |---|---|
402
- | `CreatedAtMixin` | `created_at` |
403
- | `TimestampMixin` | `created_at` + `updated_at` |
404
- | `CreatedByMixin` | `created_by` |
405
- | `UpdatedByMixin` | `updated_by` |
406
- | `AuditMixin` | `created_by` + `updated_by` |
407
- | `SoftDeleteMixin` | `is_deleted` + `deleted_at` |
419
+ | Mixin | 提供的列 |
420
+ | ---------------------- | ------------------------------------------ |
421
+ | `CreatedAtMixin` | `created_at` |
422
+ | `TimestampMixin` | `created_at` + `updated_at` |
423
+ | `CreatedByMixin` | `created_by` |
424
+ | `UpdatedByMixin` | `updated_by` |
425
+ | `AuditMixin` | `created_by` + `updated_by` |
426
+ | `SoftDeleteMixin` | `is_deleted` + `deleted_at` |
408
427
  | `SoftDeleteAuditMixin` | `is_deleted` + `deleted_at` + `deleted_by` |
409
428
 
410
429
  ```python
411
- from fastapi_augment.db.sqlalchemy import ModelBase, TimestampMixin, SoftDeleteMixin
430
+ from fastapi_augment.db.sqlalchemy import ModelBase
431
+ from fastapi_augment.db.sqlalchemy.mixins import TimestampMixin, SoftDeleteMixin
412
432
 
413
433
  class User(TimestampMixin, SoftDeleteMixin, ModelBase):
414
434
  __tablename__ = 'users'
@@ -504,19 +524,20 @@ set_log_level('info')
504
524
 
505
525
  **支持的轮转粒度:**
506
526
 
507
- | 粒度 | 说明 |
508
- |---|---|
509
- | `'second'` / `'minute'` / `'hour'` | 每整秒/分/点 |
510
- | `'day'`(默认) | 每天 00:00 |
511
- | `'week'` | 每周一 00:00 |
512
- | `'month'` | 每月 1 日 00:00 |
513
- | `'year'` | 每年 1 月 1 日 00:00 |
527
+ | 粒度 | 说明 |
528
+ | ---------------------------------- | -------------------- |
529
+ | `'second'` / `'minute'` / `'hour'` | 每整秒/分/点 |
530
+ | `'day'`(默认) | 每天 00:00 |
531
+ | `'week'` | 每周一 00:00 |
532
+ | `'month'` | 每月 1 日 00:00 |
533
+ | `'year'` | 每年 1 月 1 日 00:00 |
514
534
 
515
535
  **核心能力:**
516
536
 
517
537
  - **request_id 注入** — 每条日志自动携带当前请求的 `request_id`,方便链路追踪
518
538
  - **uvicorn 接管** — 统一 `uvicorn.error` / `uvicorn.access` 的日志名称为 `uvicorn`,屏蔽第三方库 DEBUG 噪声
519
- - **多进程安全** — 日志轮转时捕获 `PermissionError`,兼容多进程部署(如 `uvicorn --workers N`)
539
+ - **多进程安全** — 日志轮转时捕获 `OSError`(含 `PermissionError`、`FileNotFoundError`),兼容多进程部署(如 `uvicorn --workers N`)
540
+ - **幂等初始化** — 重复导入或多次调用 `setup_logger()` 不会叠加处理器或工厂链
520
541
  - **控制台开关** — `enable_console=False` 可关闭控制台输出,仅保留文件日志
521
542
 
522
543
  ### 健康检查 — `health`
@@ -537,11 +558,20 @@ app = create_app(
537
558
 
538
559
  ```json
539
560
  {
540
- "status": "healthy",
541
- "checks": [
542
- {"name": "app", "status": "healthy", "latencyMs": 0, "details": {"status": "running", "version": "1.0.0", "uptimeSeconds": 3600}},
543
- {"name": "database", "status": "healthy", "latencyMs": 2.3}
544
- ]
561
+ "status": "healthy",
562
+ "checks": [
563
+ {
564
+ "name": "app",
565
+ "status": "healthy",
566
+ "latencyMs": 0,
567
+ "details": {
568
+ "status": "running",
569
+ "version": "1.0.0",
570
+ "uptimeSeconds": 3600
571
+ }
572
+ },
573
+ { "name": "database", "status": "healthy", "latencyMs": 2.3 }
574
+ ]
545
575
  }
546
576
  ```
547
577
 
@@ -623,7 +653,7 @@ fastapi_augment/
623
653
  │ ├── engine.py # EngineManager / NodeConfig / ClusterTopology
624
654
  │ ├── session.py # SessionFactory(读写分离)
625
655
  │ ├── model_base.py # ModelBase(ULID 主键)
626
- │ ├── crud_base.py # CrudBase(泛型 CRUD + paginate)
656
+ │ ├── repository_base.py # RepositoryBase(泛型仓储 + paginate)
627
657
  │ ├── migrate.py # 数据库迁移 CLI
628
658
  │ ├── migrations/ # Alembic 迁移环境(env.py / script.py.mako)
629
659
  │ └── mixins/ # Timestamp / Audit / SoftDelete
@@ -6,13 +6,13 @@
6
6
 
7
7
  - **应用工厂** — 一行代码创建 FastAPI 实例,自动装配中间件、路由、生命周期与数据库
8
8
  - **生命周期管理** — 多注册表、优先级、超时控制、异常策略的启动/关闭钩子
9
- - **读写分离** — 单库 / 主从 / 集群拓扑的异步引擎管理,Session 自动路由
10
- - **泛型 CRUD** — 类型安全的异步 CRUD 仓库,支持关键字过滤与原生表达式
9
+ - **读写分离** — 单库 / 主从 / 集群拓扑的异步引擎管理,线程安全的 Session 自动路由
10
+ - **泛型仓储** — 类型安全的异步 Repository,支持直接实例化与子类继承两种方式
11
11
  - **可组合 Mixin** — 时间戳、审计、软删除等列混入,自由组合
12
12
  - **统一响应** — 全局 `APIResponse` 格式,自动追踪 `request_id`
13
13
  - **HTTP 异常** — 完整的 4xx 异常子类,内置默认文案
14
14
  - **OpenAPI 优化** — 自动清理 422 响应、可选 Bearer 认证
15
- - **日志管理** — request_id 自动注入、uvicorn 接管、多进程安全轮转、一键配置
15
+ - **日志管理** — request_id 自动注入、uvicorn 接管、多进程安全轮转、幂等初始化、一键配置
16
16
  - **健康检查** — 可扩展的检查器模式,内置应用状态与数据库连通性检查,一行开关
17
17
  - **配置管理** — 基于 pydantic-settings,支持 `.env` 文件、环境变量前缀、嵌套配置
18
18
  - **数据库迁移 CLI** — 一行命令生成/执行迁移,自动发现用户模型
@@ -41,8 +41,9 @@ from fastapi import APIRouter
41
41
  from fastapi_augment import create_app
42
42
  from fastapi_augment.db.sqlalchemy import (
43
43
  ClusterTopology, NodeConfig, EngineManager, SessionFactory,
44
- ModelBase, CrudBase, TimestampMixin,
44
+ ModelBase, RepositoryBase,
45
45
  )
46
+ from fastapi_augment.db.sqlalchemy.mixins import TimestampMixin
46
47
  from fastapi_augment.schemas import response_success
47
48
 
48
49
  # ── 1. 数据库拓扑 ──────────────────────────────────────
@@ -61,13 +62,13 @@ class User(TimestampMixin, ModelBase):
61
62
 
62
63
  # ── 3. 路由 ────────────────────────────────────────────
63
64
  router = APIRouter()
64
- user_crud = CrudBase(User)
65
+ user_repo = RepositoryBase(User)
65
66
 
66
67
 
67
68
  @router.get('/users')
68
69
  async def list_users():
69
70
  async with sessions.read_session() as session:
70
- users = await user_crud.list(session, is_active=True, limit=10)
71
+ users = await user_repo.list(session, is_active=True, limit=10)
71
72
  return response_success(data=users)
72
73
 
73
74
 
@@ -87,14 +88,14 @@ app = create_app(
87
88
 
88
89
  统一创建 FastAPI 实例,自动装配以下组件:
89
90
 
90
- | 组件 | 说明 |
91
- |---|---|
92
- | 生命周期 | 接入 `fastapi_lifespan`,合并用户注册表与 `core_registry` |
93
- | 中间件 | 自动添加 `RequestIdMiddleware`,可选 CORS |
94
- | 路由 | 支持 `APIRouter` 列表或 `(router, kwargs)` 元组 |
95
- | OpenAPI | 自动清理 422 响应、可选 Bearer 认证 |
96
- | 数据库 | 可选挂载 `EngineManager` / `SessionFactory` 到 `app.state` |
97
- | 健康检查 | `health_check=True` 一键启用 `/health` 端点 |
91
+ | 组件 | 说明 |
92
+ | -------- | ---------------------------------------------------------- |
93
+ | 生命周期 | 接入 `fastapi_lifespan`,合并用户注册表与 `core_registry` |
94
+ | 中间件 | 自动添加 `RequestIdMiddleware`,可选 CORS |
95
+ | 路由 | 支持 `APIRouter` 列表或 `(router, kwargs)` 元组 |
96
+ | OpenAPI | 自动清理 422 响应、可选 Bearer 认证 |
97
+ | 数据库 | 可选挂载 `EngineManager` / `SessionFactory` 到 `app.state` |
98
+ | 健康检查 | `health_check=True` 一键启用 `/health` 端点 |
98
99
 
99
100
  ```python
100
101
  from fastapi_augment import create_app, HookRegistry
@@ -107,7 +108,7 @@ async def init_cache() -> None:
107
108
 
108
109
  app = create_app(
109
110
  title='My Service',
110
- registries=[registry],
111
+ registries=registry, # 单个或列表均可
111
112
  cors_allow_origins=['*'],
112
113
  openapi_enable_bearer_auth=True,
113
114
  health_check=True, # 启用健康检查
@@ -166,7 +167,7 @@ topology = ClusterTopology(
166
167
  )
167
168
 
168
169
  manager = EngineManager(topology).start()
169
- # 读引擎轮询(round-robin)
170
+ # 读引擎轮询(round-robin),线程安全
170
171
  read_engine = manager.next_read_engine()
171
172
  ```
172
173
 
@@ -197,6 +198,8 @@ async def list_users(session: AsyncSession = Depends(sessions.depends_read)):
197
198
  ...
198
199
  ```
199
200
 
201
+ > **缓存说明:** `read_session()` 按 `AsyncEngine` 缓存 `async_sessionmaker`,线程安全,避免重复创建。打印 `SessionFactory` 实例可查看已缓存的引擎信息。
202
+
200
203
  #### 模型基类 — `ModelBase`
201
204
 
202
205
  基于 ULID 主键的声明式模型基类:
@@ -209,61 +212,74 @@ class User(TimestampMixin, ModelBase):
209
212
  name: str
210
213
  ```
211
214
 
212
- #### 泛型 CRUD — `CrudBase`
215
+ #### 泛型仓储 — `RepositoryBase`
213
216
 
214
- 类型安全的异步 CRUD 仓库,CRUD 方法只 **flush**,不 commit,事务边界由调用方控制:
217
+ 类型安全的异步仓储基类,Repository 方法只 **flush**,不 commit,事务边界由调用方控制。
218
+ 支持两种使用方式:
215
219
 
216
220
  ```python
217
- from fastapi_augment.db.sqlalchemy import CrudBase
221
+ from fastapi_augment.db.sqlalchemy import RepositoryBase
222
+
223
+ # 方式 1:直接实例化 — 显式传入模型类
224
+ user_repo = RepositoryBase(User)
225
+
226
+ # 方式 2:子类继承 — 通过泛型参数绑定模型,可扩展自定义方法
227
+ class UserRepo(RepositoryBase[User]):
228
+ async def find_by_email(self, session, email: str) -> User | None:
229
+ return await self.get_one(session, email=email)
218
230
 
219
- user_crud = CrudBase(User)
231
+ user_repo = UserRepo() # 无需再传 User
232
+ ```
233
+
234
+ **CRUD 操作:**
220
235
 
236
+ ```python
221
237
  # Create(静态方法)
222
238
  async with sessions.transaction() as session:
223
- await user_crud.create(session, User(name='alice'))
224
- await user_crud.create_many(session, [User(name='bob'), User(name='carol')])
239
+ await user_repo.create(session, User(name='alice'))
240
+ await user_repo.create_many(session, [User(name='bob'), User(name='carol')])
225
241
 
226
242
  # Read(实例方法)
227
243
  async with sessions.read_session() as session:
228
- user = await user_crud.get(session, id_='01HXK...')
229
- user = await user_crud.get_one(session, name='alice')
230
- users = await user_crud.list(session, role='admin', order_by=['-created_at'], limit=10)
231
- total = await user_crud.count(session, is_active=True)
232
- has_admin = await user_crud.exists(session, role='admin')
244
+ user = await user_repo.get(session, id_='01HXK...')
245
+ user = await user_repo.get_one(session, name='alice')
246
+ users = await user_repo.list(session, role='admin', order_by=['-created_at'], limit=10)
247
+ total = await user_repo.count(session, is_active=True)
248
+ has_admin = await user_repo.exists(session, role='admin')
233
249
 
234
250
  # 分页查询(返回 dict:items / page / size / total / pages)
235
- result = await user_crud.paginate(session, page=1, size=10, role='admin', order_by=['-created_at'])
251
+ result = await user_repo.paginate(session, page=1, size=10, role='admin', order_by=['-created_at'])
236
252
  # result = {'items': [...], 'page': 1, 'size': 10, 'total': 100, 'pages': 10}
237
253
 
238
254
  # Update
239
255
  async with sessions.transaction() as session:
240
- await user_crud.update(session, user, name='new_name')
241
- affected = await user_crud.update_by_id(session, id_='01HXK...', name='new_name')
256
+ await user_repo.update(session, user, name='new_name')
257
+ affected = await user_repo.update_by_id(session, id_='01HXK...', name='new_name')
242
258
 
243
259
  # Delete
244
260
  async with sessions.transaction() as session:
245
- await user_crud.delete(session, user)
246
- deleted = await user_crud.delete_by_id(session, id_='01HXK...')
247
- count = await user_crud.delete_where(session, is_active=False)
261
+ await user_repo.delete(session, user)
262
+ deleted = await user_repo.delete_by_id(session, id_='01HXK...')
263
+ count = await user_repo.delete_where(session, is_active=False)
248
264
  ```
249
265
 
250
266
  **过滤语法:**
251
267
 
252
268
  ```python
253
269
  # 关键字过滤 — 等值匹配
254
- await user_crud.list(session, name='alice')
270
+ await user_repo.list(session, name='alice')
255
271
 
256
272
  # 序列 — 自动转为 IN 查询
257
- await user_crud.list(session, id_=['01HXK...', '01HXL...'])
273
+ await user_repo.list(session, id_=['01HXK...', '01HXL...'])
258
274
 
259
275
  # None — 自动转为 IS NULL
260
- await user_crud.list(session, deleted_at=None)
276
+ await user_repo.list(session, deleted_at=None)
261
277
 
262
278
  # 原生 SQLAlchemy 表达式
263
- await user_crud.list(session, expressions=(User.age > 18,))
279
+ await user_repo.list(session, expressions=(User.age > 18,))
264
280
 
265
281
  # 排序:字段名前缀 - 表示降序
266
- await user_crud.list(session, order_by=['-created_at', 'name'])
282
+ await user_repo.list(session, order_by=['-created_at', 'name'])
267
283
  ```
268
284
 
269
285
  ### 数据库迁移 CLI — `fastapi-augment-migrate`
@@ -347,34 +363,35 @@ fastapi-augment-migrate upgrade --project-dir /path/to/project
347
363
 
348
364
  #### CLI 参数一览
349
365
 
350
- | 子命令 | 参数 | 说明 |
351
- |---|---|---|
352
- | `init` | `--db-url` | 数据库 URL(默认 `sqlite:///app.db`) |
353
- | | `--project-dir` | 项目根目录(默认当前目录) |
354
- | `generate` | `--message` | **必填**,迁移描述 |
355
- | | `--models` | **必填**,模型模块路径,逗号分隔 |
356
- | | `--project-dir` | 项目根目录(默认当前目录) |
357
- | `upgrade` | `--db-url` | 数据库 URL(不传则从 alembic.ini 读取) |
358
- | | `--revision` | 目标版本(默认 `head`) |
359
- | | `--downgrade` | 降级模式 |
360
- | | `--project-dir` | 项目根目录(默认当前目录) |
366
+ | 子命令 | 参数 | 说明 |
367
+ | ---------- | --------------- | --------------------------------------- |
368
+ | `init` | `--db-url` | 数据库 URL(默认 `sqlite:///app.db`) |
369
+ | | `--project-dir` | 项目根目录(默认当前目录) |
370
+ | `generate` | `--message` | **必填**,迁移描述 |
371
+ | | `--models` | **必填**,模型模块路径,逗号分隔 |
372
+ | | `--project-dir` | 项目根目录(默认当前目录) |
373
+ | `upgrade` | `--db-url` | 数据库 URL(不传则从 alembic.ini 读取) |
374
+ | | `--revision` | 目标版本(默认 `head`) |
375
+ | | `--downgrade` | 降级模式 |
376
+ | | `--project-dir` | 项目根目录(默认当前目录) |
361
377
 
362
378
  ### 模型 Mixin — `db.sqlalchemy.mixins`
363
379
 
364
380
  可组合的列混入,按需叠加:
365
381
 
366
- | Mixin | 提供的列 |
367
- |---|---|
368
- | `CreatedAtMixin` | `created_at` |
369
- | `TimestampMixin` | `created_at` + `updated_at` |
370
- | `CreatedByMixin` | `created_by` |
371
- | `UpdatedByMixin` | `updated_by` |
372
- | `AuditMixin` | `created_by` + `updated_by` |
373
- | `SoftDeleteMixin` | `is_deleted` + `deleted_at` |
382
+ | Mixin | 提供的列 |
383
+ | ---------------------- | ------------------------------------------ |
384
+ | `CreatedAtMixin` | `created_at` |
385
+ | `TimestampMixin` | `created_at` + `updated_at` |
386
+ | `CreatedByMixin` | `created_by` |
387
+ | `UpdatedByMixin` | `updated_by` |
388
+ | `AuditMixin` | `created_by` + `updated_by` |
389
+ | `SoftDeleteMixin` | `is_deleted` + `deleted_at` |
374
390
  | `SoftDeleteAuditMixin` | `is_deleted` + `deleted_at` + `deleted_by` |
375
391
 
376
392
  ```python
377
- from fastapi_augment.db.sqlalchemy import ModelBase, TimestampMixin, SoftDeleteMixin
393
+ from fastapi_augment.db.sqlalchemy import ModelBase
394
+ from fastapi_augment.db.sqlalchemy.mixins import TimestampMixin, SoftDeleteMixin
378
395
 
379
396
  class User(TimestampMixin, SoftDeleteMixin, ModelBase):
380
397
  __tablename__ = 'users'
@@ -470,19 +487,20 @@ set_log_level('info')
470
487
 
471
488
  **支持的轮转粒度:**
472
489
 
473
- | 粒度 | 说明 |
474
- |---|---|
475
- | `'second'` / `'minute'` / `'hour'` | 每整秒/分/点 |
476
- | `'day'`(默认) | 每天 00:00 |
477
- | `'week'` | 每周一 00:00 |
478
- | `'month'` | 每月 1 日 00:00 |
479
- | `'year'` | 每年 1 月 1 日 00:00 |
490
+ | 粒度 | 说明 |
491
+ | ---------------------------------- | -------------------- |
492
+ | `'second'` / `'minute'` / `'hour'` | 每整秒/分/点 |
493
+ | `'day'`(默认) | 每天 00:00 |
494
+ | `'week'` | 每周一 00:00 |
495
+ | `'month'` | 每月 1 日 00:00 |
496
+ | `'year'` | 每年 1 月 1 日 00:00 |
480
497
 
481
498
  **核心能力:**
482
499
 
483
500
  - **request_id 注入** — 每条日志自动携带当前请求的 `request_id`,方便链路追踪
484
501
  - **uvicorn 接管** — 统一 `uvicorn.error` / `uvicorn.access` 的日志名称为 `uvicorn`,屏蔽第三方库 DEBUG 噪声
485
- - **多进程安全** — 日志轮转时捕获 `PermissionError`,兼容多进程部署(如 `uvicorn --workers N`)
502
+ - **多进程安全** — 日志轮转时捕获 `OSError`(含 `PermissionError`、`FileNotFoundError`),兼容多进程部署(如 `uvicorn --workers N`)
503
+ - **幂等初始化** — 重复导入或多次调用 `setup_logger()` 不会叠加处理器或工厂链
486
504
  - **控制台开关** — `enable_console=False` 可关闭控制台输出,仅保留文件日志
487
505
 
488
506
  ### 健康检查 — `health`
@@ -503,11 +521,20 @@ app = create_app(
503
521
 
504
522
  ```json
505
523
  {
506
- "status": "healthy",
507
- "checks": [
508
- {"name": "app", "status": "healthy", "latencyMs": 0, "details": {"status": "running", "version": "1.0.0", "uptimeSeconds": 3600}},
509
- {"name": "database", "status": "healthy", "latencyMs": 2.3}
510
- ]
524
+ "status": "healthy",
525
+ "checks": [
526
+ {
527
+ "name": "app",
528
+ "status": "healthy",
529
+ "latencyMs": 0,
530
+ "details": {
531
+ "status": "running",
532
+ "version": "1.0.0",
533
+ "uptimeSeconds": 3600
534
+ }
535
+ },
536
+ { "name": "database", "status": "healthy", "latencyMs": 2.3 }
537
+ ]
511
538
  }
512
539
  ```
513
540
 
@@ -589,7 +616,7 @@ fastapi_augment/
589
616
  │ ├── engine.py # EngineManager / NodeConfig / ClusterTopology
590
617
  │ ├── session.py # SessionFactory(读写分离)
591
618
  │ ├── model_base.py # ModelBase(ULID 主键)
592
- │ ├── crud_base.py # CrudBase(泛型 CRUD + paginate)
619
+ │ ├── repository_base.py # RepositoryBase(泛型仓储 + paginate)
593
620
  │ ├── migrate.py # 数据库迁移 CLI
594
621
  │ ├── migrations/ # Alembic 迁移环境(env.py / script.py.mako)
595
622
  │ └── mixins/ # Timestamp / Audit / SoftDelete
@@ -0,0 +1 @@
1
+ 0.1.2