fastapi-augment 0.1.5__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 (88) hide show
  1. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/PKG-INFO +200 -23
  2. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/README.md +199 -22
  3. fastapi_augment-0.1.6/VERSION +1 -0
  4. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/pyproject.toml +10 -0
  5. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/common/__init__.py +27 -4
  6. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/common/app_discovery.py +25 -24
  7. fastapi_augment-0.1.6/src/fastapi_augment/common/asgi_types.py +89 -0
  8. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/common/exception_handlers.py +1 -1
  9. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/common/utils/strings.py +4 -3
  10. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/config/base_settings.py +10 -10
  11. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/config/database_settings.py +3 -3
  12. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/engine.py +16 -10
  13. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/repository_base.py +306 -12
  14. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/factory.py +1 -1
  15. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/health/router.py +2 -1
  16. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/lifespan.py +3 -20
  17. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/logger/setup.py +2 -1
  18. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/openapi.py +4 -4
  19. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/schemas/__init__.py +4 -1
  20. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/schemas/request.py +3 -5
  21. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/schemas/response.py +3 -3
  22. fastapi_augment-0.1.6/src/fastapi_augment/schemas/types.py +46 -0
  23. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment.egg-info/PKG-INFO +200 -23
  24. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment.egg-info/SOURCES.txt +4 -1
  25. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_app_discovery.py +72 -22
  26. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_db_repository.py +2 -1
  27. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_exception_handlers.py +1 -1
  28. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_factory.py +3 -3
  29. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_lifespan.py +1 -1
  30. fastapi_augment-0.1.6/tests/test_soft_delete.py +353 -0
  31. fastapi_augment-0.1.6/tests/test_strings.py +153 -0
  32. fastapi_augment-0.1.5/VERSION +0 -1
  33. fastapi_augment-0.1.5/src/fastapi_augment/schemas/types.py +0 -11
  34. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/setup.cfg +0 -0
  35. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/__init__.py +0 -0
  36. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/common/constants.py +0 -0
  37. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/common/exceptions.py +0 -0
  38. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/common/utils/__init__.py +0 -0
  39. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/common/utils/paths.py +0 -0
  40. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/config/__init__.py +0 -0
  41. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/__init__.py +0 -0
  42. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/__init__.py +0 -0
  43. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/alembic/README +0 -0
  44. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/alembic/__init__.py +0 -0
  45. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/alembic/env.py +0 -0
  46. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/alembic/script.py.mako +0 -0
  47. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/base.py +0 -0
  48. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/migrate.py +0 -0
  49. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/mixins/__init__.py +0 -0
  50. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/mixins/audit.py +0 -0
  51. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/mixins/soft_delete.py +0 -0
  52. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/mixins/timestamp.py +0 -0
  53. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/model_base.py +0 -0
  54. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/query_parser.py +0 -0
  55. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/db/sqlalchemy/session.py +0 -0
  56. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/health/__init__.py +0 -0
  57. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/health/checker.py +0 -0
  58. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/health/checkers.py +0 -0
  59. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/logger/__init__.py +0 -0
  60. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/logger/filters.py +0 -0
  61. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/logger/handlers.py +0 -0
  62. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/logger/record_factory.py +0 -0
  63. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/middlewares/__init__.py +0 -0
  64. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/middlewares/base.py +0 -0
  65. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/middlewares/request_id.py +0 -0
  66. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/py.typed +0 -0
  67. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/schemas/base.py +0 -0
  68. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment/schemas/pagination.py +0 -0
  69. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment.egg-info/dependency_links.txt +0 -0
  70. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment.egg-info/entry_points.txt +0 -0
  71. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment.egg-info/requires.txt +0 -0
  72. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/src/fastapi_augment.egg-info/top_level.txt +0 -0
  73. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_config.py +0 -0
  74. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_constants.py +0 -0
  75. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_database_settings.py +0 -0
  76. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_db_engine.py +0 -0
  77. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_db_session_models.py +0 -0
  78. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_exceptions.py +0 -0
  79. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_health.py +0 -0
  80. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_log.py +0 -0
  81. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_middlewares.py +0 -0
  82. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_migrate.py +0 -0
  83. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_model_base.py +0 -0
  84. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_openapi.py +0 -0
  85. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_paths.py +0 -0
  86. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_query_parser.py +0 -0
  87. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_schemas.py +0 -0
  88. {fastapi_augment-0.1.5 → fastapi_augment-0.1.6}/tests/test_settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-augment
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: FastAPI 通用代码工具包,跨项目复用
5
5
  Author-email: zarkhan <hanguangzheng@qq.com>
