我喜欢将元数据打包到可执行文件中的(慢慢消失的)原因之一setup.py是能够具有可选依赖项,这些依赖项是其他依赖项的组合。从pip 21.2 开始,无需运行代码就可以做到这一点。
包的可选依赖项(也称为extras)是一组命名的依赖项,通过将它们的名称放在包名称后面的方括号内来安装。例如pip install httpx[http2],将安装httpx以及支持 HTTP/2 所需的可选依赖项。您可以一次指定多个额外的:pip install httpx[http2,cli]将安装 HTTP/2 和httpx的 CLI 界面所需的一切。
我喜欢在开发中使用可选依赖项的组合,通过dev额外包含运行测试、构建文档和在交互式开发中有用但不应该出现在 CI 中的工具所需的一切。在运行测试时我不需要Sphinx而在构建文档时我不需要pytest。但是当我在这个项目上工作时,我需要一个更好的调试器或MyPy。
在一个setup.py文件中,它看起来像这样:
extras = { "tests": ["pytest"], "docs": ["sphinx"], } extras["dev"] = extras["tests"] + extras["docs"] + ["pdbpp"] setup( name="my-pkg", # ... extras_require=extras, )
pip install -e .[dev]现在将安装pytest、sphinx和pdbpp. 这样,我的开发环境就准备好了。这对于公共项目特别有用,因为为您的贡献者设置本地开发环境的工作减少到了一行。
这很方便,它让我在很长一段时间内无法使用pyproject.toml(PEP 621 )进行静态配置。其中,这意味着我的可选依赖项中的重复.
然而,我也一直对静态元数据和Hatch或Flit等现代打包工具很感兴趣。
当我和 Python 社区一起发现Cog时,我第一次将脚趾伸入静态水域。Cog允许通过将模板逻辑隐藏在注释后面来在静态文件中应用内联模板。
所以我声明了我的开发特定依赖项(在这种情况下只是pdbpp),然后是一个Cog模板块,它读取和解析pyproject.toml(本身!)并添加来自testsand的依赖项docs:
[project.optional-dependencies] tests = ["pytest"] docs = ["sphinx"] dev = [ "pdbpp", # [[[cog # import pathlib, tomli # cfg = tomli.loads(pathlib.Path("pyproject.toml").read_text()) # opt = cfg["project"]["optional-dependencies"] # for dep in opt["tests"] + opt["docs"]: # print(f'"{dep}",') # ]]] "pytest", "sphinx", # [[[end]]] ]
如您所见,pytest和Sphinx是dev列表的一部分,每当我更改tests或docs运行Cog# ]]]时,列表和之间的列表# [[[end]]]都会更新。
为了运行Cog并确保文件没有过期,我使用了两个同样在 CI 中运行的tox目标:cogCheck
[tox] envlist = cogCheck,cog # ... [testenv:cogCheck] description = "Ensure pyproject.toml is up to date" skip_install = true deps = {[testenv:cog]deps} commands = python -m cogapp --check -P pyproject.toml [testenv:cog] description = "Update pyproject.toml's metadata" skip_install = true deps = cogapp>=3.3.0 tomli commands = python -m cogapp -rP pyproject.toml
这很酷,而且我还使用Cog将我的 README 导入并修改为 PyPI 的长描述,所以它会保留下来。但是,它也有点笨拙,尤其是对于没有长描述的公司内部项目。
终于把我们带到了今天的话题!
Python 的打包进度可能很慢,但很稳定。从pip 21.2 开始,您可以在可选依赖项中引用您自己的项目:
[project] name = "my-pkg" [project.optional-dependencies] tests = ["pytest"] docs = ["sphinx"] dev = [ "my-pkg[tests,docs]", # <-- !!! "pdbpp", ]
由于dev最终用户不使用,因此需要一个前沿(也就是只有一年的)pip版本并不是什么大问题。
这是一个在野外看起来如何的示例:structlog的pyproject.toml.
可能会为您节省一些时间的额外提示:额外的名称像包名称一样标准化。额外foo_bar的必须称为foo-bar. 理想情况下完全停止在额外名称中使用下划线。
对于那些对版本固定非常直言不讳的人来说,我使用可选依赖项而不是 pin 文件可能会令人惊讶。
我没有固定我的开源包的开发依赖项,因为没有足够的活动来证明依赖项更新的不断提交流失是合理的。就目前而言,更实际的是在发生中断的 CI 时修复它(这种情况很少见),而不是成为大多数提交是依赖项更新的项目之一。
这是我的权衡计算——你的可能完全不同。如果您的 CI 由于依赖项更新而定期中断,您应该查看 pin 文件和服务,例如Dependabot。
当然,递归可选依赖关系不仅仅对我用于说明的这个示例有用。