以正确的方式开源 Python 项目

大多数Python开发者至少都写过一个像工具、脚本、库或框架等对其他人也有用的工具。我写这篇文章的目的是让现有Python代码的开源过程尽可能清晰和无痛。我不是简单的指——“创建一个GitHub库,提交,在Reddit上发布,每天调用它”。在本文的结尾,你可以把现有的代码转换成一个能够鼓励他人使用和贡献的开源项目。

然而每一个项目都是不同的,但其中将现有代码开源的流程对所有的Python项目都是类似的。在另一个受欢迎的文章系列里我写了“以正确方式开始一个Django项目”,我将概述在开源Python项目我发现的有必要的步骤。

更新 (8月17号): 感谢@pydann提醒我Cookiecutter的存在,@audreyr的一个不起的项目。我在文章结尾添加了其中的一段。看一下Audrey的项目吧!

更新 2 (8月18号):感谢@ChristianHeimes(和其他人)关于ontox这一段。Christian也让我想起了PEP 440和其他一些都已实现很棒的改进建议。

工具和概念

特别是,我发现一些工具和概念十分有用或者说是必要的。下面我就会谈及这方面主题,包括需要运行的精确的命令和需要设置的配置值。其终极目标就是让整个流程简单明了。

  1. 项目布局(目录结构)
  2. setuptools 和 setup.py文件
  3. git版本控制
  4. GitHub 项目管理
    1. GitHub的”Issues” 如下作用:
      1. bug跟踪
      2. 请求特性
      3. 计划好的新特性
      4. 发布或者版本管理
  5. git-flow git工作流
  6. py.test 单元测试
  7. tox 标准化测试
  8. Sphinx 自动生成HTML文档
  9. TravisCI 持续测试集成
  10. ReadTheDocs 持续文档集成
  11. Cookiecutter  为开始下一个项目自动生成这些步骤

项目布局

当准备一个项目时,正确合理的布局(目录结构)是十分重要的。一个合理的布局意味着想参与开发者不必花时间来寻找某些代码的位置; 凭直觉就可以找到文件的位置。因为我们在处理一个项目,就意味着可能需要到处移动一些东西。

让我们从顶层开始。大多数项目都有很多顶层文件(如setup.py, README.md, requirements等等)。每个项目至少应该有下面三个目录:

  1. doc目录,包括项目文档
  2. 项目目录,以项目命名,存储实际的Python包
  3. test目录,包含下面两部分
    1. 在这个目录下包括了测试代码和资源
    2. 作为一个独立顶级包

为了更好理解文件该如何组织,这里是一个我的简单项目:sandman 布局快照。

如你所看到那样,这里有一些顶层文件,一个docs目录(建立一个空目录,因为sphinx会将生成的文档放到这里),一个sandman目录,以及一个在sandman目录下的test目录。

setuptools 和 setup.py文件

setup.py文件,你可能已经在其它包中看到过,被distuils包用来安装Python包的。对于任何一个项目,它都是一个很重要的文件,因为它包含了版本,包依赖信息,PyPi需要的项目描述,你的名字和联系信息,以及其它一些信息。它允许以编程的方式搜索安装包,提供元数据和指令说明让工具如何做。

setuptools包(实际上就是对distutils的增强)简单化了建立发布python包。使用setuptools给python包打包,和distutils打包没什么区别。这实在是没有任何理由不使用它。

setup.py应该放在你的项目的根目录。setup.py中最重要的一部分就是调用setuptools.setup,这里面包含了此包所需的所有元信息。这里就是sandman的setup.py的所有内容

(感谢Christian Heimes的建议让setup.py更符合人们的语言习惯。反过来,也让我借用其它的项目一目了然了。)

大多数内容浅显易懂,可以从setuptools文档查看到,所以我只会触及”有趣”的部分。使用sandman.__version__和gettinglong_description方法(尽管我也记不住是哪一个,但是却可以从其它项目的setup.py中获得)来减少我们需要写的引用代码。相反,维护项目的版本有三个地方(setup.py, 包自身的__version__, 以及文档),我们也可以使用包的version来填充setup里面的version参数

long_description被Pypi在你项目的PyPI主页当做文档使用。这里有其他一个文件,README.md,其中包含几乎相同的内容,我使用pandoc依据README.md自动生成README.rst,因此我们只需看README.rst就行了,并将它的内容设置为long_description。

py.test (上面讨论过) 中有一个特殊的条目(pytest类)设置允许Python检查setup.py可否正常工作。这段代码直接来自py.test指导文档。

文件中的其他内容都是在设置文档中描述的安装参数。

其他的setup.py参数

