fastapi-augment 0.1.2__tar.gz → 0.1.4__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 (83) hide show
  1. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/PKG-INFO +117 -22
  2. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/README.md +116 -21
  3. fastapi_augment-0.1.4/VERSION +1 -0
  4. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/common/constants.py +2 -1
  5. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/common/exception_handlers.py +10 -10
  6. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/common/exceptions.py +18 -18
  7. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/common/utils/__init__.py +1 -1
  8. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/common/utils/paths.py +3 -3
  9. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/common/utils/strings.py +17 -4
  10. fastapi_augment-0.1.4/src/fastapi_augment/config/__init__.py +12 -0
  11. fastapi_augment-0.1.4/src/fastapi_augment/config/base_settings.py +253 -0
  12. fastapi_augment-0.1.4/src/fastapi_augment/config/database_settings.py +219 -0
  13. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/__init__.py +12 -0
  14. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/alembic/env.py +1 -1
  15. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/engine.py +9 -2
  16. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/migrate.py +7 -7
  17. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/mixins/__init__.py +1 -1
  18. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/mixins/audit.py +3 -3
  19. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/mixins/soft_delete.py +4 -4
  20. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/mixins/timestamp.py +3 -3
  21. fastapi_augment-0.1.4/src/fastapi_augment/db/sqlalchemy/query_parser.py +447 -0
  22. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/repository_base.py +37 -6
  23. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/session.py +32 -23
  24. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/factory.py +20 -12
  25. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/health/__init__.py +6 -6
  26. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/health/checker.py +13 -9
  27. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/health/checkers.py +38 -21
  28. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/health/router.py +13 -8
  29. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/lifespan.py +23 -21
  30. {fastapi_augment-0.1.2/src/fastapi_augment/log → fastapi_augment-0.1.4/src/fastapi_augment/logger}/__init__.py +9 -9
  31. {fastapi_augment-0.1.2/src/fastapi_augment/log → fastapi_augment-0.1.4/src/fastapi_augment/logger}/filters.py +3 -3
  32. {fastapi_augment-0.1.2/src/fastapi_augment/log → fastapi_augment-0.1.4/src/fastapi_augment/logger}/handlers.py +14 -15
  33. fastapi_augment-0.1.2/src/fastapi_augment/log/factory.py → fastapi_augment-0.1.4/src/fastapi_augment/logger/record_factory.py +5 -5
  34. fastapi_augment-0.1.2/src/fastapi_augment/log/config.py → fastapi_augment-0.1.4/src/fastapi_augment/logger/setup.py +17 -9
  35. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/middlewares/base.py +4 -4
  36. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/middlewares/request_id.py +3 -3
  37. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/openapi.py +6 -22
  38. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment.egg-info/PKG-INFO +117 -22
  39. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment.egg-info/SOURCES.txt +9 -6
  40. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_config.py +23 -23
  41. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_db_repository.py +429 -412
  42. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_factory.py +2 -2
  43. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_log.py +21 -21
  44. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_openapi.py +0 -31
  45. fastapi_augment-0.1.4/tests/test_query_parser.py +246 -0
  46. fastapi_augment-0.1.4/tests/test_settings.py +128 -0
  47. fastapi_augment-0.1.2/VERSION +0 -1
  48. fastapi_augment-0.1.2/src/fastapi_augment/config/__init__.py +0 -8
  49. fastapi_augment-0.1.2/src/fastapi_augment/config/settings.py +0 -104
  50. fastapi_augment-0.1.2/tests/test_settings.py +0 -78
  51. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/pyproject.toml +0 -0
  52. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/setup.cfg +0 -0
  53. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/__init__.py +0 -0
  54. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/common/__init__.py +0 -0
  55. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/__init__.py +0 -0
  56. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/alembic/README +0 -0
  57. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/alembic/__init__.py +0 -0
  58. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/alembic/script.py.mako +0 -0
  59. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/base.py +0 -0
  60. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/model_base.py +0 -0
  61. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/middlewares/__init__.py +0 -0
  62. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/py.typed +0 -0
  63. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/schemas/__init__.py +0 -0
  64. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/schemas/base.py +0 -0
  65. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/schemas/pagination.py +0 -0
  66. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/schemas/request.py +0 -0
  67. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/schemas/response.py +0 -0
  68. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment/schemas/types.py +0 -0
  69. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment.egg-info/dependency_links.txt +0 -0
  70. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment.egg-info/entry_points.txt +0 -0
  71. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment.egg-info/requires.txt +0 -0
  72. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/src/fastapi_augment.egg-info/top_level.txt +0 -0
  73. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_constants.py +0 -0
  74. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_db_engine.py +0 -0
  75. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_db_session_models.py +0 -0
  76. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_exception_handlers.py +0 -0
  77. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_exceptions.py +0 -0
  78. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_health.py +0 -0
  79. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_lifespan.py +0 -0
  80. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_middlewares.py +0 -0
  81. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_migrate.py +0 -0
  82. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_model_base.py +0 -0
  83. {fastapi_augment-0.1.2 → fastapi_augment-0.1.4}/tests/test_schemas.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-augment
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: FastAPI 通用代码工具包,跨项目复用
5
5
  Author-email: zarkhan <hanguangzheng@qq.com>
