sdpy-kit 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.
- kit/__init__.py +68 -0
- kit/ai/__init__.py +47 -0
- kit/ai/base_service.py +94 -0
- kit/ai/chat_service.py +110 -0
- kit/ai/embedding_service.py +133 -0
- kit/ai/exceptions.py +57 -0
- kit/ai/failover.py +244 -0
- kit/ai/image_service.py +99 -0
- kit/ai/model_builder.py +119 -0
- kit/ai/model_client.py +214 -0
- kit/ai/pool.py +411 -0
- kit/ai/profiles.py +373 -0
- kit/ai/rerank_service.py +93 -0
- kit/ai/runner.py +580 -0
- kit/ai/types.py +28 -0
- kit/common_kit.py +221 -0
- kit/config/__init__.py +44 -0
- kit/config/kit_config.py +147 -0
- kit/config/log_config.py +134 -0
- kit/db/__init__.py +70 -0
- kit/db/base_db_kit.py +406 -0
- kit/db/crud.py +361 -0
- kit/db/engine.py +230 -0
- kit/db/exceptions.py +34 -0
- kit/db/models/__init__.py +33 -0
- kit/db/models/base.py +56 -0
- kit/db/mysql_kit.py +89 -0
- kit/db/pg_kit.py +87 -0
- kit/db/session.py +85 -0
- kit/db/sqlite_kit.py +284 -0
- kit/db/transaction.py +109 -0
- kit/doc_kit.py +1502 -0
- kit/file_kit.py +429 -0
- kit/mcp_kit.py +547 -0
- kit/mineru_kit.py +918 -0
- kit/redis_kit.py +392 -0
- kit/zvec_kit.py +671 -0
- sdpy_kit-0.1.0.dist-info/METADATA +128 -0
- sdpy_kit-0.1.0.dist-info/RECORD +40 -0
- sdpy_kit-0.1.0.dist-info/WHEEL +4 -0
kit/common_kit.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2021-2026 Clark Chang. All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
|
16
|
+
Project: sdpy
|
|
17
|
+
Author: Clark Chang
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# date: 2026-08-22
|
|
21
|
+
"""公共工具类,提供雪花算法 ID 生成和系统时间格式化功能
|
|
22
|
+
|
|
23
|
+
重构说明(破坏性内部、外部零风险):
|
|
24
|
+
- 将原先 `_initialize` / `configure` / `_INITIALIZED` 三套冗余的初始化
|
|
25
|
+
逻辑收敛为唯一的 `configure` 入口 + 惰性 `_ensure_initialized`。
|
|
26
|
+
- 用 `_DATACENTER_ID is None` 语义替代 `_INITIALIZED` 布尔标记,状态更清晰。
|
|
27
|
+
- 公开方法名与签名不变。
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import threading
|
|
31
|
+
import time
|
|
32
|
+
from datetime import datetime
|
|
33
|
+
from random import randint
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CommonKit:
|
|
37
|
+
"""公共工具类,提供分布式唯一 ID 生成和系统时间格式化功能
|
|
38
|
+
|
|
39
|
+
雪花算法 ID 结构(共 63 位,符号位为 0):
|
|
40
|
+
- timestamp(41位):毫秒级时间戳,约可用 69 年
|
|
41
|
+
- datacenter_id(5位):数据中心 ID,有效范围 1-31
|
|
42
|
+
- machine_id(5位):机器 ID,有效范围 1-31
|
|
43
|
+
- sequence(12位):每毫秒内序列号,最大值 4095
|
|
44
|
+
|
|
45
|
+
支持多线程并发调用,保证 ID 唯一性。
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
_TIMESTAMP_SHIFT: int = 22
|
|
49
|
+
_DATACENTER_ID_SHIFT: int = 17
|
|
50
|
+
_MACHINE_ID_SHIFT: int = 12
|
|
51
|
+
|
|
52
|
+
_MAX_DATACENTER_ID: int = 31
|
|
53
|
+
_MAX_MACHINE_ID: int = 31
|
|
54
|
+
_MAX_SEQUENCE: int = 4095
|
|
55
|
+
|
|
56
|
+
_EPOCH: int = 1577836800000
|
|
57
|
+
|
|
58
|
+
# None 表示尚未初始化(未调用 configure)
|
|
59
|
+
_DATACENTER_ID: int | None = None
|
|
60
|
+
_MACHINE_ID: int | None = None
|
|
61
|
+
|
|
62
|
+
_lock: threading.Lock = threading.Lock()
|
|
63
|
+
|
|
64
|
+
_last_timestamp: int = -1
|
|
65
|
+
_sequence: int = 0
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def configure(cls, datacenter_id: int, machine_id: int) -> None:
|
|
69
|
+
"""配置雪花算法的数据中心 ID 和机器 ID
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
datacenter_id: 数据中心 ID,有效范围 1-31
|
|
73
|
+
machine_id: 机器 ID,有效范围 1-31
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
ValueError: 参数超出有效范围时抛出
|
|
77
|
+
"""
|
|
78
|
+
if not (1 <= datacenter_id <= cls._MAX_DATACENTER_ID):
|
|
79
|
+
raise ValueError(
|
|
80
|
+
f"数据中心 ID 必须在 1-{cls._MAX_DATACENTER_ID} 之间,"
|
|
81
|
+
f"当前值: {datacenter_id}"
|
|
82
|
+
)
|
|
83
|
+
if not (1 <= machine_id <= cls._MAX_MACHINE_ID):
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"机器 ID 必须在 1-{cls._MAX_MACHINE_ID} 之间,当前值: {machine_id}"
|
|
86
|
+
)
|
|
87
|
+
with cls._lock:
|
|
88
|
+
cls._DATACENTER_ID = datacenter_id
|
|
89
|
+
cls._MACHINE_ID = machine_id
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def _ensure_initialized(cls) -> None:
|
|
93
|
+
"""惰性初始化:首次使用时采用默认配置 (1, 1)"""
|
|
94
|
+
if cls._DATACENTER_ID is None:
|
|
95
|
+
cls.configure(1, 1)
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def _get_current_timestamp(cls) -> int:
|
|
99
|
+
"""获取当前时间戳(毫秒)"""
|
|
100
|
+
return int(time.time() * 1000)
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def _wait_for_next_millis(cls, last_timestamp: int) -> int:
|
|
104
|
+
"""等待直到进入下一毫秒"""
|
|
105
|
+
timestamp = cls._get_current_timestamp()
|
|
106
|
+
while timestamp <= last_timestamp:
|
|
107
|
+
timestamp = cls._get_current_timestamp()
|
|
108
|
+
return timestamp
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def generate_snowflake_id(
|
|
112
|
+
cls, datacenter_id: int | None = None, machine_id: int | None = None
|
|
113
|
+
) -> int:
|
|
114
|
+
"""生成雪花算法 ID(数值类型)
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
datacenter_id: 数据中心 ID(可选),有效范围 1-31,默认使用配置值
|
|
118
|
+
machine_id: 机器 ID(可选),有效范围 1-31,默认使用配置值
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
int: 63 位雪花 ID 数值
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
ValueError: 参数超出有效范围或时钟回拨时抛出
|
|
125
|
+
"""
|
|
126
|
+
if datacenter_id is None or machine_id is None:
|
|
127
|
+
cls._ensure_initialized()
|
|
128
|
+
assert cls._DATACENTER_ID is not None
|
|
129
|
+
assert cls._MACHINE_ID is not None
|
|
130
|
+
if datacenter_id is None:
|
|
131
|
+
datacenter_id = cls._DATACENTER_ID
|
|
132
|
+
if machine_id is None:
|
|
133
|
+
machine_id = cls._MACHINE_ID
|
|
134
|
+
|
|
135
|
+
if not (1 <= datacenter_id <= cls._MAX_DATACENTER_ID):
|
|
136
|
+
raise ValueError(f"数据中心 ID 必须在 1-{cls._MAX_DATACENTER_ID} 之间")
|
|
137
|
+
if not (1 <= machine_id <= cls._MAX_MACHINE_ID):
|
|
138
|
+
raise ValueError(f"机器 ID 必须在 1-{cls._MAX_MACHINE_ID} 之间")
|
|
139
|
+
|
|
140
|
+
with cls._lock:
|
|
141
|
+
timestamp = cls._get_current_timestamp()
|
|
142
|
+
|
|
143
|
+
if timestamp < cls._last_timestamp:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
f"时钟回拨检测:当前时间戳({timestamp})小于上次使用时间戳"
|
|
146
|
+
f"({cls._last_timestamp}),无法生成 ID,请检查系统时间"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if timestamp == cls._last_timestamp:
|
|
150
|
+
cls._sequence = (cls._sequence + 1) & cls._MAX_SEQUENCE
|
|
151
|
+
if cls._sequence == 0:
|
|
152
|
+
timestamp = cls._wait_for_next_millis(cls._last_timestamp)
|
|
153
|
+
else:
|
|
154
|
+
cls._sequence = 0
|
|
155
|
+
|
|
156
|
+
cls._last_timestamp = timestamp
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
((timestamp - cls._EPOCH) << cls._TIMESTAMP_SHIFT)
|
|
160
|
+
| (datacenter_id << cls._DATACENTER_ID_SHIFT)
|
|
161
|
+
| (machine_id << cls._MACHINE_ID_SHIFT)
|
|
162
|
+
| cls._sequence
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def generate_snow_id(
|
|
167
|
+
cls, datacenter_id: int | None = None, machine_id: int | None = None
|
|
168
|
+
) -> str:
|
|
169
|
+
"""生成雪花算法 ID(字符串类型)
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
datacenter_id: 数据中心 ID(可选),有效范围 1-31,默认使用配置值
|
|
173
|
+
machine_id: 机器 ID(可选),有效范围 1-31,默认使用配置值
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
str: 雪花 ID 字符串形式
|
|
177
|
+
"""
|
|
178
|
+
return str(cls.generate_snowflake_id(datacenter_id, machine_id))
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def generate_timestamp_id() -> str:
|
|
182
|
+
"""生成基于当前时间的唯一 ID(20 位)
|
|
183
|
+
|
|
184
|
+
格式: YYYYMMDDHHMMSSfffRRR,后 3 位为随机数确保同毫秒内唯一性
|
|
185
|
+
"""
|
|
186
|
+
timestamp = datetime.now().strftime("%Y%m%d%H%M%S%f")[:-3]
|
|
187
|
+
random_suffix = randint(0, 999)
|
|
188
|
+
return f"{timestamp}{random_suffix:03d}"
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def format_current_time(format_pattern: str = "yyyyMMdd") -> str:
|
|
192
|
+
"""格式化当前系统时间
|
|
193
|
+
|
|
194
|
+
支持格式: yyyyMMdd, yyyyMMddhhmm, yyyyMMddhhmmss,
|
|
195
|
+
yyyy-MM-dd, yyyy-MM-dd hh:mm, yyyy-MM-dd hh:mm:ss
|
|
196
|
+
"""
|
|
197
|
+
format_map = {
|
|
198
|
+
"yyyyMMdd": "%Y%m%d",
|
|
199
|
+
"yyyyMMddhhmm": "%Y%m%d%H%M",
|
|
200
|
+
"yyyyMMddhhmmss": "%Y%m%d%H%M%S",
|
|
201
|
+
"yyyy-MM-dd": "%Y-%m-%d",
|
|
202
|
+
"yyyy-MM-dd hh:mm": "%Y-%m-%d %H:%M",
|
|
203
|
+
"yyyy-MM-dd hh:mm:ss": "%Y-%m-%d %H:%M:%S",
|
|
204
|
+
}
|
|
205
|
+
if format_pattern not in format_map:
|
|
206
|
+
raise ValueError(
|
|
207
|
+
f"不支持的格式模式: {format_pattern},支持的格式有: "
|
|
208
|
+
f"{', '.join(format_map.keys())}"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
return time.strftime(format_map[format_pattern])
|
|
213
|
+
except Exception as e:
|
|
214
|
+
raise RuntimeError(f"获取系统时间失败: {e}")
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def reset(cls) -> None:
|
|
218
|
+
"""重置 ID 生成器状态"""
|
|
219
|
+
with cls._lock:
|
|
220
|
+
cls._last_timestamp = -1
|
|
221
|
+
cls._sequence = 0
|
kit/config/__init__.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2021-2026 Clark Chang. All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
|
16
|
+
Project: sdpy
|
|
17
|
+
Author: Clark Chang
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# date: 2026-09-08
|
|
21
|
+
"""Kit 配置子包:统一导出配置与日志入口"""
|
|
22
|
+
|
|
23
|
+
from kit.config.kit_config import Settings, get_settings
|
|
24
|
+
from kit.config.log_config import (
|
|
25
|
+
critical,
|
|
26
|
+
debug,
|
|
27
|
+
error,
|
|
28
|
+
get_default_logger,
|
|
29
|
+
info,
|
|
30
|
+
setup_logger,
|
|
31
|
+
warning,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"Settings",
|
|
36
|
+
"get_settings",
|
|
37
|
+
"setup_logger",
|
|
38
|
+
"get_default_logger",
|
|
39
|
+
"debug",
|
|
40
|
+
"info",
|
|
41
|
+
"warning",
|
|
42
|
+
"error",
|
|
43
|
+
"critical",
|
|
44
|
+
]
|
kit/config/kit_config.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2021-2026 Clark Chang. All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
|
16
|
+
Project: sdpy
|
|
17
|
+
Author: Clark Chang
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# date: 2025-04-19
|
|
21
|
+
# source: 迁移自 config/sys_config.py(kit 自包含配置,修改须与源文件评估双向同步)
|
|
22
|
+
"""Kit 统一配置模块"""
|
|
23
|
+
|
|
24
|
+
from functools import lru_cache
|
|
25
|
+
|
|
26
|
+
from pydantic import Field
|
|
27
|
+
from pydantic_settings import BaseSettings
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Settings(BaseSettings):
|
|
31
|
+
APP_NAME: str = Field(default="SDPY App")
|
|
32
|
+
VERSION: str = Field(default="0.1.0")
|
|
33
|
+
DEBUG: bool = Field(default=True)
|
|
34
|
+
HOST: str = Field(default="0.0.0.0")
|
|
35
|
+
PORT: int = Field(default=8000)
|
|
36
|
+
|
|
37
|
+
LOG_LEVEL: str = Field(default="INFO")
|
|
38
|
+
LOG_TO_CONSOLE: bool = Field(default=True)
|
|
39
|
+
LOG_TO_FILE: bool = Field(default=False)
|
|
40
|
+
LOG_DIR: str = Field(default="./logs")
|
|
41
|
+
LOG_FILE: str = Field(default="app.log")
|
|
42
|
+
|
|
43
|
+
REDIS_HOST: str = Field(default="localhost")
|
|
44
|
+
REDIS_PORT: int = Field(default=6379)
|
|
45
|
+
REDIS_DB: int = Field(default=0)
|
|
46
|
+
REDIS_PASSWORD: str | None = Field(default=None)
|
|
47
|
+
|
|
48
|
+
# 数据库统一配置(PostgreSQL / MySQL 共用)
|
|
49
|
+
# 切换数据库只需修改 DEFAULT_DB_TYPE,无需修改连接参数
|
|
50
|
+
DEFAULT_DB_TYPE: str = Field(default="postgresql")
|
|
51
|
+
|
|
52
|
+
DB_HOST: str = Field(default="localhost")
|
|
53
|
+
# PostgreSQL 专用
|
|
54
|
+
DB_PORT: int = Field(default=5432)
|
|
55
|
+
DB_DATABASE: str = Field(default="postgres")
|
|
56
|
+
DB_USER: str = Field(default="admin")
|
|
57
|
+
DB_PASSWORD: str = Field(default="1qaz@WSX3edc")
|
|
58
|
+
|
|
59
|
+
# MySQL 专用覆盖(当端口/用户/库名与 PG 不同时使用)
|
|
60
|
+
MYSQL_PORT: int = Field(default=3306)
|
|
61
|
+
MYSQL_DATABASE: str = Field(default="mydb")
|
|
62
|
+
MYSQL_USER: str = Field(default="root")
|
|
63
|
+
MYSQL_CHARSET: str = Field(default="utf8mb4")
|
|
64
|
+
|
|
65
|
+
# SQLite 专用(切换到 SQLite 时取消注释)
|
|
66
|
+
SQLITE_DB_PATH: str = Field(default="./data/sqlite.db")
|
|
67
|
+
|
|
68
|
+
# zvec 向量库配置(本地嵌入式向量库,独立于 kit.db)
|
|
69
|
+
ZVEC_DATA_DIR: str = Field(
|
|
70
|
+
default="./data/zvec", description="zvec Collection 根目录"
|
|
71
|
+
)
|
|
72
|
+
ZVEC_QUERY_THREADS: int | None = Field(
|
|
73
|
+
default=None, description="查询线程数,None 自适应"
|
|
74
|
+
)
|
|
75
|
+
ZVEC_OPTIMIZE_THREADS: int | None = Field(
|
|
76
|
+
default=None, description="索引构建线程数,None 自适应"
|
|
77
|
+
)
|
|
78
|
+
ZVEC_MEMORY_LIMIT_MB: int | None = Field(
|
|
79
|
+
default=None, description="内存软上限(MB),None 自适应"
|
|
80
|
+
)
|
|
81
|
+
ZVEC_DEFAULT_METRIC: str = Field(default="cosine", description="默认向量距离度量")
|
|
82
|
+
ZVEC_LOG_DIR: str = Field(
|
|
83
|
+
default="./logs/zvec", description="zvec 引擎独立日志目录"
|
|
84
|
+
)
|
|
85
|
+
ZVEC_LOG_LEVEL: str = Field(
|
|
86
|
+
default="warn", description="zvec 引擎日志级别(debug/info/warn/error/fatal)"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
FILE_STORAGE_ROOT: str = Field(default="./data")
|
|
90
|
+
|
|
91
|
+
# AI 模型档案配置文件路径(chat/embedding/rerank/image 四类型多厂商档案)
|
|
92
|
+
AI_MODELS_CONFIG_PATH: str = Field(
|
|
93
|
+
default="./config/ai_models.json", description="AI 模型档案配置文件路径"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# AI 候选链优先级(可选,逗号分隔档案名):设置后覆盖 JSON default 顺序;
|
|
97
|
+
# JSON 中不存在的档案名会被丢弃并告警
|
|
98
|
+
AI_CHAT_PROFILES: str | None = Field(
|
|
99
|
+
default=None, description="chat 类型候选链优先级(逗号分隔档案名)"
|
|
100
|
+
)
|
|
101
|
+
AI_EMBEDDING_PROFILES: str | None = Field(
|
|
102
|
+
default=None, description="embedding 类型候选链优先级(逗号分隔档案名)"
|
|
103
|
+
)
|
|
104
|
+
AI_RERANK_PROFILES: str | None = Field(
|
|
105
|
+
default=None, description="rerank 类型候选链优先级(逗号分隔档案名)"
|
|
106
|
+
)
|
|
107
|
+
AI_IMAGE_PROFILES: str | None = Field(
|
|
108
|
+
default=None, description="image 类型候选链优先级(逗号分隔档案名)"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# MCP 客户端配置(通用 MCP 服务器档案,schema 见 config/mcp_servers.json)
|
|
112
|
+
MCP_SERVERS_CONFIG_PATH: str = Field(
|
|
113
|
+
default="./config/mcp_servers.json", description="MCP 服务器档案文件路径"
|
|
114
|
+
)
|
|
115
|
+
MCP_CALL_TIMEOUT: int = Field(
|
|
116
|
+
default=60,
|
|
117
|
+
ge=1,
|
|
118
|
+
le=600,
|
|
119
|
+
description="MCP 调用默认超时(秒),档案未指定 timeout 时使用",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# MinerU 精准解析 API 配置(https://mineru.net/apiManage/docs)
|
|
123
|
+
MINERU_API_KEY: str | None = Field(
|
|
124
|
+
default=None, description="MinerU API Key(API 管理页面自建)"
|
|
125
|
+
)
|
|
126
|
+
MINERU_BASE_API_URL: str = Field(
|
|
127
|
+
default="https://mineru.net", description="MinerU API 基础地址"
|
|
128
|
+
)
|
|
129
|
+
MINERU_TIMEOUT: int = Field(
|
|
130
|
+
default=60, ge=1, le=600, description="单次 HTTP 请求超时(秒)"
|
|
131
|
+
)
|
|
132
|
+
MINERU_POLL_INTERVAL: float = Field(
|
|
133
|
+
default=2.0, ge=0.5, description="任务轮询间隔(秒)"
|
|
134
|
+
)
|
|
135
|
+
MINERU_MAX_WAIT: int = Field(
|
|
136
|
+
default=600, ge=10, description="任务轮询总超时(秒),超时抛 MinerUError"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
class Config:
|
|
140
|
+
env_file = ".env"
|
|
141
|
+
env_file_encoding = "utf-8"
|
|
142
|
+
extra = "ignore"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@lru_cache()
|
|
146
|
+
def get_settings() -> Settings:
|
|
147
|
+
return Settings()
|
kit/config/log_config.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2021-2026 Clark Chang. All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
|
16
|
+
Project: sdpy
|
|
17
|
+
Author: Clark Chang
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# date: 2025-04-19
|
|
21
|
+
# source: 迁移自 config/logger_config.py(kit 自包含日志,修改须与源文件评估双向同步)
|
|
22
|
+
"""Kit 统一日志配置模块"""
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
from logging.handlers import RotatingFileHandler
|
|
28
|
+
|
|
29
|
+
from kit.config.kit_config import get_settings
|
|
30
|
+
|
|
31
|
+
settings = get_settings()
|
|
32
|
+
|
|
33
|
+
DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
34
|
+
DEFAULT_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
35
|
+
|
|
36
|
+
LOG_LEVEL_MAPPING = {
|
|
37
|
+
"DEBUG": logging.DEBUG,
|
|
38
|
+
"INFO": logging.INFO,
|
|
39
|
+
"WARNING": logging.WARNING,
|
|
40
|
+
"WARN": logging.WARNING,
|
|
41
|
+
"ERROR": logging.ERROR,
|
|
42
|
+
"CRITICAL": logging.CRITICAL,
|
|
43
|
+
"FATAL": logging.CRITICAL,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def ensure_log_dir(log_dir: str) -> None:
|
|
48
|
+
"""确保日志目录存在"""
|
|
49
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def setup_logger(
|
|
53
|
+
name: str | None = None,
|
|
54
|
+
log_level: str | None = None,
|
|
55
|
+
log_file: str | None = None,
|
|
56
|
+
log_format: str = DEFAULT_LOG_FORMAT,
|
|
57
|
+
date_format: str = DEFAULT_DATE_FORMAT,
|
|
58
|
+
max_bytes: int = 5 * 1024 * 1024,
|
|
59
|
+
backup_count: int = 5,
|
|
60
|
+
) -> logging.Logger:
|
|
61
|
+
"""设置全局统一的日志配置"""
|
|
62
|
+
logger = logging.getLogger(name)
|
|
63
|
+
|
|
64
|
+
level_str = log_level or settings.LOG_LEVEL
|
|
65
|
+
logger.setLevel(LOG_LEVEL_MAPPING.get(level_str.upper(), logging.INFO))
|
|
66
|
+
|
|
67
|
+
if logger.handlers:
|
|
68
|
+
return logger
|
|
69
|
+
|
|
70
|
+
formatter = logging.Formatter(log_format, datefmt=date_format)
|
|
71
|
+
|
|
72
|
+
if settings.LOG_TO_CONSOLE:
|
|
73
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
74
|
+
console_handler.setFormatter(formatter)
|
|
75
|
+
logger.addHandler(console_handler)
|
|
76
|
+
|
|
77
|
+
if settings.LOG_TO_FILE:
|
|
78
|
+
ensure_log_dir(settings.LOG_DIR)
|
|
79
|
+
log_file_path = log_file or os.path.join(settings.LOG_DIR, settings.LOG_FILE)
|
|
80
|
+
file_handler = RotatingFileHandler(
|
|
81
|
+
log_file_path,
|
|
82
|
+
maxBytes=max_bytes,
|
|
83
|
+
backupCount=backup_count,
|
|
84
|
+
encoding="utf-8",
|
|
85
|
+
)
|
|
86
|
+
file_handler.setFormatter(formatter)
|
|
87
|
+
logger.addHandler(file_handler)
|
|
88
|
+
|
|
89
|
+
return logger
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_default_logger(
|
|
93
|
+
name: str | None = None,
|
|
94
|
+
log_level: str | None = None,
|
|
95
|
+
log_file: str | None = None,
|
|
96
|
+
) -> logging.Logger:
|
|
97
|
+
"""获取默认配置的日志器"""
|
|
98
|
+
return setup_logger(name, log_level, log_file)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def debug(logger: logging.Logger, msg: str, *args, **kwargs) -> None:
|
|
102
|
+
"""记录调试信息"""
|
|
103
|
+
logger.debug(msg, *args, **kwargs)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def info(logger: logging.Logger, msg: str, *args, **kwargs) -> None:
|
|
107
|
+
"""记录一般信息"""
|
|
108
|
+
logger.info(msg, *args, **kwargs)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def warning(logger: logging.Logger, msg: str, *args, **kwargs) -> None:
|
|
112
|
+
"""记录警告信息"""
|
|
113
|
+
logger.warning(msg, *args, **kwargs)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def error(logger: logging.Logger, msg: str, *args, **kwargs) -> None:
|
|
117
|
+
"""记录错误信息"""
|
|
118
|
+
logger.error(msg, *args, **kwargs)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def critical(logger: logging.Logger, msg: str, *args, **kwargs) -> None:
|
|
122
|
+
"""记录严重错误信息"""
|
|
123
|
+
logger.critical(msg, *args, **kwargs)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
__all__ = [
|
|
127
|
+
"setup_logger",
|
|
128
|
+
"get_default_logger",
|
|
129
|
+
"debug",
|
|
130
|
+
"info",
|
|
131
|
+
"warning",
|
|
132
|
+
"error",
|
|
133
|
+
"critical",
|
|
134
|
+
]
|
kit/db/__init__.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2021-2026 Clark Chang. All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
|
16
|
+
Project: sdpy
|
|
17
|
+
Author: Clark Chang
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# date: 2026-08-23
|
|
21
|
+
"""kit.db — 基于 SQLModel 的统一数据库访问层
|
|
22
|
+
|
|
23
|
+
支持 PostgreSQL、MySQL、SQLite 三种数据库,提供:
|
|
24
|
+
- 线程安全的引擎管理(engine.py)
|
|
25
|
+
- 统一会话入口 session_scope(session.py)
|
|
26
|
+
- 不提交事务的泛型 CRUDBase(crud.py)
|
|
27
|
+
- @transactional 装饰器(transaction.py)
|
|
28
|
+
- 统一异常体系 DBError(exceptions.py)
|
|
29
|
+
- SQLModel 混入类(models/base.py)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from kit.db.crud import CRUDBase
|
|
33
|
+
from kit.db.engine import (
|
|
34
|
+
MYSQL,
|
|
35
|
+
POSTGRESQL,
|
|
36
|
+
SQLITE,
|
|
37
|
+
close_all_engines,
|
|
38
|
+
close_engine,
|
|
39
|
+
create_tables,
|
|
40
|
+
get_engine,
|
|
41
|
+
list_tables,
|
|
42
|
+
table_exists,
|
|
43
|
+
)
|
|
44
|
+
from kit.db.exceptions import DBError, NotFoundError
|
|
45
|
+
from kit.db.session import get_session, session_scope
|
|
46
|
+
from kit.db.transaction import transactional
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
# 引擎管理
|
|
50
|
+
"get_engine",
|
|
51
|
+
"create_tables",
|
|
52
|
+
"list_tables",
|
|
53
|
+
"table_exists",
|
|
54
|
+
"close_all_engines",
|
|
55
|
+
"close_engine",
|
|
56
|
+
# 常量
|
|
57
|
+
"POSTGRESQL",
|
|
58
|
+
"MYSQL",
|
|
59
|
+
"SQLITE",
|
|
60
|
+
# 会话
|
|
61
|
+
"session_scope",
|
|
62
|
+
"get_session",
|
|
63
|
+
# 异常
|
|
64
|
+
"DBError",
|
|
65
|
+
"NotFoundError",
|
|
66
|
+
# CRUD
|
|
67
|
+
"CRUDBase",
|
|
68
|
+
# 事务
|
|
69
|
+
"transactional",
|
|
70
|
+
]
|