fastapi-augment 0.1.0__py3-none-any.whl

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 (50) hide show
  1. fastapi_augment/__init__.py +24 -0
  2. fastapi_augment/common/__init__.py +61 -0
  3. fastapi_augment/common/constants.py +26 -0
  4. fastapi_augment/common/exception_handlers.py +178 -0
  5. fastapi_augment/common/exceptions.py +162 -0
  6. fastapi_augment/common/utils/__init__.py +5 -0
  7. fastapi_augment/common/utils/strings.py +175 -0
  8. fastapi_augment/config/__init__.py +8 -0
  9. fastapi_augment/config/settings.py +104 -0
  10. fastapi_augment/db/__init__.py +5 -0
  11. fastapi_augment/db/sqlalchemy/__init__.py +20 -0
  12. fastapi_augment/db/sqlalchemy/alembic/__init__.py +5 -0
  13. fastapi_augment/db/sqlalchemy/alembic/env.py +141 -0
  14. fastapi_augment/db/sqlalchemy/base.py +9 -0
  15. fastapi_augment/db/sqlalchemy/crud_base.py +426 -0
  16. fastapi_augment/db/sqlalchemy/engine.py +238 -0
  17. fastapi_augment/db/sqlalchemy/migrate.py +356 -0
  18. fastapi_augment/db/sqlalchemy/mixins/__init__.py +18 -0
  19. fastapi_augment/db/sqlalchemy/mixins/audit.py +61 -0
  20. fastapi_augment/db/sqlalchemy/mixins/soft_delete.py +80 -0
  21. fastapi_augment/db/sqlalchemy/mixins/timestamp.py +48 -0
  22. fastapi_augment/db/sqlalchemy/model_base.py +47 -0
  23. fastapi_augment/db/sqlalchemy/session.py +160 -0
  24. fastapi_augment/factory.py +238 -0
  25. fastapi_augment/health/__init__.py +34 -0
  26. fastapi_augment/health/checker.py +101 -0
  27. fastapi_augment/health/checkers.py +109 -0
  28. fastapi_augment/health/router.py +87 -0
  29. fastapi_augment/lifespan.py +450 -0
  30. fastapi_augment/log/__init__.py +26 -0
  31. fastapi_augment/log/config.py +201 -0
  32. fastapi_augment/log/factory.py +32 -0
  33. fastapi_augment/log/filters.py +23 -0
  34. fastapi_augment/log/handlers.py +81 -0
  35. fastapi_augment/middlewares/__init__.py +20 -0
  36. fastapi_augment/middlewares/base.py +79 -0
  37. fastapi_augment/middlewares/request_id.py +82 -0
  38. fastapi_augment/openapi.py +110 -0
  39. fastapi_augment/py.typed +0 -0
  40. fastapi_augment/schemas/__init__.py +29 -0
  41. fastapi_augment/schemas/base.py +32 -0
  42. fastapi_augment/schemas/pagination.py +46 -0
  43. fastapi_augment/schemas/request.py +28 -0
  44. fastapi_augment/schemas/response.py +139 -0
  45. fastapi_augment/schemas/types.py +11 -0
  46. fastapi_augment-0.1.0.dist-info/METADATA +654 -0
  47. fastapi_augment-0.1.0.dist-info/RECORD +50 -0
  48. fastapi_augment-0.1.0.dist-info/WHEEL +5 -0
  49. fastapi_augment-0.1.0.dist-info/entry_points.txt +2 -0
  50. fastapi_augment-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,160 @@
