sdpy-kit 0.2.0__tar.gz → 0.2.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.
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/PKG-INFO +111 -25
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/README.md +110 -24
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/__init__.py +1 -1
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/db/__init__.py +2 -1
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/db/base_db_kit.py +133 -3
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/db/engine.py +15 -8
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/db/exceptions.py +11 -0
- sdpy_kit-0.2.2/kit/db/mysql_kit.py +338 -0
- sdpy_kit-0.2.2/kit/db/pg_kit.py +412 -0
- sdpy_kit-0.2.2/kit/db/sqlite_kit.py +408 -0
- sdpy_kit-0.2.0/kit/db/mysql_kit.py +0 -89
- sdpy_kit-0.2.0/kit/db/pg_kit.py +0 -87
- sdpy_kit-0.2.0/kit/db/sqlite_kit.py +0 -327
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/.gitignore +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/__init__.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/base_service.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/chat_service.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/embedding_service.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/exceptions.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/failover.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/image_service.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/model_builder.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/model_client.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/pool.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/profiles.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/rerank_service.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/runner.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/ai/types.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/anydoc_kit.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/common_kit.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/config/__init__.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/config/kit_config.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/config/log_config.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/db/crud.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/db/models/__init__.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/db/models/base.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/db/query.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/db/session.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/db/transaction.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/docx_kit.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/file_kit.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/marker_kit.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/mcp_kit.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/md2doc_kit.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/mineru_kit.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/redis_kit.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/wx_article_kit.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/kit/zvec_kit.py +0 -0
- {sdpy_kit-0.2.0 → sdpy_kit-0.2.2}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sdpy-kit
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.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
|
|
@@ -84,7 +84,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
84
84
|
| 模块 | 类 / 入口 | 能力 | 需要 extra |
|
|
85
85
|
|------|-----------|------|-----------|
|
|
86
86
|
| `kit.config` | `get_settings` / `get_default_logger` | 配置单例与统一日志 | 无 |
|
|
87
|
-
| `kit.db` | `SQLiteKit` / `PgKit` / `MySqlKit` / `CRUDBase` | 统一 CRUD、原生 SQL
|
|
87
|
+
| `kit.db` | `SQLiteKit` / `PgKit` / `MySqlKit` / `CRUDBase` | 统一 CRUD、原生 SQL、原生表操作、事务、表管理 | `pg` / `mysql` / `sqliteai` |
|
|
88
88
|
| `kit.ai` | `AI`(同步门面)/ 4 个原生异步 Service | 对话 / 向量化 / 重排序 / 文生图 | `ai` |
|
|
89
89
|
| `kit.mcp_kit` | `McpKit` | MCP 工具发现与调用 | `mcp` |
|
|
90
90
|
| `kit.redis_kit` | `RedisKit` | Redis string / hash / list / set / zset | `redis` |
|
|
@@ -110,8 +110,8 @@ uv add sdpy-kit
|
|
|
110
110
|
|
|
111
111
|
| extra | 能力 | 安装 |
|
|
112
112
|
|-------|------|------|
|
|
113
|
-
| `pg` | PgKit(PostgreSQL
|
|
114
|
-
| `mysql` | MySqlKit(MySQL
|
|
113
|
+
| `pg` | PgKit(PostgreSQL 驱动;向量方法另需服务端 pgvector) | `pip install "sdpy-kit[pg]"` |
|
|
114
|
+
| `mysql` | MySqlKit(MySQL 驱动;向量仅存储,需 MySQL 9.x) | `pip install "sdpy-kit[mysql]"` |
|
|
115
115
|
| `sqliteai` | SQLiteKit 向量方法(numpy / sqliteai-vector,SQLite 向量扩展) | `pip install "sdpy-kit[sqliteai]"` |
|
|
116
116
|
| `zvec` | ZvecKit(zvec,阿里独立向量库) | `pip install "sdpy-kit[zvec]"` |
|
|
117
117
|
| `redis` | RedisKit | `pip install "sdpy-kit[redis]"` |
|
|
@@ -336,7 +336,7 @@ from kit.db import session_scope, transactional, DBError # 推荐
|
|
|
336
336
|
from kit import session_scope, transactional, DBError # 等价
|
|
337
337
|
```
|
|
338
338
|
|
|
339
|
-
### 1. 三个数据库 Kit(API 完全一致)
|
|
339
|
+
### 1. 三个数据库 Kit(23 个共享方法 API 完全一致)
|
|
340
340
|
|
|
341
341
|
| 类 | 导入 | 数据库 | extra |
|
|
342
342
|
|----|------|--------|-------|
|
|
@@ -344,13 +344,14 @@ from kit import session_scope, transactional, DBError # 等价
|
|
|
344
344
|
| `PgKit` | `from kit.db.pg_kit import PgKit` | PostgreSQL | `pg` |
|
|
345
345
|
| `MySqlKit` | `from kit.db.mysql_kit import MySqlKit` | MySQL | `mysql` |
|
|
346
346
|
|
|
347
|
-
**共有方法(
|
|
347
|
+
**共有方法(23 个,全部为 classmethod,内部自动提交)**
|
|
348
348
|
|
|
349
349
|
| 分组 | 方法 | 说明 |
|
|
350
350
|
|------|------|------|
|
|
351
351
|
| 连接 | `connect() -> bool` | 建引擎并验证连通 |
|
|
352
352
|
| 连接 | `close() -> None` | 关闭引擎 |
|
|
353
353
|
| 连接 | `is_connected() -> bool` | 探测当前是否可连 |
|
|
354
|
+
| 连接 | `raw_connection()` | 取原生 DBAPI 连接(PG=psycopg2 / MySQL=pymysql / SQLite=sqlite3);调用方负责 commit 与 close,psycopg2 / pymysql 默认不自动提交 |
|
|
354
355
|
| ORM | `create(model, data) -> Model` | 插入单条 |
|
|
355
356
|
| ORM | `create_multi(model, data_list) -> list[Model]` | 批量插入 |
|
|
356
357
|
| ORM | `get(model, id) -> Model \| None` | 按主键查询 |
|
|
@@ -362,6 +363,9 @@ from kit import session_scope, transactional, DBError # 等价
|
|
|
362
363
|
| ORM | `count(model) -> int` | 记录总数 |
|
|
363
364
|
| 原生 SQL | `query(sql, params=None) -> list[dict]` | 原生查询,返回字典列表 |
|
|
364
365
|
| 原生 SQL | `execute(sql, params=None) -> int` | 原生写入,返回影响行数 |
|
|
366
|
+
| 原生表 | `create_raw_table(table_name, columns, if_not_exists=True) -> None` | 按「列名 → 列定义」建普通表;⚠ 列定义须按目标库方言书写,不做方言翻译 |
|
|
367
|
+
| 原生表 | `raw_insert(table_name, data) -> int` | 插入一行,返回影响行数 |
|
|
368
|
+
| 原生表 | `raw_get_by(table_name, column, value) -> dict \| None` | 按单列等值取首行 |
|
|
365
369
|
| 事务 | `transaction()` | 事务上下文管理器,`with PgKit.transaction() as session:` |
|
|
366
370
|
| 表 | `list_tables() -> list[str]` | 列出全部表名 |
|
|
367
371
|
| 表 | `table_exists(table_name) -> bool` | 判断表是否存在 |
|
|
@@ -369,11 +373,15 @@ from kit import session_scope, transactional, DBError # 等价
|
|
|
369
373
|
| 表 | `get_table_info(table_name) -> list[dict]` | 获取列信息(三个 Kit 各自实现) |
|
|
370
374
|
|
|
371
375
|
```python
|
|
376
|
+
from sqlalchemy import BigInteger
|
|
372
377
|
from sqlmodel import SQLModel, Field
|
|
378
|
+
from kit.common_kit import CommonKit
|
|
373
379
|
from kit.db.sqlite_kit import SQLiteKit
|
|
374
380
|
|
|
375
381
|
class User(SQLModel, table=True):
|
|
376
|
-
|
|
382
|
+
# 主键:64 位整数 + 雪花 ID(应用侧生成,不用数据库自增)
|
|
383
|
+
id: int = Field(default_factory=CommonKit.generate_snowflake_id,
|
|
384
|
+
primary_key=True, sa_type=BigInteger)
|
|
377
385
|
name: str
|
|
378
386
|
|
|
379
387
|
SQLiteKit.connect()
|
|
@@ -392,33 +400,105 @@ with SQLiteKit.transaction() as session: # 多操作原子提
|
|
|
392
400
|
```
|
|
393
401
|
|
|
394
402
|
> 切换数据库:把 import 的 `SQLiteKit` 换成 `PgKit` / `MySqlKit` 即可,其余代码不变。
|
|
403
|
+
> 注意三点例外:① `create_raw_table` 的列定义文本须按目标库方言改写;② 向量能力
|
|
404
|
+
> **三库同名同参但能力不同**(可由 `VECTOR_CAPABILITIES` feature-detect),尤其
|
|
405
|
+
> **MySQL 侧检索不可用**;③ `create_vector_table` 的 `dims` 为**必填**参数。
|
|
395
406
|
|
|
396
|
-
### 2.
|
|
397
|
-
|
|
398
|
-
无需额外依赖:
|
|
399
|
-
|
|
400
|
-
| 方法 | 说明 |
|
|
401
|
-
|------|------|
|
|
402
|
-
| `raw_connection()` | 取原生 DBAPI 连接(sqlite-vector 扩展已自动加载),调用方负责关闭 |
|
|
403
|
-
| `create_raw_table(table_name, columns, if_not_exists=True) -> None` | 按「列名 → 列定义」建普通表 |
|
|
404
|
-
| `raw_insert(table_name, data) -> int` | 插入一行,返回影响行数 |
|
|
405
|
-
| `raw_get_by(table_name, column, value) -> dict \| None` | 按单列等值取首行 |
|
|
407
|
+
### 2. 向量方法(三库同名同参,能力差异化)
|
|
406
408
|
|
|
407
|
-
|
|
409
|
+
> 签名在 `SQLiteKit` / `PgKit` / `MySqlKit` 上完全一致;能力按后端不同(见下表)。
|
|
410
|
+
> extra 前提:SQLite 需 `sdpy-kit[sqliteai]`;PG 需 `sdpy-kit[pg]` **且服务端已装 pgvector**
|
|
411
|
+
> (`create_vector_table` 会自动尝试 `CREATE EXTENSION`);MySQL 需 **9.x**(Community 即可存,
|
|
412
|
+
> 但检索需 HeatWave on OCI / MySQL AI)——建表前会校验服务端版本,低于 9.x 抛 `DBError`。
|
|
408
413
|
|
|
409
414
|
| 方法 | 说明 |
|
|
410
415
|
|------|------|
|
|
411
|
-
| `create_vector_table(table_name, id_col="id",
|
|
412
|
-
| `insert_vectors(table_name, vectors, ids=None, extra_data=None) -> int` |
|
|
413
|
-
| `search_vectors(table_name, query_vector, top_k=10, distance_type="
|
|
414
|
-
| `vector_version() -> str` |
|
|
416
|
+
| `create_vector_table(table_name, dims, id_col="id", column="embedding", extra_cols=None) -> None` | 建含向量列的表;**`dims` 必填**(SQLite 用 `CHECK` 落实为数据库层约束;PG 硬约束并回读校验已存在表;MySQL 为上限语义) |
|
|
417
|
+
| `insert_vectors(table_name, vectors, ids=None, extra_data=None, id_col="id", column="embedding") -> int` | 批量插入向量(单事务提交);`ids=None` 时由 Kit 生成雪花 ID;接受 `list[list[float]]` 或二维 numpy 数组 |
|
|
418
|
+
| `search_vectors(table_name, query_vector, top_k=10, distance_type="L2", filter_sql=None, exact=True, id_col="id", column="embedding") -> list[dict]` | KNN 检索,按距离升序返回 `{"id", "distance"}`;`exact=True`(默认)禁止近似加速;**MySQL 侧抛 `VectorNotSupportedError`** |
|
|
419
|
+
| `vector_version() -> str` | 向量后端版本;不可用时返回 `"unavailable"`(MySQL 侧为服务端版本号) |
|
|
420
|
+
|
|
421
|
+
**`id_col` / `column`(逃生口)**:让**自建 DDL 的向量表**也能读写/检索。需要复合主键、额外约束、
|
|
422
|
+
分区或与既有业务表合表时,用 `execute()` 自建 DDL 再用这两个参数接入;SQLite 侧要求 `id_col`
|
|
423
|
+
为 rowid 别名(`INTEGER PRIMARY KEY`)。
|
|
424
|
+
|
|
425
|
+
**`exact` 参数(默认 `True` = 禁止近似加速)**:
|
|
426
|
+
|
|
427
|
+
| 后端 | `exact=True`(默认) | `exact=False` | 是否提速 |
|
|
428
|
+
|------|---------------------|---------------|----------|
|
|
429
|
+
| `PgKit` | 同事务 `SET LOCAL enable_indexscan = off`(有索引也精确;**代价:该查询不使用任何索引**,含过滤列索引) | 不干预查询计划(有索引即近似召回,`distance` 仍为真值) | ✅ 仅"大表 + 已建索引"时 |
|
|
430
|
+
| `SQLiteKit` | `vector_full_scan`(**真值 L2**) | `vector_quantize_scan`(量化 L2 = 真值 × 255) | ❌ **不提速**(每次调用重建量化) |
|
|
431
|
+
| `MySqlKit` | 不适用 | 同左 | — |
|
|
432
|
+
|
|
433
|
+
**能力差异(仅 L2 可跨库比较数值;float32 vs float64,需带容差)**:
|
|
434
|
+
|
|
435
|
+
| 项 | `SQLiteKit`(sqlite-vector) | `PgKit`(pgvector) | `MySqlKit`(9.x 原生) |
|
|
436
|
+
|----|------------------------------|---------------------|------------------------|
|
|
437
|
+
| 支持的 `distance_type` | **仅 L2** | `COSINE` / `L2` / `L1` / `DOT`(负内积) / `SQUARED_L2` | **无(检索不可用)** |
|
|
438
|
+
| 默认 `distance_type` | `"L2"` | `"L2"`(三库统一,不按后端覆写) | `"L2"`(形参保留,调用即抛错) |
|
|
439
|
+
| 非法取值 | 抛 `ValueError` | 抛 `ValueError` | `search_vectors` 抛 `VectorNotSupportedError` |
|
|
440
|
+
| `distance` 口径 | `exact=True` 真值 L2 / `exact=False` 量化(×255) | 真实距离 | — |
|
|
441
|
+
| 维度约束 | `CHECK`(仅新表;旧表靠方法内校验) | 强制(不匹配即报错;建表时回读比对) | 上限约束(超限报错) |
|
|
442
|
+
| `filter_sql` 与 `top_k` | **先过滤再取 top_k**(与 PG 一致) | **先过滤再取 top_k** | 不适用 |
|
|
443
|
+
| 索引 | 无(精确路径为全表扫描) | kit 不建索引;**自建后须 `exact=False` 才会被使用** | 无法建(B-Tree 拒绝向量列) |
|
|
444
|
+
|
|
445
|
+
> MySQL `DISTANCE()` / `VECTOR_DISTANCE()` 仅 HeatWave on OCI 与 MySQL AI 提供
|
|
446
|
+
> (Community 实测报 `1305 FUNCTION ... does not exist`);故 `MySqlKit` 只覆盖存储侧。
|
|
447
|
+
|
|
448
|
+
**主键与 id(三库统一)**:向量表主键均为 `BIGINT`(SQLite 用等价的 64 位 `INTEGER PRIMARY KEY`),
|
|
449
|
+
**不使用数据库自增**;`ids=None` 时由 Kit 用 `CommonKit.generate_snowflake_id()` 生成雪花 ID
|
|
450
|
+
(18~19 位,int64)。
|
|
451
|
+
⚠ 雪花 ID 超过 `2^53`,经 JSON 传给 JavaScript 会丢精度,需按字符串传输;
|
|
452
|
+
多实例部署需各自 `CommonKit.configure(datacenter_id, machine_id)`(默认 `(1,1)` 会重复)。
|
|
415
453
|
|
|
416
454
|
```python
|
|
417
|
-
|
|
455
|
+
# SQLite(默认精确:distance 为真值 L2)
|
|
456
|
+
from kit.db.sqlite_kit import SQLiteKit
|
|
457
|
+
SQLiteKit.create_vector_table("images", 384) # dims 必填
|
|
418
458
|
SQLiteKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
|
|
419
459
|
rows = SQLiteKit.search_vectors("images", [0.1] * 384, top_k=5)
|
|
460
|
+
rows = SQLiteKit.search_vectors("images", [0.1] * 384, top_k=5, exact=False) # 旧量化口径(不提速)
|
|
461
|
+
|
|
462
|
+
# PostgreSQL(签名相同;可选 COSINE/L2/L1/DOT/SQUARED_L2)
|
|
463
|
+
from kit.db.pg_kit import PgKit
|
|
464
|
+
PgKit.create_vector_table("images", 384)
|
|
465
|
+
PgKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
|
|
466
|
+
rows = PgKit.search_vectors("images", [0.1] * 384, top_k=5, distance_type="COSINE")
|
|
467
|
+
# 大表 + 自建索引时才用 exact=False(否则索引不被使用,见上表)
|
|
468
|
+
PgKit.execute("CREATE INDEX ON images USING hnsw (embedding vector_cosine_ops)")
|
|
469
|
+
rows = PgKit.search_vectors("images", [0.1] * 384, top_k=5,
|
|
470
|
+
distance_type="COSINE", exact=False)
|
|
471
|
+
|
|
472
|
+
# MySQL(仅存储;search_vectors 抛 VectorNotSupportedError)
|
|
473
|
+
from kit.db.mysql_kit import MySqlKit
|
|
474
|
+
MySqlKit.create_vector_table("images", 384)
|
|
475
|
+
MySqlKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
|
|
476
|
+
|
|
477
|
+
# 能力自省(无需 try/except)
|
|
478
|
+
PgKit.VECTOR_CAPABILITIES
|
|
479
|
+
# {'store': True, 'search': True, 'exact': True,
|
|
480
|
+
# 'distances': ('COSINE', 'L2', 'L1', 'DOT', 'SQUARED_L2'),
|
|
481
|
+
# 'distance_kind': 'true', 'fast_distance_kind': 'true'}
|
|
482
|
+
|
|
483
|
+
# 自建 DDL 的表:用 id_col / column 接入
|
|
484
|
+
PgKit.execute("CREATE TABLE my_vec (vid BIGINT PRIMARY KEY, vec vector(384))")
|
|
485
|
+
PgKit.insert_vectors("my_vec", [[0.1] * 384], ids=[1], id_col="vid", column="vec")
|
|
486
|
+
PgKit.search_vectors("my_vec", [0.1] * 384, top_k=5, id_col="vid", column="vec")
|
|
420
487
|
```
|
|
421
488
|
|
|
489
|
+
#### 0.2.2 行为变更(升级须知)
|
|
490
|
+
|
|
491
|
+
| 编号 | 变更 | 影响 |
|
|
492
|
+
|------|------|------|
|
|
493
|
+
| B1 | `search_vectors` 新增 `exact: bool = True`,默认**精确** | SQLite 返回的 `distance` 由量化值(≈L2×255)变为**真值 L2**;需旧口径请显式 `exact=False` |
|
|
494
|
+
| B2 | `filter_sql` 语义统一为**先过滤、再取 top_k** | 传 `filter_sql` 的调用返回值**变多/变全**(旧结果 ⊆ 新结果);曾自行放大 `top_k` 兜底的调用可收紧 |
|
|
495
|
+
| B3 | 默认 `distance_type` 由 `"COSINE"` 改为 `"L2"` | 依赖默认值的调用方请显式传 `distance_type="COSINE"` |
|
|
496
|
+
| B4 | `create_vector_table` 的 `dims` **必填**,参数顺序为 `(table_name, dims, id_col, column, extra_cols)` | `create_vector_table("t")` 类调用需补维度;位置传 `id_col` 的调用改为关键字 |
|
|
497
|
+
| B5 | MySQL 的 `search_vectors` 异常由 `NotImplementedError` 改为 `VectorNotSupportedError`(继承 `DBError`) | `except NotImplementedError` 需改为 `except VectorNotSupportedError`(或 `except DBError`) |
|
|
498
|
+
|
|
499
|
+
> 新增(非破坏性):`VECTOR_CAPABILITIES` 能力声明、`id_col` / `column` 逃生口参数、
|
|
500
|
+
> `insert_vectors` 支持二维 numpy 数组与批量提交。
|
|
501
|
+
|
|
422
502
|
### 3. CRUDBase(配合 session_scope,不自动提交)
|
|
423
503
|
|
|
424
504
|
```python
|
|
@@ -481,10 +561,16 @@ def transfer(session: Session, from_id: int, to_id: int, amount: float) -> None:
|
|
|
481
561
|
### 5. 模型混入类与查询构建器
|
|
482
562
|
|
|
483
563
|
```python
|
|
564
|
+
from sqlalchemy import BigInteger
|
|
565
|
+
from sqlmodel import Field, SQLModel
|
|
566
|
+
|
|
567
|
+
from kit.common_kit import CommonKit
|
|
484
568
|
from kit.db.models import TimestampMixin, SoftDeleteMixin, AuditMixin
|
|
485
569
|
|
|
486
|
-
class Article(TimestampMixin, SoftDeleteMixin, table=True):
|
|
487
|
-
|
|
570
|
+
class Article(TimestampMixin, SoftDeleteMixin, SQLModel, table=True):
|
|
571
|
+
# 主键:64 位整数 + 雪花 ID(应用侧生成,不用数据库自增)
|
|
572
|
+
id: int = Field(default_factory=CommonKit.generate_snowflake_id,
|
|
573
|
+
primary_key=True, sa_type=BigInteger)
|
|
488
574
|
title: str
|
|
489
575
|
```
|
|
490
576
|
|
|
@@ -14,7 +14,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
14
14
|
| 模块 | 类 / 入口 | 能力 | 需要 extra |
|
|
15
15
|
|------|-----------|------|-----------|
|
|
16
16
|
| `kit.config` | `get_settings` / `get_default_logger` | 配置单例与统一日志 | 无 |
|
|
17
|
-
| `kit.db` | `SQLiteKit` / `PgKit` / `MySqlKit` / `CRUDBase` | 统一 CRUD、原生 SQL
|
|
17
|
+
| `kit.db` | `SQLiteKit` / `PgKit` / `MySqlKit` / `CRUDBase` | 统一 CRUD、原生 SQL、原生表操作、事务、表管理 | `pg` / `mysql` / `sqliteai` |
|
|
18
18
|
| `kit.ai` | `AI`(同步门面)/ 4 个原生异步 Service | 对话 / 向量化 / 重排序 / 文生图 | `ai` |
|
|
19
19
|
| `kit.mcp_kit` | `McpKit` | MCP 工具发现与调用 | `mcp` |
|
|
20
20
|
| `kit.redis_kit` | `RedisKit` | Redis string / hash / list / set / zset | `redis` |
|
|
@@ -40,8 +40,8 @@ uv add sdpy-kit
|
|
|
40
40
|
|
|
41
41
|
| extra | 能力 | 安装 |
|
|
42
42
|
|-------|------|------|
|
|
43
|
-
| `pg` | PgKit(PostgreSQL
|
|
44
|
-
| `mysql` | MySqlKit(MySQL
|
|
43
|
+
| `pg` | PgKit(PostgreSQL 驱动;向量方法另需服务端 pgvector) | `pip install "sdpy-kit[pg]"` |
|
|
44
|
+
| `mysql` | MySqlKit(MySQL 驱动;向量仅存储,需 MySQL 9.x) | `pip install "sdpy-kit[mysql]"` |
|
|
45
45
|
| `sqliteai` | SQLiteKit 向量方法(numpy / sqliteai-vector,SQLite 向量扩展) | `pip install "sdpy-kit[sqliteai]"` |
|
|
46
46
|
| `zvec` | ZvecKit(zvec,阿里独立向量库) | `pip install "sdpy-kit[zvec]"` |
|
|
47
47
|
| `redis` | RedisKit | `pip install "sdpy-kit[redis]"` |
|
|
@@ -266,7 +266,7 @@ from kit.db import session_scope, transactional, DBError # 推荐
|
|
|
266
266
|
from kit import session_scope, transactional, DBError # 等价
|
|
267
267
|
```
|
|
268
268
|
|
|
269
|
-
### 1. 三个数据库 Kit(API 完全一致)
|
|
269
|
+
### 1. 三个数据库 Kit(23 个共享方法 API 完全一致)
|
|
270
270
|
|
|
271
271
|
| 类 | 导入 | 数据库 | extra |
|
|
272
272
|
|----|------|--------|-------|
|
|
@@ -274,13 +274,14 @@ from kit import session_scope, transactional, DBError # 等价
|
|
|
274
274
|
| `PgKit` | `from kit.db.pg_kit import PgKit` | PostgreSQL | `pg` |
|
|
275
275
|
| `MySqlKit` | `from kit.db.mysql_kit import MySqlKit` | MySQL | `mysql` |
|
|
276
276
|
|
|
277
|
-
**共有方法(
|
|
277
|
+
**共有方法(23 个,全部为 classmethod,内部自动提交)**
|
|
278
278
|
|
|
279
279
|
| 分组 | 方法 | 说明 |
|
|
280
280
|
|------|------|------|
|
|
281
281
|
| 连接 | `connect() -> bool` | 建引擎并验证连通 |
|
|
282
282
|
| 连接 | `close() -> None` | 关闭引擎 |
|
|
283
283
|
| 连接 | `is_connected() -> bool` | 探测当前是否可连 |
|
|
284
|
+
| 连接 | `raw_connection()` | 取原生 DBAPI 连接(PG=psycopg2 / MySQL=pymysql / SQLite=sqlite3);调用方负责 commit 与 close,psycopg2 / pymysql 默认不自动提交 |
|
|
284
285
|
| ORM | `create(model, data) -> Model` | 插入单条 |
|
|
285
286
|
| ORM | `create_multi(model, data_list) -> list[Model]` | 批量插入 |
|
|
286
287
|
| ORM | `get(model, id) -> Model \| None` | 按主键查询 |
|
|
@@ -292,6 +293,9 @@ from kit import session_scope, transactional, DBError # 等价
|
|
|
292
293
|
| ORM | `count(model) -> int` | 记录总数 |
|
|
293
294
|
| 原生 SQL | `query(sql, params=None) -> list[dict]` | 原生查询,返回字典列表 |
|
|
294
295
|
| 原生 SQL | `execute(sql, params=None) -> int` | 原生写入,返回影响行数 |
|
|
296
|
+
| 原生表 | `create_raw_table(table_name, columns, if_not_exists=True) -> None` | 按「列名 → 列定义」建普通表;⚠ 列定义须按目标库方言书写,不做方言翻译 |
|
|
297
|
+
| 原生表 | `raw_insert(table_name, data) -> int` | 插入一行,返回影响行数 |
|
|
298
|
+
| 原生表 | `raw_get_by(table_name, column, value) -> dict \| None` | 按单列等值取首行 |
|
|
295
299
|
| 事务 | `transaction()` | 事务上下文管理器,`with PgKit.transaction() as session:` |
|
|
296
300
|
| 表 | `list_tables() -> list[str]` | 列出全部表名 |
|
|
297
301
|
| 表 | `table_exists(table_name) -> bool` | 判断表是否存在 |
|
|
@@ -299,11 +303,15 @@ from kit import session_scope, transactional, DBError # 等价
|
|
|
299
303
|
| 表 | `get_table_info(table_name) -> list[dict]` | 获取列信息(三个 Kit 各自实现) |
|
|
300
304
|
|
|
301
305
|
```python
|
|
306
|
+
from sqlalchemy import BigInteger
|
|
302
307
|
from sqlmodel import SQLModel, Field
|
|
308
|
+
from kit.common_kit import CommonKit
|
|
303
309
|
from kit.db.sqlite_kit import SQLiteKit
|
|
304
310
|
|
|
305
311
|
class User(SQLModel, table=True):
|
|
306
|
-
|
|
312
|
+
# 主键:64 位整数 + 雪花 ID(应用侧生成,不用数据库自增)
|
|
313
|
+
id: int = Field(default_factory=CommonKit.generate_snowflake_id,
|
|
314
|
+
primary_key=True, sa_type=BigInteger)
|
|
307
315
|
name: str
|
|
308
316
|
|
|
309
317
|
SQLiteKit.connect()
|
|
@@ -322,33 +330,105 @@ with SQLiteKit.transaction() as session: # 多操作原子提
|
|
|
322
330
|
```
|
|
323
331
|
|
|
324
332
|
> 切换数据库:把 import 的 `SQLiteKit` 换成 `PgKit` / `MySqlKit` 即可,其余代码不变。
|
|
333
|
+
> 注意三点例外:① `create_raw_table` 的列定义文本须按目标库方言改写;② 向量能力
|
|
334
|
+
> **三库同名同参但能力不同**(可由 `VECTOR_CAPABILITIES` feature-detect),尤其
|
|
335
|
+
> **MySQL 侧检索不可用**;③ `create_vector_table` 的 `dims` 为**必填**参数。
|
|
325
336
|
|
|
326
|
-
### 2.
|
|
327
|
-
|
|
328
|
-
无需额外依赖:
|
|
329
|
-
|
|
330
|
-
| 方法 | 说明 |
|
|
331
|
-
|------|------|
|
|
332
|
-
| `raw_connection()` | 取原生 DBAPI 连接(sqlite-vector 扩展已自动加载),调用方负责关闭 |
|
|
333
|
-
| `create_raw_table(table_name, columns, if_not_exists=True) -> None` | 按「列名 → 列定义」建普通表 |
|
|
334
|
-
| `raw_insert(table_name, data) -> int` | 插入一行,返回影响行数 |
|
|
335
|
-
| `raw_get_by(table_name, column, value) -> dict \| None` | 按单列等值取首行 |
|
|
337
|
+
### 2. 向量方法(三库同名同参,能力差异化)
|
|
336
338
|
|
|
337
|
-
|
|
339
|
+
> 签名在 `SQLiteKit` / `PgKit` / `MySqlKit` 上完全一致;能力按后端不同(见下表)。
|
|
340
|
+
> extra 前提:SQLite 需 `sdpy-kit[sqliteai]`;PG 需 `sdpy-kit[pg]` **且服务端已装 pgvector**
|
|
341
|
+
> (`create_vector_table` 会自动尝试 `CREATE EXTENSION`);MySQL 需 **9.x**(Community 即可存,
|
|
342
|
+
> 但检索需 HeatWave on OCI / MySQL AI)——建表前会校验服务端版本,低于 9.x 抛 `DBError`。
|
|
338
343
|
|
|
339
344
|
| 方法 | 说明 |
|
|
340
345
|
|------|------|
|
|
341
|
-
| `create_vector_table(table_name, id_col="id",
|
|
342
|
-
| `insert_vectors(table_name, vectors, ids=None, extra_data=None) -> int` |
|
|
343
|
-
| `search_vectors(table_name, query_vector, top_k=10, distance_type="
|
|
344
|
-
| `vector_version() -> str` |
|
|
346
|
+
| `create_vector_table(table_name, dims, id_col="id", column="embedding", extra_cols=None) -> None` | 建含向量列的表;**`dims` 必填**(SQLite 用 `CHECK` 落实为数据库层约束;PG 硬约束并回读校验已存在表;MySQL 为上限语义) |
|
|
347
|
+
| `insert_vectors(table_name, vectors, ids=None, extra_data=None, id_col="id", column="embedding") -> int` | 批量插入向量(单事务提交);`ids=None` 时由 Kit 生成雪花 ID;接受 `list[list[float]]` 或二维 numpy 数组 |
|
|
348
|
+
| `search_vectors(table_name, query_vector, top_k=10, distance_type="L2", filter_sql=None, exact=True, id_col="id", column="embedding") -> list[dict]` | KNN 检索,按距离升序返回 `{"id", "distance"}`;`exact=True`(默认)禁止近似加速;**MySQL 侧抛 `VectorNotSupportedError`** |
|
|
349
|
+
| `vector_version() -> str` | 向量后端版本;不可用时返回 `"unavailable"`(MySQL 侧为服务端版本号) |
|
|
350
|
+
|
|
351
|
+
**`id_col` / `column`(逃生口)**:让**自建 DDL 的向量表**也能读写/检索。需要复合主键、额外约束、
|
|
352
|
+
分区或与既有业务表合表时,用 `execute()` 自建 DDL 再用这两个参数接入;SQLite 侧要求 `id_col`
|
|
353
|
+
为 rowid 别名(`INTEGER PRIMARY KEY`)。
|
|
354
|
+
|
|
355
|
+
**`exact` 参数(默认 `True` = 禁止近似加速)**:
|
|
356
|
+
|
|
357
|
+
| 后端 | `exact=True`(默认) | `exact=False` | 是否提速 |
|
|
358
|
+
|------|---------------------|---------------|----------|
|
|
359
|
+
| `PgKit` | 同事务 `SET LOCAL enable_indexscan = off`(有索引也精确;**代价:该查询不使用任何索引**,含过滤列索引) | 不干预查询计划(有索引即近似召回,`distance` 仍为真值) | ✅ 仅"大表 + 已建索引"时 |
|
|
360
|
+
| `SQLiteKit` | `vector_full_scan`(**真值 L2**) | `vector_quantize_scan`(量化 L2 = 真值 × 255) | ❌ **不提速**(每次调用重建量化) |
|
|
361
|
+
| `MySqlKit` | 不适用 | 同左 | — |
|
|
362
|
+
|
|
363
|
+
**能力差异(仅 L2 可跨库比较数值;float32 vs float64,需带容差)**:
|
|
364
|
+
|
|
365
|
+
| 项 | `SQLiteKit`(sqlite-vector) | `PgKit`(pgvector) | `MySqlKit`(9.x 原生) |
|
|
366
|
+
|----|------------------------------|---------------------|------------------------|
|
|
367
|
+
| 支持的 `distance_type` | **仅 L2** | `COSINE` / `L2` / `L1` / `DOT`(负内积) / `SQUARED_L2` | **无(检索不可用)** |
|
|
368
|
+
| 默认 `distance_type` | `"L2"` | `"L2"`(三库统一,不按后端覆写) | `"L2"`(形参保留,调用即抛错) |
|
|
369
|
+
| 非法取值 | 抛 `ValueError` | 抛 `ValueError` | `search_vectors` 抛 `VectorNotSupportedError` |
|
|
370
|
+
| `distance` 口径 | `exact=True` 真值 L2 / `exact=False` 量化(×255) | 真实距离 | — |
|
|
371
|
+
| 维度约束 | `CHECK`(仅新表;旧表靠方法内校验) | 强制(不匹配即报错;建表时回读比对) | 上限约束(超限报错) |
|
|
372
|
+
| `filter_sql` 与 `top_k` | **先过滤再取 top_k**(与 PG 一致) | **先过滤再取 top_k** | 不适用 |
|
|
373
|
+
| 索引 | 无(精确路径为全表扫描) | kit 不建索引;**自建后须 `exact=False` 才会被使用** | 无法建(B-Tree 拒绝向量列) |
|
|
374
|
+
|
|
375
|
+
> MySQL `DISTANCE()` / `VECTOR_DISTANCE()` 仅 HeatWave on OCI 与 MySQL AI 提供
|
|
376
|
+
> (Community 实测报 `1305 FUNCTION ... does not exist`);故 `MySqlKit` 只覆盖存储侧。
|
|
377
|
+
|
|
378
|
+
**主键与 id(三库统一)**:向量表主键均为 `BIGINT`(SQLite 用等价的 64 位 `INTEGER PRIMARY KEY`),
|
|
379
|
+
**不使用数据库自增**;`ids=None` 时由 Kit 用 `CommonKit.generate_snowflake_id()` 生成雪花 ID
|
|
380
|
+
(18~19 位,int64)。
|
|
381
|
+
⚠ 雪花 ID 超过 `2^53`,经 JSON 传给 JavaScript 会丢精度,需按字符串传输;
|
|
382
|
+
多实例部署需各自 `CommonKit.configure(datacenter_id, machine_id)`(默认 `(1,1)` 会重复)。
|
|
345
383
|
|
|
346
384
|
```python
|
|
347
|
-
|
|
385
|
+
# SQLite(默认精确:distance 为真值 L2)
|
|
386
|
+
from kit.db.sqlite_kit import SQLiteKit
|
|
387
|
+
SQLiteKit.create_vector_table("images", 384) # dims 必填
|
|
348
388
|
SQLiteKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
|
|
349
389
|
rows = SQLiteKit.search_vectors("images", [0.1] * 384, top_k=5)
|
|
390
|
+
rows = SQLiteKit.search_vectors("images", [0.1] * 384, top_k=5, exact=False) # 旧量化口径(不提速)
|
|
391
|
+
|
|
392
|
+
# PostgreSQL(签名相同;可选 COSINE/L2/L1/DOT/SQUARED_L2)
|
|
393
|
+
from kit.db.pg_kit import PgKit
|
|
394
|
+
PgKit.create_vector_table("images", 384)
|
|
395
|
+
PgKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
|
|
396
|
+
rows = PgKit.search_vectors("images", [0.1] * 384, top_k=5, distance_type="COSINE")
|
|
397
|
+
# 大表 + 自建索引时才用 exact=False(否则索引不被使用,见上表)
|
|
398
|
+
PgKit.execute("CREATE INDEX ON images USING hnsw (embedding vector_cosine_ops)")
|
|
399
|
+
rows = PgKit.search_vectors("images", [0.1] * 384, top_k=5,
|
|
400
|
+
distance_type="COSINE", exact=False)
|
|
401
|
+
|
|
402
|
+
# MySQL(仅存储;search_vectors 抛 VectorNotSupportedError)
|
|
403
|
+
from kit.db.mysql_kit import MySqlKit
|
|
404
|
+
MySqlKit.create_vector_table("images", 384)
|
|
405
|
+
MySqlKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
|
|
406
|
+
|
|
407
|
+
# 能力自省(无需 try/except)
|
|
408
|
+
PgKit.VECTOR_CAPABILITIES
|
|
409
|
+
# {'store': True, 'search': True, 'exact': True,
|
|
410
|
+
# 'distances': ('COSINE', 'L2', 'L1', 'DOT', 'SQUARED_L2'),
|
|
411
|
+
# 'distance_kind': 'true', 'fast_distance_kind': 'true'}
|
|
412
|
+
|
|
413
|
+
# 自建 DDL 的表:用 id_col / column 接入
|
|
414
|
+
PgKit.execute("CREATE TABLE my_vec (vid BIGINT PRIMARY KEY, vec vector(384))")
|
|
415
|
+
PgKit.insert_vectors("my_vec", [[0.1] * 384], ids=[1], id_col="vid", column="vec")
|
|
416
|
+
PgKit.search_vectors("my_vec", [0.1] * 384, top_k=5, id_col="vid", column="vec")
|
|
350
417
|
```
|
|
351
418
|
|
|
419
|
+
#### 0.2.2 行为变更(升级须知)
|
|
420
|
+
|
|
421
|
+
| 编号 | 变更 | 影响 |
|
|
422
|
+
|------|------|------|
|
|
423
|
+
| B1 | `search_vectors` 新增 `exact: bool = True`,默认**精确** | SQLite 返回的 `distance` 由量化值(≈L2×255)变为**真值 L2**;需旧口径请显式 `exact=False` |
|
|
424
|
+
| B2 | `filter_sql` 语义统一为**先过滤、再取 top_k** | 传 `filter_sql` 的调用返回值**变多/变全**(旧结果 ⊆ 新结果);曾自行放大 `top_k` 兜底的调用可收紧 |
|
|
425
|
+
| B3 | 默认 `distance_type` 由 `"COSINE"` 改为 `"L2"` | 依赖默认值的调用方请显式传 `distance_type="COSINE"` |
|
|
426
|
+
| B4 | `create_vector_table` 的 `dims` **必填**,参数顺序为 `(table_name, dims, id_col, column, extra_cols)` | `create_vector_table("t")` 类调用需补维度;位置传 `id_col` 的调用改为关键字 |
|
|
427
|
+
| B5 | MySQL 的 `search_vectors` 异常由 `NotImplementedError` 改为 `VectorNotSupportedError`(继承 `DBError`) | `except NotImplementedError` 需改为 `except VectorNotSupportedError`(或 `except DBError`) |
|
|
428
|
+
|
|
429
|
+
> 新增(非破坏性):`VECTOR_CAPABILITIES` 能力声明、`id_col` / `column` 逃生口参数、
|
|
430
|
+
> `insert_vectors` 支持二维 numpy 数组与批量提交。
|
|
431
|
+
|
|
352
432
|
### 3. CRUDBase(配合 session_scope,不自动提交)
|
|
353
433
|
|
|
354
434
|
```python
|
|
@@ -411,10 +491,16 @@ def transfer(session: Session, from_id: int, to_id: int, amount: float) -> None:
|
|
|
411
491
|
### 5. 模型混入类与查询构建器
|
|
412
492
|
|
|
413
493
|
```python
|
|
494
|
+
from sqlalchemy import BigInteger
|
|
495
|
+
from sqlmodel import Field, SQLModel
|
|
496
|
+
|
|
497
|
+
from kit.common_kit import CommonKit
|
|
414
498
|
from kit.db.models import TimestampMixin, SoftDeleteMixin, AuditMixin
|
|
415
499
|
|
|
416
|
-
class Article(TimestampMixin, SoftDeleteMixin, table=True):
|
|
417
|
-
|
|
500
|
+
class Article(TimestampMixin, SoftDeleteMixin, SQLModel, table=True):
|
|
501
|
+
# 主键:64 位整数 + 雪花 ID(应用侧生成,不用数据库自增)
|
|
502
|
+
id: int = Field(default_factory=CommonKit.generate_snowflake_id,
|
|
503
|
+
primary_key=True, sa_type=BigInteger)
|
|
418
504
|
title: str
|
|
419
505
|
```
|
|
420
506
|
|
|
@@ -41,7 +41,7 @@ from kit.db.engine import (
|
|
|
41
41
|
list_tables,
|
|
42
42
|
table_exists,
|
|
43
43
|
)
|
|
44
|
-
from kit.db.exceptions import DBError, NotFoundError
|
|
44
|
+
from kit.db.exceptions import DBError, NotFoundError, VectorNotSupportedError
|
|
45
45
|
from kit.db.session import get_session, session_scope
|
|
46
46
|
from kit.db.transaction import transactional
|
|
47
47
|
|
|
@@ -63,6 +63,7 @@ __all__ = [
|
|
|
63
63
|
# 异常
|
|
64
64
|
"DBError",
|
|
65
65
|
"NotFoundError",
|
|
66
|
+
"VectorNotSupportedError",
|
|
66
67
|
# CRUD
|
|
67
68
|
"CRUDBase",
|
|
68
69
|
# 事务
|
|
@@ -23,9 +23,13 @@ from __future__ import annotations
|
|
|
23
23
|
"""数据库 Kit 共享基类(DRY 收敛)
|
|
24
24
|
|
|
25
25
|
三个数据库 Kit(PgKit / MySqlKit / SQLiteKit)在重构前拥有约 400 行几乎
|
|
26
|
-
逐行相同的代码(连接管理 / ORM / 原生 SQL / 事务 /
|
|
27
|
-
与 get_table_info 的 SQL
|
|
28
|
-
DB_TYPE 并覆写 get_table_info(SQLite 另含向量操作)。
|
|
26
|
+
逐行相同的代码(连接管理 / ORM / 原生 SQL / 原生表操作 / 事务 / 表操作),
|
|
27
|
+
仅 DB_TYPE 与 get_table_info 的 SQL 不同。本基类将全部共享实现收敛,子类
|
|
28
|
+
只声明 DB_TYPE 并覆写 get_table_info(SQLite 另含向量操作)。
|
|
29
|
+
|
|
30
|
+
2026-10-11:将原本仅 SQLiteKit 具备的 4 个方言无关方法(raw_connection /
|
|
31
|
+
create_raw_table / raw_insert / raw_get_by)上移到本基类,使三个 Kit 在该层
|
|
32
|
+
的能力与签名完全一致(共享方法 19 → 23)。
|
|
29
33
|
|
|
30
34
|
设计要点:
|
|
31
35
|
- 所有公开方法保留原签名与语义,外部调用零影响。
|
|
@@ -46,8 +50,10 @@ from contextlib import contextmanager
|
|
|
46
50
|
from typing import cast
|
|
47
51
|
|
|
48
52
|
from sqlalchemy import text
|
|
53
|
+
from sqlalchemy.pool import PoolProxiedConnection
|
|
49
54
|
from sqlmodel import Session, SQLModel
|
|
50
55
|
|
|
56
|
+
from kit.common_kit import CommonKit
|
|
51
57
|
from kit.config.log_config import get_default_logger
|
|
52
58
|
from kit.db.crud import CRUDBase
|
|
53
59
|
from kit.db.engine import (
|
|
@@ -64,6 +70,11 @@ logger = get_default_logger(__name__)
|
|
|
64
70
|
# 字段数据:列名到值的映射(object 覆盖全部 SQLModel 支持类型)
|
|
65
71
|
type FieldData = dict[str, object]
|
|
66
72
|
|
|
73
|
+
# 向量检索的默认距离类型:三库能力集的交集(SQLite 仅支持 L2),
|
|
74
|
+
# 由三个数据库 Kit 的 search_vectors 共用,避免各 Kit 默认值漂移。
|
|
75
|
+
# 默认值不得按后端覆写——否则同一份代码换 import 后会在静默中改变度量语义。
|
|
76
|
+
DEFAULT_DISTANCE_TYPE = "L2"
|
|
77
|
+
|
|
67
78
|
# CRUD 实例缓存:CRUDBase 无状态(仅存 model),按 model 复用避免重复实例化
|
|
68
79
|
# 值类型取宽基类 SQLModel,规避 PEP 695 类型参数上界约束
|
|
69
80
|
_crud_cache: dict[type, CRUDBase[SQLModel, SQLModel, SQLModel]] = {}
|
|
@@ -83,6 +94,47 @@ def _get_crud[T: SQLModel](model: type[T]) -> CRUDBase[T, T, T]:
|
|
|
83
94
|
return cast(CRUDBase[T, T, T], crud)
|
|
84
95
|
|
|
85
96
|
|
|
97
|
+
def to_vector_literal(vector: builtins.list[float]) -> str:
|
|
98
|
+
"""把 float 列表转换为向量文本字面量(如 [0.1, 0.2] → "[0.1,0.2]")
|
|
99
|
+
|
|
100
|
+
供共享相同文本格式的数据库后端复用:PgKit(pgvector 的 vector 类型)与
|
|
101
|
+
MySqlKit(STRING_TO_VECTOR)。SQLiteKit 走 float32 bytes,不使用本函数。
|
|
102
|
+
"""
|
|
103
|
+
return "[" + ",".join(repr(float(value)) for value in vector) + "]"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def vector_insert_placeholders(
|
|
107
|
+
columns: builtins.list[str], id_col: str, column: str
|
|
108
|
+
) -> str:
|
|
109
|
+
"""生成向量表 INSERT 的占位符串(供三个数据库 Kit 的 insert_vectors 共用)
|
|
110
|
+
|
|
111
|
+
绑定参数名与列名**解耦**:id 列固定为 ``:id``、向量列固定为 ``:vec``、
|
|
112
|
+
其余列用 ``:列名``。这样调用方覆写 ``id_col`` / ``column``(自建 DDL 场景)时,
|
|
113
|
+
参数字典的键不受列名影响。
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def _one(name: str) -> str:
|
|
117
|
+
if name == column:
|
|
118
|
+
return ":vec"
|
|
119
|
+
if name == id_col:
|
|
120
|
+
return ":id"
|
|
121
|
+
return f":{name}"
|
|
122
|
+
|
|
123
|
+
return ", ".join(_one(name) for name in columns)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def generate_snowflake_ids(count: int) -> builtins.list[int]:
|
|
127
|
+
"""生成 count 个雪花 ID(int64;当前 18 位,上限 19 位)
|
|
128
|
+
|
|
129
|
+
供三个数据库 Kit 的向量方法在调用方未提供 id(``ids=None``)时兜底使用:
|
|
130
|
+
id 由**应用侧(kit)生成**而非数据库自增,保证三库语义一致,同时避免
|
|
131
|
+
PostgreSQL 序列不随显式 id 推进的问题(BIGSERIAL → 纯 BIGINT 主键)。
|
|
132
|
+
|
|
133
|
+
⚠ 雪花 ID 超过 2^53,经 JSON 传给 JavaScript 会丢精度,需按字符串传输。
|
|
134
|
+
"""
|
|
135
|
+
return [CommonKit.generate_snowflake_id() for _ in range(count)]
|
|
136
|
+
|
|
137
|
+
|
|
86
138
|
class BaseDbKit(ABC):
|
|
87
139
|
"""数据库工具类共享基类
|
|
88
140
|
|
|
@@ -129,6 +181,19 @@ class BaseDbKit(ABC):
|
|
|
129
181
|
except Exception:
|
|
130
182
|
return False
|
|
131
183
|
|
|
184
|
+
@classmethod
|
|
185
|
+
def raw_connection(cls) -> PoolProxiedConnection:
|
|
186
|
+
"""获取原生 DBAPI 连接(引擎侧扩展加载已由 engine.py 自动完成)
|
|
187
|
+
|
|
188
|
+
PostgreSQL 返回 psycopg2 连接、MySQL 返回 pymysql 连接、SQLite 返回
|
|
189
|
+
sqlite3 连接(sqlite-vector 扩展已自动加载)。用于执行驱动特有函数与
|
|
190
|
+
静态 DDL。
|
|
191
|
+
|
|
192
|
+
⚠ 调用方负责提交与关闭连接。psycopg2 / pymysql 默认**不开启自动提交**,
|
|
193
|
+
与 sqlite3 的使用习惯不同:需要落库时必须显式 commit。
|
|
194
|
+
"""
|
|
195
|
+
return get_engine(cls.DB_TYPE).raw_connection()
|
|
196
|
+
|
|
132
197
|
# ==================== ORM 操作(自动管理 session) ====================
|
|
133
198
|
|
|
134
199
|
@classmethod
|
|
@@ -350,6 +415,71 @@ class BaseDbKit(ABC):
|
|
|
350
415
|
logger.info(f"✅ 执行更新 → 影响 {affected} 行")
|
|
351
416
|
return affected
|
|
352
417
|
|
|
418
|
+
# ==================== 原生表操作 ====================
|
|
419
|
+
|
|
420
|
+
@classmethod
|
|
421
|
+
def create_raw_table(
|
|
422
|
+
cls, table_name: str, columns: dict[str, str], if_not_exists: bool = True
|
|
423
|
+
) -> None:
|
|
424
|
+
"""根据「列名 → 列定义」字典创建普通表
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
table_name: 表名
|
|
428
|
+
columns: 列定义字典,如 {"id": "BIGSERIAL PRIMARY KEY", "name": "TEXT"}
|
|
429
|
+
if_not_exists: 是否附加 IF NOT EXISTS(默认 True)
|
|
430
|
+
|
|
431
|
+
⚠ 列定义须按**目标数据库方言**书写,本方法只做字符串拼接与执行,
|
|
432
|
+
不做方言翻译:
|
|
433
|
+
PostgreSQL: {"id": "BIGSERIAL PRIMARY KEY", "name": "TEXT"}
|
|
434
|
+
MySQL: {"id": "INT AUTO_INCREMENT PRIMARY KEY", "name": "VARCHAR(100)"}
|
|
435
|
+
SQLite: {"id": "INTEGER PRIMARY KEY", "name": "TEXT"}
|
|
436
|
+
"""
|
|
437
|
+
column_defs = ", ".join(
|
|
438
|
+
f"{name} {definition}" for name, definition in columns.items()
|
|
439
|
+
)
|
|
440
|
+
if_not_exists_sql = "IF NOT EXISTS " if if_not_exists else ""
|
|
441
|
+
sql = f"CREATE TABLE {if_not_exists_sql}{table_name} ({column_defs})"
|
|
442
|
+
with get_engine(cls.DB_TYPE).connect() as conn:
|
|
443
|
+
conn.execute(text(sql))
|
|
444
|
+
conn.commit()
|
|
445
|
+
logger.info(f"✅ 创建表 {table_name}")
|
|
446
|
+
|
|
447
|
+
@classmethod
|
|
448
|
+
def raw_insert(cls, table_name: str, data: dict[str, object]) -> int:
|
|
449
|
+
"""向指定表插入一行数据(键为列名)
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
table_name: 表名
|
|
453
|
+
data: 列名 → 值 字典
|
|
454
|
+
|
|
455
|
+
Returns:
|
|
456
|
+
受影响的行数
|
|
457
|
+
"""
|
|
458
|
+
columns = ", ".join(data.keys())
|
|
459
|
+
placeholders = ", ".join(f":{name}" for name in data)
|
|
460
|
+
sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
|
|
461
|
+
return cls.execute(sql, dict(data))
|
|
462
|
+
|
|
463
|
+
@classmethod
|
|
464
|
+
def raw_get_by(
|
|
465
|
+
cls, table_name: str, column: str, value: object
|
|
466
|
+
) -> dict[str, object] | None:
|
|
467
|
+
"""按单列等值查询一行
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
table_name: 表名
|
|
471
|
+
column: 列名
|
|
472
|
+
value: 查询值
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
匹配的第一行(字典);无匹配返回 None
|
|
476
|
+
"""
|
|
477
|
+
rows = cls.query(
|
|
478
|
+
f"SELECT * FROM {table_name} WHERE {column} = :value LIMIT 1",
|
|
479
|
+
{"value": value},
|
|
480
|
+
)
|
|
481
|
+
return rows[0] if rows else None
|
|
482
|
+
|
|
353
483
|
# ==================== 事务管理 ====================
|
|
354
484
|
|
|
355
485
|
@classmethod
|