sdpy-kit 0.1.0__tar.gz → 0.1.2__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.
Files changed (42) hide show
  1. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/PKG-INFO +8 -1
  2. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/__init__.py +68 -68
  3. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/config/kit_config.py +0 -6
  4. sdpy_kit-0.1.2/kit/db/query.py +338 -0
  5. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/db/sqlite_kit.py +39 -1
  6. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/pyproject.toml +10 -0
  7. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/.gitignore +0 -0
  8. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/README.md +0 -0
  9. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/__init__.py +0 -0
  10. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/base_service.py +0 -0
  11. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/chat_service.py +0 -0
  12. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/embedding_service.py +0 -0
  13. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/exceptions.py +0 -0
  14. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/failover.py +0 -0
  15. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/image_service.py +0 -0
  16. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/model_builder.py +0 -0
  17. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/model_client.py +0 -0
  18. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/pool.py +0 -0
  19. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/profiles.py +0 -0
  20. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/rerank_service.py +0 -0
  21. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/runner.py +0 -0
  22. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/ai/types.py +0 -0
  23. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/common_kit.py +0 -0
  24. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/config/__init__.py +0 -0
  25. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/config/log_config.py +0 -0
  26. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/db/__init__.py +0 -0
  27. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/db/base_db_kit.py +0 -0
  28. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/db/crud.py +0 -0
  29. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/db/engine.py +0 -0
  30. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/db/exceptions.py +0 -0
  31. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/db/models/__init__.py +0 -0
  32. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/db/models/base.py +0 -0
  33. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/db/mysql_kit.py +0 -0
  34. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/db/pg_kit.py +0 -0
  35. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/db/session.py +0 -0
  36. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/db/transaction.py +0 -0
  37. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/doc_kit.py +0 -0
  38. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/file_kit.py +0 -0
  39. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/mcp_kit.py +0 -0
  40. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/mineru_kit.py +0 -0
  41. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/redis_kit.py +0 -0
  42. {sdpy_kit-0.1.0 → sdpy_kit-0.1.2}/kit/zvec_kit.py +0 -0
@@ -1,20 +1,27 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sdpy-kit
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/MCP/Redis/文件/文档/向量工具集
5
5
  Author-email: Clark <changhongyuan@126.com>
6
6
  License-Expression: MIT
7
+ Keywords: ai,kit,mcp,mysql,postgresql,redis,sdpy,sqlalchemy,sqlite,sqlmodel,向量检索,工具集,开发工具,数据库
7
8
  Classifier: Development Status :: 4 - Beta
8
9
  Classifier: Intended Audience :: Developers
9
10
  Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Natural Language :: Chinese (Simplified)
12
+ Classifier: Operating System :: OS Independent
10
13
  Classifier: Programming Language :: Python :: 3 :: Only
11
14
  Classifier: Programming Language :: Python :: 3.13
12
15
  Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Database
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
13
19
  Requires-Python: >=3.13
14
20
  Requires-Dist: fastmcp>=4.0.3
15
21
  Requires-Dist: firecrawl-anydoc>=0.2.4
16
22
  Requires-Dist: httpx>=0.28.1
17
23
  Requires-Dist: markdown-it-py>=3.0
24
+ Requires-Dist: numpy>=2.0
18
25
  Requires-Dist: pimd[pdfa]>=2.2.5
19
26
  Requires-Dist: psycopg2-binary>=2.9.12
20
27
  Requires-Dist: pydantic-settings>=2.0
