sdpy-kit 0.6.1__tar.gz → 0.6.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 (54) hide show
  1. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/PKG-INFO +79 -56
  2. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/README.md +78 -55
  3. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/__init__.py +1 -1
  4. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/.gitignore +0 -0
  5. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/__init__.py +0 -0
  6. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/base_service.py +0 -0
  7. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/chat_service.py +0 -0
  8. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/embedding_service.py +0 -0
  9. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/exceptions.py +0 -0
  10. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/failover.py +0 -0
  11. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/image_service.py +0 -0
  12. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/model_builder.py +0 -0
  13. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/model_client.py +0 -0
  14. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/pool.py +0 -0
  15. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/profiles.py +0 -0
  16. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/rerank_service.py +0 -0
  17. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/runner.py +0 -0
  18. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/ai/types.py +0 -0
  19. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/common_kit.py +0 -0
  20. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/config/__init__.py +0 -0
  21. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/config/kit_config.py +0 -0
  22. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/config/log_config.py +0 -0
  23. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/crawl/__init__.py +0 -0
  24. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/crawl/article_io.py +0 -0
  25. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/crawl/article_models.py +0 -0
  26. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/crawl/deepseek_kit.py +0 -0
  27. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/crawl/web_article_kit.py +0 -0
  28. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/crawl/wx_article_kit.py +0 -0
  29. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/__init__.py +0 -0
  30. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/audit.py +0 -0
  31. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/base_db_kit.py +0 -0
  32. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/crud.py +0 -0
  33. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/engine.py +0 -0
  34. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/exceptions.py +0 -0
  35. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/models/__init__.py +0 -0
  36. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/models/base.py +0 -0
  37. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/mysql_kit.py +0 -0
  38. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/pg_kit.py +0 -0
  39. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/query.py +0 -0
  40. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/session.py +0 -0
  41. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/sqlite_kit.py +0 -0
  42. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/db/transaction.py +0 -0
  43. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/docs/__init__.py +0 -0
  44. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/docs/anydoc_kit.py +0 -0
  45. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/docs/docx_kit.py +0 -0
  46. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/docs/marker_kit.py +0 -0
  47. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/docs/md2doc_kit.py +0 -0
  48. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/docs/mineru_kit.py +0 -0
  49. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/docs/rc_docparser_kit.py +0 -0
  50. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/file_kit.py +0 -0
  51. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/laya_kit.py +0 -0
  52. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/license_kit.py +0 -0
  53. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/kit/redis_kit.py +0 -0
  54. {sdpy_kit-0.6.1 → sdpy_kit-0.6.2}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sdpy-kit
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/Redis/文件/文档/向量工具集
5
5
  Author-email: Clark <changhongyuan@126.com>
6
6
  License-Expression: MIT
@@ -80,7 +80,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
80
80
 
81
81
  - **统一数据库访问层**:PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致,切换数据库只改一行 import;内置审计字段自动填充(`created_*` / `updated_*` / `deleted`)与软删除查询过滤;
82
82
  - **AI 多模型服务池**:chat / embedding / rerank / image 四类能力,多厂商候选链 + 故障自动转移;
83
- - **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(PDF + 图片 → md + images/) / 离线 docx 解析(md + images/) / Laya 类型化决策 / 微信公众号文章抓取存储 / 通用网页文章抓取存储。
83
+ - **工具集**:Redis / 文件 / 雪花 ID / 离线授权签发与校验 / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(PDF + 图片 → md + images/) / 离线 docx 解析(md + images/) / Laya 类型化决策 / 微信公众号文章抓取存储 / 通用网页文章抓取存储 / DeepSeek 搜索与抓取。
84
84
 
85
85
  > 要求 Python 3.13+
86
86
 
@@ -94,6 +94,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
94
94
  | `kit.redis_kit` | `RedisKit` | Redis string / hash / list / set / zset | `redis` |
95
95
  | `kit.file_kit` | `FileKit` | 文件与目录读写、移动、遍历 | 无 |
96
96
  | `kit.common_kit` | `CommonKit` | 雪花 ID、时间戳 ID、时间格式化 | 无 |
97
+ | `kit.license_kit` | `LicenseKit` / `LicenseVerifier` | Ed25519 离线授权:密钥对、机器码、签发、验签(签名 → 产品 → 机器码 → 有效期) | 无 |
97
98
  | `kit.docs.anydoc_kit` | `AnyDocKit` | docx / pdf / xlsx 等 → Markdown | `anydoc` |
98
99
  | `kit.docs.docx_kit` | `DocxKit` | Word 模板动态填充(章节 / 列表 / 表格) | `docx` |
99
100
  | `kit.docs.rc_docparser_kit` | `RcDocParserKit` | 离线 docx → Markdown + Images(无模型) | `docx-parse` |
@@ -187,7 +188,7 @@ SQLITE_DB_PATH=./data/sqlite.db
187
188
 
188
189
  ### 环境变量 ↔ 能力对照表
189
190
 
