sdpy-kit 0.4.2__tar.gz → 0.4.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.
Files changed (49) hide show
  1. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/PKG-INFO +1 -1
  2. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/__init__.py +1 -1
  3. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/license_kit.py +154 -147
  4. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/.gitignore +0 -0
  5. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/README.md +0 -0
  6. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/__init__.py +0 -0
  7. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/base_service.py +0 -0
  8. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/chat_service.py +0 -0
  9. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/embedding_service.py +0 -0
  10. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/exceptions.py +0 -0
  11. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/failover.py +0 -0
  12. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/image_service.py +0 -0
  13. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/model_builder.py +0 -0
  14. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/model_client.py +0 -0
  15. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/pool.py +0 -0
  16. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/profiles.py +0 -0
  17. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/rerank_service.py +0 -0
  18. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/runner.py +0 -0
  19. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/ai/types.py +0 -0
  20. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/anydoc_kit.py +0 -0
  21. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/common_kit.py +0 -0
  22. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/config/__init__.py +0 -0
  23. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/config/kit_config.py +0 -0
  24. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/config/log_config.py +0 -0
  25. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/crawl/__init__.py +0 -0
  26. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/crawl/article_models.py +0 -0
  27. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/crawl/web_article_kit.py +0 -0
  28. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/crawl/wx_article_kit.py +0 -0
  29. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/db/__init__.py +0 -0
  30. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/db/base_db_kit.py +0 -0
  31. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/db/crud.py +0 -0
  32. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/db/engine.py +0 -0
  33. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/db/exceptions.py +0 -0
  34. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/db/models/__init__.py +0 -0
  35. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/db/models/base.py +0 -0
  36. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/db/mysql_kit.py +0 -0
  37. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/db/pg_kit.py +0 -0
  38. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/db/query.py +0 -0
  39. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/db/session.py +0 -0
  40. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/db/sqlite_kit.py +0 -0
  41. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/db/transaction.py +0 -0
  42. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/docx_kit.py +0 -0
  43. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/file_kit.py +0 -0
  44. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/laya_kit.py +0 -0
  45. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/marker_kit.py +0 -0
  46. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/md2doc_kit.py +0 -0
  47. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/mineru_kit.py +0 -0
  48. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/kit/redis_kit.py +0 -0
  49. {sdpy_kit-0.4.2 → sdpy_kit-0.4.3}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sdpy-kit
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/Redis/文件/文档/向量工具集
5
5
  Author-email: Clark <changhongyuan@126.com>
6
6
  License-Expression: MIT
@@ -23,7 +23,7 @@ Author: Clark Chang
23
23
  数据库访问通过 kit.db 子包提供(基于 SQLModel)。
24
24
  """
25
25
 
26
- __version__ = "0.4.2"
26
+ __version__ = "0.4.3"
27
27
 
28
28
  from kit.db import (
29
29
  MYSQL,
@@ -23,9 +23,9 @@ Author: Clark Chang
23
23
  面向单机分发场景的轻量授权体系(防君子定位):应用以打包 exe 形式离线分发,
24
24
  无远端授权服务。工作流:
25
25
 
26
- 1. 客户机 `export_fingerprint_file()` 导出 `.fp` 指纹文件;
27
- 2. 签发方 `parse_fingerprint_file()` 解析,填入 `LicensePayload` 后
28
- `sign()` 生成 `.lic` 授权文件回签;
26
+ 1. 客户机 `LicenseKit.export_fingerprint_file()` 导出 `.fp` 指纹文件;
27
+ 2. 签发方 `LicenseKit.parse_fingerprint_file()` 解析,填入 `LicensePayload`
28
+ 后 `LicenseKit.sign()` 生成 `.lic` 授权文件回签;
29
29
  3. 客户机 `LicenseVerifier.verify()` 验签 + 绑定校验(product / 机器码 / 有效期)。
30
30
 
31
31
  安全边界(务必知悉):
@@ -41,19 +41,20 @@ Author: Clark Chang
41
41
  规范化 JSON 序列化后签名,杜绝字段顺序 / 空白差异导致验签漂移。
42
42
 
43
43
  用法:
44
- from kit.license_kit import (
45
- LicensePayload, LicenseVerifier, export_fingerprint_file,
46
- generate_keypair, parse_fingerprint_file, sign,
47
- )
44
+ from kit.license_kit import LicenseKit, LicensePayload, LicenseVerifier
48
45
 
49
- private_pem, public_pem = generate_keypair()
46
+ private_pem, public_pem = LicenseKit.generate_keypair()
50
47
  payload = LicensePayload(product="myapp", machine_code="0123456789abcdef",
51
48
  issued_at=datetime.now(timezone.utc),
52
49
  expires_at=None)
53
- lic_text = sign(payload, private_pem)
50
+ lic_text = LicenseKit.sign(payload, private_pem)
54
51
 
55
52
  verifier = LicenseVerifier(public_pem, Path("license.lic"))
56
53
  info = verifier.verify(product="myapp", machine_code="0123456789abcdef")
54
+
55
+ 形态说明:0.4.2 为模块级函数,0.4.3 起收进静态工具类 `LicenseKit`
56
+ (与 FileKit / LayaKit 等其它 kit 形态一致);`LicenseVerifier` 因持有
57
+ 公钥缓存与验签上下文实例状态,保持独立类。
57
58
  """