有一些sandman 用不到的启动参数,在你的包里可能会用到。举个例子,你可能正在分派一些脚本并希望你的用户能够从命令行执行。在这个例子中,脚本会和你其他的代码一起安装在正常的site-packages位置。用户安装完后,没有其他的简单方法运行它。基于这一点,setup可以带有一个的脚本参数来指明Python脚本应该如何安装。在包中安装一个调用go_foo.py的脚本,这个用来启动的调用包括下面这行:

确保在脚本中填入相对路径,并不仅仅是一个名称 (如scripts = ['scripts/foo_scripts/go_foo.py']).同样,你的脚本应该以”shebang”行和”python”开始,如下:

distutils将会在安装过程中自动用当前解释器位置取代这一行。

如果你的包比我们这里讨论的要复杂,你可在官方文档中参看启动工具文档和分布python模块。

在这两者中,你可以解决一些你可能会遇到的问题。

代码管理:git, 项目管理:gitHub

在“以正确的方式开始一个Django项目”中,我建议版本控制使用git 或者 mercurial。如果对于以共享与贡献的项目来说,只有一个选择:git。事实上,从长远来说,如果你想人们能使用和参与贡献,那么不仅使用git很有必要,而且,你也能够使用GitHub来管理维护你的项目。

这并不是夸大其词(尽管很多人会以它为嚼头)。然而,管它好与差,git和GitHub事实上已经成为了开源项目的实际标准了。GitHub是很多潜在的贡献者最想注册的和最熟悉的。所以,我深信,这并不是掉以轻心,而是深思熟虑的产物。

新建一个README.md文件

在GitHub的代码仓库中,项目的描述是从项目的根目录中的:README.md文件获取的。这个文件应该包含下面几点:

  • 项目描述
  • 项目ReadTheDocs页面连接[@Lesus 注:请查看 工具与概念 ]
  • 一个用来显示当前构建状态的TravisCI按钮。
  • “Quickstart” 文档 (怎么快速安装和使用你的项目)
  • 若有非python依赖包,请列举它以及怎么安装它

它(README)读起来很傻的感觉,但是确是一个很重要的文件。它可能是你未来的用户或者贡献者首先从它了解你的项目的。花些时间来写一个清楚明白的说明和使用GFM(GitHubFlavoredMarkdown)来使它更好看。实际上,如果使用原生的Markdown来写文档不爽,那么可以在Github上使用立即预览来创建或者修改这个文件

我们还没触及列表中的第二和第三项(ReadTheDocs和TravisCI),你会在接下来看到。

使用”Issues”页

跟生活中的很多事情一样,你投入GitHub越多,你收获的越多。因为用户会使用GitHub的“Issues”页面反馈bug,使用该页面跟踪特性要求和改进是很有意义的。

更重要的是,它允许贡献者以一种优雅的方式看到:一个可能实现特性的列表以及自动化的管理合并请求流程(pull request)。GitHub的issues可以与评论、你项目里的其他issues及其他项目里的issues等交织,这使得“issues”页面成为一个有关所有bug修复、改进和新特性要求信息汇总的地方。

确保“Issues”及时更新,至少及时回应新的问题。作为一个贡献者,没有什么比修复bug后看着它呈现在issues页面并等待着被合并更有吸引力的了。

使用git-flow这个明智的git工作流

为使事情对自己和贡献者更容易,我建议使用非常流行的git-flow分支模型。

概述
开发分支是你工作的主要分支,它也是将成为下一个release.feature的分支,代表着即将实现的新特性和尚未部署的修复内容(一个完整的功能分支有开发分支合并而来)。通过release的创建更新master。

安装
按照你系统平台的git-flow安装指导操作,在这里。

安装完后,你可以使用下附命令迁移你的已有项目

Branch细节

脚本将询问你一些配置问题,git-flow的默认建议值可以很好的工作。你可能会注意到你的默认分支被设置成develop。现在,让我们后头描述一下git-flow…嗯,flow,更详细一点。这样做的最简单的方法是讨论一下不同的分支及模型中的分支类型。

Master

master分支一直是存放“生产就绪”的代码。所有的提交都不应该提交到master分支上。当然,master分支上的代码只会从一个产品发布分支创建并结束后合并进来。这样在master上的代码一直是可以发布为产品的。并且,master也是一直处于可预计的状态,所以你永远不需要担心如果master分支修改了而某一个其他分支没有相应的修改。

Develop

你的大部分工作是在develop分支上完成的。这个分支包含所有的完成的特性和修改的bug以便发布;每日构建或者持续集成服务器需要针对develop分支来进行,因为它代表着将会被包含在下一个发布里的代码。

对于一次性的提交,可以随便提交到develop上。

