Python打包库
译者:徐宏富
Sun-2014-05-25 blog.ionelmc.ro
我认为应该重新审视打包最佳做法,现如今,有许多的好的工具没有被使用过,或被充分使用。重新评估最佳的做法,通常来说一直都是件好事情。
例如,现在你的python代码包要在多个python版本中测试,这些版本有着不同的依赖库和设置等。
在打包时,我喜欢遵循一些基本的原则:
.如果你可以使用py.test或者nose这样的工具帮助测试,就不要浪费时间去建一个专有的运行测 试器。因为这些庞大的插件生态系统能够改进你的测试。
.如有可能,尽早阻止问题发生。这主要是要设计严格而详细的测试,去阻止一些常见的错误。
.收集覆盖数据,记录下来,甄别原因。
.测试所有可能的配置。
结构
这是相当重要的,每一件事都围绕着它。我比较喜欢如下类型的布局:
这种src目录是一种很好的途径,是因为:
.你得到了导入入口。这个当前目录隐含着系统路径(sys.path)。假如你用site-pakages来进行安装或导入,就不是这样了。用户就不会和你一样,有着相同的当前工作目录。
.这样的约束,使得测试和打包均受益:
你将不得不检测安装代码(例如:安装某种虚拟环境),这能确保代码开发工作--打包代码是正确的,否则你的测试会失败。早早的,在你公开发布这样的有问题的软件包前,就能发现问题。
.你将不得不安装这些软件包。如果你曾经在pypi发布过软件包,由于没测试过安装配置,当遇到这些软件包缺少模板或者使用了破损的依赖库时,就不会上传成功。只有成功的建立源分发(sdist)模式,才能确保安装成功。
.这能预防你从setup.py脚本里导入代码。从主包或者模型里导入这些不可用的依赖库时,是糟糕的令人气炸的体验。首先就得避免这种可能发生。
.更简单的打包代码和表现形式。这种表现形式简单易写。例如你打包一个有许多模块和文件的Django APP时。同样的,你在面对有多个包的大型库时也不必担心。这样写,被打包的代码和打包用的代码一目了然。
没有src而去写manifest.in 文档是很困难的。如果你的manifest.in文档是破损的,你的测试也会失败。有src目录就会容易很多:你只须把src移植放入manifest.in文档里。
把破损的软件发布到pypi上不好处理。
没有src,在你编辑安装命令时就会很混乱(是用”setup.py develop” 还是用 "pip install -e”?)。没有代码分离src目录,就会迫使setuptools工具把你的项目根目录安装在系统路径(sys.path)上。这样就会充满大量的没用的东西,造成setup.py文档,还有其他的测试或配置脚本不知不觉的不能导入。
这里有个更好的工具--tox,你不需要处理安装包,只需要进行测试。你不必担心,它可以自动自如的帮你安装好软件包。
更少的用户错误出现。甚至几乎没有。
各种工具也不会混淆代码和非代码。
有一种说法是:扁平比嵌套好。但对数据来说并非如此。文件系统毕竟也是数据,紧凑、标准化的数据结构是可取的。
您会注意到我没有在已安装的软件包中包含测试。这是因为:
模块发现工具将会和你的测试模块冲突。奇怪的事情通常出现在测试模块中。内置help帮助就是模块发现。例如:
测试通常要额外的对象运行,它们自身没什么用,你不能直接空空的运行它们。
测试与开发有关,而不是用途。
库的用户不可能代替库的开发者去测试库,例如:当你测试app时,你没有去测试Django---因为Django已经测试过了。
备选方案
你可以这样用src--更少的布局,很少的例子。
这两种布局变得流行是因为,几年前,打包时有很多问题,只为测试而安装打包是不可行的。人们还在推荐它们,仅仅是旧的和过时的假设。
很多项目不当的使用它们,是因为除了Twisted trial外,很多的测试运行器,都有不正确的当前工作目录默认值。如果你没有检测已经安装好的代码,就会错误的去检测的别的代码。Trial通过暂时改变当前工作目录去做正确的事情,而很多项目没有使用trial。
Setup 脚本
很不幸,当前的打包工具有很多的陷阱。Setup.py脚本应该尽量简单。
上面的脚本的特点:
.没有exec 或者 import 权略
.所有东西来自src--包或者根级别模块
.明确的编码
运行测试
再一次,人们倾向于用python setup.py test 去运行软件包测试。我认为并不值得这么去做,setup.py test 是个失败的试验,它尝试着去复制CPAN的测试系统。Python没有个通用的测试结果协议,所以它也没有个通用的测试命令,去达成目的。就目前来说,我们需要有人去建立规格和服务,以便值得我们这样去做,并支持他们。认识到失败在哪里,并在必要的时候能重新回到图板,我认为这是很重要的。毫无疑问,没相应的服务和工具,是在用setup.py test命令,提供这种增值服务的。有些事情错的很明显。
我认为,pypi现在去做这件事情已经为时过晚,因为现在有一个牢靠的、自由的、非常有弹性的候选者---Travis。它已经和Github紧紧的整合在一起,能自动获得Pull-Rquest支持。
Tox是一款很好的当地测试工具,它能运行所有可能的配置(每一种配置就一个tox环境)。我喜欢把这些测试用这些环境条件组成一个矩阵:
.检测 检测包裹的元数据。比如,重组文本在你的详细描述中是否有效。
.清理 清理覆盖数据
.报告 用所有累积的数据生成报告
.文档 生成sphnix文档
无论有没有测量覆盖率,我一直都喜欢用环境去测试。如果你总是用覆盖率去测量,你就不大可能捕获到,各种竞争条件下敏感的表现变化。
测试矩阵
根据依赖关系,你最终会得到各种python版本、依赖库版本、不同的设置的巨大组合。通常的,人们在tox.ini或者仅仅在.travis.yml中对所有的东西硬编码。在没有完成本地测试,或者在travis中进行连续测试完成前,这些测试就结束了。我那样做过,不喜欢这样。我尝试过在tox.ini和.travis.yml中复制所有环境,还不是喜欢它。
由于没有现成可用的选择去生成配置,我用模块生成器脚本去生成tox.ini和.travis.yml。这种方法更好,很干净,你很容易跳过特定的环境测试(例如,跳过Django 1.4 在Python 3中的测试),只需要做少量的改动工作。
要点(全部代码)
Ci/bootstrap.py
这是生成器脚本。无论什么时候,你想生成配置就运行它。
Ci/templates/.travis.yml
有好东西在里面--非常有用的libSegFault.so.trick.
它基本上只运行tox.
如果你已经足够耐心通读了全文,你就会注意到:
Travis使用的tox的每一项都在在矩阵里,这使得测试Travis连续性同时,也进行了当地测试。
Tox的环境命令是:clean,check,2.6-1.3,2.6-1.4,...,report.
覆盖测量+环境配置的运行代码是没有安装的(usedevelop=true),最后覆盖能同各种量结合在一起。
没有覆盖+环境配置的代码将进行源分发(sdist),装进虚拟环境中(tox的默认行为),以便及早的发现有关打包的问题。
各种环境中运行的结果最终生成单一报告。
拥有一份在tox.ini中各种环境的完整测试报告优势明显:
你是在本地有干扰的条下并行测试的(除非你的测试需要非常严格的隔离),你还可以用drone.io代替Travis并行运行一切。
你可以在本地为所有东西测量累积覆盖(把所有的环境下的覆盖合并到一起)。
测量覆盖
这种机制-- 一种跟踪时间和多个构建的覆盖范围的好方法,能够自动的把覆盖变化的信息添加到Github Pull Request上。
简单总结
把代码放进src
用tox或detox
有无覆盖两种情况均要测试
为tox.ini和.travis.ini使用生成器脚本
在Travis中用tox运行测试,确保和本地测试条件一致
太复杂?请使用python package template.
还不够清楚?请阅读:Hyneky’s post about the src layout.
也可以查阅:Short list of packaging pitfalls.