58
59
 
59
60
  import base64
@@ -125,7 +126,7 @@ class LicenseExpiredError(LicenseError):
125
126
 
126
127
 
127
128
  # ---------------------------------------------------------------------------
128
- # 机器指纹(Windows 专属;winreg/ctypes 延迟导入保证模块可在任意平台 import)
129
+ # 机器指纹私有实现(Windows 专属;winreg/ctypes 延迟导入保证模块任意平台可 import)
129
130
  # ---------------------------------------------------------------------------
130
131
 
131
132
 
@@ -175,118 +176,14 @@ def _collect_components() -> tuple[str, str]:
175
176
  return _read_machine_guid(), _read_volume_serial()
176
177
 
177
178
 
178
- def get_machine_code() -> str:
179
- """计算本机机器码:组合指纹 SHA256 短码(16 hex 小写)
180
-
181
- Returns:
182
- str: 16 位十六进制机器码
183
-
184
- Raises:
185
- LicenseUnsupportedPlatformError: 非 Windows 平台
186
- LicenseFormatError: 指纹组件读取失败
187
- """
188
- machine_guid, volume_serial = _collect_components()
179
+ def _machine_code_of(machine_guid: str, volume_serial: str) -> str:
180
+ """由原始组件计算机器码短码(签发与导出共用,避免两处公式漂移)"""
189
181
  digest = hashlib.sha256(f"{machine_guid}|{volume_serial}".encode("utf-8"))
190
182
  return digest.hexdigest()[:MACHINE_CODE_LENGTH]
191
183
 
192
184
 