特性

对于一些大的特性,就需要创建一个特性分支。特性分支从develop分支创建出来。它们可以是对于下一个发布的一些小小的增强或者更进一步的修改。而这,依然需要从现在开始工作。为了从一个新的分支上开始工作,使用:

这命令创建了一个新的分支:feature/。通常会把代码提交到这个分支。当特性已经完成并且准备好发布的时候,它就应当用一下的命令将它合并会develop分支:

这会把代码合并进develop分支,并且删除 feature/分支

Release

一个release分支是当你准备好进行产品发布的时候从develop分支创建出来的。使用以下的命令来创建:

注意,这是发布版本号第一次创建。所有完成的,准备好发布的分支必须已经合并到develop分支上。在release分支创建后,发布你的代码。任何小的bug修改需要提交到 release/ 分支上。当所有的bug被修复之后,运行以下的命令:

这个命令会把你的release/  分支合并到master和develop分支,这意味着你永远不需要担心这几个分支会缺少一些必要的产品变更(可能是因为一个快速的bug修复导致的)。

Hotfix

然而hotfix分支可能会很有用,在现实世界中很少使用,至少我是这样认为的。hotfix就像master分支下创建的feature分支: 如果你已经关闭了release分支,但是之后又认识到还有一些很重要的东西需要一起发布,那么就在master分支(由$git flow release finish 创建的标签)下创建一个hotfix分支,就像这样:

当你完成改变和增加你的版本号使之独一无二(bump your version number),然后完成hotfix分支:

这好像一个release分支(因为它本质上就是一种release分支),会在master和develop分支上提交修改。

我猜想它们很少使用的原因是因为已经存在一种可以给已发布的代码做出修改的机制:提交到一个未完成的release分支。当然,可能一开始,团队使用git flow release finish .. 太早了,然后第二天又发现需要快速修改。随着时间的推移,他们就会为一个release 分支多留一些时间,所以,不会再需要hotfix分支。另一种需要hotfix分支情况就是如果你立即需要在产品中加入新的特性,等不及在develop分支中加入改变。不过(期望)这些都是小概率事件。

virtualenv和virtualenvwrapper

lan Bicking的virtualenv工具事实上已经成为了隔离Python环境的标准途径了。它的目标很简单:如果你的一台机子中有很多Python项目,每个都有不同的依赖(可能相同的包,但是依赖不同的版本),仅仅在一个Python安装环境中管理这些依赖几乎是不可能的。

virtualenv创建了一个“虚拟的”Python安装环境,每个环境都是相互隔离的,都有自己的site-packages, distribute和 使用pip安装包到虚拟环境而不是系统Python安装环境。 而且在你的虚拟环境中来回切换只是一个命令的事。

Doug Hellmann的virtualenvwrapper使创建和管理多个虚拟环境更容易的隔离工具。让我们继续前进,马上安装这两个工具:

如你所见,后者依赖于前者,所以简单的安装virtualenvwrapper就足够了。注意,如果你使用的是Python3,PEP-405通过venv包和pyvenv命令提供了Python原生虚拟环境的支持,在python3.3中已实现。你应该使用这个而不是前面提到的工具。

一旦你安装了virtualenvwrapper,你需要添加一行内容到你的.zhsrc文件(对bash用户来说是.bashrc文件):

这样在你的shell中增加了一些有用的命令(记得第一次使用时source一下你的.zshrc文件以使它生效)。虽然你可以使用mkvirtualenv命令直接创建一个virtualenv,但使用mkproject [OPTIONS] DEST_DIR创建一个“项目”将更有用。因为我们已经有一个现有的项目了,所有我们只需为我们的项目创建一个新的virtualenv,下附命令可以达到这效果:

你会注意到你的shell提示符在你的virtualenv之后(我的是“ossproject”,你可以使用任何你喜欢的名字)。现在任何通过pip安装的模块将安装到你的virtualenv下的site-packages。

要停止在你的项目上工作并切换回系统使用deactivate命令。你会看到命令提示符前你的virtualenv名字消失了。要重新回到你的项目上工作的话运行workon ,你会回到你的virtualenv。

除了简单地为你的项目创建virtualenv,你还会用它做其他事:生成你的requirements.txt文件,使用requirements.txt文件和-r标识可安装所有项目的依赖项。要创建该文件,在你的virtualenv运行以下命令(一旦你代码和virtualenv一起工作,就是那里):

你会得到一个所有你项目需要模块的列表,它以后可以被setup.py文件使用列出你的依赖关系。这里有一点需要注意:我经常在requirements.txt中将“==”改为“>=“,这样代表“我正使用包的任何的后来版本”。你是否应该或需要在项目这样做取决于实际情况,但我应该指出来。

