如何在setuptools安装后命令中获取安装包列表并校验版本
单仓库子包版本校验的Post-install脚本实现
问题核心
pkg_resources.working_set在安装阶段无法获取刚安装的子包——因为setuptools还未将新安装的包加入到working_set,直接调用会触发KeyError。
可行解决方案
方案1:解析安装命令参数(适合直接安装场景)
用户执行pip install .或指定子包安装时,sys.argv会包含安装目标信息。结合setuptools_scm的版本获取逻辑,直接校验子包与仓库版本是否一致。
示例代码:
import sys from setuptools_scm import get_version from setuptools import setup from setuptools.command.install import install def post_install(): # 筛选安装命令中的子包路径或名称 install_targets = [ arg for arg in sys.argv if arg.startswith('.') or '/' in arg or arg in ['subpkg-a', 'subpkg-b'] ] # 获取仓库根版本 repo_version = get_version(root='..', relative_to=__file__) # 逐个校验子包版本 for target in install_targets: subpkg_version = get_version(root=target, relative_to=__file__) if subpkg_version != repo_version: raise RuntimeError(f"子包版本不匹配:{target} 版本{subpkg_version},仓库版本{repo_version}") class CustomInstall(install): def run(self): install.run(self) post_install() setup( # 其他配置项... cmdclass={'install': CustomInstall} )
方案2:扫描site-packages目录(通用场景)
安装完成后,直接扫描Python的site-packages目录,读取子包的*.dist-info/METADATA文件获取版本信息,再与仓库版本对比。
示例代码:
import os import glob import site from setuptools_scm import get_version from setuptools import setup from setuptools.command.install import install def get_installed_subpkg_versions(subpkg_names): # 获取所有site-packages目录 site_dirs = site.getsitepackages() + [site.getusersitepackages()] installed_versions = {} for pkg_name in subpkg_names: # 匹配子包的dist-info目录 dist_pattern = f"{pkg_name.replace('-', '_')}-*.dist-info" for dir_path in site_dirs: for dist_dir in glob.glob(os.path.join(dir_path, dist_pattern)): with open(os.path.join(dist_dir, 'METADATA'), 'r') as f: for line in f: if line.startswith('Version:'): installed_versions[pkg_name] = line.strip().split(': ')[1] break return installed_versions def post_install(): repo_version = get_version(root='..', relative_to=__file__) target_subpkgs = ['subpkg-a', 'subpkg-b'] installed_versions = get_installed_subpkg_versions(target_subpkgs) for pkg, ver in installed_versions.items(): if ver != repo_version: raise RuntimeError(f"子包{pkg}版本不匹配:已安装{ver},仓库版本{repo_version}") class CustomInstall(install): def run(self): install.run(self) post_install() setup( # 其他配置项... cmdclass={'install': CustomInstall} )
方案3:调用pip命令获取已安装包(简单直接)
在post-install脚本中执行pip list --format=freeze,解析输出结果拿到子包版本,再做一致性校验。
示例代码:
import sys import subprocess from setuptools_scm import get_version from setuptools import setup from setuptools.command.install import install def post_install(): repo_version = get_version(root='..', relative_to=__file__) target_subpkgs = ['subpkg-a', 'subpkg-b'] # 调用pip获取已安装包列表 result = subprocess.run( [sys.executable, '-m', 'pip', 'list', '--format=freeze'], capture_output=True, text=True, check=True ) # 解析为包名:版本的字典 installed_pkgs = { line.split('==')[0]: line.split('==')[1] for line in result.stdout.splitlines() if '==' in line } for pkg in target_subpkgs: if pkg not in installed_pkgs: raise RuntimeError(f"子包{pkg}未完成安装") if installed_pkgs[pkg] != repo_version: raise RuntimeError(f"子包{pkg}版本不匹配:已安装{installed_pkgs[pkg]},仓库版本{repo_version}") class CustomInstall(install): def run(self): install.run(self) post_install() setup( # 其他配置项... cmdclass={'install': CustomInstall} )
注意事项
- 确保校验逻辑在所有子包安装完成后执行:如果是单仓库联合安装,把校验放在顶层包的post-install中,或让所有子包的校验依赖顶层版本。
- 适配开发模式(
pip install -e .):这种场景下site-packages是源码链接,需直接从子包目录用setuptools_scm获取版本,而非扫描site-packages。
内容的提问来源于stack exchange,提问作者Intrastellar Explorer
相关产品推荐
相关产品推荐