193
- def export_fingerprint_file(path: Path) -> Path:
194
- """导出指纹文件(.fp):机器码 + 原始组件 + 导出时间
195
-
196
- 原始组件仅用于报障诊断(作者侧可看出哪个组件不匹配),验签只比短码。
197
-
198
- Args:
199
- path: 指纹文件输出路径
200
-
201
- Returns:
202
- Path: 实际写入的文件路径
203
-
204
- Raises:
205
- LicenseUnsupportedPlatformError: 非 Windows 平台
206
- LicenseFormatError: 指纹组件读取失败
207
- """
208
- machine_guid, volume_serial = _collect_components()
209
- machine_code = hashlib.sha256(
210
- f"{machine_guid}|{volume_serial}".encode("utf-8")
211
- ).hexdigest()[:MACHINE_CODE_LENGTH]
212
- document = {
213
- "kind": _FINGERPRINT_KIND,
214
- "format_version": FINGERPRINT_FORMAT_VERSION,
215
- "machine_code": machine_code,
216
- "components": {
217
- "machine_guid": machine_guid,
218
- "volume_serial": volume_serial,
219
- },
220
- "exported_at": datetime.now(timezone.utc).isoformat(),
221
- }
222
- target = Path(path)
223
- target.parent.mkdir(parents=True, exist_ok=True)
224
- target.write_text(
225
- json.dumps(document, ensure_ascii=False, indent=2), encoding="utf-8"
226
- )
227
- logger.info("指纹文件已导出:%s(机器码 %s)", target, machine_code)
228
- return target
229
-
230
-
231
- def parse_fingerprint_file(path: Path) -> str:
232
- """解析指纹文件,返回机器码(签发侧使用;跨机器无法重算原文,只校验结构)
233
-
234
- Args:
235
- path: 指纹文件路径
236
-
237
- Returns:
238
- str: 16 位十六进制机器码
239
-
240
- Raises:
241
- LicenseFormatError: 文件缺失 / JSON 损坏 / 标识或版本不符 / 机器码格式非法
242
- """
243
- source = Path(path)
244
- if not source.is_file():
245
- raise LicenseFormatError(f"指纹文件不存在:{source}")
246
- try:
247
- document = json.loads(source.read_text(encoding="utf-8"))
248
- except (OSError, json.JSONDecodeError) as exc:
249
- raise LicenseFormatError(f"指纹文件读取或解析失败:{exc}") from exc
250
- if not isinstance(document, dict):
251
- raise LicenseFormatError("指纹文件结构不合法(顶层应为 JSON 对象)")
252
- if document.get("kind") != _FINGERPRINT_KIND:
253
- raise LicenseFormatError("指纹文件标识不符(kind)")
254
- version = document.get("format_version")
255
- if not isinstance(version, int) or version > FINGERPRINT_FORMAT_VERSION:
256
- raise LicenseFormatError(f"指纹文件格式版本不识别:{version!r}")
257
- machine_code = document.get("machine_code")
258
- if not isinstance(machine_code, str) or not _HEX16_RE.fullmatch(machine_code):
259
- raise LicenseFormatError("指纹文件缺少合法机器码(16 hex)")
260
- return machine_code
261
-
262
-
263
- # ---------------------------------------------------------------------------
264
- # 密钥
265
- # ---------------------------------------------------------------------------
266
-
267
-
268
- def generate_keypair() -> tuple[str, str]:
269
- """生成 Ed25519 密钥对(一次性初始化用)
270
-
271
- Returns:
272
- tuple[str, str]: (private_pem, public_pem),均为 PEM 文本;
273
- 私钥务必本机妥善保管(丢失 = 已发授权全部作废)
274
- """
275
- private_key = Ed25519PrivateKey.generate()
276
- private_pem = private_key.private_bytes(
277
- encoding=serialization.Encoding.PEM,
278
- format=serialization.PrivateFormat.PKCS8,
279
- encryption_algorithm=serialization.NoEncryption(),
280
- ).decode("utf-8")
281
- public_pem = private_key.public_key().public_bytes(
282
- encoding=serialization.Encoding.PEM,
283
- format=serialization.PublicFormat.SubjectPublicKeyInfo,
284
- ).decode("utf-8")
285
- return private_pem, public_pem
286
-
287
-
288
185
  # ---------------------------------------------------------------------------
289
- # 载荷与签名(格式唯一事实源——签发与验签共用本模块实现)
186
+ # 载荷(签发与验签共用)
290
187
  # ---------------------------------------------------------------------------
291
188
 
292
189
 
@@ -340,39 +237,149 @@ def _canonical_bytes(payload: dict) -> bytes:
340
237
  ).encode("utf-8")
341
238
 
342
239
 