@@ -1,68 +1,68 @@
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 模块——统一数据库访问层 + 工具集
22
-
23
- 数据库访问通过 kit.db 子包提供(基于 SQLModel)。
24
- """
25
-
26
- __version__ = "0.1.0"
27
-
28
- from kit.db import (
29
- MYSQL,
30
- POSTGRESQL,
31
- SQLITE,
32
- CRUDBase,
33
- DBError,
34
- NotFoundError,
35
- close_all_engines,
36
- close_engine,
37
- create_tables,
38
- get_engine,
39
- get_session,
40
- list_tables,
41
- session_scope,
42
- table_exists,
43
- transactional,
44
- )
45
-
46
- __all__ = [
47
- # 引擎管理
48
- "get_engine",
49
- "create_tables",
50
- "list_tables",
51
- "table_exists",
52
- "close_all_engines",
53
- "close_engine",
54
- # 常量
55
- "POSTGRESQL",
56
- "MYSQL",
57
- "SQLITE",
58
- # 会话
59
- "session_scope",
60
- "get_session",
61
- # 异常
62
- "DBError",
63
- "NotFoundError",
64
- # CRUD
65
- "CRUDBase",
66
- # 事务
67
- "transactional",
68
- ]
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 模块——统一数据库访问层 + 工具集
22
+
23
+ 数据库访问通过 kit.db 子包提供(基于 SQLModel)。
24
+ """
25
+
26
+ __version__ = "0.1.2"
27
+
28
+ from kit.db import (
29
+ MYSQL,
30
+ POSTGRESQL,
31
+ SQLITE,
32
+ CRUDBase,
33
+ DBError,
34
+ NotFoundError,
35
+ close_all_engines,
36
+ close_engine,
37
+ create_tables,
38
+ get_engine,
39
+ get_session,
40
+ list_tables,
41
+ session_scope,
42
+ table_exists,
43
+ transactional,
44
+ )
45
+
46
+ __all__ = [
47
+ # 引擎管理
48
+ "get_engine",
49
+ "create_tables",
50
+ "list_tables",
51
+ "table_exists",
52
+ "close_all_engines",
53
+ "close_engine",
54
+ # 常量
55
+ "POSTGRESQL",
56
+ "MYSQL",
57
+ "SQLITE",
58
+ # 会话
59
+ "session_scope",
60
+ "get_session",
61
+ # 异常
62
+ "DBError",
63
+ "NotFoundError",
64
+ # CRUD
65
+ "CRUDBase",
66
+ # 事务
67
+ "transactional",
68
+ ]
@@ -28,12 +28,6 @@ from pydantic_settings import BaseSettings
28
28
 
29
29
 
30
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
31
  LOG_LEVEL: str = Field(default="INFO")
38
32
  LOG_TO_CONSOLE: bool = Field(default=True)
39
33
  LOG_TO_FILE: bool = Field(default=False)
