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 +21 -0
- lumary-0.1.0/PKG-INFO +173 -0
- lumary-0.1.0/README.md +134 -0
- lumary-0.1.0/lumary/__init__.py +45 -0
- lumary-0.1.0/lumary/application.py +231 -0
- lumary-0.1.0/lumary/common/__init__.py +12 -0
- lumary-0.1.0/lumary/common/enums.py +29 -0
- lumary-0.1.0/lumary/common/mixins/__init__.py +8 -0
- lumary-0.1.0/lumary/common/mixins/sqlalchemy.py +28 -0
- lumary-0.1.0/lumary/common/utils.py +49 -0
- lumary-0.1.0/lumary/db/__init__.py +5 -0
- lumary-0.1.0/lumary/db/sqlalchemy/__init__.py +18 -0
- lumary-0.1.0/lumary/db/sqlalchemy/base.py +11 -0
- lumary-0.1.0/lumary/db/sqlalchemy/crud.py +228 -0
- lumary-0.1.0/lumary/db/sqlalchemy/engine.py +89 -0
- lumary-0.1.0/lumary/db/sqlalchemy/model.py +42 -0
- lumary-0.1.0/lumary/db/sqlalchemy/session.py +50 -0
- lumary-0.1.0/lumary/exceptions.py +26 -0
- lumary-0.1.0/lumary/handlers.py +113 -0
- lumary-0.1.0/lumary/lifespan.py +334 -0
- lumary-0.1.0/lumary/middleware.py +44 -0
- lumary-0.1.0/lumary/openapi.py +62 -0
- lumary-0.1.0/lumary/py.typed +0 -0
- lumary-0.1.0/lumary/schemas.py +106 -0
- lumary-0.1.0/lumary/websocket.py +343 -0
- lumary-0.1.0/lumary.egg-info/PKG-INFO +173 -0
- lumary-0.1.0/lumary.egg-info/SOURCES.txt +31 -0
- lumary-0.1.0/lumary.egg-info/dependency_links.txt +1 -0
- lumary-0.1.0/lumary.egg-info/requires.txt +13 -0
- lumary-0.1.0/lumary.egg-info/top_level.txt +1 -0
- lumary-0.1.0/pyproject.toml +68 -0
- lumary-0.1.0/setup.cfg +4 -0
- lumary-0.1.0/tests/test_lumary.py +22 -0
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,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,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
|
+
)
|