6
6
  License-Expression: MIT
@@ -54,7 +54,7 @@ Requires-Dist: fastapi-augment[config,orjson,sqlalchemy,uvicorn]; extra == "stan
54
54
  - **健康检查** — 可扩展的检查器模式,内置应用状态与数据库连通性检查,一行开关
55
55
  - **配置管理** — 基于 pydantic-settings,支持 `.env` 文件、环境变量前缀、嵌套配置
56
56
  - **数据库迁移 CLI** — 一行命令生成/执行迁移,自动发现用户模型
57
- - **应用发现** — 自动发现子包 `__all__` 导出的 FastAPI 应用,支持排除与导入串校验
57
+ - **应用发现** — 自动发现子包 `__all__` 导出的 ASGI 应用(不限于 FastAPI),支持排除与导入串校验
58
58
 
59
59
  ## 安装
60
60
 
@@ -121,6 +121,19 @@ app = create_app(
121
121
  )
122
122
  ```
123
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
+
124
137
  ## 核心模块
125
138
 
126
139
  ### 应用工厂 — `create_app()`
@@ -304,6 +317,37 @@ async with sessions.transaction() as session:
304
317
  count = await user_repo.delete_where(session, is_active=False)
305
318
  ```
306
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
+
307
351
  **过滤语法:**
308
352
 
309
353
  ```python
@@ -718,6 +762,118 @@ cfg = Settings.from_toml('config.toml')
718
762
  支持 `SettingsConfigDict` 的所有参数(`env_prefix`、`secrets_dir`、`yaml_file` 等),
719
763
  与模型字段值自动区分,无需关心分类。
720
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
+
721
877
  ### 中间件 — `middlewares`
722
878
 
723
879
  #### `RequestIdMiddleware`
@@ -732,26 +888,29 @@ request_id = get_request_id()
732
888
 
733
889
  ### 应用发现 — `common.app_discovery`
734
890
 
735
- 递归发现 `apps` 包下所有子包通过 `__all__` 导出的 FastAPI 应用实例,用于多应用聚合部署与启动前校验。
891
+ 递归发现 `apps` 包下所有子包通过 `__all__` 导出的 ASGI 应用(不限于 FastAPI),用于多应用聚合部署与启动前校验。
736
892
 
737
- **约定:** 每个业务子包(如 `apps.platform`)在 `__init__.py` 的 `__all__` 中导出自己创建的 FastAPI 实例(如 `platform_app`);只有出现在 `__all__` 且确实是 `FastAPI` 实例的对象才被识别为"应用"。
893
+ **约定:** 每个业务子包(如 `apps.platform`)在 `__init__.py` 的 `__all__` 中导出自己创建的 ASGI 应用(如 `platform_app`,不限于 FastAPI 实例);只有出现在 `__all__` 且通过 `is_asgi_app` 判定的对象才被识别为"应用"(判定规则见下)。
738
894
 
739
895
  ```python
740
- from fastapi_augment.common import discover_fastapi_apps, FastAPIAppSpec, validate_asgi_import
896
+ from fastapi_augment.common import ASGIAppSpec, discover_asgi_apps, validate_asgi_import
741
897
 
742
898
  # 发现全部应用(排除主应用,获取主应用之外的其它应用)
743
- apps: list[FastAPIAppSpec] = discover_fastapi_apps(root='apps', exclude='apps.platform:platform_app')
899
+ apps: list[ASGIAppSpec] = discover_asgi_apps(root='apps', exclude='apps.platform:platform_app')
744
900
 
745
901
  # 每个应用可直接启动(import_string 即 uvicorn 导入串)
746
902
  for spec in apps:
747
903
  uvicorn.run(spec.import_string) # 'apps.platform:platform_app'
748
904
 