@@ -0,0 +1,338 @@
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
+ """查询构建器(不可变)+ 线程安全查询缓存
22
+
23
+ QueryBuilder 采用不可变设计:每个链式方法返回新实例,避免状态副作用。
24
+ count() 从原始模型构建独立计数查询,仅复制 WHERE 条件,不受 limit/offset 影响。
25
+ """
26
+
27
+ import functools
28
+ import threading
29
+ import time
30
+ from collections.abc import Callable
31
+ from datetime import UTC, datetime, timedelta
32
+ from typing import Any, TypeVar
33
+
34
+ from sqlalchemy import func
35
+ from sqlalchemy.orm import load_only
36
+ from sqlmodel import Session, SQLModel, select
37
+
38
+ from kit.config.log_config import get_default_logger
39
+
40
+ logger = get_default_logger(__name__)
41
+
42
+ T = TypeVar("T", bound=SQLModel)
43
+
44
+
45
+ class QueryBuilder:
46
+ """不可变链式查询构建器
47
+
48
+ 每个链式方法返回新的 QueryBuilder 实例,避免修改原始 builder 的状态。
49
+ 这允许同一个 builder 安全复用于不同查询。
50
+
51
+ 用法::
52
+
53
+ qb = QueryBuilder(User, session)
54
+ active = qb.filter(User.is_active == True).all()
55
+ # qb 的状态未被修改,可继续复用
56
+ young = qb.filter(User.age < 30).all()
57
+ """
58
+
59
+ def __init__(self, model: type[T], session: Session):
60
+ self._model = model
61
+ self._session = session
62
+ self._statement = select(model)
63
+
64
+ def _clone(self) -> "QueryBuilder[T]":
65
+ """创建当前 builder 的浅拷贝(SQLAlchemy Select 不可变,直接引用即可)"""
66
+ new = QueryBuilder.__new__(QueryBuilder)
67
+ new._model = self._model
68
+ new._session = self._session
69
+ new._statement = self._statement
70
+ return new
71
+
72
+ def filter(self, *conditions) -> "QueryBuilder[T]":
73
+ """添加过滤条件"""
74
+ builder = self._clone()
75
+ if conditions:
76
+ builder._statement = builder._statement.where(*conditions)
77
+ return builder
78
+
79
+ def filter_by(self, **kwargs) -> "QueryBuilder[T]":
80
+ """按字段值过滤(等值条件)"""
81
+ conditions = [
82
+ getattr(self._model, key) == value for key, value in kwargs.items()
83
+ ]
84
+ return self.filter(*conditions)
85
+
86
+ def join(self, target, onclause=None, isouter: bool = False) -> "QueryBuilder[T]":
87
+ """添加 JOIN"""
88
+ builder = self._clone()
89
+ builder._statement = (
90
+ builder._statement.join(target, onclause, isouter=isouter)
91
+ if onclause
92
+ else builder._statement.join(target, isouter=isouter)
93
+ )
94
+ return builder
95
+
96
+ def order_by(self, *columns) -> "QueryBuilder[T]":
97
+ """排序"""
98
+ builder = self._clone()
99
+ builder._statement = builder._statement.order_by(*columns)
100
+ return builder
101
+
102
+ def limit(self, limit: int) -> "QueryBuilder[T]":
103
+ """限制返回行数"""
104
+ builder = self._clone()
105
+ builder._statement = builder._statement.limit(limit)
106
+ return builder
107
+
108
+ def offset(self, offset: int) -> "QueryBuilder[T]":
109
+ """跳过前 N 行"""
110
+ builder = self._clone()
111
+ builder._statement = builder._statement.offset(offset)
112
+ return builder
113
+
114
+ def distinct(self) -> "QueryBuilder[T]":
115
+ """去重"""
116
+ builder = self._clone()
117
+ builder._statement = builder._statement.distinct()
118
+ return builder
119
+
120
+ def only(self, *field_names: str) -> "QueryBuilder[T]":
121
+ """只加载指定字段"""
122
+ builder = self._clone()
123
+ columns = [getattr(self._model, name) for name in field_names]
124
+ builder._statement = builder._statement.options(load_only(*columns))
125
+ return builder
126
+
127
+ # ==================== 执行方法 ====================
128
+
129
+ def all(self) -> list[T]:
130
+ """获取所有记录"""
131
+ return list(self._session.exec(self._statement).all())
132
+
133
+ def first(self) -> T | None:
134
+ """获取第一条记录"""
135
+ return self._session.exec(self._statement.limit(1)).first()
136
+
137
+ def count(self) -> int:
138
+ """获取记录数(不受 limit/offset 影响)"""
139
+ # 从原始模型构建计数查询,仅保留 WHERE 条件,忽略 limit/offset
140
+ count_stmt = select(func.count()).select_from(self._model)
141
+ whereclause = self._statement.whereclause
142
+ if whereclause is not None:
143
+ count_stmt = count_stmt.where(whereclause)
144
+ return self._session.exec(count_stmt).one()
145
+
146
+ def exists(self) -> bool:
147
+ """检查是否存在符合条件的记录"""
148
+ from sqlalchemy import exists as sa_exists
149
+
150
+ exists_stmt = select(sa_exists().where(self._statement.whereclause))
151
+ return bool(self._session.exec(exists_stmt).one())
152
+
153
+ def paginate(self, page: int = 1, per_page: int = 20) -> dict[str, Any]:
154
+ """分页查询
155
+
156
+ Args:
157
+ page: 页码(从 1 开始)
158
+ per_page: 每页条数
159
+
160
+ Returns:
161
+ 包含 items/total/page/per_page/pages/has_prev/has_next 的字典
162
+ """
163
+ offset = (page - 1) * per_page
164
+ total = self.count()
165
+
166
+ paginated = self._statement.offset(offset).limit(per_page)
167
+ items = list(self._session.exec(paginated).all())
168
+
169
+ return {
170
+ "items": items,
171
+ "total": total,
172
+ "page": page,
173
+ "per_page": per_page,
174
+ "pages": (total + per_page - 1) // per_page if per_page > 0 else 0,
175
+ "has_prev": page > 1,
176
+ "has_next": page * per_page < total,
177
+ }
178
+
179
+
180
+ class QueryCache:
181
+ """线程安全的查询缓存(TTL + LRU 淘汰)"""
182
+
183
+ def __init__(self, default_ttl: int = 300, maxsize: int = 1000):
184
+ self._cache: dict[str, dict[str, Any]] = {}
185
+ self._default_ttl = default_ttl
186
+ self._maxsize = maxsize
187
+ self._lock = threading.RLock()
188
+
189
+ def get(self, key: str) -> Any | None:
190
+ """获取缓存值"""
191
+ with self._lock:
192
+ if key not in self._cache:
193
+ return None
194
+ item = self._cache[key]
195
+ if item["expires_at"] > datetime.now(UTC):
196
+ return item["value"]
197
+ del self._cache[key]
198
+ return None
199
+
200
+ def set(self, key: str, value: Any, ttl: int | None = None) -> None:
201
+ """设置缓存值"""
202
+ with self._lock:
203
+ # 达到上限时清理最早的条目
204
+ if len(self._cache) >= self._maxsize:
205
+ oldest = min(self._cache, key=lambda k: self._cache[k]["expires_at"])
206
+ del self._cache[oldest]
207
+ self._cache[key] = {
208
+ "value": value,
209
+ "expires_at": datetime.now(UTC)
210
+ + timedelta(seconds=ttl or self._default_ttl),
211
+ }
212
+
213
+ def delete(self, key: str) -> None:
214
+ """删除缓存"""
215
+ with self._lock:
216
+ self._cache.pop(key, None)
217
+
218
+ def clear(self) -> None:
219
+ """清空缓存"""
220
+ with self._lock:
221
+ self._cache.clear()
222
+
223
+ def clean_expired(self) -> int:
224
+ """清理过期缓存,返回清理的条目数"""
225
+ with self._lock:
226
+ now = datetime.now(UTC)
227
+ expired = [k for k, v in self._cache.items() if v["expires_at"] <= now]
228
+ for k in expired:
229
+ del self._cache[k]
230
+ return len(expired)
231
+
232
+
233
+ class QueryOptimizer:
234
+ """查询优化器:分析执行计划、建议索引、监控性能"""
235
+
236
+ def __init__(self, session: Session):
237
+ self.session = session
238
+ self._stats: list[dict[str, Any]] = []
239
+
240
+ def explain(self, statement) -> dict[str, Any]:
241
+ """分析查询执行计划"""
242
+ from sqlalchemy import text
243
+
244
+ dialect = self.session.get_bind().dialect.name
245
+ compiled = statement.compile(self.session.get_bind())
246
+
247
+ try:
248
+ if dialect == "postgresql":
249
+ result = self.session.execute(text(f"EXPLAIN (FORMAT JSON) {compiled}"))
250
+ return {"dialect": dialect, "plan": result.scalar()}
251
+ elif dialect == "mysql":
252
+ result = self.session.execute(text(f"EXPLAIN FORMAT=JSON {compiled}"))
253
+ return {"dialect": dialect, "plan": result.scalar()}
254
+ elif dialect == "sqlite":
255
+ result = self.session.execute(text(f"EXPLAIN QUERY PLAN {compiled}"))
256
+ return {
257
+ "dialect": dialect,
258
+ "plan": [dict(row._mapping) for row in result.fetchall()],
259
+ }
260
+ else:
261
+ return {"dialect": dialect, "plan": None, "error": "不支持的数据库"}
262
+ except Exception as e:
263
+ logger.error(f"❌ 查询分析失败: {e}")
264
+ return {"error": str(e)}
265
+
266
+ def suggest_indexes(
267
+ self, model: type[SQLModel], query_fields: list[str]
268
+ ) -> list[str]:
269
+ """建议索引"""
270
+ table_name = model.__tablename__
271
+ suggestions = [
272
+ f"CREATE INDEX idx_{table_name}_{field} ON {table_name}({field});"
273
+ for field in query_fields
274
+ ]
275
+ if len(query_fields) > 1:
276
+ composite = "_".join(query_fields[:3])
277
+ suggestions.append(
278
+ f"CREATE INDEX idx_{table_name}_{composite} "
279
+ f"ON {table_name}({', '.join(query_fields[:3])});"
280
+ )
281
+ return suggestions
282
+
283
+ def measure(self, statement, params: dict | None = None) -> dict[str, Any]:
284
+ """测量查询性能"""
285
+ start = time.time()
286
+ try:
287
+ result = self.session.execute(statement, params or {})
288
+ rows = result.fetchall()
289
+ elapsed = time.time() - start
290
+
291
+ stats: dict[str, Any] = {
292
+ "execution_time": elapsed,
293
+ "row_count": len(rows),
294
+ "timestamp": datetime.now(UTC).isoformat(),
295
+ }
296
+ if elapsed > 1.0:
297
+ stats["warning"] = "慢查询"
298
+ logger.warning(f"⚠️ 慢查询: {elapsed:.2f}s, 返回 {len(rows)} 行")
299
+ self._stats.append(stats)
300
+ return stats
301
+ except Exception as e:
302
+ logger.error(f"❌ 性能测量失败: {e}")
303
+ return {"error": str(e)}
304
+
305
+ @property
306
+ def stats(self) -> list[dict[str, Any]]:
307
+ """获取查询统计信息"""
308
+ return list(self._stats)
309
+
310
+ def clear_stats(self) -> None:
311
+ """清空统计信息"""
312
+ self._stats.clear()
313
+
314
+
315
+ def cached_query(ttl: int = 300, maxsize: int = 1000) -> Callable:
316
+ """查询缓存装饰器
317
+
318
+ 注意:被装饰的函数不应将 Session 作为参数,否则每次缓存键不同。
319
+ """
320
+ cache = QueryCache(default_ttl=ttl, maxsize=maxsize)
321
+
322
+ def decorator(func: Callable) -> Callable:
323
+ @functools.wraps(func)
324
+ def wrapper(*args, **kwargs):
325
+ cache_key = f"{func.__name__}:{repr(args)}:{repr(kwargs)}"
326
+ cached = cache.get(cache_key)
327
+ if cached is not None:
328
+ logger.debug(f"✅ 缓存命中: {func.__name__}")
329
+ return cached
330
+ result = func(*args, **kwargs)
331
+ cache.set(cache_key, result, ttl)
332
+ return result
333
+
334
+ # 暴露缓存实例供外部管理
335
+ wrapper._cache = cache # type: ignore
336
+ return wrapper
337
+
338
+ return decorator
@@ -84,7 +84,9 @@ class SQLiteKit(BaseDbKit):
84
84
  columns: 列定义字典,如 {"id": "TEXT PRIMARY KEY", "name": "TEXT NOT NULL"}
