自动更新requirements.txt,Python环境现有很多方案。比如装 pipenv/Hatch/Poetry等等。感觉大材小用,而且需要记忆一些使用频率并不高的命令。也会有同学用pip freeze >> requirements.txt
,这样会加入很多冗余的细节。羡慕隔壁JS基础管理工具,npm install/uninstall
或yarn add/remove
一键自动更新package.json。
以下这段脚本修饰了install/uninstall命令,可以实现自动更新requirements.txt。原理比较粗暴:1)事前事后pip freeze,2)与install/uninstall命令后的参数进行比较,忽略安装a包时候依赖安装的b1/b2/b3包。保存为ppm
,赋予可执行权限,并拷贝到 /bin目录 即可。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from pathlib import Path
import subprocess
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.cli.main_parser import parse_command
args = sys.argv[1:]
cmd_name, cmd_args = parse_command(args)
WHICH_PYTHON = subprocess.run(["which", 'python'], capture_output=True,
text=True).stdout.strip()
def freeze_pkgs():
freeze = subprocess.run([WHICH_PYTHON, '-m', 'pip', 'freeze'],
capture_output=True,
text=True).stdout.strip()
pkg_list = freeze.split('\n')
return {canonicalize_name(pkg.split('==')[0]): pkg.split('==')[
1] for pkg in pkg_list}
def update_requirements(pkgs):
requirements = Path("requirements.txt")
if not requirements.exists():
requirements.open('w').close()
with requirements.open('r+') as f:
requires = {}
for l in f.read().splitlines():
pkg, ver = l.split("==")
if cmd_name == 'uninstall' and pkg in pkgs.keys():
continue
else:
requires[pkg] = ver.strip() + '\n'
if cmd_name == 'install':
requires.update(pkgs)
f.seek(0)
f.writelines('=='.join(item) for item in requires.items())
f.truncate()
def main():
if cmd_name in ("install", "uninstall"):
pkgs_maybe = {
canonicalize_name(i.split('==')[0])
for i in cmd_args if not i.startswith('-')
}
pkgs_bfr = freeze_pkgs()
subprocess.run([WHICH_PYTHON, '-m', 'pip'] + args, check=False)
pkgs_afr = freeze_pkgs()
if cmd_name == "install":
pkgs = (set(pkgs_afr.keys()) - set(pkgs_bfr.keys())) & pkgs_maybe
if pkgs:
add_pkgs = {k: v for k, v in pkgs_afr.items() if k in pkgs}
update_requirements(add_pkgs)
else:
pkgs = (set(pkgs_bfr.keys()) - set(pkgs_afr.keys())) & pkgs_maybe
if pkgs:
rm_pkgs = {k: v for k, v in pkgs_bfr.items() if k in pkgs}
update_requirements(rm_pkgs)
else:
subprocess.run([WHICH_PYTHON, '-m', 'pip'] + args, check=False)
if __name__ == "__main__":
main()