1
+ """
2
+ @Author : hangu
3
+ @CreateDate : 2026/8/31
4
+ @Description : Async session factory with read/write splitting, built on top of EngineManager.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from collections.abc import AsyncGenerator
9
+ from contextlib import asynccontextmanager
10
+ from typing import Any
11
+
12
+ from sqlalchemy.ext.asyncio import (
13
+ AsyncSession,
14
+ async_sessionmaker,
15
+ )
16
+
17
+ from .engine import EngineManager
18
+
19
+
20
+ class SessionFactory:
21
+ """
22
+ Async session factory that transparently routes reads and writes
23
+ to the correct database engine.
24
+
25
+ Usage::
26
+
27
+ manager = EngineManager(topology).start()
28
+ factory = SessionFactory(manager)
29
+
30
+ # Write session (always hits primary)
31
+ async with factory.write_session() as session:
32
+ session.add(User(name='alice'))
33
+ await session.commit()
34
+
35
+ # Read session (round-robin across replicas)
36
+ async with factory.read_session() as session:
37
+ result = await session.execute(select(User))
38
+
39
+ # FastAPI dependencies
40
+ @app.get('/users')
41
+ async def list_users(session: AsyncSession = Depends(factory.depends_read)):
42
+ ...
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ engine_manager: EngineManager,
48
+ *,
49
+ expire_on_commit: bool = False,
50
+ session_kwargs: dict[str, Any] | None = None,
51
+ ) -> None:
52
+ self._manager = engine_manager
53
+ self._session_kwargs = session_kwargs or {}
54
+ self._expire_on_commit = expire_on_commit
55
+
56
+ self._write_factory = async_sessionmaker(
57
+ bind=engine_manager.write_engine,
58
+ class_=AsyncSession,
59
+ expire_on_commit=expire_on_commit,
60
+ **self._session_kwargs,
61
+ )
62
+
63
+ # 缓存读引擎对应的 session factory,避免每次 read_session 重复创建
64
+ self._read_factories: dict[str, async_sessionmaker[AsyncSession]] = {}
65
+
66
+ # ── Properties ───────────────────────────────────────────────────────
67
+
68
+ @property
69
+ def engine_manager(self) -> EngineManager:
70
+ """The underlying engine manager.
71
+
72
+ Returns:
73
+ The engine manager.
74
+ """
75
+
76
+ return self._manager
77
+
78
+ # ── Session Factories (async context managers) ───────────────────────
79
+
80
+ @asynccontextmanager
81
+ async def write_session(self) -> AsyncGenerator[AsyncSession, None]:
82
+ """Yield a session bound to the **primary** (write) engine
83
+
84
+ Returns:
85
+ An async session bound to the primary engine.
86
+ """
87
+ async with self._write_factory() as session:
88
+ yield session
89
+
90
+ @asynccontextmanager
91
+ async def read_session(self) -> AsyncGenerator[AsyncSession, None]:
92
+ """Yield a session bound to a **read** engine (round-robin across replicas).
93
+
94
+ Returns:
95
+ An async session bound to a read engine.
96
+ """
97
+ read_engine = self._manager.next_read_engine()
98
+ engine_key = id(read_engine)
99
+
100
+ factory = self._read_factories.get(engine_key)
101
+ if factory is None:
102
+ factory = async_sessionmaker(
103
+ bind=read_engine,
104
+ class_=AsyncSession,
105
+ expire_on_commit=self._expire_on_commit,
106
+ **self._session_kwargs,
107
+ )
108
+ self._read_factories[engine_key] = factory
109
+
110
+ async with factory() as session:
111
+ yield session
112
+
113
+ # ── FastAPI Dependencies ─────────────────────────────────────────────
114
+
115
+ async def depends_write(self) -> AsyncGenerator[AsyncSession, None]:
116
+ """FastAPI ``Depends()`` — inject a write session.
117
+
118
+ Returns:
119
+ An async session bound to the primary engine.
120
+ """
121
+ async with self.write_session() as session:
122
+ yield session
123
+
124
+ async def depends_read(self) -> AsyncGenerator[AsyncSession, None]:
125
+ """FastAPI ``Depends()`` — inject a read session.
126
+
127
+ Returns:
128
+ An async session bound to a read engine.
129
+ """
130
+ async with self.read_session() as session:
131
+ yield session
132
+
133
+ # ── Transactional helpers ────────────────────────────────────────────
134
+
135
+ @asynccontextmanager
136
+ async def transaction(self) -> AsyncGenerator[AsyncSession, None]:
137
+ """Write session with automatic commit / rollback.
138
+
139
+ Commits on clean exit, rolls back on any exception::
140
+
141
+ async with factory.transaction() as session:
142
+ session.add(obj)
143
+ # auto-commit here
144
+
145
+ Returns:
146
+ An async session bound to the primary engine.
147
+ """
148
+ async with self.write_session() as session:
149
+ try:
150
+ yield session
151
+ await session.commit()
152
+ except Exception:
153
+ await session.rollback()
154
+ raise
155
+
156
+ # ── Lifecycle ────────────────────────────────────────────────────────
157
+
158
+ async def dispose(self) -> None:
159
+ """Dispose the underlying engine manager and all connection pools."""
160
+ await self._manager.dispose()
@@ -0,0 +1,238 @@
1
+ """
2
+ @Author : hangu
3
+ @CreateDate : 2026/9/3
4
+ @Description : FastAPI 应用工厂
5
+ - 统一创建 FastAPI 实例并自动装配生命周期、数据库、中间件、路由
6
+ - 支持可选的 SQLAlchemy 读写分离集成
7
+ - 通过 app.state 暴露核心组件供业务层使用
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from time import time
12
+ from typing import Sequence, Callable, Any
13
+
14
+ from fastapi import FastAPI, APIRouter
15
+ from starlette.middleware import Middleware
16
+
17
+ from .common.exception_handlers import register_exception_handlers
18
+ from .lifespan import HookRegistry, fastapi_lifespan
19
+ from .middlewares import RequestIdMiddleware
20
+ from .openapi import configure_openapi_schema, OpenAPICustomConfig
21
+ from .health import create_health_router
22
+
23
+ # -------------------------- 类型别名 --------------------------
24
+ # 路由注册回调:接收 app 实例,负责 include_router 等操作
25
+ _RouteRegistrar = Callable[[FastAPI], None]
26
+
27
+
28
+ def create_app(
29
+ *,
30
+ title: str = 'FastAPI',
31
+ version: str = '0.1.0',
32
+ description: str = '',
33
+ debug: bool = False,
34
+ docs_url: str | None = '/docs',
35
+ redoc_url: str | None = '/redoc',
36
+ openapi_url: str | None = '/openapi.json',
37
+ # CORS配置:None=不启用;传入非空序列才启用CORS中间件
38
+ cors_allow_origins: Sequence[str] | None = None,
39
+ cors_allow_methods: Sequence[str] | None = None,
40
+ cors_allow_headers: Sequence[str] | None = None,
41
+ # 生命周期钩子
42
+ registries: Sequence[HookRegistry] | None = None,
43
+ # 中间件
44
+ middlewares: Sequence[Middleware] | None = None,
45
+ # 路由注册
46
+ routers: Sequence[APIRouter | tuple[APIRouter, dict[str, Any]]] | None = None,
47
+ route_registrars: Sequence[_RouteRegistrar] | None = None,
48
+ # OpenAPI 自定义参数新增
49
+ openapi_remove_422: bool = True,
50
+ openapi_remove_validation_error: bool = True,
51
+ openapi_enable_bearer_auth: bool = False,
52
+ # 异常处理器
53
+ register_exceptions: bool = True,
54
+ # 数据库集成(可选)
55
+ engine_manager: Any | None = None,
56
+ session_factory: Any | None = None,
57
+ # 健康检查
58
+ health_check: bool = False,
59
+ # 额外 FastAPI 参数
60
+ **kwargs: Any,
61
+ ) -> FastAPI:
62
+ """创建并配置 FastAPI 应用实例。
63
+
64
+ 工厂函数将以下组件统一装配到应用上:
65
+ 1. 生命周期管理 — 自动接入 :func:`fastapi_lifespan`,合并用户注册表与 core_registry
66
+ 2. 中间件 — 按列表顺序添加(先添加的在内层)
67
+ 3. 路由 — 支持直接传入 APIRouter 或 (router, kwargs) 元组
68
+ 4. 异常处理器 — 自动注册统一异常处理,返回标准 APIResponse 格式
69
+ 5. 数据库 — 可选地将 EngineManager / SessionFactory 挂载到 app.state
70
+
71
+ 装配完成后,``app.state`` 上可访问以下属性:
72
+ - ``app.state.registries`` — 生命周期注册表列表
73
+ - ``app.state.engine_manager`` — 数据库引擎管理器(若提供)
74
+ - ``app.state.session_factory`` — 会话工厂(若提供)
75
+
76
+ Example::
77
+
78
+ from fastapi_augment.factory import create_app
79
+ from fastapi_augment.lifespan import HookRegistry
80
+ from fastapi_augment.db.sqlalchemy import EngineManager, SessionFactory, ClusterTopology, NodeConfig
81
+
82
+ # 数据库
83
+ topology = ClusterTopology(primary=NodeConfig(url='sqlite+aiosqlite:///app.db'))
84
+ manager = EngineManager(topology).start()
85
+ sessions = SessionFactory(manager)
86
+
87
+ # 自定义钩子
88
+ my_registry = HookRegistry()
89
+
90
+ @my_registry.on_startup
91
+ async def init_cache() -> None:
92
+ ...
93
+
94
+ app = create_app(
95
+ title='My Service',
96
+ registries=[my_registry],
97
+ engine_manager=manager,
98
+ session_factory=sessions,
99
+ )
100
+
101
+ Args:
102
+ title: 应用标题
103
+ version: 应用版本
104
+ description: 应用描述
105
+ debug: 是否开启调试模式
106
+ docs_url: Swagger UI 路径,None 禁用
107
+ redoc_url: ReDoc 路径,None 禁用
108
+ openapi_url: OpenAPI schema 路径,None 禁用
109
+ cors_allow_origins: CORS 允许的源列表,None=不启用CORS
110
+ cors_allow_methods: CORS 允许的 HTTP 方法列表,None=不启用CORS
111
+ cors_allow_headers: CORS 允许的 HTTP 头列表,None=不启用CORS
112
+ registries: 生命周期钩子注册表
113
+ middlewares: Starlette 中间件列表
114
+ routers: 路由列表,元素可以是 APIRouter 或 (router, kwargs) 元组
115
+ route_registrars: 路由注册回调列表,接收 app 参数
116
+ openapi_remove_422: 是否移除 422 验证错误响应
117
+ openapi_remove_validation_error: 是否移除验证错误参数
118
+ openapi_enable_bearer_auth: 是否启用 Bearer 认证
119
+ register_exceptions: 是否自动注册统一异常处理器,默认 True
120
+ engine_manager: 数据库引擎管理器实例(可选)
121
+ session_factory: 会话工厂实例(可选)
122
+ health_check: 是否启用健康检查端点(默认 ``/health``);
123
+ 当传入 ``engine_manager`` 时自动包含数据库连通性检查
124
+ **kwargs: 传递给 FastAPI() 构造函数的额外参数(不允许传 lifespan)
125
+
126
+ Returns:
127
+ 配置完成的 FastAPI 应用实例
128
+
129
+ Raises:
130
+ ValueError: 当 kwargs 中包含 lifespan 时抛出
131
+ """
132
+ # ---- 前置校验:禁止外部传入 lifespan ----
133
+ if 'lifespan' in kwargs:
134
+ raise ValueError(
135
+ '不允许通过 kwargs 传递 lifespan,'
136
+ '请使用 registries 参数注册生命周期钩子,工厂会自动管理 lifespan'
137
+ )
138
+
139
+ # ---- 1. 构建 FastAPI 实例 ----
140
+ app = FastAPI(
141
+ title=title,
142
+ version=version,
143
+ description=description,
144
+ debug=debug,
145
+ docs_url=docs_url,
146
+ redoc_url=redoc_url,
147
+ openapi_url=openapi_url,
148
+ middleware=middlewares,
149
+ **kwargs,
150
+ )
151
+
152
+ # ---- 2. 添加 RequestId 中间件 ----
153
+ app.add_middleware(RequestIdMiddleware)
154
+
155
+ # ---- 3. CORS 配置 ----
156
+ if cors_allow_origins:
157
+ from fastapi.middleware.cors import CORSMiddleware
158
+
159
+ opts = dict(
160
+ allow_origins=cors_allow_origins,
161
+ allow_credentials='*' not in cors_allow_origins,
162
+ allow_methods=cors_allow_methods or ['*'],
163
+ allow_headers=cors_allow_headers or ['*']
164
+ )
165
+ app.add_middleware(CORSMiddleware, **opts) # type: ignore
166
+
167
+ # ---- 4. 生命周期注册表 ----
168
+ resolved_registries = _resolve_registries(registries)
169
+ app.state.registries = resolved_registries
170
+
171
+ # 设置 lifespan(使用模块级的 fastapi_lifespan,它会自动将 core_registry 插入首位)
172
+ app.router.lifespan_context = fastapi_lifespan
173
+
174
+ # ---- 5. 路由 ----
175
+ if routers:
176
+ for item in routers:
177
+ if isinstance(item, APIRouter):
178
+ app.include_router(item)
179
+ elif isinstance(item, tuple) and len(item) == 2:
180
+ router, router_kwargs = item
181
+ app.include_router(router, **router_kwargs)
182
+ else:
183
+ raise TypeError(
184
+ f'routers 元素必须为 APIRouter 或 (APIRouter, dict) 元组,'
185
+ f'实际为 {type(item).__name__}'
186
+ )
187
+
188
+ # ---- 6. 路由注册回调 ----
189
+ if route_registrars:
190
+ for registrar in route_registrars:
191
+ registrar(app)
192
+
193
+ # ---- 7. OpenAPI 配置 ----
194
+ configure_openapi_schema(
195
+ app,
196
+ config=OpenAPICustomConfig(
197
+ remove_422=openapi_remove_422,
198
+ remove_validation_error_schema=openapi_remove_validation_error,
199
+ enable_bearer_auth=openapi_enable_bearer_auth
200
+ )
201
+ )
202
+
203
+ # ---- 8. 异常处理器 ----
204
+ if register_exceptions:
205
+ register_exception_handlers(app)
206
+
207
+ # ---- 9. 数据库组件挂载到 app.state ----
208
+ if engine_manager is not None:
209
+ app.state.engine_manager = engine_manager
210
+
211
+ if session_factory is not None:
212
+ app.state.session_factory = session_factory
213
+
214
+ # ---- 10. 健康检查 ----
215
+ if health_check:
216
+ app.state.start_time = time()
217
+ app.include_router(create_health_router(include_db_check=engine_manager is not None))
218
+
219
+ return app
220
+
221
+
222
+ # -------------------------- 内部辅助 --------------------------
223
+
224
+ def _resolve_registries(
225
+ registries: Sequence[HookRegistry] | None,
226
+ ) -> list[HookRegistry]:
227
+ """将 registries 参数规范化为列表。
228
+
229
+ Args:
230
+ registries: 用户传入的注册表参数
231
+
232
+ Returns:
233
+ 规范化后的 HookRegistry 列表
234
+ """
235
+ if registries is None:
236
+ return []
237
+
238
+ return list(registries)
@@ -0,0 +1,34 @@
1
+ """
2
+ @Author : zarkhan
3
+ @CreateDate : 2026/9/6
4
+ @Description: 健康检查模块
5
+ - 可扩展的检查器模式(继承 BaseChecker)
6
+ - 内置 AppChecker(应用状态)和 DatabaseChecker(数据库连通性)
7
+ - create_health_router() 一键创建健康检查路由
8
+ """
9
+ from .checker import (
10
+ BaseChecker,
11
+ CheckResult,
12
+ HealthResponse,
13
+ STATUS_HEALTHY,
14
+ STATUS_DEGRADED,
15
+ STATUS_UNHEALTHY,
16
+ )
17
+ from .checkers import AppChecker, DatabaseChecker
18
+ from .router import create_health_router
19
+
20
+ __all__ = [
21
+ # 基础
22
+ 'BaseChecker',
23
+ 'CheckResult',
24
+ 'HealthResponse',
25
+ # 状态常量
26
+ 'STATUS_HEALTHY',
27
+ 'STATUS_DEGRADED',
28
+ 'STATUS_UNHEALTHY',
29
+ # 内置检查器
30
+ 'AppChecker',
31
+ 'DatabaseChecker',
32
+ # 路由工厂
33
+ 'create_health_router',
34
+ ]
@@ -0,0 +1,101 @@
1
+ """
2
+ @Author : zarkhan
3
+ @CreateDate : 2026/9/6
4
+ @Description: 健康检查基础模型与检查器抽象基类
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Any
10
+
11
+ from pydantic import Field
12
+
13
+ from ..schemas import SchemaBase
14
+
15
+
16
+ # ── 结果模型 ──────────────────────────────────────────────────────────
17
+
18
+
19
+ class CheckResult(SchemaBase):
20
+ """单项检查结果。
21
+
22
+ Attributes:
23
+ name: 检查项名称
24
+ status: 状态(healthy / degraded / unhealthy)
25
+ latency_ms: 检查耗时(毫秒)
26
+ details: 附加详情
27
+ """
28
+ name: str = Field(description='检查项名称')
29
+ status: str = Field(description='状态:healthy / degraded / unhealthy')
30
+ latency_ms: float = Field(default=0.0, description='检查耗时(毫秒)')
31
+ details: dict[str, Any] | None = Field(default=None, description='附加详情')
32
+
33
+
34
+ class HealthResponse(SchemaBase):
35
+ """健康检查响应体。
36
+
37
+ Attributes:
38
+ status: 总体状态(取所有检查项中最差的状态)
39
+ checks: 各检查项结果列表
40
+ """
41
+ status: str = Field(description='总体状态:healthy / degraded / unhealthy')
42
+ checks: list[CheckResult] = Field(default_factory=list, description='各检查项结果')
43
+
44
+
45
+ # ── 状态常量 ──────────────────────────────────────────────────────────
46
+
47
+ STATUS_HEALTHY = 'healthy'
48
+ STATUS_DEGRADED = 'degraded'
49
+ STATUS_UNHEALTHY = 'unhealthy'
50
+
51
+ # 状态严重程度排序,用于聚合时取最差状态
52
+ _STATUS_SEVERITY = {STATUS_HEALTHY: 0, STATUS_DEGRADED: 1, STATUS_UNHEALTHY: 2}
53
+
54
+
55
+ def _worst_status(*statuses: str) -> str:
56
+ """从多个状态中取最差的一个。
57
+
58
+ Args:
59
+ *statuses: 待比较的状态字符串
60
+
61
+ Returns:
62
+ 最差状态
63
+ """
64
+ return max(statuses, key=lambda s: _STATUS_SEVERITY.get(s, 99))
65
+
66
+
67
+ # ── 检查器基类 ────────────────────────────────────────────────────────
68
+
69
+
70
+ class BaseChecker(ABC):
71
+ """健康检查器抽象基类。
72
+
73
+ 所有检查器必须实现 ``name`` 属性和 ``check`` 异步方法::
74
+
75
+ class RedisChecker(BaseChecker):
76
+ @property
77
+ def name(self) -> str:
78
+ return 'redis'
79
+
80
+ async def check(self, app: FastAPI) -> CheckResult:
81
+ # 执行检查逻辑
82
+ return CheckResult(name=self.name, status='healthy', latency_ms=1.2)
83
+ """
84
+
85
+ @property
86
+ @abstractmethod
87
+ def name(self) -> str:
88
+ """检查项名称,用于标识和展示。"""
89
+ ...
90
+
91
+ @abstractmethod
92
+ async def check(self, app: Any) -> CheckResult:
93
+ """执行健康检查。
94
+
95
+ Args:
96
+ app: FastAPI 应用实例,可从中读取 app.state 等
97
+
98
+ Returns:
99
+ 单项检查结果
100
+ """
101
+ ...
@@ -0,0 +1,109 @@
1
+ """
2
+ @Author : zarkhan
3
+ @CreateDate : 2026/9/6
4
+ @Description: 内置健康检查器
5
+ - AppChecker:应用基本信息(状态、版本、运行时长)
6
+ - DatabaseChecker:数据库连通性(基于 EngineManager)
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import time
11
+ from typing import Any
12
+
13
+ from fastapi import FastAPI
14
+ from sqlalchemy import text
15
+
16
+ from .checker import (
17
+ BaseChecker,
18
+ CheckResult,
19
+ STATUS_HEALTHY,
20
+ STATUS_UNHEALTHY,
21
+ )
22
+
23
+
24
+ class AppChecker(BaseChecker):
25
+ """应用基本健康检查。
26
+
27
+ 返回应用运行状态、版本、运行时长。
28
+ 通过 ``app.state.start_time`` 获取启动时间戳(由工厂或用户设置)。
29
+ """
30
+
31
+ @property
32
+ def name(self) -> str:
33
+ return 'app'
34
+
35
+ async def check(self, app: Any) -> CheckResult:
36
+ """检查应用状态。
37
+
38
+ Args:
39
+ app: FastAPI 应用实例
40
+
41
+ Returns:
42
+ 包含版本、状态、运行时长的检查结果
43
+ """
44
+ start_time = getattr(app.state, 'start_time', None)
45
+ uptime = time.time() - start_time if start_time else 0.0
46
+
47
+ return CheckResult(
48
+ name=self.name,
49
+ status=STATUS_HEALTHY,
50
+ latency_ms=0.0,
51
+ details={
52
+ 'status': 'running',
53
+ 'version': app.version if isinstance(app, FastAPI) else '',
54
+ 'uptime_seconds': round(uptime, 2),
55
+ },
56
+ )
57
+
58
+
59
+ class DatabaseChecker(BaseChecker):
60
+ """数据库连通性健康检查。
61
+
62
+ 通过 ``app.state.engine_manager`` 获取写引擎,执行 ``SELECT 1`` 验证连通性。
63
+ 若未挂载 ``engine_manager``,则跳过检查并返回 ``unhealthy``。
64
+ """
65
+
66
+ def __init__(self, name: str = 'database') -> None:
67
+ self._name = name
68
+
69
+ @property
70
+ def name(self) -> str:
71
+ return self._name
72
+
73
+ async def check(self, app: Any) -> CheckResult:
74
+ """检查数据库连通性。
75
+
76
+ Args:
77
+ app: FastAPI 应用实例
78
+
79
+ Returns:
80
+ 数据库连通性检查结果
81
+ """
82
+ engine_manager = getattr(app.state, 'engine_manager', None)
83
+ if engine_manager is None:
84
+ return CheckResult(
85
+ name=self.name,
86
+ status=STATUS_UNHEALTHY,
87
+ details={'error': 'engine_manager not found on app.state'},
88
+ )
89
+
90
+ start = time.perf_counter()
91
+ try:
92
+ engine = engine_manager.write_engine
93
+ async with engine.connect() as conn:
94
+ await conn.execute(text('SELECT 1'))
95
+ latency = (time.perf_counter() - start) * 1000
96
+
97
+ return CheckResult(
98
+ name=self.name,
99
+ status=STATUS_HEALTHY,
100
+ latency_ms=round(latency, 2),
101
+ )
102
+ except Exception as e:
103
+ latency = (time.perf_counter() - start) * 1000
104
+ return CheckResult(
105
+ name=self.name,
106
+ status=STATUS_UNHEALTHY,
107
+ latency_ms=round(latency, 2),
108
+ details={'error': str(e)},
109
+ )