85
85
  if_not_exists: 是否附加 IF NOT EXISTS(默认 True)
86
86
  """
87
- column_defs = ", ".join(f"{name} {definition}" for name, definition in columns.items())
87
+ column_defs = ", ".join(
88
+ f"{name} {definition}" for name, definition in columns.items()
89
+ )
88
90
  if_not_exists_sql = "IF NOT EXISTS " if if_not_exists else ""
89
91
  sql = f"CREATE TABLE {if_not_exists_sql}{table_name} ({column_defs})"
90
92
  with get_engine(cls.DB_TYPE).connect() as conn:
@@ -92,6 +94,42 @@ class SQLiteKit(BaseDbKit):
92
94
  conn.commit()
93
95
  logger.info(f"✅ 创建表 {table_name}")
94
96
 
97
+ @classmethod
98
+ def raw_insert(cls, table_name: str, data: dict[str, object]) -> int:
99
+ """向指定表插入一行数据(键为列名)
100
+
101
+ Args:
102
+ table_name: 表名
103
+ data: 列名 → 值 字典
104
+
105
+ Returns:
106
+ 受影响的行数
107
+ """
108
+ columns = ", ".join(data.keys())
109
+ placeholders = ", ".join(f":{name}" for name in data)
110
+ sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
111
+ return cls.execute(sql, dict(data))
112
+
113
+ @classmethod
114
+ def raw_get_by(
115
+ cls, table_name: str, column: str, value: object
116
+ ) -> dict[str, object] | None:
117
+ """按单列等值查询一行
118
+
119
+ Args:
120
+ table_name: 表名
121
+ column: 列名
122
+ value: 查询值
123
+
124
+ Returns:
125
+ 匹配的第一行(字典);无匹配返回 None
126
+ """
127
+ rows = cls.query(
128
+ f"SELECT * FROM {table_name} WHERE {column} = :value LIMIT 1",
129
+ {"value": value},
130
+ )
131
+ return rows[0] if rows else None
132
+
95
133
  @classmethod
96
134
  def get_table_info(cls, table_name: str) -> list[dict[str, object]]:
97
135
  """获取表的列信息
