agent-Specialization/scripts/gen_requirements_lock.py
JOJO 1d3703b0e5 build(deps): 固化 Python 依赖版本,新增 lock 与生成脚本
为便携 release 打包与可复现安装做准备(P3 依赖固化)。

- 新增 requirements.lock.txt:锁定 9 个运行时直接依赖的精确版本
- requirements.txt 顶部加注释,说明与 lock 文件的分工(宽松 vs 精确)
- 新增 scripts/gen_requirements_lock.py:从已验证环境一键重生 lock,
  避免手工对照版本出错

调查结论(已核实):
- 项目源码真实第三方依赖与 requirements.txt 完全一致,无遗漏
- tiktoken 已不再使用(token 统计改用 API usage 字段),无需纳入
- 三个 node 项目的 package-lock.json 均已存在并被 git 追踪,npm ci
  可直接用,node 端无需改动

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 17:10:28 +08:00

84 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""根据 requirements.txt 生成 requirements.lock.txt精确版本
读取 requirements.txt 列出的直接依赖名,从当前已安装环境查出精确版本,
写出 requirements.lock.txt。在「已验证可用」的环境中运行确保锁定的是
一组实测能跑起来的版本。
用法:
python -m scripts.gen_requirements_lock
python scripts/gen_requirements_lock.py
"""
from __future__ import annotations
import importlib.metadata as md
import re
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
REQ_PATH = REPO_ROOT / "requirements.txt"
LOCK_PATH = REPO_ROOT / "requirements.lock.txt"
_LOCK_HEADER = """\
# 锁定版本依赖(构建 / release 打包用)。由 scripts/gen_requirements_lock.py 生成。
#
# 与 requirements.txt 的关系:
# - requirements.txt 人读、宽松,列出运行时直接依赖(不锁版本)
# - requirements.lock.txt本文件 精确版本,供便携 release 打包与可复现安装
#
# 安装pip install -r requirements.lock.txt
# 更新:在已验证可用的环境执行 python -m scripts.gen_requirements_lock
#
# 注:这里只锁项目运行时直接依赖;其传递依赖由 pip 在安装时解析。
"""
def _parse_requirement_names(text: str) -> list[str]:
"""从 requirements.txt 提取包名(忽略注释、空行、已带版本约束的尾巴)。"""
names: list[str] = []
for raw in text.splitlines():
line = raw.strip()
if not line or line.startswith("#"):
continue
# 去掉版本约束/extras仅留包名 flask[async]>=2 -> flask
name = re.split(r"[<>=!~\[ ;]", line, 1)[0].strip()
if name:
names.append(name)
return names
def main() -> int:
if not REQ_PATH.exists():
print(f"找不到 {REQ_PATH}", file=sys.stderr)
return 1
names = _parse_requirement_names(REQ_PATH.read_text(encoding="utf-8"))
if not names:
print("requirements.txt 中没有可锁定的依赖。", file=sys.stderr)
return 1
lines: list[str] = []
missing: list[str] = []
for name in names:
try:
version = md.version(name)
lines.append(f"{name}=={version}")
except md.PackageNotFoundError:
missing.append(name)
lines.append(f"# {name} (未安装,无法锁定版本)")
LOCK_PATH.write_text(_LOCK_HEADER + "\n" + "\n".join(lines) + "\n", encoding="utf-8")
print(f"已写出 {LOCK_PATH}{len(names)} 项依赖)")
for line in lines:
print(" " + line)
if missing:
print(f"\n警告:以下依赖未安装,未能锁定版本:{', '.join(missing)}", file=sys.stderr)
print("请在已安装全部依赖的环境中重新运行。", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
sys.exit(main())