lumary 0.1.0__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.
lumary-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zarkhan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
lumary-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: lumary
3
+ Version: 0.1.0
4
+ Summary: 基于 FastAPI 封装的生产级 Web 应用框架
5
+ Author-email: zarkhan <hanguangzheng@qq.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/zarkhan/lumary
8
+ Project-URL: Repository, https://github.com/zarkhan/lumary
9
+ Project-URL: Documentation, https://github.com/zarkhan/lumary#readme
10
+ Keywords: fastapi,web,framework,lumary,async
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Framework :: FastAPI
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Internet :: WWW/HTTP
21
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: fastapi>=0.136.0
27
+ Requires-Dist: sqlalchemy>=2.0.0
28
+ Requires-Dist: python-ulid>=3.1.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8.0; extra == "dev"
31
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
32
+ Requires-Dist: httpx>=0.27; extra == "dev"
33
+ Requires-Dist: ruff>=0.4; extra == "dev"
34
+ Requires-Dist: build>=1.2; extra == "dev"
35
+ Requires-Dist: twine>=5.0; extra == "dev"
36
+ Requires-Dist: uvicorn[standard]>=0.30.0; extra == "dev"
37
+ Requires-Dist: asyncpg>=0.29.0; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # Lumary
41
+
42
+ 基于 FastAPI 封装的生产级 Web 应用框架,开箱即用。
43
+
44
+ ## 特性
45
+
46
+ - **应用封装** — `Lumary` 类继承 FastAPI,内置异常处理、CORS 中间件、自定义 OpenAPI 文档、健康检查
47
+ - **生命周期钩子** — `@on_startup` / `@on_shutdown` 装饰器,支持优先级排序与异常终止控制
48
+ - **统一响应格式** — `APIResponse`、`PageData`、`PageQuery` 等标准化 Schema
49
+ - **业务异常** — `BusinessException` 支持自定义错误码与消息,全局自动捕获
50
+ - **WebSocket 管理** — `WSConnectionManager` 支持分组、单播、广播、上下文管理器
51
+ - **枚举基类** — `BaseEnum` 提供 `val` / `label` 属性访问
52
+ - **SQLAlchemy 混入** — `SoftDeleteMixin` 软删除支持(可选依赖)
53
+ - **子应用管控** — 支持动态挂载子应用,禁止子应用嵌套
54
+
55
+ ## 安装
56
+
57
+ ```bash
58
+ pip install lumary
59
+ ```
60
+
61
+ 如需 SQLAlchemy 支持:
62
+
63
+ ```bash
64
+ pip install lumary[sqlalchemy]
65
+ ```
66
+
67
+ ## 快速开始
68
+
69
+ ```python
70
+ from lumary import Lumary, on_startup, on_shutdown
71
+
72
+ app = Lumary(title='My Project', debug=True, enable_health_check=True)
73
+
74
+
75
+ @on_startup(priority=100)
76
+ async def connect_db():
77
+ print('Database connected')
78
+
79
+
80
+ @on_shutdown
81
+ async def close_db():
82
+ print('Database closed')
83
+ ```
84
+
85
+ ### WebSocket
86
+
87
+ ```python
88
+ from fastapi import WebSocket
89
+ from lumary import Lumary, WSConnectionManager
90
+
91
+ app = Lumary(title='Chat')
92
+ manager = WSConnectionManager()
93
+
94
+
95
+ @app.websocket('/ws/{room}')
96
+ async def ws_endpoint(websocket: WebSocket, room: str):
97
+ async with manager.lifespan(websocket, group=room) as cid:
98
+ while True:
99
+ data = await websocket.receive_json()
100
+ await manager.broadcast_json(data, group=room, exclude={cid})
101
+ ```
102
+
103
+ ### 统一响应
104
+
105
+ ```python
106
+ from lumary import response_success, response_fail, APIResponse
107
+
108
+ @app.get('/users/{uid}')
109
+ async def get_user(uid: int) -> APIResponse:
110
+ user = await fetch_user(uid)
111
+ if not user:
112
+ return response_fail(code=404, message='用户不存在')
113
+ return response_success(data=user)
114
+ ```
115
+
116
+ ### 业务异常
117
+
118
+ ```python
119
+ from lumary import BusinessException
120
+
121
+ async def transfer(from_id: int, to_id: int, amount: float):
122
+ if amount <= 0:
123
+ raise BusinessException(code=400, message='金额必须大于零')
124
+ ...
125
+ ```
126
+
127
+ ## 模块一览
128
+
129
+ | 模块 | 说明 |
130
+ |------|------|
131
+ | `lumary.application` | 应用类 `Lumary` |
132
+ | `lumary.lifespan` | 生命周期钩子 `on_startup` / `on_shutdown` |
133
+ | `lumary.schemas` | 统一响应模型 `APIResponse` / `PageData` / `PageQuery` |
134
+ | `lumary.exceptions` | 业务异常 `BusinessException` |
135
+ | `lumary.handlers` | 全局异常处理器 |
136
+ | `lumary.middleware` | 中间件注册 |
137
+ | `lumary.openapi` | 自定义 OpenAPI 文档 |
138
+ | `lumary.websocket` | WebSocket 连接管理器 `WSConnectionManager` |
139
+ | `lumary.common.enums` | 枚举基类 `BaseEnum` |
140
+ | `lumary.common.mixins` | SQLAlchemy 混入类 |
141
+ | `lumary.db` | 数据库工具 |
142
+
143
+ ## 开发
144
+
145
+ ```bash
146
+ # 克隆仓库
147
+ git clone https://github.com/zarkhan/lumary.git
148
+ cd lumary
149
+
150
+ # 创建虚拟环境 & 安装开发依赖
151
+ python -m venv .venv
152
+ .venv\Scripts\activate # Windows
153
+ source .venv/bin/activate # macOS / Linux
154
+ pip install -e ".[dev]"
155
+
156
+ # 代码检查
157
+ ruff check lumary/
158
+
159
+ # 运行测试
160
+ pytest
161
+ ```
162
+
163
+ ## 打包发布
164
+
165
+ ```bash
166
+ python -m build
167
+ twine check dist/*
168
+ twine upload dist/*
169
+ ```
170
+
171
+ ## License
172
+
173
+ [MIT](LICENSE)
lumary-0.1.0/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # Lumary
2
+
3
+ 基于 FastAPI 封装的生产级 Web 应用框架,开箱即用。
4
+
5
+ ## 特性
6
+
7
+ - **应用封装** — `Lumary` 类继承 FastAPI,内置异常处理、CORS 中间件、自定义 OpenAPI 文档、健康检查
8
+ - **生命周期钩子** — `@on_startup` / `@on_shutdown` 装饰器,支持优先级排序与异常终止控制
9
+ - **统一响应格式** — `APIResponse`、`PageData`、`PageQuery` 等标准化 Schema
10
+ - **业务异常** — `BusinessException` 支持自定义错误码与消息,全局自动捕获
11
+ - **WebSocket 管理** — `WSConnectionManager` 支持分组、单播、广播、上下文管理器
12
+ - **枚举基类** — `BaseEnum` 提供 `val` / `label` 属性访问
13
+ - **SQLAlchemy 混入** — `SoftDeleteMixin` 软删除支持(可选依赖)
14
+ - **子应用管控** — 支持动态挂载子应用,禁止子应用嵌套
15
+
16
+ ## 安装
17
+
18
+ ```bash
19
+ pip install lumary
20
+ ```
21
+
22
+ 如需 SQLAlchemy 支持:
23
+
24
+ ```bash
25
+ pip install lumary[sqlalchemy]
26
+ ```
27
+
28
+ ## 快速开始
29
+
30
+ ```python
31
+ from lumary import Lumary, on_startup, on_shutdown
32
+
33
+ app = Lumary(title='My Project', debug=True, enable_health_check=True)
34
+
35
+
36
+ @on_startup(priority=100)
37
+ async def connect_db():
38
+ print('Database connected')
39
+
40
+
41
+ @on_shutdown
42
+ async def close_db():
43
+ print('Database closed')
44
+ ```
45
+
46
+ ### WebSocket
47
+
48
+ ```python
49
+ from fastapi import WebSocket
50
+ from lumary import Lumary, WSConnectionManager
51
+
52
+ app = Lumary(title='Chat')
53
+ manager = WSConnectionManager()
54
+
55
+
56
+ @app.websocket('/ws/{room}')
57
+ async def ws_endpoint(websocket: WebSocket, room: str):
58
+ async with manager.lifespan(websocket, group=room) as cid:
59
+ while True:
60
+ data = await websocket.receive_json()
61
+ await manager.broadcast_json(data, group=room, exclude={cid})
62
+ ```
63
+
64
+ ### 统一响应
65
+
66
+ ```python
67
+ from lumary import response_success, response_fail, APIResponse
68
+
69
+ @app.get('/users/{uid}')
70
+ async def get_user(uid: int) -> APIResponse:
71
+ user = await fetch_user(uid)
72
+ if not user:
73
+ return response_fail(code=404, message='用户不存在')
74
+ return response_success(data=user)
75
+ ```
76
+
77
+ ### 业务异常
78
+
79
+ ```python
80
+ from lumary import BusinessException
81
+
82
+ async def transfer(from_id: int, to_id: int, amount: float):
83
+ if amount <= 0:
84
+ raise BusinessException(code=400, message='金额必须大于零')
85
+ ...
86
+ ```
87
+
88
+ ## 模块一览
89
+
90
+ | 模块 | 说明 |
91
+ |------|------|
92
+ | `lumary.application` | 应用类 `Lumary` |
93
+ | `lumary.lifespan` | 生命周期钩子 `on_startup` / `on_shutdown` |
94
+ | `lumary.schemas` | 统一响应模型 `APIResponse` / `PageData` / `PageQuery` |
95
+ | `lumary.exceptions` | 业务异常 `BusinessException` |
96
+ | `lumary.handlers` | 全局异常处理器 |
97
+ | `lumary.middleware` | 中间件注册 |
98
+ | `lumary.openapi` | 自定义 OpenAPI 文档 |
99
+ | `lumary.websocket` | WebSocket 连接管理器 `WSConnectionManager` |
100
+ | `lumary.common.enums` | 枚举基类 `BaseEnum` |
101
+ | `lumary.common.mixins` | SQLAlchemy 混入类 |
102
+ | `lumary.db` | 数据库工具 |
103
+
104
+ ## 开发
105
+
106
+ ```bash
107
+ # 克隆仓库
108
+ git clone https://github.com/zarkhan/lumary.git
109
+ cd lumary
110
+
111
+ # 创建虚拟环境 & 安装开发依赖
112
+ python -m venv .venv
113
+ .venv\Scripts\activate # Windows
114
+ source .venv/bin/activate # macOS / Linux
115
+ pip install -e ".[dev]"
116
+
117
+ # 代码检查
118
+ ruff check lumary/
119
+
120
+ # 运行测试
121
+ pytest
122
+ ```
123
+
124
+ ## 打包发布
125
+
126
+ ```bash
127
+ python -m build
128
+ twine check dist/*
129
+ twine upload dist/*
130
+ ```
131
+
132
+ ## License
133
+
134
+ [MIT](LICENSE)
@@ -0,0 +1,45 @@
1
+ """
2
+ @Author : zarkhan
3
+ @CreateDate : 2026/5/14
4
+ @Description:
5
+ """
6
+ from .common.enums import BaseEnum
7
+ from .common.mixins.sqlalchemy import SoftDeleteMixin
8
+ from .application import Lumary
9
+ from .lifespan import on_startup, on_shutdown
10
+ from .exceptions import BusinessException
11
+ from .schemas import (
12
+ BaseSchema,
13
+ APIResponse,
14
+ PageData,
15
+ PageQuery,
16
+ response_success,
17
+ response_fail
18
+ )
19
+ from .websocket import WSConnectionManager
20
+
21
+ __version__ = '0.1.0'
22
+
23
+ __all__ = [
24
+ # 枚举基类
25
+ 'BaseEnum',
26
+ # SQLAlchemy 混入类
27
+ 'SoftDeleteMixin',
28
+ # 核心
29
+ 'Lumary',
30
+ # 生命周期
31
+ 'on_startup',
32
+ 'on_shutdown',
33
+ # 异常
34
+ 'BusinessException',
35
+ # Schema
36
+ 'BaseSchema',
37
+ 'APIResponse',
38
+ 'PageQuery',
39
+ 'PageData',
40
+ # 快捷函数
41
+ 'response_success',
42
+ 'response_fail',
43
+ # WebSocket连接管理器
44
+ 'WSConnectionManager'
45
+ ]
@@ -0,0 +1,231 @@
1
+ """
2
+ @Author : zarkhan
3
+ @CreateDate : 2026/5/14
4
+ @Description:
5
+ """
6
+ from importlib import import_module
7
+ from logging import getLogger
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from fastapi import FastAPI, Request
12
+ from fastapi.exceptions import FastAPIError
13
+
14
+ from .handlers import setup_exception_handlers
15
+ from .lifespan import fastapi_lifespan
16
+ from .middleware import setup_middlewares
17
+ from .openapi import setup_custom_openapi
18
+ from .schemas import APIResponse, SystemHealthInfo, response_success
19
+
20
+ logger = getLogger(__name__)
21
+
22
+ # ===============================
23
+ # 默认元数据常量
24
+ # ===============================
25
+ _DEFAULT_TERMS_OF_SERVICE = 'https://www.zarkhan.com/terms/'
26
+ _DEFAULT_CONTACT = {
27
+ 'name': 'ZarkHan',
28
+ 'url': 'https://www.zarkhan.com'
29
+ }
30
+ _DEFAULT_LICENSE_INFO = {
31
+ 'name': 'MIT',
32
+ 'url': 'https://opensource.org/licenses/MIT'
33
+ }
34
+ _IGNORE_DIRS = {'__pycache__', 'tests', 'test', 'utils'}
35
+
36
+
37
+ # ===============================
38
+ # 应用类
39
+ # ===============================
40
+ class Lumary(FastAPI):
41
+ """基于FastAPI封装的生产级Web应用类
42
+
43
+ 集成:异常处理、中间件、自定义文档、子应用管控、生命周期管理
44
+ """
45
+ # 减少内存占用 + 提升属性访问速度
46
+ __slots__ = ('user_lifespan', 'is_sub_app')
47
+
48
+ def __init__(
49
+ self,
50
+ *,
51
+ debug: bool = False,
52
+ title: str = 'Lumary',
53
+ summary: str = '',
54
+ description: str = '',
55
+ version: str = '0.1.0',
56
+ enable_cors: bool = True,
57
+ allow_origins: list[str] | None = None,
58
+ allow_methods: list[str] | None = None,
59
+ allow_headers: list[str] | None = None,
60
+ enable_health_check: bool = False,
61
+ enable_access_log: bool = True,
62
+ **kwargs: Any
63
+ ):
64
+ """初始化
65
+
66
+ Args:
67
+ debug: 是否启用调试模式
68
+ title: 应用标题
69
+ summary: 应用简介
70
+ description: 应用描述
71
+ version: 应用版本
72
+ enable_cors: 是否启用 CORS 中间件
73
+ allow_origins: 允许的源列表
74
+ allow_methods: 允许的方法列表
75
+ allow_headers: 允许的头列表
76
+ enable_health_check: 是否启用健康检查
77
+ enable_access_log: 是否启用访问日志
78
+ **kwargs: 其他参数
79
+ """
80
+ user_lifespan = kwargs.pop('lifespan', None)
81
+
82
+ # 👇 设置默认值
83
+ kwargs.setdefault('terms_of_service', _DEFAULT_TERMS_OF_SERVICE)
84
+ kwargs.setdefault('contact', _DEFAULT_CONTACT)
85
+ kwargs.setdefault('license_info', _DEFAULT_LICENSE_INFO)
86
+ kwargs.setdefault('lifespan', fastapi_lifespan)
87
+
88
+ # 👇 如果非调试模式 → 关闭文档
89
+ if not debug:
90
+ kwargs['openapi_url'] = None
91
+ kwargs['docs_url'] = None
92
+ kwargs['redoc_url'] = None
93
+ kwargs['swagger_ui_oauth2_redirect_url'] = None
94
+
95
+ # 👇 调用父类初始化
96
+ super().__init__(
97
+ debug=debug,
98
+ title=title,
99
+ summary=summary,
100
+ description=description,
101
+ version=version,
102
+ **kwargs
103
+ )
104
+
105
+ # 👇 设置异常处理
106
+ setup_exception_handlers(self)
107
+
108
+ # 👇 设置中间件
109
+ setup_middlewares(
110
+ self,
111
+ enable_cors=enable_cors,
112
+ allow_origins=allow_origins,
113
+ allow_methods=allow_methods,
114
+ allow_headers=allow_headers
115
+ )
116
+
117
+ # 👇 设置自定义文档
118
+ setup_custom_openapi(self)
119
+
120
+ # 👇 标记当前实例不是子应用
121
+ self.user_lifespan = user_lifespan
122
+ self.is_sub_app = False
123
+
124
+ # 👇 如果启用健康检查 → 注册健康检查接口
125
+ if enable_health_check:
126
+ self._register_health_check()
127
+
128
+ def _register_health_check(self) -> None:
129
+ """注册健康检查接口"""
130
+
131
+ @self.get('/health', tags=['system'], summary='服务健康检查')
132
+ async def health(_request: Request) -> APIResponse[SystemHealthInfo]:
133
+ """服务健康检查
134
+
135
+ Returns:
136
+ 响应数据
137
+ """
138
+ data = SystemHealthInfo(
139
+ name=self.title,
140
+ version=self.version,
141
+ debug=self.debug
142
+ )
143
+ return response_success(data=data, message='服务运行正常')
144
+
145
+ def _load_sub_app(self, module_path: str, app_name: str) -> 'Lumary | None':
146
+ """动态导入单个子应用
147
+
148
+ Args:
149
+ module_path: 模块路径
150
+ app_name: 应用变量名
151
+
152
+ Returns:
153
+ 子应用实例
154
+ """
155
+ try:
156
+ # 动态导入模块
157
+ module = import_module(module_path)
158
+ sub_app = getattr(module, app_name, None)
159
+
160
+ # 👇 如果存在且类型正确 → 返回子应用实例
161
+ if isinstance(sub_app, type(self)):
162
+ return sub_app
163
+
164
+ logger.warning(f'The sub app {module_path}.{app_name} does not exist or is not the correct type')
165
+ except Exception as e:
166
+ logger.error(f'❌ Loading sub app module exception: {module_path}, exception info: {str(e)}')
167
+
168
+ return None
169
+
170
+ def mount(self, path: str, app: 'Lumary', name: str | None = None) -> None:
171
+ """挂载子应用
172
+
173
+ Args:
174
+ path: 挂载路径
175
+ app: 子应用实例
176
+ name:
177
+ """
178
+ # 👇 如果当前实例是子应用 → 直接报错禁止
179
+ if self.is_sub_app:
180
+ raise FastAPIError('❌ To prevent continuing to mount subapps in subapps, use APIRouter!')
181
+
182
+ # 👇 标记子应用
183
+ app.is_sub_app = True
184
+ # 警告
185
+ # 👇 清空子应用不允许的配置
186
+ if app.root_path:
187
+ logger.warning('The sub-app root_path has been automatically cleared')
188
+ app.root_path = ''
189
+ if app.user_lifespan:
190
+ logger.warning('️The sub-app lifespan has been automatically cleared')
191
+ app.user_lifespan = None
192
+
193
+ # 主应用 → 正常执行原生 mount
194
+ super().mount(path, app, name)
195
+ logger.info(f'🚀 The sub-app is mounted: {path} -> {app.title}')
196
+
197
+ def mount_sub_apps(self, apps_path: str | Path) -> None:
198
+ """挂载子应用
199
+
200
+ Args:
201
+ apps_path: 子应用路径
202
+ """
203
+ apps_path = Path(apps_path)
204
+
205
+ if not apps_path.exists():
206
+ logger.warning(f'{apps_path} 目录不存在,跳过子应用挂载')
207
+ return
208
+
209
+ # 👇 遍历目录
210
+ for path in apps_path.iterdir():
211
+ if not path.is_dir():
212
+ continue
213
+
214
+ # 👇 获取文件夹名称
215
+ folder_name = path.name
216
+
217
+ # 跳过以下划线/点开头 或 在忽略列表中的目录
218
+ if folder_name.startswith(('_', '.')) or (folder_name in _IGNORE_DIRS):
219
+ continue
220
+
221
+ # 👇 构建模块路径和变量名
222
+ module_path = f'apps.{folder_name}'
223
+ app_var_name = f'{folder_name}_app'
224
+ mount_path = f'/{folder_name}'
225
+
226
+ # 👇 动态导入子应用
227
+ app = self._load_sub_app(module_path, app_var_name)
228
+
229
+ # 👇 如果导入成功 → 挂载子应用
230
+ if app is not None:
231
+ self.mount(mount_path, app, folder_name)
@@ -0,0 +1,12 @@
1
+ """
2
+ @Author : zarkhan
3
+ @CreateDate : 2026/5/14
4
+ @Description:
5
+ """
6
+ from .enums import BaseEnum
7
+ from .utils import auto_load_subapp_models
8
+
9
+ __all__ = [
10
+ 'BaseEnum',
11
+ 'auto_load_subapp_models'
12
+ ]
@@ -0,0 +1,29 @@
1
+ """
2
+ @Author : zarkhan
3
+ @CreateDate : 2026/5/14
4
+ @Description:
5
+ """
6
+ from enum import Enum
7
+ from typing import Any
8
+
9
+
10
+ class BaseEnum(Enum):
11
+ """枚举基类"""
12
+
13
+ @property
14
+ def val(self) -> Any:
15
+ """获取枚举值
16
+
17
+ Returns:
18
+ 枚举值
19
+ """
20
+ return self.value[0]
21
+
22
+ @property
23
+ def label(self) -> str:
24
+ """获取枚举标签
25
+
26
+ Returns:
27
+ 枚举描述标签
28
+ """
29
+ return self.value[1] # 取描述
@@ -0,0 +1,8 @@
1
+ """
2
+ @Author : zarkhan
3
+ @CreateDate : 2026/5/14
4
+ @Description:
5
+ """
6
+ from .sqlalchemy import SoftDeleteMixin
7
+
8
+ __all__ = ['SoftDeleteMixin']
@@ -0,0 +1,28 @@
1
+ """
2
+ @Author : zarkhan
3
+ @CreateDate : 2026/5/14
4
+ @Description:
5
+ """
6
+ from datetime import datetime
7
+
8
+ from sqlalchemy import DateTime, Boolean
9
+ from sqlalchemy.orm import Mapped, mapped_column
10
+
11
+
12
+ class SoftDeleteMixin:
13
+ """SQLAlchemy 软删除混入类
14
+
15
+ 为业务模型提供 `is_deleted` 和 `deleted_at` 字段,
16
+ 用于实现逻辑删除,以保证数据的完整性和可追溯性
17
+ """
18
+ is_deleted: Mapped[bool] = mapped_column(
19
+ Boolean,
20
+ default=False,
21
+ comment='是否删除'
22
+ )
23
+
24
+ deleted_at: Mapped[datetime | None] = mapped_column(
25
+ DateTime,
26
+ nullable=True,
27
+ comment='删除时间'
28
+ )