@@ -11,17 +11,27 @@ authors = [
11
11
  { name = "Clark", email = "changhongyuan@126.com" }
12
12
  ]
13
13
  requires-python = ">=3.13"
14
+ keywords = [
15
+ "sdpy", "kit", "工具集", "数据库", "postgresql", "mysql", "sqlite",
16
+ "sqlmodel", "sqlalchemy", "ai", "mcp", "redis", "向量检索", "开发工具",
17
+ ]
14
18
  classifiers = [
15
19
  "Development Status :: 4 - Beta",
16
20
  "Intended Audience :: Developers",
17
21
  "License :: OSI Approved :: MIT License",
22
+ "Natural Language :: Chinese (Simplified)",
23
+ "Operating System :: OS Independent",
18
24
  "Programming Language :: Python :: 3 :: Only",
19
25
  "Programming Language :: Python :: 3.13",
20
26
  "Programming Language :: Python :: 3.14",
27
+ "Topic :: Database",
28
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
29
+ "Topic :: Software Development :: Libraries :: Python Modules",
21
30
  ]
22
31
  dependencies = [
23
32
  "pydantic>=2.0",
24
33
  "pydantic-settings>=2.0",
34
+ "numpy>=2.0",
25
35
  "sqlalchemy~=2.0.51",
26
36
  "sqlmodel>=0.0.39",
27
37
  "pymysql>=1.2.0",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes