common.py 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. """发布脚本共用的版本、清单和隔离验证环境。仅依赖 Python 标准库。"""
  2. from __future__ import annotations
  3. import hashlib
  4. import json
  5. import os
  6. from pathlib import Path, PurePosixPath
  7. import re
  8. VERSION_RE = re.compile(r"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?\Z")
  9. def version(value: str) -> str:
  10. value = value.strip()
  11. if not VERSION_RE.fullmatch(value):
  12. raise ValueError("版本格式应为 2.0.0 或 2.0.0-rc.1")
  13. return value
  14. def sha256(path: Path) -> str:
  15. return hashlib.sha256(path.read_bytes()).hexdigest()
  16. def validate_package(root: Path, *, installed: bool = False) -> dict:
  17. root = root.resolve()
  18. manifest = json.loads((root / "release-manifest.json").read_text())
  19. if manifest.get("format") != 1 or manifest.get("python") != "3.12":
  20. raise ValueError("不支持的发布清单或 Python 版本")
  21. if version((root / "VERSION").read_text()) != version(manifest["version"]):
  22. raise ValueError("VERSION 与发布清单不一致")
  23. files = manifest.get("files", {})
  24. for required in ("VERSION", "main.py", "config.py", ".env.example", "release/deploy.py", "release/requirements.lock"):
  25. if required not in files:
  26. raise ValueError(f"发布包缺少必需文件:{required}")
  27. for name, expected in files.items():
  28. relative = PurePosixPath(name)
  29. if relative.is_absolute() or ".." in relative.parts or "\\" in name:
  30. raise ValueError(f"非法清单路径:{name}")
  31. path = root / name
  32. if any(part.is_symlink() for part in (path, *path.parents) if part != root and root in part.parents):
  33. raise ValueError(f"发布文件不允许是符号链接:{name}")
  34. if not path.is_file() or sha256(path) != expected["sha256"]:
  35. raise ValueError(f"文件缺失或校验失败:{name}")
  36. if not installed:
  37. actual = set()
  38. for path in root.rglob("*"):
  39. if path.is_symlink():
  40. raise ValueError(f"发布包包含符号链接:{path.relative_to(root)}")
  41. if path.is_file():
  42. actual.add(path.relative_to(root).as_posix())
  43. if actual != set(files) | {"release-manifest.json"}:
  44. raise ValueError("发布包包含清单外文件")
  45. return manifest
  46. def validation_env(temporary: Path) -> dict[str, str]:
  47. """测试和自检只使用临时 SQLite 与假密钥,不读取服务器配置。"""
  48. env = dict(os.environ)
  49. env.update(DEEP_AGENT_ENV_PRIORITY="none", DB_DIALECT="sqlite",
  50. DB_SQLITE_PATH=str(temporary / "validation.sqlite"), CHECKPOINTER="memory",
  51. OPENAI_API_KEY="sk-release-offline", OPENAI_BASE_URL="https://example.invalid",
  52. DEEPAGENT_MODEL="deepseek-flash", BILLING_ALLOW_UNPRICED="0",
  53. PYTHONDONTWRITEBYTECODE="1", PYTHONPATH="", LOG_LEVEL="WARNING")
  54. env["ALGORITHM_JOB_DIR"] = str(temporary / "algorithm_jobs")
  55. return env