sdpy-kit 0.2.2__tar.gz → 0.2.3__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.2 → sdpy_kit-0.2.3}/PKG-INFO +46 -3
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/README.md +45 -2
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/__init__.py +25 -1
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/db/__init__.py +23 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/db/base_db_kit.py +58 -5
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/db/pg_kit.py +18 -3
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/db/sqlite_kit.py +55 -7
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/.gitignore +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/__init__.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/base_service.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/chat_service.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/embedding_service.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/exceptions.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/failover.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/image_service.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/model_builder.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/model_client.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/pool.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/profiles.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/rerank_service.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/runner.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/ai/types.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/anydoc_kit.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/common_kit.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/config/__init__.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/config/kit_config.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/config/log_config.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/db/crud.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/db/engine.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/db/exceptions.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/db/models/__init__.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/db/models/base.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/db/mysql_kit.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/db/query.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/db/session.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/db/transaction.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/docx_kit.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/file_kit.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/marker_kit.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/mcp_kit.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/md2doc_kit.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/mineru_kit.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/redis_kit.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/wx_article_kit.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/kit/zvec_kit.py +0 -0
- {sdpy_kit-0.2.2 → sdpy_kit-0.2.3}/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.3
|
|
4
4
|
Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/MCP/Redis/文件/文档/向量工具集
|
|
5
5
|
Author-email: Clark <changhongyuan@126.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -370,13 +370,23 @@ from kit import session_scope, transactional, DBError # 等价
|
|
|
370
370
|
| 表 | `list_tables() -> list[str]` | 列出全部表名 |
|
|
371
371
|
| 表 | `table_exists(table_name) -> bool` | 判断表是否存在 |
|
|
372
372
|
| 表 | `create_tables(models) -> None` | 按模型列表建表 |
|
|
373
|
-
| 表 | `get_table_info(table_name) -> list[dict]` |
|
|
373
|
+
| 表 | `get_table_info(table_name) -> list[dict]` | 获取列信息(按列序;公共键 4 个:`column_name`/`data_type`/`is_nullable`/`column_default`) |
|
|
374
|
+
|
|
375
|
+
> **列表参数(`IN`)**:`query` / `execute` 的 `params` 中值为 `list`/`tuple`/`set`/`frozenset`
|
|
376
|
+
> 时按 `IN` 展开,可直接写 `WHERE id IN :ids`(三库通用)。`IN` **不保证顺序**;参数列表过大
|
|
377
|
+
> 应分批(PG 单语句上限 65535 参数)。
|
|
378
|
+
>
|
|
379
|
+
> **`LIKE` 的方言差异**:SQLite 对 ASCII 不区分大小写,PostgreSQL 区分(用 `ILIKE`);
|
|
380
|
+
> 跨库一致写法:`LOWER(col) LIKE LOWER(:pattern)`。
|
|
381
|
+
>
|
|
382
|
+
> **导出面**:`from kit.db import PgKit, SQLiteKit, build_pg_url, to_vector_literal, …`
|
|
383
|
+
> (`kit` 顶层与 `kit.db` 导出同一套符号)。
|
|
374
384
|
|
|
375
385
|
```python
|
|
376
386
|
from sqlalchemy import BigInteger
|
|
377
387
|
from sqlmodel import SQLModel, Field
|
|
378
388
|
from kit.common_kit import CommonKit
|
|
379
|
-
from kit.db.sqlite_kit import SQLiteKit
|
|
389
|
+
from kit.db import SQLiteKit # 也可 from kit.db.sqlite_kit import SQLiteKit
|
|
380
390
|
|
|
381
391
|
class User(SQLModel, table=True):
|
|
382
392
|
# 主键:64 位整数 + 雪花 ID(应用侧生成,不用数据库自增)
|
|
@@ -393,6 +403,7 @@ users = SQLiteKit.list(User, skip=0, limit=10, order_by="id", desc=True)
|
|
|
393
403
|
|
|
394
404
|
rows = SQLiteKit.query("SELECT * FROM user WHERE name = :name", {"name": "李四"})
|
|
395
405
|
affected = SQLiteKit.execute("UPDATE user SET name = '王五' WHERE id = :id", {"id": user.id})
|
|
406
|
+
rows = SQLiteKit.query("SELECT * FROM user WHERE id IN :ids", {"ids": [1, 2, 3]}) # 列表参数
|
|
396
407
|
|
|
397
408
|
with SQLiteKit.transaction() as session: # 多操作原子提交
|
|
398
409
|
SQLiteKit.create(User, {"name": "批量一"})
|
|
@@ -499,6 +510,38 @@ PgKit.search_vectors("my_vec", [0.1] * 384, top_k=5, id_col="vid", column="vec")
|
|
|
499
510
|
> 新增(非破坏性):`VECTOR_CAPABILITIES` 能力声明、`id_col` / `column` 逃生口参数、
|
|
500
511
|
> `insert_vectors` 支持二维 numpy 数组与批量提交。
|
|
501
512
|
|
|
513
|
+
#### 0.2.3 变更
|
|
514
|
+
|
|
515
|
+
| 编号 | 变更 | 影响 |
|
|
516
|
+
|------|------|------|
|
|
517
|
+
| B6 | **`PgKit.get_table_info` 按数据库解析语义定位表**(`search_path` 解析到的那张表) | 多 schema 下同名表**不再混行**;单 schema / `public` 场景**无影响**;只接受非限定表名 |
|
|
518
|
+
| — | `query` / `execute` 支持**列表参数**(`IN :ids`,三库通用) | 纯新增;`set`/`frozenset` 自动归一化为 `list` |
|
|
519
|
+
| — | 新增 `SQLiteKit.encode_vector` / `decode_vector`(小端 float32) | 纯新增:可在自有事务内写向量表,见下 |
|
|
520
|
+
| — | 导出面统一:`from kit.db import PgKit, MySqlKit, SQLiteKit, build_pg_url, …` | 纯新增;既有导出未删除/未改名 |
|
|
521
|
+
|
|
522
|
+
**两段查询范式(推荐用法)**:`search_vectors` 只返回 `[{"id", "distance"}]`,载荷二次取回:
|
|
523
|
+
|
|
524
|
+
```python
|
|
525
|
+
hits = SQLiteKit.search_vectors("item_vectors", query_vector, top_k=5)
|
|
526
|
+
rows = SQLiteKit.query("SELECT id, title FROM items WHERE id IN :ids",
|
|
527
|
+
{"ids": [h["id"] for h in hits]}) # IN 不保证顺序,需按 hits 重排
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**自有事务内写向量(SQLite)**:
|
|
531
|
+
|
|
532
|
+
```python
|
|
533
|
+
conn = SQLiteKit.raw_connection() # DBAPI 连接:位置参数 ? + 手工 commit
|
|
534
|
+
try:
|
|
535
|
+
conn.execute("INSERT INTO item_vectors (id, embedding) VALUES (?, ?)",
|
|
536
|
+
(item_id, SQLiteKit.encode_vector(vec)))
|
|
537
|
+
conn.commit()
|
|
538
|
+
finally:
|
|
539
|
+
conn.close()
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
> 相似度换算建议:SQLite 仅支持 L2,若向量入库前已**归一化**,则 `cos = 1 − L2²/2`;
|
|
543
|
+
> PG 可直接用 `distance_type="COSINE"`。
|
|
544
|
+
|
|
502
545
|
### 3. CRUDBase(配合 session_scope,不自动提交)
|
|
503
546
|
|
|
504
547
|
```python
|
|
@@ -300,13 +300,23 @@ from kit import session_scope, transactional, DBError # 等价
|
|
|
300
300
|
| 表 | `list_tables() -> list[str]` | 列出全部表名 |
|
|
301
301
|
| 表 | `table_exists(table_name) -> bool` | 判断表是否存在 |
|
|
302
302
|
| 表 | `create_tables(models) -> None` | 按模型列表建表 |
|
|
303
|
-
| 表 | `get_table_info(table_name) -> list[dict]` |
|
|
303
|
+
| 表 | `get_table_info(table_name) -> list[dict]` | 获取列信息(按列序;公共键 4 个:`column_name`/`data_type`/`is_nullable`/`column_default`) |
|
|
304
|
+
|
|
305
|
+
> **列表参数(`IN`)**:`query` / `execute` 的 `params` 中值为 `list`/`tuple`/`set`/`frozenset`
|
|
306
|
+
> 时按 `IN` 展开,可直接写 `WHERE id IN :ids`(三库通用)。`IN` **不保证顺序**;参数列表过大
|
|
307
|
+
> 应分批(PG 单语句上限 65535 参数)。
|
|
308
|
+
>
|
|
309
|
+
> **`LIKE` 的方言差异**:SQLite 对 ASCII 不区分大小写,PostgreSQL 区分(用 `ILIKE`);
|
|
310
|
+
> 跨库一致写法:`LOWER(col) LIKE LOWER(:pattern)`。
|
|
311
|
+
>
|
|
312
|
+
> **导出面**:`from kit.db import PgKit, SQLiteKit, build_pg_url, to_vector_literal, …`
|
|
313
|
+
> (`kit` 顶层与 `kit.db` 导出同一套符号)。
|
|
304
314
|
|
|
305
315
|
```python
|
|
306
316
|
from sqlalchemy import BigInteger
|
|
307
317
|
from sqlmodel import SQLModel, Field
|
|
308
318
|
from kit.common_kit import CommonKit
|
|
309
|
-
from kit.db.sqlite_kit import SQLiteKit
|
|
319
|
+
from kit.db import SQLiteKit # 也可 from kit.db.sqlite_kit import SQLiteKit
|
|
310
320
|
|
|
311
321
|
class User(SQLModel, table=True):
|
|
312
322
|
# 主键:64 位整数 + 雪花 ID(应用侧生成,不用数据库自增)
|
|
@@ -323,6 +333,7 @@ users = SQLiteKit.list(User, skip=0, limit=10, order_by="id", desc=True)
|
|
|
323
333
|
|
|
324
334
|
rows = SQLiteKit.query("SELECT * FROM user WHERE name = :name", {"name": "李四"})
|
|
325
335
|
affected = SQLiteKit.execute("UPDATE user SET name = '王五' WHERE id = :id", {"id": user.id})
|
|
336
|
+
rows = SQLiteKit.query("SELECT * FROM user WHERE id IN :ids", {"ids": [1, 2, 3]}) # 列表参数
|
|
326
337
|
|
|
327
338
|
with SQLiteKit.transaction() as session: # 多操作原子提交
|
|
328
339
|
SQLiteKit.create(User, {"name": "批量一"})
|
|
@@ -429,6 +440,38 @@ PgKit.search_vectors("my_vec", [0.1] * 384, top_k=5, id_col="vid", column="vec")
|
|
|
429
440
|
> 新增(非破坏性):`VECTOR_CAPABILITIES` 能力声明、`id_col` / `column` 逃生口参数、
|
|
430
441
|
> `insert_vectors` 支持二维 numpy 数组与批量提交。
|
|
431
442
|
|
|
443
|
+
#### 0.2.3 变更
|
|
444
|
+
|
|
445
|
+
| 编号 | 变更 | 影响 |
|
|
446
|
+
|------|------|------|
|
|
447
|
+
| B6 | **`PgKit.get_table_info` 按数据库解析语义定位表**(`search_path` 解析到的那张表) | 多 schema 下同名表**不再混行**;单 schema / `public` 场景**无影响**;只接受非限定表名 |
|
|
448
|
+
| — | `query` / `execute` 支持**列表参数**(`IN :ids`,三库通用) | 纯新增;`set`/`frozenset` 自动归一化为 `list` |
|
|
449
|
+
| — | 新增 `SQLiteKit.encode_vector` / `decode_vector`(小端 float32) | 纯新增:可在自有事务内写向量表,见下 |
|
|
450
|
+
| — | 导出面统一:`from kit.db import PgKit, MySqlKit, SQLiteKit, build_pg_url, …` | 纯新增;既有导出未删除/未改名 |
|
|
451
|
+
|
|
452
|
+
**两段查询范式(推荐用法)**:`search_vectors` 只返回 `[{"id", "distance"}]`,载荷二次取回:
|
|
453
|
+
|
|
454
|
+
```python
|
|
455
|
+
hits = SQLiteKit.search_vectors("item_vectors", query_vector, top_k=5)
|
|
456
|
+
rows = SQLiteKit.query("SELECT id, title FROM items WHERE id IN :ids",
|
|
457
|
+
{"ids": [h["id"] for h in hits]}) # IN 不保证顺序,需按 hits 重排
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
**自有事务内写向量(SQLite)**:
|
|
461
|
+
|
|
462
|
+
```python
|
|
463
|
+
conn = SQLiteKit.raw_connection() # DBAPI 连接:位置参数 ? + 手工 commit
|
|
464
|
+
try:
|
|
465
|
+
conn.execute("INSERT INTO item_vectors (id, embedding) VALUES (?, ?)",
|
|
466
|
+
(item_id, SQLiteKit.encode_vector(vec)))
|
|
467
|
+
conn.commit()
|
|
468
|
+
finally:
|
|
469
|
+
conn.close()
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
> 相似度换算建议:SQLite 仅支持 L2,若向量入库前已**归一化**,则 `cos = 1 − L2²/2`;
|
|
473
|
+
> PG 可直接用 `distance_type="COSINE"`。
|
|
474
|
+
|
|
432
475
|
### 3. CRUDBase(配合 session_scope,不自动提交)
|
|
433
476
|
|
|
434
477
|
```python
|
|
@@ -23,26 +23,37 @@ Author: Clark Chang
|
|
|
23
23
|
数据库访问通过 kit.db 子包提供(基于 SQLModel)。
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
-
__version__ = "0.2.
|
|
26
|
+
__version__ = "0.2.3"
|
|
27
27
|
|
|
28
28
|
from kit.db import (
|
|
29
29
|
MYSQL,
|
|
30
30
|
POSTGRESQL,
|
|
31
31
|
SQLITE,
|
|
32
32
|
CRUDBase,
|
|
33
|
+
DEFAULT_DISTANCE_TYPE,
|
|
33
34
|
DBError,
|
|
35
|
+
MySqlKit,
|
|
34
36
|
NotFoundError,
|
|
37
|
+
PgKit,
|
|
38
|
+
SQLiteKit,
|
|
39
|
+
VectorNotSupportedError,
|
|
40
|
+
build_mysql_url,
|
|
41
|
+
build_pg_url,
|
|
42
|
+
build_sqlite_url,
|
|
35
43
|
close_all_engines,
|
|
36
44
|
close_engine,
|
|
37
45
|
create_tables,
|
|
46
|
+
generate_snowflake_ids,
|
|
38
47
|
get_engine,
|
|
39
48
|
get_session,
|
|
40
49
|
list_tables,
|
|
41
50
|
session_scope,
|
|
42
51
|
table_exists,
|
|
52
|
+
to_vector_literal,
|
|
43
53
|
transactional,
|
|
44
54
|
)
|
|
45
55
|
|
|
56
|
+
# 顶层公共面与 kit.db 保持一致(同一套符号,避免两处入口漂移)
|
|
46
57
|
__all__ = [
|
|
47
58
|
# 引擎管理
|
|
48
59
|
"get_engine",
|
|
@@ -51,18 +62,31 @@ __all__ = [
|
|
|
51
62
|
"table_exists",
|
|
52
63
|
"close_all_engines",
|
|
53
64
|
"close_engine",
|
|
65
|
+
# 连接串构造(单一来源)
|
|
66
|
+
"build_pg_url",
|
|
67
|
+
"build_mysql_url",
|
|
68
|
+
"build_sqlite_url",
|
|
54
69
|
# 常量
|
|
55
70
|
"POSTGRESQL",
|
|
56
71
|
"MYSQL",
|
|
57
72
|
"SQLITE",
|
|
73
|
+
# 数据库 Kit
|
|
74
|
+
"PgKit",
|
|
75
|
+
"MySqlKit",
|
|
76
|
+
"SQLiteKit",
|
|
58
77
|
# 会话
|
|
59
78
|
"session_scope",
|
|
60
79
|
"get_session",
|
|
61
80
|
# 异常
|
|
62
81
|
"DBError",
|
|
63
82
|
"NotFoundError",
|
|
83
|
+
"VectorNotSupportedError",
|
|
64
84
|
# CRUD
|
|
65
85
|
"CRUDBase",
|
|
66
86
|
# 事务
|
|
67
87
|
"transactional",
|
|
88
|
+
# 向量工具(公共)
|
|
89
|
+
"to_vector_literal",
|
|
90
|
+
"generate_snowflake_ids",
|
|
91
|
+
"DEFAULT_DISTANCE_TYPE",
|
|
68
92
|
]
|
|
@@ -29,11 +29,19 @@ Author: Clark Chang
|
|
|
29
29
|
- SQLModel 混入类(models/base.py)
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
+
from kit.db.base_db_kit import (
|
|
33
|
+
DEFAULT_DISTANCE_TYPE,
|
|
34
|
+
generate_snowflake_ids,
|
|
35
|
+
to_vector_literal,
|
|
36
|
+
)
|
|
32
37
|
from kit.db.crud import CRUDBase
|
|
33
38
|
from kit.db.engine import (
|
|
34
39
|
MYSQL,
|
|
35
40
|
POSTGRESQL,
|
|
36
41
|
SQLITE,
|
|
42
|
+
build_mysql_url,
|
|
43
|
+
build_pg_url,
|
|
44
|
+
build_sqlite_url,
|
|
37
45
|
close_all_engines,
|
|
38
46
|
close_engine,
|
|
39
47
|
create_tables,
|
|
@@ -42,7 +50,10 @@ from kit.db.engine import (
|
|
|
42
50
|
table_exists,
|
|
43
51
|
)
|
|
44
52
|
from kit.db.exceptions import DBError, NotFoundError, VectorNotSupportedError
|
|
53
|
+
from kit.db.mysql_kit import MySqlKit
|
|
54
|
+
from kit.db.pg_kit import PgKit
|
|
45
55
|
from kit.db.session import get_session, session_scope
|
|
56
|
+
from kit.db.sqlite_kit import SQLiteKit
|
|
46
57
|
from kit.db.transaction import transactional
|
|
47
58
|
|
|
48
59
|
__all__ = [
|
|
@@ -53,10 +64,18 @@ __all__ = [
|
|
|
53
64
|
"table_exists",
|
|
54
65
|
"close_all_engines",
|
|
55
66
|
"close_engine",
|
|
67
|
+
# 连接串构造(单一来源,测试/工具可复用)
|
|
68
|
+
"build_pg_url",
|
|
69
|
+
"build_mysql_url",
|
|
70
|
+
"build_sqlite_url",
|
|
56
71
|
# 常量
|
|
57
72
|
"POSTGRESQL",
|
|
58
73
|
"MYSQL",
|
|
59
74
|
"SQLITE",
|
|
75
|
+
# 数据库 Kit
|
|
76
|
+
"PgKit",
|
|
77
|
+
"MySqlKit",
|
|
78
|
+
"SQLiteKit",
|
|
60
79
|
# 会话
|
|
61
80
|
"session_scope",
|
|
62
81
|
"get_session",
|
|
@@ -68,4 +87,8 @@ __all__ = [
|
|
|
68
87
|
"CRUDBase",
|
|
69
88
|
# 事务
|
|
70
89
|
"transactional",
|
|
90
|
+
# 向量工具(公共)
|
|
91
|
+
"to_vector_literal",
|
|
92
|
+
"generate_snowflake_ids",
|
|
93
|
+
"DEFAULT_DISTANCE_TYPE",
|
|
71
94
|
]
|
|
@@ -43,13 +43,14 @@ create_raw_table / raw_insert / raw_get_by)上移到本基类,使三个 Kit
|
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
45
|
import builtins
|
|
46
|
+
import re
|
|
46
47
|
import threading
|
|
47
48
|
from abc import ABC, abstractmethod
|
|
48
49
|
from collections.abc import Generator
|
|
49
50
|
from contextlib import contextmanager
|
|
50
51
|
from typing import cast
|
|
51
52
|
|
|
52
|
-
from sqlalchemy import text
|
|
53
|
+
from sqlalchemy import TextClause, bindparam, text
|
|
53
54
|
from sqlalchemy.pool import PoolProxiedConnection
|
|
54
55
|
from sqlmodel import Session, SQLModel
|
|
55
56
|
|
|
@@ -135,6 +136,50 @@ def generate_snowflake_ids(count: int) -> builtins.list[int]:
|
|
|
135
136
|
return [CommonKit.generate_snowflake_id() for _ in range(count)]
|
|
136
137
|
|
|
137
138
|
|
|
139
|
+
# 可参与 IN 展开的参数类型(按类型白名单判断,绝不用 Iterable:
|
|
140
|
+
# 否则字符串会被逐字符展开)
|
|
141
|
+
_EXPANDING_TYPES = (list, tuple, set, frozenset)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _prepare_statement(
|
|
145
|
+
sql: str, params: FieldData | None
|
|
146
|
+
) -> tuple[TextClause, FieldData | None]:
|
|
147
|
+
"""把 SQL 文本 + 参数字典转为(可执行语句, 归一化后的参数)
|
|
148
|
+
|
|
149
|
+
`params` 中值为**序列类型**(list / tuple / set / frozenset)的键按 SQLAlchemy
|
|
150
|
+
**expanding** 语义绑定,使 ``WHERE id IN :ids`` 可直接书写(三库通用)。
|
|
151
|
+
|
|
152
|
+
三条实现约束:
|
|
153
|
+
|
|
154
|
+
1. 按类型白名单判断(见 `_EXPANDING_TYPES`),字符串不会被当作列表;
|
|
155
|
+
2. 只对**确实出现在 SQL 中**的参数名做 expanding —— 否则传入"未使用的序列参数"
|
|
156
|
+
会触发 SQLAlchemy 的 ``ArgumentError: This text() construct doesn't define
|
|
157
|
+
a bound parameter …``,该报错对使用方没有意义;
|
|
158
|
+
3. `set` / `frozenset` 归一化为 `list` —— SQLAlchemy 的 expanding 只接受
|
|
159
|
+
**可下标序列**(直接传 set 会抛 ``TypeError: 'set' object is not subscriptable``);
|
|
160
|
+
``IN`` 与顺序无关,故归一化不改变语义。
|
|
161
|
+
"""
|
|
162
|
+
statement = text(sql)
|
|
163
|
+
if not params:
|
|
164
|
+
return statement, params
|
|
165
|
+
|
|
166
|
+
normalized: FieldData = {}
|
|
167
|
+
expanding: list[object] = []
|
|
168
|
+
for name, value in params.items():
|
|
169
|
+
if isinstance(value, _EXPANDING_TYPES):
|
|
170
|
+
if isinstance(value, (set, frozenset)):
|
|
171
|
+
value = list(value)
|
|
172
|
+
normalized[name] = value
|
|
173
|
+
if re.search(rf":{re.escape(name)}\b", sql):
|
|
174
|
+
expanding.append(bindparam(name, expanding=True))
|
|
175
|
+
else:
|
|
176
|
+
normalized[name] = value
|
|
177
|
+
|
|
178
|
+
if expanding:
|
|
179
|
+
statement = statement.bindparams(*expanding) # type: ignore[arg-type]
|
|
180
|
+
return statement, normalized
|
|
181
|
+
|
|
182
|
+
|
|
138
183
|
class BaseDbKit(ABC):
|
|
139
184
|
"""数据库工具类共享基类
|
|
140
185
|
|
|
@@ -385,13 +430,19 @@ class BaseDbKit(ABC):
|
|
|
385
430
|
|
|
386
431
|
Args:
|
|
387
432
|
sql: SQL 查询语句(使用命名参数 :param)
|
|
388
|
-
params:
|
|
433
|
+
params: 参数字典。值为 list / tuple / set / frozenset 的参数按 ``IN`` 展开,
|
|
434
|
+
可直接书写 ``WHERE id IN :ids``(三库通用)
|
|
389
435
|
|
|
390
436
|
Returns:
|
|
391
437
|
查询结果列表,每行为一个字典
|
|
438
|
+
|
|
439
|
+
Note:
|
|
440
|
+
``IN`` **不保证返回顺序**,需要顺序请自行按 id 重排;参数列表过大时
|
|
441
|
+
(如 PG 单语句参数上限 65535)应分批查询。
|
|
392
442
|
"""
|
|
443
|
+
statement, bound = _prepare_statement(sql, params)
|
|
393
444
|
with session_scope(cls.DB_TYPE, autocommit=False) as session:
|
|
394
|
-
result = session.execute(
|
|
445
|
+
result = session.execute(statement, bound or {})
|
|
395
446
|
rows = result.fetchall()
|
|
396
447
|
columns = result.keys()
|
|
397
448
|
data = [dict(zip(columns, row)) for row in rows]
|
|
@@ -404,13 +455,15 @@ class BaseDbKit(ABC):
|
|
|
404
455
|
|
|
405
456
|
Args:
|
|
406
457
|
sql: SQL 语句(使用命名参数 :param)
|
|
407
|
-
params:
|
|
458
|
+
params: 参数字典。值为 list / tuple / set / frozenset 的参数按 ``IN`` 展开
|
|
459
|
+
(如 ``DELETE FROM t WHERE id IN :ids``,三库通用)
|
|
408
460
|
|
|
409
461
|
Returns:
|
|
410
462
|
受影响的行数
|
|
411
463
|
"""
|
|
464
|
+
statement, bound = _prepare_statement(sql, params)
|
|
412
465
|
with session_scope(cls.DB_TYPE) as session:
|
|
413
|
-
result = session.execute(
|
|
466
|
+
result = session.execute(statement, bound or {})
|
|
414
467
|
affected = getattr(result, "rowcount", 0) or 0
|
|
415
468
|
logger.info(f"✅ 执行更新 → 影响 {affected} 行")
|
|
416
469
|
return affected
|
|
@@ -113,13 +113,22 @@ class PgKit(BaseDbKit):
|
|
|
113
113
|
|
|
114
114
|
@classmethod
|
|
115
115
|
def get_table_info(cls, table_name: str) -> list[dict[str, object]]:
|
|
116
|
-
"""
|
|
116
|
+
"""获取表的列信息(按列序返回)
|
|
117
117
|
|
|
118
118
|
Args:
|
|
119
|
-
table_name:
|
|
119
|
+
table_name: 表名(**非限定名**,如 ``items``)
|
|
120
120
|
|
|
121
121
|
Returns:
|
|
122
|
-
|
|
122
|
+
列信息字典列表,公共键 4 个:``column_name`` / ``data_type`` /
|
|
123
|
+
``is_nullable``(``"YES"``/``"NO"``)/ ``column_default``;
|
|
124
|
+
``data_type`` 为**后端原生类型名**(不做跨库归一化)
|
|
125
|
+
|
|
126
|
+
Note:
|
|
127
|
+
- 表按 **PostgreSQL 自身的解析规则**定位(``to_regclass``,即 ``search_path``
|
|
128
|
+
顺序),与 ``SELECT … FROM 表名`` 命中的那张表一致——因此不会因多 schema
|
|
129
|
+
同名表而返回混行;表不存在时返回空列表。
|
|
130
|
+
- **只接受非限定表名**:跨 schema 请用 `query()` 显式指定 ``table_schema``。
|
|
131
|
+
- 与 `list_tables()`(枚举**当前默认 schema**)语义不同,二者非互斥。
|
|
123
132
|
"""
|
|
124
133
|
sql = """
|
|
125
134
|
SELECT
|
|
@@ -129,6 +138,12 @@ class PgKit(BaseDbKit):
|
|
|
129
138
|
column_default
|
|
130
139
|
FROM information_schema.columns
|
|
131
140
|
WHERE table_name = :table_name
|
|
141
|
+
AND table_schema = (
|
|
142
|
+
SELECT n.nspname
|
|
143
|
+
FROM pg_class cl
|
|
144
|
+
JOIN pg_namespace n ON n.oid = cl.relnamespace
|
|
145
|
+
WHERE cl.oid = to_regclass(:table_name)
|
|
146
|
+
)
|
|
132
147
|
ORDER BY ordinal_position
|
|
133
148
|
"""
|
|
134
149
|
return cls.query(sql, {"table_name": table_name})
|
|
@@ -26,7 +26,8 @@ from __future__ import annotations
|
|
|
26
26
|
业务代码无需管理 session/transaction,直接调用即可。
|
|
27
27
|
|
|
28
28
|
本类继承 BaseDbKit,复用全部共享实现(连接管理 / ORM / 原生 SQL / 原生表操作 /
|
|
29
|
-
事务 / 表操作),并额外提供基于 sqlite-vector 扩展的 4
|
|
29
|
+
事务 / 表操作),并额外提供基于 sqlite-vector 扩展的 4 个向量方法,以及 2 个
|
|
30
|
+
**编码原语**(`encode_vector` / `decode_vector`,仅本后端具备)。
|
|
30
31
|
|
|
31
32
|
使用方式::
|
|
32
33
|
|
|
@@ -74,6 +75,9 @@ SUPPORTED_DISTANCE_TYPES: tuple[str, ...] = ("L2",)
|
|
|
74
75
|
# float32 = 4 字节/维:BLOB 长度与维度的换算基准
|
|
75
76
|
BYTES_PER_DIM = 4
|
|
76
77
|
|
|
78
|
+
# sqlite-vector 的向量表示:**小端** float32(显式指定,不依赖本机字节序)
|
|
79
|
+
_VECTOR_DTYPE = "<f4"
|
|
80
|
+
|
|
77
81
|
|
|
78
82
|
class SQLiteKit(BaseDbKit):
|
|
79
83
|
"""SQLite 数据库工具类
|
|
@@ -120,6 +124,54 @@ class SQLiteKit(BaseDbKit):
|
|
|
120
124
|
|
|
121
125
|
# ==================== 向量操作 ====================
|
|
122
126
|
|
|
127
|
+
@classmethod
|
|
128
|
+
def encode_vector(cls, vector: list[float]) -> bytes:
|
|
129
|
+
"""把向量编码为 sqlite-vector 使用的 float32 **小端** BLOB(4 字节/维)
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
vector: 一维向量(float 序列,也接受一维 numpy 数组)
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
``len(vector) * 4`` 字节的 BLOB
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
ValueError: 向量为空,或输入不是一维(二维数组会被静默编成多个向量)
|
|
139
|
+
|
|
140
|
+
Note:
|
|
141
|
+
本方法是**后端专属原语**(文本后端对应 `to_vector_literal`,两者不可互换):
|
|
142
|
+
用于调用方在**自有事务/连接**内拼写 INSERT 时编码,例如::
|
|
143
|
+
|
|
144
|
+
with SQLiteKit.raw_connection() as conn:
|
|
145
|
+
conn.execute(
|
|
146
|
+
"INSERT INTO item_vectors (id, embedding) VALUES (?, ?)",
|
|
147
|
+
(item_id, SQLiteKit.encode_vector(vec)),
|
|
148
|
+
)
|
|
149
|
+
conn.commit()
|
|
150
|
+
"""
|
|
151
|
+
# numpy 仅向量方法需要(vec extra),延迟导入保持基础安装可用
|
|
152
|
+
import numpy as np
|
|
153
|
+
|
|
154
|
+
array = np.asarray(vector, dtype=_VECTOR_DTYPE)
|
|
155
|
+
if array.size == 0:
|
|
156
|
+
raise ValueError("vector 不能为空")
|
|
157
|
+
if array.ndim != 1:
|
|
158
|
+
raise ValueError(f"encode_vector 只接受一维向量,当前 ndim={array.ndim}")
|
|
159
|
+
return array.tobytes()
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def decode_vector(cls, blob: bytes) -> list[float]:
|
|
163
|
+
"""把 float32 小端 BLOB 解码为 float 列表(便于自检与断言)
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
blob: `encode_vector` 产出的字节串(4 的整数倍)
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
float 列表
|
|
170
|
+
"""
|
|
171
|
+
import numpy as np
|
|
172
|
+
|
|
173
|
+
return [float(x) for x in np.frombuffer(blob, dtype=_VECTOR_DTYPE)]
|
|
174
|
+
|
|
123
175
|
@classmethod
|
|
124
176
|
def create_vector_table(
|
|
125
177
|
cls,
|
|
@@ -225,13 +277,11 @@ class SQLiteKit(BaseDbKit):
|
|
|
225
277
|
)
|
|
226
278
|
|
|
227
279
|
engine = get_engine(cls.DB_TYPE)
|
|
228
|
-
# numpy 仅向量方法需要(vec extra),延迟导入保持基础安装可用
|
|
229
|
-
import numpy as np
|
|
230
280
|
|
|
231
281
|
# 按「列集合」分组:键集相同的行才能共用同一条批量语句(executemany)
|
|
232
282
|
groups: dict[tuple[str, ...], list[dict[str, object]]] = {}
|
|
233
283
|
for i, vec in enumerate(vectors):
|
|
234
|
-
vec_bytes =
|
|
284
|
+
vec_bytes = cls.encode_vector(vec)
|
|
235
285
|
columns: list[str] = [id_col, column]
|
|
236
286
|
params: dict[str, object] = {"id": ids[i], "vec": vec_bytes}
|
|
237
287
|
|
|
@@ -323,10 +373,8 @@ class SQLiteKit(BaseDbKit):
|
|
|
323
373
|
|
|
324
374
|
dims = len(query_vector)
|
|
325
375
|
engine = get_engine(cls.DB_TYPE)
|
|
326
|
-
# numpy 仅向量方法需要(vec extra),延迟导入保持基础安装可用
|
|
327
|
-
import numpy as np
|
|
328
376
|
|
|
329
|
-
query_bytes =
|
|
377
|
+
query_bytes = cls.encode_vector(query_vector)
|
|
330
378
|
|
|
331
379
|
with engine.connect() as conn:
|
|
332
380
|
# 维度前置校验:把「Invalid vector blob found at rowid N」换成语义明确的错误
|
|
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
|
|
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
|