749
- # 启动前校验主应用导入串是否真实存在且为 FastAPI 实例
905
+ # 启动前校验主应用导入串是否真实存在且为可调用的 ASGI 应用(uvicorn 可直接启动)
750
906
  validate_asgi_import('apps.platform:platform_app')
751
907
  ```
752
908
 
753
909
  - **`exclude` 支持三种标识** — 模块名(`apps.platform`)、导出名(`platform_app`)或 `module:name` 导入串(`apps.platform:platform_app`)
754
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 实际启动为准。
755
914
 
756
915
  ## 开发与发布
757
916
 
@@ -767,42 +926,60 @@ validate_asgi_import('apps.platform:platform_app')
767
926
  # lint(读取 pyproject.toml 的 [tool.ruff] 配置)
768
927
  uvx ruff check src tests
769
928
 
770
- # 测试
929
+ # 类型检查(读取 pyproject.toml 的 [tool.mypy] 配置)
930
+ uv run mypy src tests
931
+
932
+ # 测试(覆盖率门禁 --cov-fail-under=90 在 pyproject.toml 的 pytest addopts 中配置)
771
933
  uv run --frozen pytest -q
772
934
  ```
773
935
 
774
- CI 已配置自动检查(`.github/workflows/lint.yml`):每次 push / PR 自动运行 ruff。
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、不发布。
775
943
 
776
944
  ### 发布 Release
777
945
 
778
- `.github/workflows/release.yml` 触发方式:
946
+ 单个 workflow(`.github/workflows/release.yml`)串行完成 GitHub Release 与 PyPI 发布,两个 Job:
779
947
 
780
- - **推送到 `master`(自动)** — 本次合并变更了 `VERSION` 时自动发布新版本(打 tag + 上传 GitHub Release);未变更则跳过,避免重复发布
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`(自动)** — 版本守卫判定"需要发布"时自动执行;不需要时跳过
781
954
  - **master 分支手动触发**(workflow_dispatch)— `release` 发布新版本 / `rebuild` 重新打包指定版本
782
955
 
783
- 发布类操作仅 master 分支可执行,自动完成:
956
+ 版本守卫规则(release 与 publish 各自独立判断):
784
957
 
785
- 1. **检查** — pytest + ruff,任一失败即停止,不发布
786
- 2. **确定版本** — 无 tag 用代码版本(`VERSION` 文件);代码版本 > 最高 tag 用代码版本;否则以最高 tag 版本为准
787
- 3. **校验版本一致性** — 目标版本与代码版本不一致时终止并引导(master 受保护,需先在 `develop` 更新 `VERSION` 与 `factory.py` 版本,PR 合并后重试)
788
- 4. **打包** — 源码打包为 `fastapi_augment-<版本>.zip` / `.tar.gz`(排除 `.venv`、缓存、构建产物)
789
- 5. **打 tag + 发布** — 打包成功后才创建/更新 `v<版本>` tag 并上传 GitHub Release;失败不留任何 tag/Release,重试不会跳版本
958
+ | 状态 | push 合并 | 手动触发 |
959
+ | ---- | --------- | -------- |
960
+ | tag 与 Release 均在且与代码版本一致 | 跳过 | 报错引导先更新 `VERSION` |
961
+ | 缺 tag 或缺 Release | 补齐发布 | 补齐发布 |
962
+ | rebuild 模式 | — | 直接放行 |
790
963
 
791
- 建议发布前先在 `develop` 分支完成版本号更新并 PR 合并到 `master`——合并触发自动发布,发布流程将直接复用代码版本。
964
+ publish Job 以 PyPI 线上版本为准(查询 `pypi.org/pypi/<project>/<version>/json`,404 才上传),PyPI 版本不可覆盖,天然不重复。
792
965
 
793
- PyPI 发布(`.github/workflows/publish.yml`)同样支持合并 `master` 自动触发,仅 `VERSION` 变更时发布(PyPI 版本不可覆盖)。
966
+ 发布类操作仅 master 分支可执行,任何一步失败都不会产生半成品:
794
967
 
795
- 支持两种模式:
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)认证
796
973
 
797
- - **release**(默认)— 按版本规则发布新版本
798
- - **rebuild** — 指定已有 tag(如 `v1.2.3`)重新打包上传,不修改代码版本
974
+ 建议发布前先在 `develop` 分支完成版本号更新并 PR 合并到 `master`——合并触发自动发布,发布流程将直接复用代码版本。
799
975
 
800
976
  ## 项目结构
801
977
 
802
978
  ```
803
979
  fastapi_augment/
804
980
  ├── common/
805
- │ ├── app_discovery.py # FastAPI 应用发现(__all__ 约定)
981
+ │ ├── app_discovery.py # ASGI 应用发现(__all__ 约定)
982
+ │ ├── asgi_types.py # ASGI 类型定义与 is_asgi_app 运行时判定(TypeGuard)
806
983
  │ ├── constants.py # 全局常量与默认错误文案
807
984
  │ ├── exceptions.py # 4xx HTTP 异常体系
808
985
  │ ├── exception_handlers.py # 全局异常处理器
@@ -17,7 +17,7 @@
17
17
  - **健康检查** — 可扩展的检查器模式,内置应用状态与数据库连通性检查,一行开关
18
18
  - **配置管理** — 基于 pydantic-settings,支持 `.env` 文件、环境变量前缀、嵌套配置
19
19
  - **数据库迁移 CLI** — 一行命令生成/执行迁移,自动发现用户模型
20
- - **应用发现** — 自动发现子包 `__all__` 导出的 FastAPI 应用,支持排除与导入串校验
20
+ - **应用发现** — 自动发现子包 `__all__` 导出的 ASGI 应用(不限于 FastAPI),支持排除与导入串校验
21
21
 
22
22
  ## 安装
23
23
 
@@ -84,6 +84,19 @@ app = create_app(
84
84
  )
85
85
  ```
86
86
 
87
+ ### 完整可跑示例 — `examples/quickstart`
88
+
89
+ 上面的最小示例浓缩了核心 API;想看到**完整工程形态**(三段式配置、模块级日志、组合根装配、生命周期建表、软删除与聚合、统一响应、测试),直接运行示例工程:
90
+
91
+ ```bash
92
+ cd examples/quickstart
93
+ uv sync --all-groups # 安装依赖(需 uv,Python >= 3.11)
94
+ uv run python src/main.py # 启动:http://127.0.0.1:8000/docs
95
+ uv run --group dev pytest -q # 跑测试(临时 SQLite,不落盘)
96
+ ```
97
+
98
+ 示例工程沉淀自真实业务项目(browser-proxy)的工程模式,与库文档各章节一一对应(配置组合示例 ↔ `src/config/`,数据库层 ↔ `src/core/database.py`,泛型仓储 ↔ `apps/api/router.py`),详见 `examples/quickstart/README.md`。
99
+
87
100
  ## 核心模块
88
101
 
89
102
  ### 应用工厂 — `create_app()`
@@ -267,6 +280,37 @@ async with sessions.transaction() as session:
267
280
  count = await user_repo.delete_where(session, is_active=False)
268
281
  ```
269
282
 
283
+ **软删除感知(模型混入 `SoftDeleteMixin` 时自动生效,非软删模型行为不变):**
284
+
285
+ ```python
286
+ # 查询默认排除已软删行;include_deleted=True 可放开
287
+ user = await user_repo.get(session, id_='01HXK...') # 已软删 → None
288
+ users = await user_repo.list(session, include_deleted=True) # 包含已软删行
289
+ total = await user_repo.count(session, include_deleted=True)
290
+
291
+ # 删除自动转软删(写 is_deleted=True + deleted_at);非软删模型仍为物理删除
292
+ await user_repo.delete(session, user)
293
+ deleted = await user_repo.delete_by_id(session, id_='01HXK...')
294
+ count = await user_repo.delete_where(session, is_active=False)
295
+
296
+ # 需要真正物理删除时显式调用 hard_delete_*
297
+ await user_repo.hard_delete_by_id(session, id_='01HXK...')
298
+ count = await user_repo.hard_delete_where(session, is_active=False)
299
+ ```
300
+
301
+ **聚合与批量更新:**
302
+
303
+ ```python
304
+ # 数值列聚合(默认排除已软删行,支持过滤条件;无匹配行返回 None)
305
+ total_amount = await user_repo.sum(session, 'amount', role='admin')
306
+ avg_amount = await user_repo.avg(session, 'amount')
307
+ min_amount = await user_repo.min(session, 'amount')
308
+ max_amount = await user_repo.max(session, 'amount')
309
+
310
+ # 批量更新(单条 UPDATE,返回受影响行数;默认跳过已软删行)
311
+ affected = await user_repo.update_where(session, {'role': 'admin'}, name='alice')
312
+ ```
313
+
270
314
  **过滤语法:**
271
315
 
272
316
  ```python
@@ -681,6 +725,118 @@ cfg = Settings.from_toml('config.toml')
681
725
  支持 `SettingsConfigDict` 的所有参数(`env_prefix`、`secrets_dir`、`yaml_file` 等),
682
726
  与模型字段值自动区分,无需关心分类。
683
727
 
728
+ #### 项目配置组合示例
729
+
730
+ 实际项目中通常组合多个配置类——全局(日志)、项目信息、Uvicorn 运行参数。以下为推荐模式:
731
+ 只定义字段与加载方式,具体值由 `.env` 与环境变量注入;库仅提供 `AugmentBaseSettings` 基类。
732
+
733
+ ```python
734
+ # src/config/settings.py —— 全局配置(日志 + 根目录)
735
+ from pathlib import Path
736
+
737
+ from fastapi_augment.common.utils import find_project_root, get_root_dir
738
+ from fastapi_augment.config import AugmentBaseSettings
739
+
740
+ __VERSION__ = '0.1.0'
741
+ _ENV_PREFIX = 'MY_SERVICE'
742
+
743
+ try:
744
+ _root_dir = find_project_root()
745
+ except Exception:
746
+ _root_dir = get_root_dir(__file__, 2)
747
+
748
+
749
+ class Settings(AugmentBaseSettings):
750
+ """项目全局配置,字段通过 MY_SERVICE_ 前缀环境变量或 .env 覆盖"""
751
+
752
+ # 日志配置(对应 fastapi_augment.logger.setup_logger 参数)
753
+ logs_dir: str | Path | None = _root_dir / 'logs'
754
+ logs_level: str | None = 'INFO'
755
+ logs_filename: str = 'app.log'
756
+ logs_rotation: str = 'hour'
757
+ logs_backup_count: int = 30
758
+ logs_encoding: str = 'utf-8'
759
+ logs_enable_console: bool = True
760
+
761
+ @property
762
+ def root_dir(self) -> Path:
763
+ return _root_dir
764
+
765
+
766
+ settings = Settings.from_env(
767
+ env_file=_root_dir / '.env',
768
+ env_prefix=f'{_ENV_PREFIX}_',
769
+ env_nested_delimiter='__',
770
+ env_parse_none_str='null',
771
+ )
772
+ ```
773
+
774
+ ```python
775
+ # src/config/project_settings.py —— 项目元信息(debug / title / version)
776
+ from fastapi_augment.config import AugmentBaseSettings
777
+
778
+ from .settings import __VERSION__, _root_dir, _ENV_PREFIX
779
+
780
+
781
+ class ProjectSettings(AugmentBaseSettings):
782
+ """项目基础配置,字段通过 MY_SERVICE_PROJECT_ 前缀覆盖"""
783
+
784
+ debug: bool = False
785
+ title: str = 'my-service'
786
+ summary: str = ''
787
+
788
+ @property
789
+ def version(self) -> str:
790
+ return __VERSION__
791
+
792
+
793
+ project_settings = ProjectSettings.from_env(
794
+ env_file=_root_dir / '.env',
795
+ env_prefix=f'{_ENV_PREFIX}_PROJECT_',
796
+ env_nested_delimiter='__',
797
+ env_parse_none_str='null',
798
+ )
799
+ ```
800
+
801
+ ```python
802
+ # src/config/uvicorn_settings.py —— Uvicorn 运行参数(不使用 uvicorn 时无需定义)
803
+ from fastapi_augment.config import AugmentBaseSettings
804
+
805
+ from .project_settings import project_settings
806
+ from .settings import _root_dir, _ENV_PREFIX
807
+
808
+
809
+ class UvicornSettings(AugmentBaseSettings):
810
+ """Uvicorn 运行配置,字段通过 MY_SERVICE_UVICORN_ 前缀覆盖"""
811
+
812
+ host: str = '0.0.0.0'
813
+ port: int = 8000
814
+ workers: int = 2
815
+ access_log: bool = True
816
+ root_path: str = ''
817
+ asgi_app_ref: str = 'apps.main:app' # 启动入口,可用环境变量覆盖
818
+
819
+ @property
820
+ def reload(self) -> bool:
821
+ """是否热重载,联动项目 debug 开关"""
822
+ return project_settings.debug
823
+
824
+
825
+ uvicorn_settings = UvicornSettings.from_env(
826
+ env_file=_root_dir / '.env',
827
+ env_prefix=f'{_ENV_PREFIX}_UVICORN_',
828
+ env_nested_delimiter='__',
829
+ env_parse_none_str='null',
830
+ )
831
+ ```
832
+
833
+ **要点:**
834
+
835
+ - 配置类、环境变量前缀、默认值均为**使用方策略**,留在项目内,不进库
836
+ - 各配置类前缀独立(`MY_SERVICE_` / `MY_SERVICE_PROJECT_` / `MY_SERVICE_UVICORN_`),互不干扰
837
+ - 不使用 uvicorn 时无需定义 `UvicornSettings`,库不强制
838
+ - 环境变量优先级高于 `.env` 文件
839
+
684
840
  ### 中间件 — `middlewares`
685
841
 
686
842
  #### `RequestIdMiddleware`
@@ -695,26 +851,29 @@ request_id = get_request_id()
695
851
 
696
852
  ### 应用发现 — `common.app_discovery`
697
853
 
698
- 递归发现 `apps` 包下所有子包通过 `__all__` 导出的 FastAPI 应用实例,用于多应用聚合部署与启动前校验。
854
+ 递归发现 `apps` 包下所有子包通过 `__all__` 导出的 ASGI 应用(不限于 FastAPI),用于多应用聚合部署与启动前校验。
699
855
 
700
- **约定:** 每个业务子包(如 `apps.platform`)在 `__init__.py` 的 `__all__` 中导出自己创建的 FastAPI 实例(如 `platform_app`);只有出现在 `__all__` 且确实是 `FastAPI` 实例的对象才被识别为"应用"。
856
+ **约定:** 每个业务子包(如 `apps.platform`)在 `__init__.py` 的 `__all__` 中导出自己创建的 ASGI 应用(如 `platform_app`,不限于 FastAPI 实例);只有出现在 `__all__` 且通过 `is_asgi_app` 判定的对象才被识别为"应用"(判定规则见下)。
701
857
 
702
858
  ```python
