如何在setup.cfg中自动整合多组依赖到all项?及setup.py报错处理
问题背景
我在为Python包编写setup.cfg时,需要根据不同用途定义多组依赖。目前手动在[options.extras_require]里维护all项来包含所有依赖,但每次依赖变更都要手动更新,非常繁琐。希望实现all项自动包含option1、option2、option3的所有依赖,无需手动维护。
当前setup.cfg的相关配置:
[options.extras_require] option1 = pkg1 pkg2 pkg3 pkg4 pkg5 option2 = pkg1 pkg2 pkg6 pkg7 pkg8 option3 = pkg3 pkg4 pkg9 pkg10 all = pkg1 pkg2 pkg3 pkg4 pkg5 pkg6 pkg7 pkg8 pkg9 pkg10
配套的极简setup.py:
from __future__ import annotations import setuptools if __name__ == "__main__": # Do not add any parameters here. Edit setup.cfg instead. setuptools.setup()
环境信息:测试用Python 3.11.8、setuptools 68.2.2、pip 23.3.1;包需兼容Python 3.9+,要适配多版本setuptools和pip。
尝试的方案及报错
我尝试修改setup.py来合并依赖,但执行pip install -e .[all]时出现错误:
修改后的setup.py:
from __future__ import annotations from setuptools import setup from setuptools.config.setupcfg import read_configuration # Run code only if file is run as a script if __name__ == "__main__": # Read the configuration from setup.cfg config = read_configuration("setup.cfg") # Get the dependencies from the other sections option1_deps = config["options"]["extras_require"]["option1"] option2_deps = config["options"]["extras_require"]["option2"] option3_deps = config["options"]["extras_require"]["option3"] # Combine dependencies all_deps = option1_deps + option2_deps + option3_deps # Update the config with the combined dependences config["options"]["extras_require"]["all"] = all_deps # Pass the udpated configuration to setuptools.setup() setup(**config["options"])
报错信息:
Preparing metadata (setup.py) ... error error: subprocess-exited-with-error × python setup.py egg_info did not run successfully. │ exit code: 1 ╰─> [1 lines of output] error in setup command: 'python_requires' must be a string containing valid version specifiers; 'SpecifierSet' object has no attribute 'split' [end of output] note: This error originates from a subprocess, and is likely not a problem with pip. error: metadata-generation-failed × Encountered error while generating package metadata. ╰─> See above for output. note: This is an issue with the package mentioned above, not pip.
解决方案
方案1:直接在setup.cfg中引用其他extras(推荐)
setuptools从38.3.0版本开始支持在extras中直接引用其他extras组,无需修改setup.py。只需修改setup.cfg的all项:
[options.extras_require] option1 = pkg1 pkg2 pkg3 pkg4 pkg5 option2 = pkg1 pkg2 pkg6 pkg7 pkg8 option3 = pkg3 pkg4 pkg9 pkg10 all = option1 option2 option3
这样执行pip install -e .[all]时,setuptools会自动递归安装option1、option2、option3的所有依赖,且依赖变更时all项会自动同步,完全无需手动维护。该方案兼容Python3.9+对应的所有setuptools版本,无需担心兼容性问题。
方案2:修复setup.py的类型问题
如果一定要通过setup.py实现,需要将read_configuration解析后的python_requires(SpecifierSet对象)转换为字符串,再传入setup():
from __future__ import annotations from setuptools import setup from setuptools.config.setupcfg import read_configuration if __name__ == "__main__": config = read_configuration("setup.cfg") # 合并并去重依赖(去重可选,避免重复条目) extras = config["options"]["extras_require"] all_deps = extras["option1"] + extras["option2"] + extras["option3"] all_deps = list(dict.fromkeys(all_deps)) # 保持依赖顺序的同时去重 extras["all"] = all_deps # 将SpecifierSet对象转为字符串,修复python_requires报错 if "python_requires" in config["options"]: config["options"]["python_requires"] = str(config["options"]["python_requires"]) setup(**config["options"])
修改后执行pip install -e .[all]即可正常安装所有依赖,同时all项会自动同步其他组的依赖变更。
内容的提问来源于stack exchange,提问作者TheEponymousProgrammer