将requirements.txt提交到你的git代码库中。此外,你现在可以添加这里的列出的包列表作为install_requirement参数的值到setup.py文件中的distutils.setup。这样做我们可以确保当上传包到PyPI后,它可以被pip安装并自动解决依赖关系。

使用py.test测试

在Python的自动测试系统里有两个主要的Python标准单元测试包(很有用)的替代品:nose和py.test。两个方案都将单元测试拓展的易于使用且增加额外的功能。说真的,哪个都是很好的选择。我更喜欢py.test因为下述几个原因:

  • 支持setuptools/distutils项目
    • Python的setup.py测试技能始终其作用
  • 支持常见的断言(assert)语法 (而不是需要记住所有jUnit风格的断言函数)
  • 更少的样板
  • 支持多种测试风格
    • 单元测试
    • 文档测试
    • nose测试

注意

如果你已经有了一个自动测试的解决方案那继续使用它吧,跳过这一节。但请记住以后的章节你将被认为在使用py.test测试,这可能会影响到配置值。

测试安装

在测试目录里,无论你如何决定都要有这个目录,创建一个名为test_.py的文件。py.test的测试发现机制将把所有test_前缀的文件当做测试文件处理(除非明确告知)。

在这个文件里放什么很大程度上取决于你。写测试是一个很大的话题,超出这篇文章的范围。最重要的,测试对你的和潜在的捐助者都是有用的。应该标识清楚每个用例是测试的什么函数。用例应该以相同的“风格”书写,这样潜在的贡献者不必猜测在你的项目中他/她应该使用三种测试风格中的哪种。

覆盖测试

自动化测试的覆盖率是一个有争议的话题。一些人认为它给出了错误的保证是一个毫无意义的度量,其他人认为它很有用。在我看在,我建议如果你已经使用自动化测试但从来没有检查过你的测试覆盖率,现在做这样一个练习。

使用py.test,我们可以使用Ned Batchelder的覆盖测试工具。使用pip安装pytest-cov。如果你之前这样运行你的测试:

你可以通过传递一些新的标识生成覆盖率报告,下面是运行sandman的一个例子:

当然不是所有项目都有100%的测试覆盖率(事实上,正如你读到的,sandman没有100%覆盖),但获得100%的覆盖率是一个有用的练习。它能够揭示我之前没有留意的缺陷与重构机会。

因为,作为测试本身,自动生成的测试覆盖报可以作为你持续集成的一部分。如果你选择这样做,部署一个标记来显示当前的测试覆盖率会为你的项目增加透明度(大多数时候会极大的鼓励他人贡献)。

使用Tox进行标准化测试

一个所有Python项目维护者都需要面对的问题是兼容性。如果你的目标是同时支持Python 2.x和Python 3.x(如果你目前只支持Python 2.x,应该这样做),实际中你如何确保你的项目支持你所说的所有版本呢?毕竟,当你运行测试时,你只使用特定的版本环境来运行测试,它很可能在Python2.7.5中运行良好但在Python 2.6和3.3出现问题。

幸运的是有一个工具致力于解决这个问题。tox提供了“Python的标准化测试”,它不仅仅是在多个版本环境中运行你的测试。它创造了一个完整的沙箱环境,在这个环境中你的包和需求被安装和测试。如果你做了更改在测试时没有异常,但意外地影响了安装,使用Tox你会发现这类问题。

通过一个.ini文件配置tox:tox.ini。它是一个很容易配置的文件,下面是从tox文档中摘出来的一个最小化配置的tox.ini:

通过设置envlist为py26和py27,tox知道需要在这两种版本环境下运行测试。tox大约支持十几个“默认”的环境沙箱,包括jython和pypy。tox这个强大的工具使用不同的版本进行测试,在不支持多版本时可配置警示。

deps是你的包依赖列表。你甚至可以让tox从PyPI地址安装所有或一些你依赖包。显然,相当多的想法和工作已融入了项目。

实际在你的所有环境下运行测试现在只需要四个按键:

一个更复杂的设置

我的书——“写地道的Python”,实际上写的是一系列的Python模块和代码。这样做是为了确保所有的示例代码按预期工作。作为我的构建过程的一部分,我运行tox来确保任何新的语法代码能正常运行。我偶尔也看看我的测试覆盖率,以确保没有语法在测试中被无意跳过。因此,我的tox.ini比上面的复杂一些,一起来看一看:

这个配置文件依旧比较简单。而结果呢?

(我从输出列表里截取了一部分)。如果想看我的测试对一个环境的覆盖率,只需运行:

你可能感兴趣的:(Python,学习笔记,开源,git)