fastapi-augment 0.1.3__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.
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/PKG-INFO +22 -17
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/README.md +21 -16
- fastapi_augment-0.1.4/VERSION +1 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/common/constants.py +2 -1
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/common/exception_handlers.py +10 -10
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/common/exceptions.py +18 -18
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/common/utils/__init__.py +1 -1
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/common/utils/paths.py +3 -3
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/common/utils/strings.py +17 -4
- fastapi_augment-0.1.4/src/fastapi_augment/config/__init__.py +12 -0
- fastapi_augment-0.1.4/src/fastapi_augment/config/base_settings.py +253 -0
- fastapi_augment-0.1.4/src/fastapi_augment/config/database_settings.py +219 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/alembic/env.py +1 -1
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/engine.py +9 -2
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/migrate.py +7 -7
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/mixins/__init__.py +1 -1
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/mixins/audit.py +3 -3
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/mixins/soft_delete.py +4 -4
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/mixins/timestamp.py +3 -3
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/query_parser.py +2 -2
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/repository_base.py +8 -5
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/session.py +5 -5
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/factory.py +15 -7
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/health/__init__.py +6 -6
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/health/checker.py +13 -9
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/health/checkers.py +38 -21
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/health/router.py +13 -8
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/lifespan.py +23 -21
- {fastapi_augment-0.1.3/src/fastapi_augment/log → fastapi_augment-0.1.4/src/fastapi_augment/logger}/__init__.py +9 -9
- {fastapi_augment-0.1.3/src/fastapi_augment/log → fastapi_augment-0.1.4/src/fastapi_augment/logger}/filters.py +3 -3
- {fastapi_augment-0.1.3/src/fastapi_augment/log → fastapi_augment-0.1.4/src/fastapi_augment/logger}/handlers.py +14 -15
- fastapi_augment-0.1.3/src/fastapi_augment/log/factory.py → fastapi_augment-0.1.4/src/fastapi_augment/logger/record_factory.py +5 -5
- fastapi_augment-0.1.3/src/fastapi_augment/log/config.py → fastapi_augment-0.1.4/src/fastapi_augment/logger/setup.py +17 -9
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/middlewares/base.py +4 -4
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/middlewares/request_id.py +3 -3
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/openapi.py +6 -9
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment.egg-info/PKG-INFO +22 -17
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment.egg-info/SOURCES.txt +7 -6
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_config.py +23 -23
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_factory.py +2 -2
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_log.py +21 -21
- fastapi_augment-0.1.4/tests/test_settings.py +128 -0
- fastapi_augment-0.1.3/VERSION +0 -1
- fastapi_augment-0.1.3/src/fastapi_augment/config/__init__.py +0 -8
- fastapi_augment-0.1.3/src/fastapi_augment/config/settings.py +0 -121
- fastapi_augment-0.1.3/tests/test_settings.py +0 -78
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/pyproject.toml +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/setup.cfg +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/__init__.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/common/__init__.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/__init__.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/__init__.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/alembic/README +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/alembic/__init__.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/alembic/script.py.mako +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/base.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/db/sqlalchemy/model_base.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/middlewares/__init__.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/py.typed +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/schemas/__init__.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/schemas/base.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/schemas/pagination.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/schemas/request.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/schemas/response.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/schemas/types.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment.egg-info/dependency_links.txt +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment.egg-info/entry_points.txt +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment.egg-info/requires.txt +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment.egg-info/top_level.txt +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_constants.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_db_engine.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_db_repository.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_db_session_models.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_exception_handlers.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_exceptions.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_health.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_lifespan.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_middlewares.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_migrate.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_model_base.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_openapi.py +0 -0
- {fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/tests/test_query_parser.py +0 -0
- {fastapi_augment-0.1.3 → 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.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: FastAPI 通用代码工具包,跨项目复用
|
|
5
5
|
Author-email: zarkhan <hanguangzheng@qq.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -597,12 +597,12 @@ raise BadRequestError(detail='用户名不能为空')
|
|
|
597
597
|
raise TooManyRequestsError(retry_after=60)
|
|
598
598
|
```
|
|
599
599
|
|
|
600
|
-
### 日志管理 — `
|
|
600
|
+
### 日志管理 — `logger`
|
|
601
601
|
|
|
602
602
|
导入即生效:自动注入 `request_id` 到每条日志、接管 uvicorn/fastapi 日志输出。
|
|
603
603
|
|
|
604
604
|
```python
|
|
605
|
-
from fastapi_augment.
|
|
605
|
+
from fastapi_augment.logger import setup_logger, set_log_level
|
|
606
606
|
|
|
607
607
|
# 一键配置:控制台 + 按天轮转文件日志
|
|
608
608
|
setup_logger(log_dir='./logs', rotation='day', backup_count=30)
|
|
@@ -691,26 +691,30 @@ app.include_router(create_health_router(
|
|
|
691
691
|
|
|
692
692
|
### 配置管理 — `config`
|
|
693
693
|
|
|
694
|
-
基于 `pydantic-settings
|
|
694
|
+
基于 `pydantic-settings`,支持多种配置来源(环境变量、.env、JSON、YAML、TOML),通过不同类方法加载:
|
|
695
695
|
|
|
696
696
|
```python
|
|
697
|
-
from fastapi_augment.config import
|
|
697
|
+
from fastapi_augment.config import AugmentBaseSettings
|
|
698
698
|
|
|
699
|
-
class Settings(
|
|
699
|
+
class Settings(AugmentBaseSettings):
|
|
700
700
|
database_url: str
|
|
701
701
|
redis_url: str = ''
|
|
702
702
|
debug: bool = False
|
|
703
703
|
secret_key: str = 'change-me'
|
|
704
704
|
|
|
705
|
-
#
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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')
|
|
711
715
|
```
|
|
712
716
|
|
|
713
|
-
支持 `SettingsConfigDict` 的所有参数(`
|
|
717
|
+
支持 `SettingsConfigDict` 的所有参数(`env_prefix`、`secrets_dir`、`yaml_file` 等),
|
|
714
718
|
与模型字段值自动区分,无需关心分类。
|
|
715
719
|
|
|
716
720
|
### 中间件 — `middlewares`
|
|
@@ -736,7 +740,8 @@ fastapi_augment/
|
|
|
736
740
|
│ └── utils/
|
|
737
741
|
│ └── strings.py # 字符串工具 / JSON 序列化
|
|
738
742
|
├── config/
|
|
739
|
-
│
|
|
743
|
+
│ ├── base_settings.py # AugmentBaseSettings 配置管理
|
|
744
|
+
│ └── database_settings.py # DatabaseSettings 数据库配置
|
|
740
745
|
├── db/
|
|
741
746
|
│ └── sqlalchemy/
|
|
742
747
|
│ ├── engine.py # EngineManager / NodeConfig / ClusterTopology
|
|
@@ -751,11 +756,11 @@ fastapi_augment/
|
|
|
751
756
|
│ ├── checker.py # BaseChecker / CheckResult / HealthResponse
|
|
752
757
|
│ ├── checkers.py # AppChecker / DatabaseChecker
|
|
753
758
|
│ └── router.py # create_health_router()
|
|
754
|
-
├──
|
|
755
|
-
│ ├──
|
|
759
|
+
├── logger/
|
|
760
|
+
│ ├── record_factory.py # request_id 注入工厂
|
|
756
761
|
│ ├── filters.py # UvicornNameRewriteFilter
|
|
757
762
|
│ ├── handlers.py # 多进程安全轮转处理器
|
|
758
|
-
│ └──
|
|
763
|
+
│ └── setup.py # setup_logger / set_log_level / set_log_format
|
|
759
764
|
├── middlewares/
|
|
760
765
|
│ ├── base.py # BaseASGIMiddleware
|
|
761
766
|
│ └── request_id.py # RequestId 中间件
|
|
@@ -560,12 +560,12 @@ raise BadRequestError(detail='用户名不能为空')
|
|
|
560
560
|
raise TooManyRequestsError(retry_after=60)
|
|
561
561
|
```
|
|
562
562
|
|
|
563
|
-
### 日志管理 — `
|
|
563
|
+
### 日志管理 — `logger`
|
|
564
564
|
|
|
565
565
|
导入即生效:自动注入 `request_id` 到每条日志、接管 uvicorn/fastapi 日志输出。
|
|
566
566
|
|
|
567
567
|
```python
|
|
568
|
-
from fastapi_augment.
|
|
568
|
+
from fastapi_augment.logger import setup_logger, set_log_level
|
|
569
569
|
|
|
570
570
|
# 一键配置:控制台 + 按天轮转文件日志
|
|
571
571
|
setup_logger(log_dir='./logs', rotation='day', backup_count=30)
|
|
@@ -654,26 +654,30 @@ app.include_router(create_health_router(
|
|
|
654
654
|
|
|
655
655
|
### 配置管理 — `config`
|
|
656
656
|
|
|
657
|
-
基于 `pydantic-settings
|
|
657
|
+
基于 `pydantic-settings`,支持多种配置来源(环境变量、.env、JSON、YAML、TOML),通过不同类方法加载:
|
|
658
658
|
|
|
659
659
|
```python
|
|
660
|
-
from fastapi_augment.config import
|
|
660
|
+
from fastapi_augment.config import AugmentBaseSettings
|
|
661
661
|
|
|
662
|
-
class Settings(
|
|
662
|
+
class Settings(AugmentBaseSettings):
|
|
663
663
|
database_url: str
|
|
664
664
|
redis_url: str = ''
|
|
665
665
|
debug: bool = False
|
|
666
666
|
secret_key: str = 'change-me'
|
|
667
667
|
|
|
668
|
-
#
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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')
|
|
674
678
|
```
|
|
675
679
|
|
|
676
|
-
支持 `SettingsConfigDict` 的所有参数(`
|
|
680
|
+
支持 `SettingsConfigDict` 的所有参数(`env_prefix`、`secrets_dir`、`yaml_file` 等),
|
|
677
681
|
与模型字段值自动区分,无需关心分类。
|
|
678
682
|
|
|
679
683
|
### 中间件 — `middlewares`
|
|
@@ -699,7 +703,8 @@ fastapi_augment/
|
|
|
699
703
|
│ └── utils/
|
|
700
704
|
│ └── strings.py # 字符串工具 / JSON 序列化
|
|
701
705
|
├── config/
|
|
702
|
-
│
|
|
706
|
+
│ ├── base_settings.py # AugmentBaseSettings 配置管理
|
|
707
|
+
│ └── database_settings.py # DatabaseSettings 数据库配置
|
|
703
708
|
├── db/
|
|
704
709
|
│ └── sqlalchemy/
|
|
705
710
|
│ ├── engine.py # EngineManager / NodeConfig / ClusterTopology
|
|
@@ -714,11 +719,11 @@ fastapi_augment/
|
|
|
714
719
|
│ ├── checker.py # BaseChecker / CheckResult / HealthResponse
|
|
715
720
|
│ ├── checkers.py # AppChecker / DatabaseChecker
|
|
716
721
|
│ └── router.py # create_health_router()
|
|
717
|
-
├──
|
|
718
|
-
│ ├──
|
|
722
|
+
├── logger/
|
|
723
|
+
│ ├── record_factory.py # request_id 注入工厂
|
|
719
724
|
│ ├── filters.py # UvicornNameRewriteFilter
|
|
720
725
|
│ ├── handlers.py # 多进程安全轮转处理器
|
|
721
|
-
│ └──
|
|
726
|
+
│ └── setup.py # setup_logger / set_log_level / set_log_format
|
|
722
727
|
├── middlewares/
|
|
723
728
|
│ ├── base.py # BaseASGIMiddleware
|
|
724
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
|
}
|
{fastapi_augment-0.1.3 → fastapi_augment-0.1.4}/src/fastapi_augment/common/exception_handlers.py
RENAMED
|
@@ -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__(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
@Author : hangu
|
|
3
3
|
@CreateDate : 2026/9/9
|
|
4
|
-
@Description :
|
|
4
|
+
@Description : 项目路径工具函数
|
|
5
5
|
"""
|
|
6
6
|
import sys
|
|
7
7
|
from pathlib import Path
|
|
@@ -10,10 +10,10 @@ from pathlib import Path
|
|
|
10
10
|
# 获取项目根目录路径
|
|
11
11
|
def get_root_dir(reference_path: str | Path, parent_index: int) -> Path:
|
|
12
12
|
"""
|
|
13
|
-
动态获取项目根目录路径,兼容源码开发环境与 PyInstaller/Nuitka
|
|
13
|
+
动态获取项目根目录路径,兼容源码开发环境与 PyInstaller/Nuitka 打包二进制环境
|
|
14
14
|
|
|
15
15
|
源码模式:以传入的参考路径为基准,向上回溯 parent_index 层目录得到项目根;
|
|
16
|
-
打包 frozen 模式:自动返回可执行文件(.exe/二进制)所在目录,忽略 reference_path、parent_index
|
|
16
|
+
打包 frozen 模式:自动返回可执行文件(.exe/二进制)所在目录,忽略 reference_path、parent_index
|
|
17
17
|
|
|
18
18
|
Args:
|
|
19
19
|
reference_path: 锚点参考路径,业务调用一般直接传入 __file__
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
@Author : hangu
|
|
3
3
|
@CreateDate : 2026/9/4
|
|
4
|
-
@Description :
|
|
4
|
+
@Description : 字符串工具函数——命名转换、随机串、JSON 序列化
|
|
5
5
|
"""
|
|
6
6
|
import json
|
|
7
7
|
import random
|
|
@@ -40,7 +40,11 @@ _ORJSON_OPT_INDENT_2 = 0x04
|
|
|
40
40
|
|
|
41
41
|
# ── 字符串转换 ──
|
|
42
42
|
def camel_to_snake(s: str) -> str:
|
|
43
|
-
"""驼峰转下划线(支持连续大写)
|
|
43
|
+
"""驼峰转下划线(支持连续大写)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
转换后的下划线命名字符串
|
|
47
|
+
"""
|
|
44
48
|
if not s:
|
|
45
49
|
return s
|
|
46
50
|
result: list[str] = []
|
|
@@ -58,7 +62,11 @@ def camel_to_snake(s: str) -> str:
|
|
|
58
62
|
|
|
59
63
|
|
|
60
64
|
def snake_to_camel(s: str) -> str:
|
|
61
|
-
"""下划线转驼峰(首字母大写)
|
|
65
|
+
"""下划线转驼峰(首字母大写)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
转换后的驼峰命名字符串
|
|
69
|
+
"""
|
|
62
70
|
if not s:
|
|
63
71
|
return s
|
|
64
72
|
return ''.join(part.capitalize() for part in s.split('_'))
|
|
@@ -72,9 +80,14 @@ def random_string(
|
|
|
72
80
|
) -> str:
|
|
73
81
|
"""生成随机字符串,支持自定义字符集
|
|
74
82
|
|
|
83
|
+
Returns:
|
|
84
|
+
指定长度的随机字符串
|
|
85
|
+
|
|
75
86
|
Raises:
|
|
76
|
-
ValueError:
|
|
87
|
+
ValueError: length 非正整数或字符集为空时(exclude 排除了所有字符)
|
|
77
88
|
"""
|
|
89
|
+
if length < 1:
|
|
90
|
+
raise ValueError(f'length 必须为正整数,当前值: {length}')
|
|
78
91
|
if chars is None:
|
|
79
92
|
chars = string.ascii_letters + string.digits
|
|
80
93
|
if exclude:
|