6
6
  License-Expression: MIT
@@ -45,10 +45,11 @@ Requires-Dist: fastapi-augment[config,orjson,sqlalchemy,uvicorn]; extra == "stan
45
45
  - **生命周期管理** — 多注册表、优先级、超时控制、异常策略的启动/关闭钩子
46
46
  - **读写分离** — 单库 / 主从 / 集群拓扑的异步引擎管理,线程安全的 Session 自动路由
47
47
  - **泛型仓储** — 类型安全的异步 Repository,支持直接实例化与子类继承两种方式
48
+ - **查询解析器** — REST 风格 query string 转 SQLAlchemy 表达式,支持 FIQL 条件、关键字搜索、排序
48
49
  - **可组合 Mixin** — 时间戳、审计、软删除等列混入,自由组合
49
50
  - **统一响应** — 全局 `APIResponse` 格式,自动追踪 `request_id`
50
51
  - **HTTP 异常** — 完整的 4xx 异常子类,内置默认文案
51
- - **OpenAPI 优化** — 自动清理 422 响应、可选 Bearer 认证
52
+ - **OpenAPI 优化** — 自动清理 422 响应与验证错误模型
52
53
  - **日志管理** — request_id 自动注入、uvicorn 接管、多进程安全轮转、幂等初始化、一键配置
53
54
  - **健康检查** — 可扩展的检查器模式,内置应用状态与数据库连通性检查,一行开关
54
55
  - **配置管理** — 基于 pydantic-settings,支持 `.env` 文件、环境变量前缀、嵌套配置
@@ -130,7 +131,7 @@ app = create_app(
130
131
  | 生命周期 | 接入 `fastapi_lifespan`,合并用户注册表与 `core_registry` |
131
132
  | 中间件 | 自动添加 `RequestIdMiddleware`,可选 CORS |
132
133
  | 路由 | 支持 `APIRouter` 列表或 `(router, kwargs)` 元组 |
133
- | OpenAPI | 自动清理 422 响应、可选 Bearer 认证 |
134
+ | OpenAPI | 自动清理 422 响应与验证错误模型 |
134
135
  | 数据库 | 可选挂载 `EngineManager` / `SessionFactory` 到 `app.state` |
135
136
  | 健康检查 | `health_check=True` 一键启用 `/health` 端点 |
136
137
 
@@ -147,7 +148,6 @@ app = create_app(
147
148
  title='My Service',
148
149
  registries=registry, # 单个或列表均可
149
150
  cors_allow_origins=['*'],
150
- openapi_enable_bearer_auth=True,
151
151
  health_check=True, # 启用健康检查
152
152
  )
153
153
  ```
@@ -260,10 +260,12 @@ from fastapi_augment.db.sqlalchemy import RepositoryBase
260
260
  # 方式 1:直接实例化 — 显式传入模型类
261
261
  user_repo = RepositoryBase(User)
262
262
 
263
+
263
264
  # 方式 2:子类继承 — 通过泛型参数绑定模型,可扩展自定义方法
264
265
  class UserRepo(RepositoryBase[User]):
265
266
  async def find_by_email(self, session, email: str) -> User | None:
266
- return await self.get_one(session, email=email)
267
+ return await self.get_first(session, email=email)
268
+
267
269
 
268
270
  user_repo = UserRepo() # 无需再传 User
269
271
  ```
@@ -279,7 +281,8 @@ async with sessions.transaction() as session:
279
281
  # Read(实例方法)
280
282
  async with sessions.read_session() as session:
281
283
  user = await user_repo.get(session, id_='01HXK...')
282
- user = await user_repo.get_one(session, name='alice')
284
+ user = await user_repo.get_first(session, name='alice') # 取第一条,无匹配返回 None
285
+ user = await user_repo.get_unique(session, email='a@b.com') # 精确唯一,多条匹配抛 MultipleResultsFound
283
286
  users = await user_repo.list(session, role='admin', order_by=['-created_at'], limit=10)
284
287
  total = await user_repo.count(session, is_active=True)
285
288
  has_admin = await user_repo.exists(session, role='admin')
@@ -319,6 +322,92 @@ await user_repo.list(session, expressions=(User.age > 18,))
319
322
  await user_repo.list(session, order_by=['-created_at', 'name'])
320
323
  ```
321
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
+
322
411
  ### 数据库迁移 CLI — `fastapi-augment-migrate`
323
412
 
324
413
  内置 Alembic 迁移工具,提供 `init` / `generate` / `upgrade` 三个子命令,开箱即用。
@@ -508,12 +597,12 @@ raise BadRequestError(detail='用户名不能为空')
508
597
  raise TooManyRequestsError(retry_after=60)
509
598
  ```
510
599
 
511
- ### 日志管理 — `log`
600
+ ### 日志管理 — `logger`
512
601
 
513
602
  导入即生效:自动注入 `request_id` 到每条日志、接管 uvicorn/fastapi 日志输出。
514
603
 
515
604
  ```python
516
- from fastapi_augment.log import setup_logger, set_log_level
605
+ from fastapi_augment.logger import setup_logger, set_log_level
517
606
 
518
607
  # 一键配置:控制台 + 按天轮转文件日志
519
608
  setup_logger(log_dir='./logs', rotation='day', backup_count=30)
@@ -602,26 +691,30 @@ app.include_router(create_health_router(
602
691
 
603
692
  ### 配置管理 — `config`
604
693
 
605
- 基于 `pydantic-settings`,通过 `from_env()` 直接传参,无需手动导入 `SettingsConfigDict`:
694
+ 基于 `pydantic-settings`,支持多种配置来源(环境变量、.env、JSON、YAML、TOML),通过不同类方法加载:
606
695
 
607
696
  ```python
608
- from fastapi_augment.config import EnvSettings
697
+ from fastapi_augment.config import AugmentBaseSettings
609
698
 
610
- class Settings(EnvSettings):
699
+ class Settings(AugmentBaseSettings):
611
700
  database_url: str
612
701
  redis_url: str = ''
613
702
  debug: bool = False
614
703
  secret_key: str = 'change-me'
615
704
 
616
- # 直接传入 .env 路径、前缀等
617
- settings = Settings.from_env(
618
- env_file='config/.env',
619
- env_prefix='APP_',
620
- env_nested_delimiter='__',
621
- )
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')
622
715
  ```
623
716
 
624
- 支持 `SettingsConfigDict` 的所有参数(`env_file`、`env_prefix`、`secrets_dir`、`yaml_file` 等),
717
+ 支持 `SettingsConfigDict` 的所有参数(`env_prefix`、`secrets_dir`、`yaml_file` 等),
625
718
  与模型字段值自动区分,无需关心分类。
626
719
 
627
720
  ### 中间件 — `middlewares`
@@ -647,13 +740,15 @@ fastapi_augment/
647
740
  │ └── utils/
648
741
  │ └── strings.py # 字符串工具 / JSON 序列化
649
742
  ├── config/
650
- │ └── settings.py # EnvSettings 配置管理
743
+ │ ├── base_settings.py # AugmentBaseSettings 配置管理
744
+ │ └── database_settings.py # DatabaseSettings 数据库配置
651
745
  ├── db/
652
746
  │ └── sqlalchemy/
653
747
  │ ├── engine.py # EngineManager / NodeConfig / ClusterTopology
654
748
  │ ├── session.py # SessionFactory(读写分离)
655
749
  │ ├── model_base.py # ModelBase(ULID 主键)
656
750
  │ ├── repository_base.py # RepositoryBase(泛型仓储 + paginate)
751
+ │ ├── query_parser.py # 查询解析器(where / lookup / keyword / sort)
657
752
  │ ├── migrate.py # 数据库迁移 CLI
658
753
  │ ├── migrations/ # Alembic 迁移环境(env.py / script.py.mako)
659
754
  │ └── mixins/ # Timestamp / Audit / SoftDelete
@@ -661,11 +756,11 @@ fastapi_augment/
661
756
  │ ├── checker.py # BaseChecker / CheckResult / HealthResponse
662
757
  │ ├── checkers.py # AppChecker / DatabaseChecker
663
758
  │ └── router.py # create_health_router()
664
- ├── log/
665
- │ ├── factory.py # request_id 注入工厂
759
+ ├── logger/
760
+ │ ├── record_factory.py # request_id 注入工厂
666
761
  │ ├── filters.py # UvicornNameRewriteFilter
667
762
  │ ├── handlers.py # 多进程安全轮转处理器
668
- │ └── config.py # setup_logger / set_log_level / set_log_format
763
+ │ └── setup.py # setup_logger / set_log_level / set_log_format
669
764
  ├── middlewares/
670
765
  │ ├── base.py # BaseASGIMiddleware
671
766
  │ └── request_id.py # RequestId 中间件
@@ -8,10 +8,11 @@
8
8
  - **生命周期管理** — 多注册表、优先级、超时控制、异常策略的启动/关闭钩子
9
9
  - **读写分离** — 单库 / 主从 / 集群拓扑的异步引擎管理,线程安全的 Session 自动路由
10
10
  - **泛型仓储** — 类型安全的异步 Repository,支持直接实例化与子类继承两种方式
11
+ - **查询解析器** — REST 风格 query string 转 SQLAlchemy 表达式,支持 FIQL 条件、关键字搜索、排序
11
12
  - **可组合 Mixin** — 时间戳、审计、软删除等列混入,自由组合
12
13
  - **统一响应** — 全局 `APIResponse` 格式,自动追踪 `request_id`
13
14
  - **HTTP 异常** — 完整的 4xx 异常子类,内置默认文案
14
- - **OpenAPI 优化** — 自动清理 422 响应、可选 Bearer 认证
15
+ - **OpenAPI 优化** — 自动清理 422 响应与验证错误模型
15
16
  - **日志管理** — request_id 自动注入、uvicorn 接管、多进程安全轮转、幂等初始化、一键配置
16
17
  - **健康检查** — 可扩展的检查器模式,内置应用状态与数据库连通性检查,一行开关
17
18
  - **配置管理** — 基于 pydantic-settings,支持 `.env` 文件、环境变量前缀、嵌套配置
@@ -93,7 +94,7 @@ app = create_app(
93
94
  | 生命周期 | 接入 `fastapi_lifespan`,合并用户注册表与 `core_registry` |
94
95
  | 中间件 | 自动添加 `RequestIdMiddleware`,可选 CORS |
95
96
  | 路由 | 支持 `APIRouter` 列表或 `(router, kwargs)` 元组 |
96
- | OpenAPI | 自动清理 422 响应、可选 Bearer 认证 |
97
+ | OpenAPI | 自动清理 422 响应与验证错误模型 |
97
98
  | 数据库 | 可选挂载 `EngineManager` / `SessionFactory` 到 `app.state` |
98
99
  | 健康检查 | `health_check=True` 一键启用 `/health` 端点 |
99
100
 
@@ -110,7 +111,6 @@ app = create_app(
110
111
  title='My Service',
111
112
  registries=registry, # 单个或列表均可
112
113
  cors_allow_origins=['*'],
113
- openapi_enable_bearer_auth=True,
114
114
  health_check=True, # 启用健康检查
115
115
  )
116
116
  ```
@@ -223,10 +223,12 @@ from fastapi_augment.db.sqlalchemy import RepositoryBase
223
223
  # 方式 1:直接实例化 — 显式传入模型类
224
224
  user_repo = RepositoryBase(User)
225
225
 
226
+
226
227
  # 方式 2:子类继承 — 通过泛型参数绑定模型,可扩展自定义方法
227
228
  class UserRepo(RepositoryBase[User]):
228
229
  async def find_by_email(self, session, email: str) -> User | None:
229
- return await self.get_one(session, email=email)
230
+ return await self.get_first(session, email=email)
231
+
230
232
 
231
233
  user_repo = UserRepo() # 无需再传 User
232
234
  ```
@@ -242,7 +244,8 @@ async with sessions.transaction() as session:
242
244
  # Read(实例方法)
243
245
  async with sessions.read_session() as session:
244
246
  user = await user_repo.get(session, id_='01HXK...')
245
- user = await user_repo.get_one(session, name='alice')
247
+ user = await user_repo.get_first(session, name='alice') # 取第一条,无匹配返回 None
248
+ user = await user_repo.get_unique(session, email='a@b.com') # 精确唯一,多条匹配抛 MultipleResultsFound
246
249
  users = await user_repo.list(session, role='admin', order_by=['-created_at'], limit=10)
247
250
  total = await user_repo.count(session, is_active=True)
248
251
  has_admin = await user_repo.exists(session, role='admin')
@@ -282,6 +285,92 @@ await user_repo.list(session, expressions=(User.age > 18,))
282
285
  await user_repo.list(session, order_by=['-created_at', 'name'])
283
286
  ```
284
287
 
288
+ ### 查询解析器 — `query_parser`
289
+
290
+ 将 REST 风格的 query string 转为 SQLAlchemy `ColumnElement` 条件表达式,可直接传入 `RepositoryBase` 的 `expressions` / `order_by` 参数。
291
+
292
+ #### where 组合条件(FIQL 风格)
293
+
294
+ ```
295
+ where = and_group ("," and_group)* , 表示 OR
296
+ and_group = unit (";" unit)* ; 表示 AND
297
+ unit = "(" where ")" | condition
298
+ condition = field OP value
299
+ ```
300
+
301
+ **支持的操作符:**
302
+
303
+ | 语法 | 含义 | 示例 |
304
+ | ---------------- | -------------------- | ------------------------- |
305
+ | `field==value` | 等于 | `status==1` |
306
+ | `field!=value` | 不等 | `status!=0` |
307
+ | `field~=value` | 模糊包含 (ILIKE) | `name~=张` |
308
+ | `field>value` | 大于 | `age>18` |
309
+ | `field>=value` | 大于等于 | `age>=18` |
310
+ | `field<value` | 小于 | `age<60` |
311
+ | `field<=value` | 小于等于 | `age<=60` |
312
+ | `field~start~end`| 区间 (BETWEEN) | `age~18~60` |
313
+
314
+ ```python
315
+ from fastapi_augment.db.sqlalchemy import parse_where
316
+
317
+ # 昵称含张 且 状态非禁用
318
+ expr = parse_where('nickname~=张;status!=0', User)
319
+
320
+ # 括号内 OR,与区间 AND
321
+ expr = parse_where('(nickname~=张,username~=王);age~20~30', User)
322
+ ```
323
+
324
+ #### lookup 精确匹配
325
+
326
+ ```python
327
+ from fastapi_augment.db.sqlalchemy import parse_lookup
328
+
329
+ # 单字段精确匹配
330
+ expr = parse_lookup('phone==13800138000', User, fields={'phone', 'email'})
331
+
332
+ # 多字段 AND(; 分隔)
333
+ expr = parse_lookup('phone==138;status==1', User, fields={'phone', 'status'})
334
+ ```
335
+
336
+ #### 关键字搜索
337
+
338
+ ```python
339
+ from fastapi_augment.db.sqlalchemy import parse_keyword
340
+
341
+ # 多字段 OR 模糊搜索
342
+ expr = parse_keyword('admin', 'username,email,nickname', User)
343
+ ```
344
+
345
+ #### 排序
346
+
347
+ ```python
348
+ from fastapi_augment.db.sqlalchemy import parse_sort
349
+
350
+ # - 前缀表示降序,无前缀为升序
351
+ order_by = parse_sort('-created_at,nickname', User)
352
+ ```
353
+
354
+ #### 一键组合 — `build_query_expressions`
355
+
356
+ ```python
357
+ from fastapi_augment.db.sqlalchemy import build_query_expressions
358
+
359
+ expressions, order_by = build_query_expressions(
360
+ User,
361
+ where='status!=0;age>=18',
362
+ q='admin',
363
+ q_field='username,nickname',
364
+ sort='-created_at',
365
+ )
366
+
367
+ # 直接传给 paginate / list
368
+ result = await user_repo.paginate(
369
+ session, page=1, size=10,
370
+ expressions=expressions, order_by=order_by,
371
+ )
372
+ ```
373
+
285
374
  ### 数据库迁移 CLI — `fastapi-augment-migrate`
286
375
 
287
376
  内置 Alembic 迁移工具,提供 `init` / `generate` / `upgrade` 三个子命令,开箱即用。
@@ -471,12 +560,12 @@ raise BadRequestError(detail='用户名不能为空')
471
560
  raise TooManyRequestsError(retry_after=60)
472
561
  ```
473
562
 
474
- ### 日志管理 — `log`
563
+ ### 日志管理 — `logger`
475
564
 
476
565
  导入即生效:自动注入 `request_id` 到每条日志、接管 uvicorn/fastapi 日志输出。
477
566
 
478
567
  ```python
479
- from fastapi_augment.log import setup_logger, set_log_level
568
+ from fastapi_augment.logger import setup_logger, set_log_level
480
569
 
481
570
  # 一键配置:控制台 + 按天轮转文件日志
482
571
  setup_logger(log_dir='./logs', rotation='day', backup_count=30)
@@ -565,26 +654,30 @@ app.include_router(create_health_router(
565
654
 
566
655
  ### 配置管理 — `config`
567
656
 
568
- 基于 `pydantic-settings`,通过 `from_env()` 直接传参,无需手动导入 `SettingsConfigDict`:
657
+ 基于 `pydantic-settings`,支持多种配置来源(环境变量、.env、JSON、YAML、TOML),通过不同类方法加载:
569
658
 
570
659
  ```python
571
- from fastapi_augment.config import EnvSettings
660
+ from fastapi_augment.config import AugmentBaseSettings
572
661
 
573
- class Settings(EnvSettings):
662
+ class Settings(AugmentBaseSettings):
574
663
  database_url: str
575
664
  redis_url: str = ''
576
665
  debug: bool = False
577
666
  secret_key: str = 'change-me'
578
667
 
579
- # 直接传入 .env 路径、前缀等
580
- settings = Settings.from_env(
581
- env_file='config/.env',
582
- env_prefix='APP_',
583
- env_nested_delimiter='__',
584
- )
668
+ # 从环境变量加载
669
+ cfg = Settings.from_env(env_prefix='APP_', env_nested_delimiter='__')
670
+
671
+ # 从 .env 文件加载
672
+ cfg = Settings.from_dotenv('.env', env_prefix='APP_')
673
+
674
+ # 从 JSON / YAML / TOML 文件加载
675
+ cfg = Settings.from_json('config.json')
676
+ cfg = Settings.from_yaml('config.yaml')
677
+ cfg = Settings.from_toml('config.toml')
585
678
  ```
586
679
 
587
- 支持 `SettingsConfigDict` 的所有参数(`env_file`、`env_prefix`、`secrets_dir`、`yaml_file` 等),
680
+ 支持 `SettingsConfigDict` 的所有参数(`env_prefix`、`secrets_dir`、`yaml_file` 等),
588
681
  与模型字段值自动区分,无需关心分类。
589
682
 
590
683
  ### 中间件 — `middlewares`
@@ -610,13 +703,15 @@ fastapi_augment/
610
703
  │ └── utils/
611
704
  │ └── strings.py # 字符串工具 / JSON 序列化
612
705
  ├── config/
613
- │ └── settings.py # EnvSettings 配置管理
706
+ │ ├── base_settings.py # AugmentBaseSettings 配置管理
707
+ │ └── database_settings.py # DatabaseSettings 数据库配置
614
708
  ├── db/
615
709
  │ └── sqlalchemy/
616
710
  │ ├── engine.py # EngineManager / NodeConfig / ClusterTopology
617
711
  │ ├── session.py # SessionFactory(读写分离)
618
712
  │ ├── model_base.py # ModelBase(ULID 主键)
619
713
  │ ├── repository_base.py # RepositoryBase(泛型仓储 + paginate)
714
+ │ ├── query_parser.py # 查询解析器(where / lookup / keyword / sort)
620
715
  │ ├── migrate.py # 数据库迁移 CLI
621
716
  │ ├── migrations/ # Alembic 迁移环境(env.py / script.py.mako)
622
717
  │ └── mixins/ # Timestamp / Audit / SoftDelete
@@ -624,11 +719,11 @@ fastapi_augment/
624
719
  │ ├── checker.py # BaseChecker / CheckResult / HealthResponse
625
720
  │ ├── checkers.py # AppChecker / DatabaseChecker
626
721
  │ └── router.py # create_health_router()
627
- ├── log/
628
- │ ├── factory.py # request_id 注入工厂
722
+ ├── logger/
723
+ │ ├── record_factory.py # request_id 注入工厂
629
724
  │ ├── filters.py # UvicornNameRewriteFilter
630
725
  │ ├── handlers.py # 多进程安全轮转处理器
631
- │ └── config.py # setup_logger / set_log_level / set_log_format
726
+ │ └── setup.py # setup_logger / set_log_level / set_log_format
632
727
  ├── middlewares/
633
728
  │ ├── base.py # BaseASGIMiddleware
634
729
  │ └── request_id.py # RequestId 中间件
@@ -0,0 +1 @@
1
+ 0.1.4
@@ -1,7 +1,7 @@
1
1
  """
2
2
  @Author : hangu
3
3
  @CreateDate : 2026/9/4
4
- @Description :
4
+ @Description : 全局常量与统一默认错误文案
5
5
  """
6
6
  from starlette import status
7
7
 
@@ -21,6 +21,7 @@ DEFAULT_ERR_MSG: dict[int, str] = {
21
21
  status.HTTP_413_CONTENT_TOO_LARGE: 'Payload too large',
22
22
  status.HTTP_414_URI_TOO_LONG: 'URI too long',
23
23
  status.HTTP_415_UNSUPPORTED_MEDIA_TYPE: 'Unsupported media type',
24
+ status.HTTP_422_UNPROCESSABLE_CONTENT: 'Unprocessable entity',
24
25
  status.HTTP_423_LOCKED: 'Locked',
25
26
  status.HTTP_429_TOO_MANY_REQUESTS: 'Too many requests',
26
27
  }
@@ -27,11 +27,11 @@ async def base_http_error_handler(
27
27
  _request: Request,
28
28
  exc: BaseHttpError,
29
29
  ) -> JSONResponse:
30
- """处理 BaseHttpError 及其所有子类(统一业务异常)。
30
+ """处理 BaseHttpError 及其所有子类(统一业务异常)
31
31
 
32
32
  将业务异常转换为统一 APIResponse 格式,
33
33
  HTTP 状态码与 exc.status_code 一致,
34
- body.code 同样使用 HTTP 状态码,body.message 使用 exc.detail。
34
+ body.code 同样使用 HTTP 状态码,body.message 使用 exc.detail
35
35
 
36
36
  Args:
37
37
  _request: Starlette Request 对象
@@ -54,11 +54,11 @@ async def http_exception_handler(
54
54
  _request: Request,
55
55
  exc: HTTPException,
56
56
  ) -> JSONResponse:
57
- """处理 Starlette/FastAPI 原生 HTTPException。
57
+ """处理 Starlette/FastAPI 原生 HTTPException
58
58
 
59
- 覆盖 FastAPI 默认处理器,将响应格式统一为 APIResponse。
59
+ 覆盖 FastAPI 默认处理器,将响应格式统一为 APIResponse
60
60
  注意:BaseHttpError 继承自 HTTPException,但 FastAPI 会优先匹配
61
- 更具体的处理器(base_http_error_handler),所以此处不会拦截业务异常。
61
+ 更具体的处理器(base_http_error_handler),所以此处不会拦截业务异常
62
62
 
63
63
  Args:
64
64
  _request: Starlette Request 对象
@@ -84,9 +84,9 @@ async def validation_exception_handler(
84
84
  _request: Request,
85
85
  exc: RequestValidationError,
86
86
  ) -> JSONResponse:
87
- """处理 Pydantic 请求参数校验异常(422)。
87
+ """处理 Pydantic 请求参数校验异常(422)
88
88
 
89
- 将校验错误详情提取到 extra.errors 中,方便前端定位具体字段。
89
+ 将校验错误详情提取到 extra.errors 中,方便前端定位具体字段
90
90
 
91
91
  Args:
92
92
  _request: Starlette Request 对象
@@ -121,10 +121,10 @@ async def general_exception_handler(
121
121
  _request: Request,
122
122
  exc: Exception,
123
123
  ) -> JSONResponse:
124
- """处理所有未被捕获的异常(兜底)。
124
+ """处理所有未被捕获的异常(兜底)
125
125
 
126
126
  记录完整异常日志(含堆栈),但响应体只返回通用提示,
127
- 避免将内部实现细节(堆栈、SQL 等)暴露给客户端。
127
+ 避免将内部实现细节(堆栈、SQL 等)暴露给客户端
128
128
 
129
129
  Args:
130
130
  _request: Starlette Request 对象
@@ -144,7 +144,7 @@ async def general_exception_handler(
144
144
  # ===================== 一键注册 =====================
145
145
 
146
146
  def register_exception_handlers(app: FastAPI) -> None:
147
- """将全部统一异常处理器注册到 FastAPI 应用。
147
+ """将全部统一异常处理器注册到 FastAPI 应用
148
148
 
149
149
  注册后,以下异常会被转换为统一的 APIResponse 格式:
150
150
  - BaseHttpError 及子类 → 对应 HTTP 状态码
@@ -13,7 +13,7 @@ from .constants import DEFAULT_ERR_MSG
13
13
 
14
14
  # ===================== 通用基类:统一封装 detail + headers 逻辑 =====================
15
15
  class BaseHttpError(HTTPException):
16
- """统一HTTP异常基类,所有4xx异常继承此类,原生兼容starlette.HTTPException.
16
+ """统一HTTP异常基类,所有4xx异常继承此类,原生兼容starlette.HTTPException
17
17
 
18
18
  子类只需声明 ``_status_code`` 类变量,无需重写 __init__::
19
19
 
@@ -38,7 +38,7 @@ class BaseHttpError(HTTPException):
38
38
  detail: str | None = None,
39
39
  headers: dict[str, Any] | None = None,
40
40
  ):
41
- """初始化http业务异常.
41
+ """初始化http业务异常
42
42
 
43
43
  Args:
44
44
  status_code: http响应状态码,不传则读取子类的 _status_code 类变量
@@ -62,82 +62,82 @@ class BaseHttpError(HTTPException):
62
62
  # ===================== 各类4xx异常子类(极简声明,无重复__init__) =====================
63
63
  # 子类只需声明 _status_code 类变量,__init__ 由基类统一处理
64
64
  class BadRequestError(BaseHttpError):
65
- """400 请求错误."""
65
+ """400 请求错误"""
66
66
  _status_code = status.HTTP_400_BAD_REQUEST
67
67
 
68
68
 
69
69
  class UnauthorizedError(BaseHttpError):
70
- """401 未授权错误."""
70
+ """401 未授权错误"""
71
71
  _status_code = status.HTTP_401_UNAUTHORIZED
72
72
 
73
73
 
74
74
  class PaymentRequiredError(BaseHttpError):
75
- """402 需要付费错误."""
75
+ """402 需要付费错误"""
76
76
  _status_code = status.HTTP_402_PAYMENT_REQUIRED
77
77
 
78
78
 
79
79
  class ForbiddenError(BaseHttpError):
80
- """403 禁止访问错误."""
80
+ """403 禁止访问错误"""
81
81
  _status_code = status.HTTP_403_FORBIDDEN
82
82
 
83
83
 
84
84
  class NotFoundError(BaseHttpError):
85
- """404 未找到资源."""
85
+ """404 未找到资源"""
86
86
  _status_code = status.HTTP_404_NOT_FOUND
87
87
 
88
88
 
89
89
  class MethodNotAllowedError(BaseHttpError):
90
- """405 请求方法不允许."""
90
+ """405 请求方法不允许"""
91
91
  _status_code = status.HTTP_405_METHOD_NOT_ALLOWED
92
92
 
93
93
 
94
94
  class NotAcceptableError(BaseHttpError):
95
- """406 客户端不支持返回格式."""
95
+ """406 客户端不支持返回格式"""
96
96
  _status_code = status.HTTP_406_NOT_ACCEPTABLE
97
97
 
98
98
 
99
99
  class RequestTimeoutError(BaseHttpError):
100
- """408 请求超时."""
100
+ """408 请求超时"""
101
101
  _status_code = status.HTTP_408_REQUEST_TIMEOUT
102
102
 
103
103
 
104
104
  class ConflictError(BaseHttpError):
105
- """409 资源冲突."""
105
+ """409 资源冲突"""
106
106
  _status_code = status.HTTP_409_CONFLICT
107
107
 
108
108
 
109
109
  class GoneError(BaseHttpError):
110
- """410 资源已永久删除."""
110
+ """410 资源已永久删除"""
111
111
  _status_code = status.HTTP_410_GONE
112
112
 
113
113
 
114
114
  class PreconditionFailedError(BaseHttpError):
115
- """412 前置校验失败."""
115
+ """412 前置校验失败"""
116
116
  _status_code = status.HTTP_412_PRECONDITION_FAILED
117
117
 
118
118
 
119
119
  class PayloadTooLargeError(BaseHttpError):
120
- """413 请求体过大."""
120
+ """413 请求体过大"""
121
121
  _status_code = status.HTTP_413_CONTENT_TOO_LARGE
122
122
 
123
123
 
124
124
  class URITooLongError(BaseHttpError):
125
- """414 URI链接过长."""
125
+ """414 URI链接过长"""
126
126
  _status_code = status.HTTP_414_URI_TOO_LONG
127
127
 
128
128
 
129
129
  class UnsupportedMediaTypeError(BaseHttpError):
130
- """415 不支持的请求媒体类型."""
130
+ """415 不支持的请求媒体类型"""
131
131
  _status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
132
132
 
133
133
 
134
134
  class LockedError(BaseHttpError):
135
- """423 资源锁定."""
135
+ """423 资源锁定"""
136
136
  _status_code = status.HTTP_423_LOCKED
137
137
 
138
138
 
139
139
  class TooManyRequestsError(BaseHttpError):
140
- """429 请求过于频繁(限流专用,支持retry_after快捷参数)."""
140
+ """429 请求过于频繁(限流专用,支持retry_after快捷参数)"""
141
141
  _status_code = status.HTTP_429_TOO_MANY_REQUESTS
142
142
 
143
143
  def __init__(