703
- from fastapi_augment.common import discover_fastapi_apps, FastAPIAppSpec, validate_asgi_import
859
+ from fastapi_augment.common import ASGIAppSpec, discover_asgi_apps, validate_asgi_import
704
860
 
705
861
  # 发现全部应用(排除主应用,获取主应用之外的其它应用)
706
- apps: list[FastAPIAppSpec] = discover_fastapi_apps(root='apps', exclude='apps.platform:platform_app')
862
+ apps: list[ASGIAppSpec] = discover_asgi_apps(root='apps', exclude='apps.platform:platform_app')
707
863
 
708
864
  # 每个应用可直接启动(import_string 即 uvicorn 导入串)
709
865
  for spec in apps:
710
866
  uvicorn.run(spec.import_string) # 'apps.platform:platform_app'
711
867
 
712
- # 启动前校验主应用导入串是否真实存在且为 FastAPI 实例
868
+ # 启动前校验主应用导入串是否真实存在且为可调用的 ASGI 应用(uvicorn 可直接启动)
713
869
  validate_asgi_import('apps.platform:platform_app')
714
870
  ```
715
871
 
716
872
  - **`exclude` 支持三种标识** — 模块名(`apps.platform`)、导出名(`platform_app`)或 `module:name` 导入串(`apps.platform:platform_app`)
717
873
  - 结果按模块名排序,顺序稳定
874
+ - **`validate_asgi_import` 不限于 FastAPI** — 只要是可调用的 ASGI 应用(Starlette / Flask 等或自定义 ASGI 函数,判定规则见下)均通过
875
+
876
+ **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 实际启动为准。
718
877
 
719
878
  ## 开发与发布
720
879
 
@@ -730,42 +889,60 @@ validate_asgi_import('apps.platform:platform_app')
730
889
  # lint(读取 pyproject.toml 的 [tool.ruff] 配置)
731
890
  uvx ruff check src tests
732
891
 
733
- # 测试
892
+ # 类型检查(读取 pyproject.toml 的 [tool.mypy] 配置)
893
+ uv run mypy src tests
894
+
895
+ # 测试(覆盖率门禁 --cov-fail-under=90 在 pyproject.toml 的 pytest addopts 中配置)
734
896
  uv run --frozen pytest -q
735
897
  ```