190
- > 表中未出现的模块(`kit.crawl.article_models`、`kit.common_kit`、`kit.docs.docx_kit`、`kit.docs.md2doc_kit`、`kit.crawl.wx_article_kit`、`kit.crawl.web_article_kit`)不读取任何环境变量。
191
+ > 表中未出现的模块(`kit.crawl.article_models`、`kit.crawl.article_io`、`kit.common_kit`、`kit.docs.docx_kit`、`kit.docs.md2doc_kit`、`kit.license_kit`)不读取任何环境变量。
191
192
 
192
193
  **基础与日志**(影响 `kit.config` 及所有模块的日志输出)
193
194
 
@@ -272,6 +273,21 @@ SQLITE_DB_PATH=./data/sqlite.db
272
273
  > `LAYA_HF_*` 同样由 kit 在**导入 laya 之前**经 `os.environ.setdefault` 应用,不覆盖显式配置;
273
274
  > `LAYA_MODEL` / `LAYA_DEVICE` 由 `Settings` 读取(进程内缓存,改后需重启进程)。
274
275
 
276
+ **DeepSeek Search / Fetch** → `kit.crawl.deepseek_kit`
277
+
278
+ | 环境变量 | 默认值 | 作用 |
279
+ |----------|--------|------|
280
+ | `DEEPSEEK_API_KEY` | 无 | Search / Fetch 鉴权 Key(与模型对话共用同一套);缺失时调用抛 `DeepSeekKitError` |
281
+ | `DEEPSEEK_BASE_URL` | `https://api.deepseek.com` | API 基础地址 |
282
+ | `DEEPSEEK_TIMEOUT` | `60`(1–600) | 单次 HTTP 请求超时(秒) |
283
+ | `DEEPSEEK_MAX_RETRIES` | `2`(0–5) | 408 / 429 / 5xx 重试次数(不含首次请求);其它 4xx 不重试 |
284
+
285
+ **文章抓取长度自检** → `kit.crawl.wx_article_kit` / `web_article_kit` / `deepseek_kit`
286
+
287
+ | 环境变量 | 默认值 | 作用 |
288
+ |----------|--------|------|
289
+ | `CRAWL_MIN_CONTENT_CHARS` | `500`(≥0) | 正文短于该长度时写入 `warnings`(非致命,不改成功语义、不截断正文);`0` 关闭该判据 |
290
+
275
291
  **云 OCR**(任何 `ocr="hosted"` 的转换)→ `kit.docs.anydoc_kit`
276
292
 
277
293
  | 环境变量 | 默认值 | 作用 |
@@ -369,9 +385,9 @@ from kit import session_scope, transactional, DBError # 等价
369
385
  | 原生表 | `create_raw_table(table_name, columns, if_not_exists=True) -> None` | 按「列名 → 列定义」建普通表;⚠ 列定义须按目标库方言书写,不做方言翻译 |
370
386
  | 原生表 | `raw_insert(table_name, data, *, ignore_conflict=False) -> int` | 插入一行;`ignore_conflict=True` 幂等(PG/SQLite `ON CONFLICT DO NOTHING`、MySQL `INSERT IGNORE`) |
371
387
  | 原生表 | `raw_update(table_name, data, where, params=None) -> int` | 按 `where`(不含 `WHERE`)更新;启用审计填充时自动补 `updated_*` |
372
- | 原生表 | `raw_touch(table_name, where, params=None) -> int` | **0.6.0**:只刷新 `updated_*`(需先 `enable`;表无该列抛 `DBError`) |
373
- | 原生表 | `raw_delete(table_name, where, params=None) -> int` | **0.6.0**:条件删除(`where` 必填) |
374
- | 原生表 | `build_raw_insert / build_raw_update / build_raw_touch / build_raw_delete` | **0.6.0**:只拼装不执行,返回 `(语句, 参数)`,供**自有事务**内 `session.execute(*builder(...))` |
388
+ | 原生表 | `raw_touch(table_name, where, params=None) -> int` | 只刷新 `updated_*`(需先 `enable`;表无该列抛 `DBError`) |
389
+ | 原生表 | `raw_delete(table_name, where, params=None) -> int` | 条件删除(`where` 必填) |
390
+ | 原生表 | `build_raw_insert / build_raw_update / build_raw_touch / build_raw_delete` | 只拼装不执行,返回 `(语句, 参数)`,供**自有事务**内 `session.execute(*builder(...))` |
375
391
  | 原生表 | `raw_get_by(table_name, column, value) -> dict \| None` | 按单列等值取首行 |
376
392
  | 事务 | `transaction()` | 事务上下文管理器,`with PgKit.transaction() as session:` |
377
393
  | 表 | `list_tables() -> list[str]` | 列出全部表名 |
@@ -505,57 +521,9 @@ PgKit.insert_vectors("my_vec", [[0.1] * 384], ids=[1], id_col="vid", column="vec
505
521
  PgKit.search_vectors("my_vec", [0.1] * 384, top_k=5, id_col="vid", column="vec")
506
522
  ```
507
523
 
508
- #### 0.2.2 行为变更(升级须知)
509
-
510
- | 编号 | 变更 | 影响 |
511
- |------|------|------|
512
- | B1 | `search_vectors` 新增 `exact: bool = True`,默认**精确** | SQLite 返回的 `distance` 由量化值(≈L2×255)变为**真值 L2**;需旧口径请显式 `exact=False` |
513
- | B2 | `filter_sql` 语义统一为**先过滤、再取 top_k** | 传 `filter_sql` 的调用返回值**变多/变全**(旧结果 ⊆ 新结果);曾自行放大 `top_k` 兜底的调用可收紧 |
514
- | B3 | 默认 `distance_type` 由 `"COSINE"` 改为 `"L2"` | 依赖默认值的调用方请显式传 `distance_type="COSINE"` |
515
- | B4 | `create_vector_table` 的 `dims` **必填**,参数顺序为 `(table_name, dims, id_col, column, extra_cols)` | `create_vector_table("t")` 类调用需补维度;位置传 `id_col` 的调用改为关键字 |
516
- | B5 | MySQL 的 `search_vectors` 异常由 `NotImplementedError` 改为 `VectorNotSupportedError`(继承 `DBError`) | `except NotImplementedError` 需改为 `except VectorNotSupportedError`(或 `except DBError`) |
517
-
518
- > 新增(非破坏性):`VECTOR_CAPABILITIES` 能力声明、`id_col` / `column` 逃生口参数、
519
- > `insert_vectors` 支持二维 numpy 数组与批量提交。
520
-
521
- #### 0.2.6 变更
522
-
523
- | 编号 | 变更 | 影响 |
524
- |------|------|------|
525
- | D1 | `prepare_statement` 的 `params` 支持**参数行列表**(`list[dict]`):**原样返回该列表** → `session.execute(*prepare_statement(sql, params))` 即批量执行(executemany),与 `build_insert_vectors` 的返回形态直接对接;行键集不一致或行值含序列 → 本地可操作 `ValueError`(与 `execute` 批量形态**同一规则**) | 向后兼容:单字典 / `None` 形态行为不变;行列表形态**不做** `IN` 展开(与批量互斥) |
526
- | D1 | 文档:修正「自有事务」示例——0.2.5 的示例把 `build_insert_vectors` 返回的**行列表**传给了单字典形态,照抄会抛 `AttributeError: 'list' object has no attribute 'items'` | **纯文档缺陷修正**(0.2.5 示例照抄即崩) |
527
- | — | 文档:批量执行补充 `rowcount` 备注——忽略型写入(如 SQLite `INSERT OR IGNORE`)的冲突行不计入 `rowcount`(即 `rowcount` = 实际插入数;⚠ 仅 SQLite 实测) | 文档 |
528
-
529
- #### 0.2.5 变更
530
-
531
- | 编号 | 变更 | 影响 |
532
- |------|------|------|
533
- | — | 新增**模块级公共函数** `prepare_statement(sql, params=None) -> (语句, 归一化参数)`:供调用方在**自有事务**内执行原生 SQL(含 `IN :ids`),**调用方无须 import `sqlalchemy`**;与 `query` / `execute` 同一实现(单一事实源) | **纯新增**:`query` / `transaction` 的签名与行为**零变更**;共享方法数仍为 **24**(模块级函数不计入) |
534
- | — | `execute` 的 `params` 支持**字典列表**(executemany,同一语句 + N 行参数一次往返);**空列表**不触库返回 `0` | 向后兼容:单字典 / `None` 形态行为不变;批量要求各行字典**键集一致**、行值为**标量**(与 `IN` 展开互斥),违规在本地抛可操作 `ValueError`(含行号),不触库 |
535
- | — | 文档:自有事务示例改用 `prepare_statement`(不再需要 `from sqlalchemy import text`);补「批量执行」说明与约束 | 文档 |
536
-
537
- > ⚠ `:name::type` 这类 PostgreSQL 风格 cast **不被 SQLAlchemy 的 `text()` 识别**(0.2.5 之前亦如此),
524
+ > ⚠ `:name::type` 这类 PostgreSQL 风格 cast **不被 SQLAlchemy 的 `text()` 识别**,
538
525
  > 请改用 `CAST(:name AS type)`。
539
526
 
540
- #### 0.2.4 变更
541
-
542
- | 编号 | 变更 | 影响 |
543
- |------|------|------|
544
- | B4 | **入参自洽性校验**(`extra_data` 的键):保留绑定名 `id` / `vec`、与 `id_col` / `column` 重复、不是合法标识符、`id_col == column` → 抛可操作的 `ValueError` | **唯一行为变更**:此前 SQLite 会**静默写坏数据**(如 `id` 列被覆盖)、PG/MySQL 报误导性错误;正常调用**零影响** |
545
- | — | 新增共享方法 `build_insert_vectors`(只拼装不执行,由 `BaseDbKit` 收敛 → 共享方法 23 → **24**) | 纯新增:可在**自有事务**内写向量行(见下) |
546
- | — | `create_vector_table` 的 `extra_cols` 支持 `{列名: 类型串}`(与 `create_raw_table` 同形) | 纯新增:既有 `[{"name","type"}]` 形态保留,两者产出 DDL 逐字一致 |
547
- | — | 后端差异收口:向量列占位符由 `VECTOR_PLACEHOLDER` 驱动(MySQL = `STRING_TO_VECTOR(:vec)`) | 内部整理:公开签名与行为不变 |
548
- | — | `SQLiteKit.insert_vectors` 的事务获取统一为 `transaction()`(与另两库同构) | 内部一致:行为等价(自成一事务并提交) |
549
-
550
- #### 0.2.3 变更
551
-
552
- | 编号 | 变更 | 影响 |
553
- |------|------|------|
554
- | B6 | **`PgKit.get_table_info` 按数据库解析语义定位表**(`search_path` 解析到的那张表) | 多 schema 下同名表**不再混行**;单 schema / `public` 场景**无影响**;只接受非限定表名 |
555
- | — | `query` / `execute` 支持**列表参数**(`IN :ids`,三库通用) | 纯新增;`set`/`frozenset` 自动归一化为 `list` |
556
- | — | 新增 `SQLiteKit.encode_vector` / `decode_vector`(小端 float32) | 纯新增:可在自有事务内写向量表,见下 |
557
- | — | 导出面统一:`from kit.db import PgKit, MySqlKit, SQLiteKit, build_pg_url, …` | 纯新增;既有导出未删除/未改名 |
558
-
559
527
  **两段查询范式(推荐用法)**:`search_vectors` 只返回 `[{"id", "distance"}]`,载荷二次取回:
560
528
 
561
529
  ```python
@@ -670,7 +638,7 @@ AuditConfig.set_fields(None) # 恢复默认
670
638
  - 写方法与 `query()` 手写 SQL 不追加过滤条件;`upsert` 的冲突回查跳过过滤。
671
639
  - `user_resolver` 返回 `None` / 空串或抛异常时跳过 `*_by` 填充,不影响写入。
672
640
 
673
- **原生写工具(0.6.0)**:`raw_insert` / `raw_update` / `raw_touch` / `raw_delete` 自带事务;
641
+ **原生写工具**:`raw_insert` / `raw_update` / `raw_touch` / `raw_delete` 自带事务;
674
642
  `build_raw_*` 只拼装(返回 `(语句, 参数)`),供 `with Kit.transaction() as session:` 内使用:
675
643
 
676
644
  ```python
@@ -943,6 +911,61 @@ CommonKit.format_current_time("yyyy-MM-dd")
943
911
 
944
912
  ---
945
913
 
914
+ ## kit.license_kit —— 离线授权签发与校验
915
+
916
+ 零第三方依赖(`cryptography` 已在 base),按需 import。校验链:签名 → 产品 → 机器码 → 有效期。
917
+
918
+ ```python
919
+ from datetime import datetime, timezone
920
+ from pathlib import Path
921
+
922
+ from kit.license_kit import LicenseKit, LicensePayload, LicenseVerifier
923
+
924
+ # 1. 生成密钥对(一次性;私钥自行保管,不随包分发)
925
+ private_pem, public_pem = LicenseKit.generate_keypair()
926
+
927
+ # 2. 取机器码(16 位十六进制;仅 Windows 支持)
928
+ machine = LicenseKit.get_machine_code()
929
+ LicenseKit.export_fingerprint_file(Path("fingerprint.txt")) # 或导出文件交给签发方
930
+ machine = LicenseKit.parse_fingerprint_file(Path("fingerprint.txt"))
931
+
932
+ # 3. 签发(离线)
933
+ payload = LicensePayload(
934
+ product="thinktank",
935
+ machine_code=machine,
936
+ tier="pro",
937
+ customer_id="C-0001",
938
+ expires_at=datetime(2027, 12, 31, tzinfo=timezone.utc),
939
+ )
940
+ token = LicenseKit.sign(payload, private_pem)
941
+
942
+ # 4. 校验(应用启动 / 请求时)
943
+ verifier = LicenseVerifier(public_pem, Path("license.json"))
944
+ claims = verifier.verify(product="thinktank", machine_code=machine)
945
+ print(claims.tier, claims.expires_at)
946
+ ```
947
+
948
+ | 符号 | 说明 |
949
+ |------|------|
950
+ | `LicenseKit.generate_keypair() -> (str, str)` | 生成 Ed25519(私钥 PEM, 公钥 PEM) |
951
+ | `LicenseKit.get_machine_code() -> str` | 本机机器码(16 位十六进制;⚠ 仅 Windows 平台) |
952
+ | `LicenseKit.export_fingerprint_file(path) -> Path` | 导出机器指纹文件 |
953
+ | `LicenseKit.parse_fingerprint_file(path) -> str` | 从指纹文件读回机器码 |
954
+ | `LicenseKit.sign(payload, private_key_pem) -> str` | 签发授权 token |
955
+ | `LicenseVerifier(public_key_pem, license_path)` | 构造验签器(绑定公钥与 license 文件) |
956
+ | `LicenseVerifier.verify(*, product, machine_code, now=None) -> LicensePayload` | 全量校验并返回载荷;`now=None` 取 aware UTC,naive 按 UTC 解释 |
957
+ | `LicenseVerifier.reverify() -> LicensePayload` | 用最近一次成功 `verify` 的上下文重新校验(文件被换会失败) |
958
+ | `LicenseVerifier.file_signature` | 当前 license 文件的签名(属性) |
959
+
960
+ `LicensePayload` 字段:`version` / `product` / `machine_code` / `tier` / `customer_id` /
961
+ `issued_at` / `expires_at`。
962
+
963
+ 异常:`LicenseError`(基类)、`LicenseFormatError`、`LicenseSignatureError`、
964
+ `LicenseProductMismatchError`、`LicenseMachineMismatchError`、`LicenseExpiredError`、
965
+ `LicenseUnsupportedPlatformError`(非 Windows 取机器码时抛)。
966
+
967
+ ---
968
+
946
969
  ## kit.docs.anydoc_kit —— 文档转 Markdown
947
970
 
948
971
  安装:`pip install "sdpy-kit[anydoc]"`(支持 `.doc .docx .ppt .pptx .xls .xlsx .csv .pdf`)
@@ -4,7 +4,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
4
4
 
5
5
  - **统一数据库访问层**:PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致,切换数据库只改一行 import;内置审计字段自动填充(`created_*` / `updated_*` / `deleted`)与软删除查询过滤;
6
6
  - **AI 多模型服务池**:chat / embedding / rerank / image 四类能力,多厂商候选链 + 故障自动转移;
7
- - **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(PDF + 图片 → md + images/) / 离线 docx 解析(md + images/) / Laya 类型化决策 / 微信公众号文章抓取存储 / 通用网页文章抓取存储。
7
+ - **工具集**:Redis / 文件 / 雪花 ID / 离线授权签发与校验 / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(PDF + 图片 → md + images/) / 离线 docx 解析(md + images/) / Laya 类型化决策 / 微信公众号文章抓取存储 / 通用网页文章抓取存储 / DeepSeek 搜索与抓取。
8
8
 
9
9
  > 要求 Python 3.13+
10
10
 
@@ -18,6 +18,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
18
18
  | `kit.redis_kit` | `RedisKit` | Redis string / hash / list / set / zset | `redis` |
19
19
  | `kit.file_kit` | `FileKit` | 文件与目录读写、移动、遍历 | 无 |
20
20
  | `kit.common_kit` | `CommonKit` | 雪花 ID、时间戳 ID、时间格式化 | 无 |
21
+ | `kit.license_kit` | `LicenseKit` / `LicenseVerifier` | Ed25519 离线授权:密钥对、机器码、签发、验签(签名 → 产品 → 机器码 → 有效期) | 无 |
21
22
  | `kit.docs.anydoc_kit` | `AnyDocKit` | docx / pdf / xlsx 等 → Markdown | `anydoc` |
22
23
  | `kit.docs.docx_kit` | `DocxKit` | Word 模板动态填充(章节 / 列表 / 表格) | `docx` |
23
24
  | `kit.docs.rc_docparser_kit` | `RcDocParserKit` | 离线 docx → Markdown + Images(无模型) | `docx-parse` |
@@ -111,7 +112,7 @@ SQLITE_DB_PATH=./data/sqlite.db
111
112
 
112
113
  ### 环境变量 ↔ 能力对照表
113
114
 
114
- > 表中未出现的模块(`kit.crawl.article_models`、`kit.common_kit`、`kit.docs.docx_kit`、`kit.docs.md2doc_kit`、`kit.crawl.wx_article_kit`、`kit.crawl.web_article_kit`)不读取任何环境变量。
115
+ > 表中未出现的模块(`kit.crawl.article_models`、`kit.crawl.article_io`、`kit.common_kit`、`kit.docs.docx_kit`、`kit.docs.md2doc_kit`、`kit.license_kit`)不读取任何环境变量。
115
116
 
116
117
  **基础与日志**(影响 `kit.config` 及所有模块的日志输出)
117
118
 
@@ -196,6 +197,21 @@ SQLITE_DB_PATH=./data/sqlite.db
196
197
  > `LAYA_HF_*` 同样由 kit 在**导入 laya 之前**经 `os.environ.setdefault` 应用,不覆盖显式配置;
197
198
  > `LAYA_MODEL` / `LAYA_DEVICE` 由 `Settings` 读取(进程内缓存,改后需重启进程)。
198
199
 
200
+ **DeepSeek Search / Fetch** → `kit.crawl.deepseek_kit`
201
+
202
+ | 环境变量 | 默认值 | 作用 |
203
+ |----------|--------|------|
204
+ | `DEEPSEEK_API_KEY` | 无 | Search / Fetch 鉴权 Key(与模型对话共用同一套);缺失时调用抛 `DeepSeekKitError` |
205
+ | `DEEPSEEK_BASE_URL` | `https://api.deepseek.com` | API 基础地址 |
206
+ | `DEEPSEEK_TIMEOUT` | `60`(1–600) | 单次 HTTP 请求超时(秒) |
207
+ | `DEEPSEEK_MAX_RETRIES` | `2`(0–5) | 408 / 429 / 5xx 重试次数(不含首次请求);其它 4xx 不重试 |
208
+
209
+ **文章抓取长度自检** → `kit.crawl.wx_article_kit` / `web_article_kit` / `deepseek_kit`
210
+
211
+ | 环境变量 | 默认值 | 作用 |
212
+ |----------|--------|------|
213
+ | `CRAWL_MIN_CONTENT_CHARS` | `500`(≥0) | 正文短于该长度时写入 `warnings`(非致命,不改成功语义、不截断正文);`0` 关闭该判据 |
214
+
199
215
  **云 OCR**(任何 `ocr="hosted"` 的转换)→ `kit.docs.anydoc_kit`
200
216
 
201
217
  | 环境变量 | 默认值 | 作用 |
@@ -293,9 +309,9 @@ from kit import session_scope, transactional, DBError # 等价
293
309
  | 原生表 | `create_raw_table(table_name, columns, if_not_exists=True) -> None` | 按「列名 → 列定义」建普通表;⚠ 列定义须按目标库方言书写,不做方言翻译 |
294
310
  | 原生表 | `raw_insert(table_name, data, *, ignore_conflict=False) -> int` | 插入一行;`ignore_conflict=True` 幂等(PG/SQLite `ON CONFLICT DO NOTHING`、MySQL `INSERT IGNORE`) |
295
311
  | 原生表 | `raw_update(table_name, data, where, params=None) -> int` | 按 `where`(不含 `WHERE`)更新;启用审计填充时自动补 `updated_*` |
296
- | 原生表 | `raw_touch(table_name, where, params=None) -> int` | **0.6.0**:只刷新 `updated_*`(需先 `enable`;表无该列抛 `DBError`) |
297
- | 原生表 | `raw_delete(table_name, where, params=None) -> int` | **0.6.0**:条件删除(`where` 必填) |
298
- | 原生表 | `build_raw_insert / build_raw_update / build_raw_touch / build_raw_delete` | **0.6.0**:只拼装不执行,返回 `(语句, 参数)`,供**自有事务**内 `session.execute(*builder(...))` |
312
+ | 原生表 | `raw_touch(table_name, where, params=None) -> int` | 只刷新 `updated_*`(需先 `enable`;表无该列抛 `DBError`) |
313
+ | 原生表 | `raw_delete(table_name, where, params=None) -> int` | 条件删除(`where` 必填) |
314
+ | 原生表 | `build_raw_insert / build_raw_update / build_raw_touch / build_raw_delete` | 只拼装不执行,返回 `(语句, 参数)`,供**自有事务**内 `session.execute(*builder(...))` |
299
315
  | 原生表 | `raw_get_by(table_name, column, value) -> dict \| None` | 按单列等值取首行 |
300
316
  | 事务 | `transaction()` | 事务上下文管理器,`with PgKit.transaction() as session:` |
301
317
  | 表 | `list_tables() -> list[str]` | 列出全部表名 |
@@ -429,57 +445,9 @@ PgKit.insert_vectors("my_vec", [[0.1] * 384], ids=[1], id_col="vid", column="vec
429
445
  PgKit.search_vectors("my_vec", [0.1] * 384, top_k=5, id_col="vid", column="vec")
430
446
  ```
431
447
 
432
- #### 0.2.2 行为变更(升级须知)
433
-
434
- | 编号 | 变更 | 影响 |
435
- |------|------|------|
436
- | B1 | `search_vectors` 新增 `exact: bool = True`,默认**精确** | SQLite 返回的 `distance` 由量化值(≈L2×255)变为**真值 L2**;需旧口径请显式 `exact=False` |
437
- | B2 | `filter_sql` 语义统一为**先过滤、再取 top_k** | 传 `filter_sql` 的调用返回值**变多/变全**(旧结果 ⊆ 新结果);曾自行放大 `top_k` 兜底的调用可收紧 |
438
- | B3 | 默认 `distance_type` 由 `"COSINE"` 改为 `"L2"` | 依赖默认值的调用方请显式传 `distance_type="COSINE"` |
439
- | B4 | `create_vector_table` 的 `dims` **必填**,参数顺序为 `(table_name, dims, id_col, column, extra_cols)` | `create_vector_table("t")` 类调用需补维度;位置传 `id_col` 的调用改为关键字 |
440
- | B5 | MySQL 的 `search_vectors` 异常由 `NotImplementedError` 改为 `VectorNotSupportedError`(继承 `DBError`) | `except NotImplementedError` 需改为 `except VectorNotSupportedError`(或 `except DBError`) |
441
-
442
- > 新增(非破坏性):`VECTOR_CAPABILITIES` 能力声明、`id_col` / `column` 逃生口参数、
443
- > `insert_vectors` 支持二维 numpy 数组与批量提交。
444
-
445
- #### 0.2.6 变更
446
-
447
- | 编号 | 变更 | 影响 |
448
- |------|------|------|
449
- | D1 | `prepare_statement` 的 `params` 支持**参数行列表**(`list[dict]`):**原样返回该列表** → `session.execute(*prepare_statement(sql, params))` 即批量执行(executemany),与 `build_insert_vectors` 的返回形态直接对接;行键集不一致或行值含序列 → 本地可操作 `ValueError`(与 `execute` 批量形态**同一规则**) | 向后兼容:单字典 / `None` 形态行为不变;行列表形态**不做** `IN` 展开(与批量互斥) |
450
- | D1 | 文档:修正「自有事务」示例——0.2.5 的示例把 `build_insert_vectors` 返回的**行列表**传给了单字典形态,照抄会抛 `AttributeError: 'list' object has no attribute 'items'` | **纯文档缺陷修正**(0.2.5 示例照抄即崩) |
451
- | — | 文档:批量执行补充 `rowcount` 备注——忽略型写入(如 SQLite `INSERT OR IGNORE`)的冲突行不计入 `rowcount`(即 `rowcount` = 实际插入数;⚠ 仅 SQLite 实测) | 文档 |
452
-
453
- #### 0.2.5 变更
454
-
455
- | 编号 | 变更 | 影响 |
456
- |------|------|------|
457
- | — | 新增**模块级公共函数** `prepare_statement(sql, params=None) -> (语句, 归一化参数)`:供调用方在**自有事务**内执行原生 SQL(含 `IN :ids`),**调用方无须 import `sqlalchemy`**;与 `query` / `execute` 同一实现(单一事实源) | **纯新增**:`query` / `transaction` 的签名与行为**零变更**;共享方法数仍为 **24**(模块级函数不计入) |
458
- | — | `execute` 的 `params` 支持**字典列表**(executemany,同一语句 + N 行参数一次往返);**空列表**不触库返回 `0` | 向后兼容:单字典 / `None` 形态行为不变;批量要求各行字典**键集一致**、行值为**标量**(与 `IN` 展开互斥),违规在本地抛可操作 `ValueError`(含行号),不触库 |
459
- | — | 文档:自有事务示例改用 `prepare_statement`(不再需要 `from sqlalchemy import text`);补「批量执行」说明与约束 | 文档 |
460
-
461
- > ⚠ `:name::type` 这类 PostgreSQL 风格 cast **不被 SQLAlchemy 的 `text()` 识别**(0.2.5 之前亦如此),
448
+ > ⚠ `:name::type` 这类 PostgreSQL 风格 cast **不被 SQLAlchemy 的 `text()` 识别**,
462
449
  > 请改用 `CAST(:name AS type)`。
463
450
 
464
- #### 0.2.4 变更
465
-
466
- | 编号 | 变更 | 影响 |
467
- |------|------|------|
468
- | B4 | **入参自洽性校验**(`extra_data` 的键):保留绑定名 `id` / `vec`、与 `id_col` / `column` 重复、不是合法标识符、`id_col == column` → 抛可操作的 `ValueError` | **唯一行为变更**:此前 SQLite 会**静默写坏数据**(如 `id` 列被覆盖)、PG/MySQL 报误导性错误;正常调用**零影响** |
469
- | — | 新增共享方法 `build_insert_vectors`(只拼装不执行,由 `BaseDbKit` 收敛 → 共享方法 23 → **24**) | 纯新增:可在**自有事务**内写向量行(见下) |
470
- | — | `create_vector_table` 的 `extra_cols` 支持 `{列名: 类型串}`(与 `create_raw_table` 同形) | 纯新增:既有 `[{"name","type"}]` 形态保留,两者产出 DDL 逐字一致 |
471
- | — | 后端差异收口:向量列占位符由 `VECTOR_PLACEHOLDER` 驱动(MySQL = `STRING_TO_VECTOR(:vec)`) | 内部整理:公开签名与行为不变 |
472
- | — | `SQLiteKit.insert_vectors` 的事务获取统一为 `transaction()`(与另两库同构) | 内部一致:行为等价(自成一事务并提交) |
473
-
474
- #### 0.2.3 变更
475
-
476
- | 编号 | 变更 | 影响 |
477
- |------|------|------|
478
- | B6 | **`PgKit.get_table_info` 按数据库解析语义定位表**(`search_path` 解析到的那张表) | 多 schema 下同名表**不再混行**;单 schema / `public` 场景**无影响**;只接受非限定表名 |
479
- | — | `query` / `execute` 支持**列表参数**(`IN :ids`,三库通用) | 纯新增;`set`/`frozenset` 自动归一化为 `list` |
480
- | — | 新增 `SQLiteKit.encode_vector` / `decode_vector`(小端 float32) | 纯新增:可在自有事务内写向量表,见下 |
481
- | — | 导出面统一:`from kit.db import PgKit, MySqlKit, SQLiteKit, build_pg_url, …` | 纯新增;既有导出未删除/未改名 |
482
-
483
451
  **两段查询范式(推荐用法)**:`search_vectors` 只返回 `[{"id", "distance"}]`,载荷二次取回:
484
452
 
485
453
  ```python
@@ -594,7 +562,7 @@ AuditConfig.set_fields(None) # 恢复默认
594
562
  - 写方法与 `query()` 手写 SQL 不追加过滤条件;`upsert` 的冲突回查跳过过滤。
595
563
  - `user_resolver` 返回 `None` / 空串或抛异常时跳过 `*_by` 填充,不影响写入。
596
564
 
597
- **原生写工具(0.6.0)**:`raw_insert` / `raw_update` / `raw_touch` / `raw_delete` 自带事务;
565
+ **原生写工具**:`raw_insert` / `raw_update` / `raw_touch` / `raw_delete` 自带事务;
598
566
  `build_raw_*` 只拼装(返回 `(语句, 参数)`),供 `with Kit.transaction() as session:` 内使用:
599
567
 
600
568
  ```python
@@ -867,6 +835,61 @@ CommonKit.format_current_time("yyyy-MM-dd")
867
835
 
868
836
  ---
869
837
 
838
+ ## kit.license_kit —— 离线授权签发与校验
839
+
840
+ 零第三方依赖(`cryptography` 已在 base),按需 import。校验链:签名 → 产品 → 机器码 → 有效期。
841
+
842
+ ```python
843
+ from datetime import datetime, timezone
844
+ from pathlib import Path
845
+
846
+ from kit.license_kit import LicenseKit, LicensePayload, LicenseVerifier
847
+
848
+ # 1. 生成密钥对(一次性;私钥自行保管,不随包分发)
849
+ private_pem, public_pem = LicenseKit.generate_keypair()
850
+
851
+ # 2. 取机器码(16 位十六进制;仅 Windows 支持)
852
+ machine = LicenseKit.get_machine_code()
853
+ LicenseKit.export_fingerprint_file(Path("fingerprint.txt")) # 或导出文件交给签发方
854
+ machine = LicenseKit.parse_fingerprint_file(Path("fingerprint.txt"))
855
+
856
+ # 3. 签发(离线)
857
+ payload = LicensePayload(
858
+ product="thinktank",
859
+ machine_code=machine,
860
+ tier="pro",
861
+ customer_id="C-0001",
862
+ expires_at=datetime(2027, 12, 31, tzinfo=timezone.utc),
863
+ )
864
+ token = LicenseKit.sign(payload, private_pem)
865
+
866
+ # 4. 校验(应用启动 / 请求时)
867
+ verifier = LicenseVerifier(public_pem, Path("license.json"))
868
+ claims = verifier.verify(product="thinktank", machine_code=machine)
869
+ print(claims.tier, claims.expires_at)
870
+ ```
871
+
872
+ | 符号 | 说明 |
873
+ |------|------|
874
+ | `LicenseKit.generate_keypair() -> (str, str)` | 生成 Ed25519(私钥 PEM, 公钥 PEM) |
875
+ | `LicenseKit.get_machine_code() -> str` | 本机机器码(16 位十六进制;⚠ 仅 Windows 平台) |
876
+ | `LicenseKit.export_fingerprint_file(path) -> Path` | 导出机器指纹文件 |
877
+ | `LicenseKit.parse_fingerprint_file(path) -> str` | 从指纹文件读回机器码 |
878
+ | `LicenseKit.sign(payload, private_key_pem) -> str` | 签发授权 token |
879
+ | `LicenseVerifier(public_key_pem, license_path)` | 构造验签器(绑定公钥与 license 文件) |
880
+ | `LicenseVerifier.verify(*, product, machine_code, now=None) -> LicensePayload` | 全量校验并返回载荷;`now=None` 取 aware UTC,naive 按 UTC 解释 |
881
+ | `LicenseVerifier.reverify() -> LicensePayload` | 用最近一次成功 `verify` 的上下文重新校验(文件被换会失败) |
882
+ | `LicenseVerifier.file_signature` | 当前 license 文件的签名(属性) |
883
+
884
+ `LicensePayload` 字段:`version` / `product` / `machine_code` / `tier` / `customer_id` /
885
+ `issued_at` / `expires_at`。
886
+
887
+ 异常:`LicenseError`(基类)、`LicenseFormatError`、`LicenseSignatureError`、
888
+ `LicenseProductMismatchError`、`LicenseMachineMismatchError`、`LicenseExpiredError`、
889
+ `LicenseUnsupportedPlatformError`(非 Windows 取机器码时抛)。
890
+
891
+ ---
892
+
870
893
  ## kit.docs.anydoc_kit —— 文档转 Markdown
871
894
 
872
895
  安装:`pip install "sdpy-kit[anydoc]"`(支持 `.doc .docx .ppt .pptx .xls .xlsx .csv .pdf`)
@@ -23,7 +23,7 @@ Author: Clark Chang
23
23
  数据库访问通过 kit.db 子包提供(基于 SQLModel)。
24
24
  """
25
25
 
26
- __version__ = "0.6.1"
26
+ __version__ = "0.6.2"
27
27
 
28
28
  from kit.db import (
29
29
  MYSQL,
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