343
- def sign(payload: LicensePayload, private_key_pem: str) -> str:
344
- """对载荷签名,返回 license 文件内容(JSON 文本,即 .lic)
240
+ # ---------------------------------------------------------------------------
241
+ # 静态工具类(与其它 kit 形态一致;格式唯一事实源——签发与验签共用本模块实现)
242
+ # ---------------------------------------------------------------------------
345
243
 
346
- Args:
347
- payload: 授权载荷
348
- private_key_pem: Ed25519 私钥 PEM(PKCS8)
349
244
 
350
- Returns:
351
- str: license 文件内容 `{"payload": {...}, "signature": "<b64>"}`
245
+ class LicenseKit:
246
+ """license 授权静态工具类:指纹、密钥、签发"""
352
247
 
353
- Raises:
354
- LicenseFormatError: 私钥解析失败或类型不是 Ed25519
355
- """
356
- try:
357
- private_key = serialization.load_pem_private_key(
358
- private_key_pem.encode("utf-8"), password=None
248
+ @staticmethod
249
+ def get_machine_code() -> str:
250
+ """计算本机机器码:组合指纹 SHA256 短码(16 hex 小写)
251
+
252
+ Returns:
253
+ str: 16 位十六进制机器码
254
+
255
+ Raises:
256
+ LicenseUnsupportedPlatformError: 非 Windows 平台
257
+ LicenseFormatError: 指纹组件读取失败
258
+ """
259
+ machine_guid, volume_serial = _collect_components()
260
+ return _machine_code_of(machine_guid, volume_serial)
261
+
262
+ @staticmethod
263
+ def export_fingerprint_file(path: Path) -> Path:
264
+ """导出指纹文件(.fp):机器码 + 原始组件 + 导出时间
265
+
266
+ 原始组件仅用于报障诊断(作者侧可看出哪个组件不匹配),验签只比短码。
267
+
268
+ Args:
269
+ path: 指纹文件输出路径
270
+
271
+ Returns:
272
+ Path: 实际写入的文件路径
273
+
274
+ Raises:
275
+ LicenseUnsupportedPlatformError: 非 Windows 平台
276
+ LicenseFormatError: 指纹组件读取失败
277
+ """
278
+ machine_guid, volume_serial = _collect_components()
279
+ document = {
280
+ "kind": _FINGERPRINT_KIND,
281
+ "format_version": FINGERPRINT_FORMAT_VERSION,
282
+ "machine_code": _machine_code_of(machine_guid, volume_serial),
283
+ "components": {
284
+ "machine_guid": machine_guid,
285
+ "volume_serial": volume_serial,
286
+ },
287
+ "exported_at": datetime.now(timezone.utc).isoformat(),
288
+ }
289
+ target = Path(path)
290
+ target.parent.mkdir(parents=True, exist_ok=True)
291
+ target.write_text(
292
+ json.dumps(document, ensure_ascii=False, indent=2), encoding="utf-8"
359
293
  )
360
- except (ValueError, TypeError) as exc:
361
- raise LicenseFormatError(f"私钥 PEM 解析失败:{exc}") from exc
362
- if not isinstance(private_key, Ed25519PrivateKey):
363
- raise LicenseFormatError("私钥类型不是 Ed25519")
364
- payload_dict = payload.model_dump(mode="json")
365
- signature = private_key.sign(_canonical_bytes(payload_dict))
366
- document = {
367
- "payload": payload_dict,
368
- "signature": base64.b64encode(signature).decode("ascii"),
369
- }
370
- logger.info(
371
- "license 已签发:product=%s machine_code=%s",
372
- payload.product,
373
- payload.machine_code,
374
- )
375
- return json.dumps(document, ensure_ascii=False, indent=2)
294
+ logger.info("指纹文件已导出:%s(机器码 %s)", target, document["machine_code"])
295
+ return target
296
+
297
+ @staticmethod
298
+ def parse_fingerprint_file(path: Path) -> str:
299
+ """解析指纹文件,返回机器码(签发侧使用;跨机器无法重算原文,只校验结构)
300
+
301
+ Args:
302
+ path: 指纹文件路径
303
+
304
+ Returns:
305
+ str: 16 位十六进制机器码
306
+
307
+ Raises:
308
+ LicenseFormatError: 文件缺失 / JSON 损坏 / 标识或版本不符 / 机器码格式非法
309
+ """
310
+ source = Path(path)
311
+ if not source.is_file():
312
+ raise LicenseFormatError(f"指纹文件不存在:{source}")
313
+ try:
314
+ document = json.loads(source.read_text(encoding="utf-8"))
315
+ except (OSError, json.JSONDecodeError) as exc:
316
+ raise LicenseFormatError(f"指纹文件读取或解析失败:{exc}") from exc
317
+ if not isinstance(document, dict):
318
+ raise LicenseFormatError("指纹文件结构不合法(顶层应为 JSON 对象)")
319
+ if document.get("kind") != _FINGERPRINT_KIND:
320
+ raise LicenseFormatError("指纹文件标识不符(kind)")
321
+ version = document.get("format_version")
322
+ if not isinstance(version, int) or version > FINGERPRINT_FORMAT_VERSION:
323
+ raise LicenseFormatError(f"指纹文件格式版本不识别:{version!r}")
324
+ machine_code = document.get("machine_code")
325
+ if not isinstance(machine_code, str) or not _HEX16_RE.fullmatch(machine_code):
326
+ raise LicenseFormatError("指纹文件缺少合法机器码(16 hex)")
327
+ return machine_code
328
+
329
+ @staticmethod
330
+ def generate_keypair() -> tuple[str, str]:
331
+ """生成 Ed25519 密钥对(一次性初始化用)
332
+
333
+ Returns:
334
+ tuple[str, str]: (private_pem, public_pem),均为 PEM 文本;
335
+ 私钥务必本机妥善保管(丢失 = 已发授权全部作废)
336
+ """
337
+ private_key = Ed25519PrivateKey.generate()
338
+ private_pem = private_key.private_bytes(
339
+ encoding=serialization.Encoding.PEM,
340
+ format=serialization.PrivateFormat.PKCS8,
341
+ encryption_algorithm=serialization.NoEncryption(),
342
+ ).decode("utf-8")
343
+ public_pem = private_key.public_key().public_bytes(
344
+ encoding=serialization.Encoding.PEM,
345
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
346
+ ).decode("utf-8")
347
+ return private_pem, public_pem
348
+
349
+ @staticmethod
350
+ def sign(payload: LicensePayload, private_key_pem: str) -> str:
351
+ """对载荷签名,返回 license 文件内容(JSON 文本,即 .lic)
352
+
353
+ Args:
354
+ payload: 授权载荷
355
+ private_key_pem: Ed25519 私钥 PEM(PKCS8)
356
+
357
+ Returns:
358
+ str: license 文件内容 `{"payload": {...}, "signature": "<b64>"}`
359
+
360
+ Raises:
361
+ LicenseFormatError: 私钥解析失败或类型不是 Ed25519
362
+ """
363
+ try:
364
+ private_key = serialization.load_pem_private_key(
365
+ private_key_pem.encode("utf-8"), password=None
366
+ )
367
+ except (ValueError, TypeError) as exc:
368
+ raise LicenseFormatError(f"私钥 PEM 解析失败:{exc}") from exc
369
+ if not isinstance(private_key, Ed25519PrivateKey):
370
+ raise LicenseFormatError("私钥类型不是 Ed25519")
371
+ payload_dict = payload.model_dump(mode="json")
372
+ signature = private_key.sign(_canonical_bytes(payload_dict))
373
+ document = {
374
+ "payload": payload_dict,
375
+ "signature": base64.b64encode(signature).decode("ascii"),
376
+ }
377
+ logger.info(
378
+ "license 已签发:product=%s machine_code=%s",
379
+ payload.product,
380
+ payload.machine_code,
381
+ )
382
+ return json.dumps(document, ensure_ascii=False, indent=2)
376
383
 
377
384
 
378
385
  # ---------------------------------------------------------------------------
@@ -445,7 +452,7 @@ class LicenseVerifier:
445
452
 
446
453
  Args:
447
454
  product: 期望的应用标识
448
- machine_code: 期望的机器码(本机 `get_machine_code()` 结果)
455
+ machine_code: 期望的机器码(本机 `LicenseKit.get_machine_code()` 结果)
449
456
  now: 判定"当前时间";None 取 aware UTC 当前时刻;naive 按 UTC 处理
450
457
 
451
458
  Returns:
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