736
898
 
737
- CI 已配置自动检查(`.github/workflows/lint.yml`):每次 push / PR 自动运行 ruff。
899
+ CI(`.github/workflows/lint.yml`,即 **CI** workflow)已配置自动检查,PR / develop push 时运行:
900
+
901
+ - **Ruff** — `uvx ruff check src tests`
902
+ - **Mypy** — `uv run mypy src tests`(Python 3.11)
903
+ - **Pytest** — Python 3.11 / 3.12 / 3.13 矩阵并行,覆盖率不低于 90%(`--cov-fail-under=90`)
904
+
905
+ 三个 Job 全部通过才允许合并到 `master`(分支保护所需状态检查为 `Ruff`、`Mypy` 与 `Pytest (Python x.y)`)。PR 阶段只做检查,不打包、不打 tag、不发布。
738
906
 
739
907
  ### 发布 Release
740
908
 
741
- `.github/workflows/release.yml` 触发方式:
909
+ 单个 workflow(`.github/workflows/release.yml`)串行完成 GitHub Release 与 PyPI 发布,两个 Job:
742
910
 
743
- - **推送到 `master`(自动)** — 本次合并变更了 `VERSION` 时自动发布新版本(打 tag + 上传 GitHub Release);未变更则跳过,避免重复发布
911
+ 1. **release Job** — 版本守卫 → 代码门禁(pytest + ruff + mypy)→ 源码打包(zip / tar.gz)→ 上传 GitHub Release(tag 由 `gh release create` 自动创建,成功时 tag 必然存在)
912
+ 2. **publish Job**(`needs: release`)— **仅当 GitHub Release 成功后才执行**:查 PyPI 是否已有该版本(无则上传)→ build → 版本一致性断言 → `twine check` 校验打包 → 上传 PyPI
913
+
914
+ 触发方式:
915
+
916
+ - **推送到 `master`(自动)** — 版本守卫判定"需要发布"时自动执行;不需要时跳过
744
917
  - **master 分支手动触发**(workflow_dispatch)— `release` 发布新版本 / `rebuild` 重新打包指定版本
745
918
 
746
- 发布类操作仅 master 分支可执行,自动完成:
919
+ 版本守卫规则(release 与 publish 各自独立判断):
747
920
 
748
- 1. **检查** — pytest + ruff,任一失败即停止,不发布
749
- 2. **确定版本** — 无 tag 用代码版本(`VERSION` 文件);代码版本 > 最高 tag 用代码版本;否则以最高 tag 版本为准
750
- 3. **校验版本一致性** — 目标版本与代码版本不一致时终止并引导(master 受保护,需先在 `develop` 更新 `VERSION` 与 `factory.py` 版本,PR 合并后重试)
751
- 4. **打包** — 源码打包为 `fastapi_augment-<版本>.zip` / `.tar.gz`(排除 `.venv`、缓存、构建产物)
752
- 5. **打 tag + 发布** — 打包成功后才创建/更新 `v<版本>` tag 并上传 GitHub Release;失败不留任何 tag/Release,重试不会跳版本
921
+ | 状态 | push 合并 | 手动触发 |
922
+ | ---- | --------- | -------- |
923
+ | tag 与 Release 均在且与代码版本一致 | 跳过 | 报错引导先更新 `VERSION` |
924
+ | 缺 tag 或缺 Release | 补齐发布 | 补齐发布 |
925
+ | rebuild 模式 | — | 直接放行 |
753
926
 
754
- 建议发布前先在 `develop` 分支完成版本号更新并 PR 合并到 `master`——合并触发自动发布,发布流程将直接复用代码版本。
927
+ publish Job 以 PyPI 线上版本为准(查询 `pypi.org/pypi/<project>/<version>/json`,404 才上传),PyPI 版本不可覆盖,天然不重复。
755
928
 
756
- PyPI 发布(`.github/workflows/publish.yml`)同样支持合并 `master` 自动触发,仅 `VERSION` 变更时发布(PyPI 版本不可覆盖)。
929
+ 发布类操作仅 master 分支可执行,任何一步失败都不会产生半成品:
757
930
 
758
- 支持两种模式:
931
+ 1. **代码门禁** — 判定需要发布时先跑 pytest + ruff + mypy,任一失败即停止(防止绕过分支保护发布未验证代码)
932
+ 2. **版本一致性校验** — 目标版本与代码版本不一致时终止并引导(master 受保护,需先在 `develop` 更新 `VERSION` 与 `factory.py` 版本,PR 合并后重试)
933
+ 3. **打包** — 源码打包为 `fastapi_augment-<版本>.zip` / `.tar.gz`(排除 `.venv`、缓存、构建产物)
934
+ 4. **Release** — 打包成功后才上传 GitHub Release;失败不留任何 tag/Release,重试不会跳版本
935
+ 5. **PyPI** — Release 成功后才上传,上传前经 `twine check` 校验 sdist/wheel 元数据,使用 `PYPI_API_TOKEN`(GitHub Secrets)认证
759
936
 
760
- - **release**(默认)— 按版本规则发布新版本
761
- - **rebuild** — 指定已有 tag(如 `v1.2.3`)重新打包上传,不修改代码版本
937
+ 建议发布前先在 `develop` 分支完成版本号更新并 PR 合并到 `master`——合并触发自动发布,发布流程将直接复用代码版本。
762
938
 
763
939
  ## 项目结构
764
940
 
765
941
  ```
766
942
  fastapi_augment/
767
943
  ├── common/
768
- │ ├── app_discovery.py # FastAPI 应用发现(__all__ 约定)
944
+ │ ├── app_discovery.py # ASGI 应用发现(__all__ 约定)
945
+ │ ├── asgi_types.py # ASGI 类型定义与 is_asgi_app 运行时判定(TypeGuard)
769
946
  │ ├── constants.py # 全局常量与默认错误文案
770
947
  │ ├── exceptions.py # 4xx HTTP 异常体系
771
948
  │ ├── exception_handlers.py # 全局异常处理器
@@ -0,0 +1 @@
1
+ 0.1.6
@@ -66,8 +66,10 @@ fastapi-augment-migrate = "fastapi_augment.db.sqlalchemy.migrate:cli_main"
66
66
  dev = [
67
67
  "aiosqlite>=0.21.0",
68
68
  "httpx2>=2.12.0",
69
+ "mypy>=1.10",
69
70
  "pytest>=8.0",
70
71
  "pytest-asyncio>=0.25.0",
72
+ "pytest-cov>=5.0",
71
73
  ]
72
74
 
73
75
  [tool.setuptools.packages.find]
@@ -88,6 +90,7 @@ version = { file = "VERSION" }
88
90
  [tool.pytest.ini_options]
89
91
  asyncio_mode = "auto"
90
92
  testpaths = ["tests"]
93
+ addopts = "--cov=fastapi_augment --cov-report=term-missing --cov-fail-under=90"
91
94
 
92
95
  [tool.ruff]
93
96
  line-length = 120
@@ -113,3 +116,10 @@ ignore = [
113
116
 
114
117
  [tool.ruff.lint.isort]
115
118
  known-first-party = ["fastapi_augment"]
119
+
120
+ # ======== 类型检查(mypy) ========
121
+ [tool.mypy]
122
+ python_version = "3.11"
123
+ files = ["src", "tests"]
124
+ warn_unused_ignores = true
125
+ no_implicit_optional = true