以正确的方式开源 Python 项目

以正确的方式开源 Python 项目

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

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

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

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

工具和概念

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

  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  为开始下一个项目自动生成这些步骤

项目布局

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

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

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

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

01 $ pwd
02 ~/code/sandman
03 $ tree
04 .
05 |- LICENSE
06 |- README.md
07 |- TODO.md
08 |- docs
09 |   |-- conf.py
10 |   |-- generated
11 |   |-- index.rst
12 |   |-- installation.rst
13 |   |-- modules.rst
14 |   |-- quickstart.rst
15 |   |-- sandman.rst
16 |- requirements.txt
17 |- sandman
18 |   |-- __init__.py
19 |   |-- exception.py
20 |   |-- model.py
21 |   |-- sandman.py
22 |   |-- test
23 |       |-- models.py
24 |       |-- test_sandman.py
25 |- setup.py

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

setuptools 和 setup.py文件

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

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

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

01 from __future__ import print_function
02 from setuptools import setup, find_packages
03 from setuptools.command.test import test as TestCommand
04 import io
05 import codecs
06 import os
07 import sys
08  
09 import sandman
10  
11 here = os.path.abspath(os.path.dirname(__file__))
12  
13 def read(*filenames, **kwargs):
14     encoding = kwargs.get('encoding', 'utf-8')
15     sep = kwargs.get('sep', '\n')
16     buf = []
17     for filename in filenames:
18         with io.open(filename, encoding=encoding) as f:
19             buf.append(f.read())
20     return sep.join(buf)
21  
22 long_description = read('README.txt', 'CHANGES.txt')
23  
24 class PyTest(TestCommand):
25     def finalize_options(self):
26         TestCommand.finalize_options(self)
27         self.test_args = []
28         self.test_suite = True
29  
30     def run_tests(self):
31         import pytest
32         errcode = pytest.main(self.test_args)
33         sys.exit(errcode)
34  
35 setup(
36     name='sandman',
37     version=sandman.__version__,
38     url='http://github.com/jeffknupp/sandman/',
39     license='Apache Software License',
40     author='Jeff Knupp',
41     tests_require=['pytest'],
42     install_requires=['Flask>=0.10.1',
43                     'Flask-SQLAlchemy>=1.0',
44                     'SQLAlchemy==0.8.2',
45                     ],
46     cmdclass={'test': PyTest},
47     author_email='jeff@jeffknupp.com',
48     description='Automated REST APIs for existing database-driven systems',
49     long_description=long_description,
50     packages=['sandman'],
51     include_package_data=True,
52     platforms='any',
53     test_suite='sandman.test.test_sandman',
54     classifiers = [
55         'Programming Language :: Python',
56         'Development Status :: 4 - Beta',
57         'Natural Language :: English',
58         'Environment :: Web Environment',
59         'Intended Audience :: Developers',
60         'License :: OSI Approved :: Apache Software License',
61         'Operating System :: OS Independent',
62         'Topic :: Software Development :: Libraries :: Python Modules',
63         'Topic :: Software Development :: Libraries :: Application Frameworks',
64         'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
65         ],
66     extras_require={
67         'testing': ['pytest'],
68     }
69 )

(感谢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的脚本,这个用来启动的调用包括下面这行:

1 scripts = ['go_foo.py'],
确保在脚本中填入相对路径,并不只仅是一个名称 (如scripts = ['scripts/foo_scripts/go_foo.py']).一样,你的脚本应该以"shebang"行和"python"开始,以下:
1 #! /usr/bin/env 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安装指导操做,在这里

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

1 $ git flow init
Branch细节

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

Master

master分支一直是存放“生产就绪”的代码。全部的提交都不该该提交到master分支上。固然,master分支上的代码只会从一个产品发布分支创 建并结束后合并进来。这样在master上的代码一直是能够发布为产品的。而且,master也是一直处于可预计的状态,因此你永远不须要担忧若是 master分支修改了而某一个其余分支没有相应的修改。

Develop

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

对于一次性的提交,能够随便提交到develop上。

特性

对于一些大的特性,就须要建立一个特性分支。特性分支从develop分支建立出来。它们能够是对于下一个发布的一些小小的加强或者更进一步的修改。而这,依然须要从如今开始工做。为了从一个新的分支上开始工做,使用:

1 $ git flow feature start <feature name>
这命令建立了一个新的分支:feature/<feature name>。一般会把代码提交到这个分支。当特性已经完成而且准备好发布的时候,它就应当用一下的命令将它合并会develop分支:
1 $ git flow feature finish <feature name>
这会把代码合并进develop分支,而且删除 feature/<feature name>分支

Release

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

1 $ git flow release start <release number>
注意,这是发布版本号第一次建立。全部完成的,准备好发布的分支必须已经合并到develop分支上。在release分支建立后,发布你的代码。任何小 的bug修改须要提交到 release/<release number>分支上。当全部的bug被修复以后,运行如下的命令:
1 $ git flow release finish <release number>
这个命令会把你的release/<release number> 分支合并到master和develop分支,这意味着你永远不须要担忧这几个分支会缺乏一些必要的产品变动(多是由于一个快速的bug修复致使的)。

Hotfix

然而 hotfix分支可能会颇有用,在现实世界中不多使用,至少我是这样认为的。hotfix就像master分支下建立的feature分支: 若是你已经关闭了release分支,可是以后又认识到还有一些很重要的东西须要一块儿发布,那么就在master分支(由$git flow release finish <release number>建立的标签)下建立一个hotfix分支,就像这样:

1 $ git flow hotfix start <release number>
当你完成改变和增长你的版本号使之独一无二(bump your version number),而后完成hotfix分支:
1 $ git flow hotfix finish <release number>

这好像一个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使建立和管理多个虚拟环境更容易的隔离工具。让咱们继续前进,立刻安装这两个工具:

1 $ pip install `virtualenvwrapper`
2 ...
3 Successfully installed `virtualenvwrapper` `virtualenv` `virtualenv`-clone stevedore
4 Cleaning up...

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

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

1 $ echo "source /usr/local/bin/virtualenvwrapper.sh" >> ~/.zshrc

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

1 $ mkvirtualenv ossproject
2  
3 New python executable in ossproject/bin/python
4 Installing setuptools............done.
5 Installing pip...............done.
6 (ossproject)$

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

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

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

1 (ossproject)$ pip freeze > requirements.txt

你会获得一个全部你项目须要模块的列表,它之后能够被setup.py文件使用列出你的依赖关系。这里有一点须要注意:我常常在 requirements.txt中将“==”改成“>=“,这样表明“我正使用包的任何的后来版本”。你是否应该或须要在项目这样作取决于实际情 况,但我应该指出来。

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

使用py.test测试

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

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

注意

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

测试安装

在测试目录里,不管你如何决定都要有这个目录,建立一个名为test_<project_name>.py的文件。py.test的测试发现机制将把全部test_前缀的文件当作测试文件处理(除非明确告知)。

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

覆盖测试

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

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

1 $ py.test

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

01 $ py.test --cov=path/to/package
02 $ py.test --cov=path/to/package --cov-report=term --cov-report=html
03 ====================================================== test session starts =======================================================
04 platform darwin -- Python 2.7.5 -- pytest-2.3.5
05 plugins: cov
06 collected 23 items
07  
08 sandman/test/test_sandman.py .......................
09 ---------------------------------------- coverage: platform darwin, python 2.7.5-final-0 -----------------------------------------
10 Name                           Stmts   Miss  Cover
11 --------------------------------------------------
12 sandman/__init__                   5      0   100%
13 sandman/exception                 10      0   100%
14 sandman/model                     48      0   100%
15 sandman/sandman                  142      0   100%
16 sandman/test/__init__              0      0   100%
17 sandman/test/models               29      0   100%
18 sandman/test/test_sandman        114      0   100%
19 --------------------------------------------------
20 TOTAL                            348      0   100%
21 Coverage HTML written to dir htmlcov
22  
23 =================================================== 23 passed in 1.14 seconds ===========================================================

固然不是全部项目都有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:

1 # content of: tox.ini , put in same dir as setup.py
2 [tox]
3 envlist = py26,py27
4 [testenv]
5 deps=pytest       # install pytest in the venvs
6 commands=py.test  # or 'nosetests' or ...

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

deps是你的包依赖列表。你甚至可让tox从PyPI地址安装全部或一些你依赖包。显然,至关多的想法和工做已融入了项目。

实际在你的全部环境下运行测试如今只须要四个按键:

1 $ tox

 

一个更复杂的设置

 


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

01 [tox]
02 envlist=py27, py34
03  
04 [testenv]
05 deps=
06     pytest
07     coverage
08     pytest-cov
09 setenv=
10     PYTHONWARNINGS=all
11  
12 [pytest]
13 adopts=--doctest-modules
14 python_files=*.py
15 python_functions=test_
16 norecursedirs=.tox .git
17  
18 [testenv:py27]
19 commands=
20     py.test --doctest-module
21  
22 [testenv:py34]
23 commands=
24     py.test --doctest-module
25  
26 [testenv:py27verbose]
27 basepython=python
28 commands=
29     py.test --doctest-module --cov=. --cov-report term
30  
31 [testenv:py34verbose]
32 basepython=python3.4
33 commands=
34     py.test --doctest-module --cov=. --cov-report term

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

01 (idiom)~/c/g/idiom git:master >>> tox
02 GLOB sdist-make: /home/jeff/code/github_code/idiom/setup.py
03 py27 inst-nodeps: /home/jeff/code/github_code/idiom/.tox/dist/Writing Idiomatic Python-1.0.zip
04 py27 runtests: commands[0] | py.test --doctest-module
05 /home/jeff/code/github_code/idiom/.tox/py27/lib/python2.7/site-packages/_pytest/assertion/oldinterpret.py:3: DeprecationWarning: The compiler package is deprecated and removed in Python 3.x.
06 from compiler import parse, ast, pycodegen
07 =============================================================== test session starts ================================================================
08 platform linux2 -- Python 2.7.5 -- pytest-2.3.5
09 plugins: cov
10 collected 150 items
11 ...
12 ============================================================ 150 passed in 0.44 seconds ============================================================
13 py33 inst-nodeps: /home/jeff/code/github_code/idiom/.tox/dist/Writing Idiomatic Python-1.0.zip
14 py33 runtests: commands[0] | py.test --doctest-module
15 =============================================================== test session starts ================================================================
16 platform linux -- Python 3.3.2 -- pytest-2.3.5
17 plugins: cov
18 collected 150 items
19 ...
20 ============================================================ 150 passed in 0.62 seconds ============================================================
21 _____________________________________________________________________ summary ______________________________________________________________________
22 py27: commands succeeded
23 py33: commands succeeded
24 congratulations :)

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

01 $ tox -e py33verbose
02 -------------------------------------------------- coverage: platform linux, python 3.3.2-final-0 --------------------------------------------------
03 Name                                                                                           Stmts   Miss  Cover
04 ------------------------------------------------------------------------------------------------------------------
05 control_structures_and_functions/a_if_statement/if_statement_multiple_lines                       11      0   100%
06 control_structures_and_functions/a_if_statement/if_statement_repeating_variable_name              10      0   100%
07 control_structures_and_functions/a_if_statement/make_use_of_pythons_truthiness                    20      3    85%
08 control_structures_and_functions/b_for_loop/enumerate                                             10      0   100%
09 control_structures_and_functions/b_for_loop/in_statement                                          10      0   100%
10 control_structures_and_functions/b_for_loop/use_else_to_determine_when_break_not_hit              31      0   100%
11 control_structures_and_functions/functions/2only/2only_use_print_as_function                       4      0   100%
12 control_structures_and_functions/functions/avoid_list_dict_as_default_value                       22      0   100%
13 control_structures_and_functions/functions/use_args_and_kwargs_to_accept_arbitrary_arguments      39     31    21%
14 control_structures_and_functions/zexceptions/aaa_dont_fear_exceptions                              0      0   100%
15 control_structures_and_functions/zexceptions/aab_eafp                                             22      2    91%
16 control_structures_and_functions/zexceptions/avoid_swallowing_exceptions                          17     12    29%
17 general_advice/dont_reinvent_the_wheel/pypi                                                        0      0   100%
18 general_advice/dont_reinvent_the_wheel/standard_library                                            0      0   100%
19 general_advice/modules_of_note/itertools                                                           0      0   100%
20 general_advice/modules_of_note/working_with_file_paths                                            39      1    97%
21 general_advice/testing/choose_a_testing_tool                                                       0      0   100%
22 general_advice/testing/separate_tests_from_code                                                    0      0   100%
23 general_advice/testing/unit_test_your_code                                                         1      0   100%
24 organizing_your_code/aa_formatting/constants                                                      16      0   100%
25 organizing_your_code/aa_formatting/formatting                                                      0      0   100%
26 organizing_your_code/aa_formatting/multiple_statements_single_line                                17      0   100%
27 organizing_your_code/documentation/follow_pep257                                                   6      2    67%
28 organizing_your_code/documentation/use_inline_documentation_sparingly                             13      1    92%
29 organizing_your_code/documentation/what_not_how                                                   24      0   100%
30 organizing_your_code/imports/arrange_imports_in_a_standard_order                                   4      0   100%
31 organizing_your_code/imports/avoid_relative_imports                                                4      0   100%
32 organizing_your_code/imports/do_not_import_from_asterisk                                           4      0   100%
33 organizing_your_code/modules_and_packages/use_modules_where_other_languages_use_object             0      0   100%
34 organizing_your_code/scripts/if_name                                                              22      0   100%
35 organizing_your_code/scripts/return_with_sys_exit                                                 32      2    94%
36 working_with_data/aa_variables/temporary_variables                                                12      0   100%
37 working_with_data/ab_strings/chain_string_functions                                               10      0   100%
38 working_with_data/ab_strings/string_join                                                          10      0   100%
39 working_with_data/ab_strings/use_format_function                                                  18      0   100%
40 working_with_data/b_lists/2only/2only_prefer_xrange_to_range                                      14     14     0%
41 working_with_data/b_lists/3only/3only_unpacking_rest                                              16      0   100%
42 working_with_data/b_lists/list_comprehensions                                                     13      0   100%
43 working_with_data/ca_dictionaries/dict_dispatch                                                   23      0   100%
44 working_with_data/ca_dictionaries/dict_get_default                                                10      1    90%
45 working_with_data/ca_dictionaries/dictionary_comprehensions                                       21      0   100%
46 working_with_data/cb_sets/make_use_of_mathematical_set_operations                                 25      0   100%
47 working_with_data/cb_sets/set_comprehensions                                                      12      0   100%
48 working_with_data/cb_sets/use_sets_to_remove_duplicates                                           34      6    82%
49 working_with_data/cc_tuples/named_tuples                                                          26      0   100%
50 working_with_data/cc_tuples/tuple_underscore                                                      15      0   100%
51 working_with_data/cc_tuples/tuples                                                                12      0   100%
52 working_with_data/classes/2only/2only_prepend_private_data_with_underscore                        43     43     0%
53 working_with_data/classes/2only/2only_use_str_for_human_readable_class_representation             18     18     0%
54 working_with_data/classes/3only/3only_prepend_private_data_with_underscore                        45      2    96%
55 working_with_data/classes/3only/3only_use_str_for_human_readable_class_representation             18      0   100%
56 working_with_data/context_managers/context_managers                                               16      7    56%
57 working_with_data/generators/use_generator_expression_for_iteration                               16      0   100%
58 working_with_data/generators/use_generators_to_lazily_load_sequences                              44      1    98%
59 ------------------------------------------------------------------------------------------------------------------
60 TOTAL                                                                                            849    146    83%
61  
62 ============================================================ 150 passed in 1.73 seconds ============================================================
63 _____________________________________________________________________ summary ______________________________________________________________________
64 py33verbose: commands succeeded
65 congratulations :)

结果很可怕啊。

setuptools整合

tox能够和setuptools整合,这样python的setup.py测试能够运行你的tox测试。将下面的代码段放到你的setup.py文件里,这段代码是直接从tox的文档里拿来的:

01 from setuptools.command.test import test as TestCommand
02 import sys
03  
04 class Tox(TestCommand):
05     def finalize_options(self):
06         TestCommand.finalize_options(self)
07         self.test_args = []
08         self.test_suite = True
09     def run_tests(self):
10         #import here, cause outside the eggs aren't loaded
11         import tox
12         errcode = tox.cmdline(self.test_args)
13         sys.exit(errcode)
14  
15 setup(
16     #...,
17     tests_require=['tox'],
18     cmdclass = {'test': Tox},
19     )

如今Python的setup.py测试将下载tox并运行它。真的很酷而且很节省时间。

Sphinx文档生成器

Sphinx是由pocoo团队开发的工具[@Lesus 注:pocoo团队开发了不少优秀的产品:如Flask, Jinja2等等]。它已经用来生成Python官方文档和大多数流行的Python包的文档。它以更容易的方式从Python代码中自动产生Python文档。

使用它完成工做

Sphinx不用了解Python程序以及怎样 从它们中提取出来。它只能翻译reStructuredText文件,也就意味着你的代码文档的reStructuredText译文须要让Sphinx 知道才能工做,可是管理维护全部的.py文件[至少是函数和类的部分]的reStructuredText译文显然是不可行的。

幸运的是,Sphinx有一个相似javadoc的扩展,叫作autodoc,能够用来从你的代码文档中萃取出reStructuredText。为了能 够充分利用Sphinx和autodoc的能力,你须要已一种特别的方式格式化你的文档。特别是,你须要使用Sphinx的Python指令时。这里就是 使用reStructuredText指令来为一个函数生成文档,使输出结果的HTML文档更漂亮:

01 def _validate(cls, method, resource=None):
02 """Return ``True`` if the the given *cls* supports the HTTP *method* found
03 on the incoming HTTP request.
04  
05 :param cls: class associated with the request's endpoint
06 :type cls: :class:`sandman.model.Model` instance
07 :param string method: HTTP method of incoming request
08 :param resource: *cls* instance associated with the request
09 :type resource: :class:`sandman.model.Model` or None
10 :rtype: bool
11  
12 """
13 if not method in cls.__methods__:
14     return False
15  
16 class_validator_name = 'validate_' + method
17  
18 if hasattr(cls, class_validator_name):
19     class_validator = getattr(cls, class_validator_name)
20     return class_validator(resource)
21  
22 return True

文档须要花费一点功夫,可是为了你的使用者,这个付出是值得的。好吧,好的文档使一个可用的项目去其糟粕。

Sphinx的autodoc扩展让咱们可使用不少指令,而这些指令能够自动的从你文档中生成文档。

安装

确认将Sphinx安装在你的virtualenv内,由于文档在项目里也是按版原本的。Sphinx不一样的版本可能会产生不一样的HTML输出。经过将其安装在你的virtualenv内,你能够以受控的方式升级你的文档。

咱们要保持咱们的文档在docs文件夹,将文档生成到docs/generated文件夹。在项目的根目录运行如下命令将根据你的文档字符自动重构文本文档:

1 $ sphinx-apidoc -F -o docs <package name>

这将产生一个包含多个文档文件的docs文件夹。此外,它建立了一个叫conf.py的文件,它将负责你的文档配置。你还会发现一个Makefile,方便使用一个命令(生成html)构建HTML文档。

在你最终生成文档以前,确保你已经在本地安装了相应的包(尽管可使用pip,但python setup.py develop是最简单的保持更新的方法),不然sphinx-apidoc没法找到你的包。

配置:conf.py

conf.py文件建立用来控制产生的文档的各个方面。它本身会很好生成文档,因此我只简单地触及两点。

版本和发布

首先,确保你的版本和发布版本号保持最新。这些数字会做为生成的文档的一部分显示,因此你不但愿它们远离了实际值。

保持你的版本最新的最简单方式就是在你的文档和setup.py文件中都从你的包的__version__属性读取。我从Flask的conf.py借用过来配置sandman的conf.py:

01 import pkg_resources
02 try:
03     release = pkg_resources.get_distribution('sandman').version
04 except pkg_resources.DistributionNotFound:
05     print 'To build the documentation, The distribution information of sandman'
06     print 'Has to be available.  Either install the package into your'
07     print 'development environment or run "setup.py develop" to setup the'
08     print 'metadata.  A virtualenv is recommended!'
09     sys.exit(1)
10 del pkg_resources
11  
12 version = '.'.join(release.split('.')[:2])

这就是说,为了让文档产生正确的版本号,你只需在你的项目的虚拟环境中简单的须要运行$python setup.py develop便可。如今你只需担忧保持__version__为最新,由于setup.py会使用它。

html_theme
考虑到更改default到html_theme,我更喜欢原生态的东西,显然这是一个我的喜爱的问题。我之因此提出这个问题是由于Python官方文档 在Python 2和Python 3将默认主题更改成Pydoc主题(后者的主题是一个自定义主题仅在CPython源代码中可用)。对一些人来讲,默认的主题使一个项目看起来“老”一 些。

PyPI

PyPI,Python包索引(以 前被称为“Cheeseshop”)是一个公开可用的Python包中央数据库。PyPI是你的项目发布的地方。一旦你的包(及其相关的元数据)上传到 PyPI,别人经过pip或easy_instal能够下载并安装它。这一点得强调一下:即便你的项目托管在GitHub,直到被上传到PyPI后你的项 目才是有用的。固然,有些人能够复制你的git库任何直接手工安装它,但更多的人想使用pip来安装它。

最后的一步

若是你已经完成了全部的前面部分中的步骤,你可能急着想把你的包上传到PyPI,供其余人使用!

先别急着作上述事情,在分发你的包以前,有一个叫作cheesecake的有用的工具备助于运行最后一步。它分析你的包并指定一个分类的数字分数。它衡量你的包在打包、安装、代码质量以及文档的数量和质量方面是否容易/正确。

除了做粗略衡量的“准备”,cheesecake在完整性检查方面很优秀。你会很快看到你的setup.py文件是否有错或者有没有忘记为一个文件制做文档。我建议在上传每一个项目到PyPI以前运行一下它,而不只只是第一个。

初始化上传

如今,你已经肯定了你的代码不是垃圾和当人们安装它时不会崩溃,让咱们把你的包放到PyPI上吧!你将会经过setuptools和setup.py脚本交互。若是这是第一次上传到PyPI,你将首先注册它:

1 $ python setup.py register
注意:若是你尚未一个免费的PyPI帐户,你将须要如今去注册一个,才能注册这个包[@Lesus 注:注册以后还须要到邮箱去验证才行]。在你已使用了上面注册以后,你就能够建立发布包和上传到PyPI了:
1 $ python setup.py sdist upload

上面这个命令创建一个源码发布版(sdist),而后上传到PyPI.若是你的包不是纯粹的Python(也就是说,你有二进制须要编译进去),你就须要发布一个二进制版,请看setuptools文档,了解更多。

发布及版本号

PyPI使用发行版本模型来肯定你软件包的哪一个版本是默承认用的。初次上传后,为使你软件包的每次更新后在PyPI可用,你须要指定一个新版本号建立一个发布。版本号管理是一个至关复杂的课题,PEP有专门的内容:PEP 440——版本识别和依赖指定。我建议参照PEP 400指南(明显地),但若是你选择使用不一样版本的方案,在setup.py中使用的版本比目前PyPI中的版本“高”,这样PyPI才会认为这是一个新版本。

 工做流

将你的第一个发布版本上传到PyPI后,基本的工做流程以下:

  1. 继续在你的项目上工做 (好比修复bug,添加新特性等等)
  2. 确保测试经过
  3. 在git-flow中建立一个发布分支“冻结”你的代码
  4. 在你项目的__init__.py文件里更新__version__number版本变量
  5. 屡次测试运行setup.py,将新版本上传到PyPI

用户但愿你保持足够的更新频率以修复bug。你要管理好你的版本号,不要“过于频繁”的发布。记住:你的用户不会手工维护他们每一个安装模块的不一样的版本。

使用TravisCI持续集成

持续集成是指一个项目中全部变化不断整合的过程(不是周期性的批量更新)。就咱们而言,这意味每次咱们GitHub提交时,咱们经过测试运行来发现是否有什么异常,正如你想象的,这是一个很是有价值的实践。不要有“忘记运行测试”的提交。若是你的提交通不过测试,你将收到一封电子邮件被告知。

TravisCI是一种使GitHub项目持续集成更容易的服务。若是你尚未帐号到这看一下注册一个,完成这些以后,在咱们进入CI以前咱们先须要建立一个简单的文件。

经过.travis.yml配置

在TravisCI上的不一样项目经过一个.travis.yml文件来配置,这个文件在项目的根目录。简要地说,咱们须要告诉Travis:

  1. 咱们项目使用的语言是什么
  2. 它使用的是语言的哪一个版本
  3. 使用什么命令安装它
  4. 使用什么命令运行项目的测试

这些都是很直接的东西。下面是sandman.travis.yml的内容:

01 language: python
02 python:
03     - "2.7"
04 install:
05     - "pip install -r requirements.txt --use-mirrors"
06     - "pip install coverage"
07     - "pip install coveralls"
08 script:
09     - "coverage run --source=sandman setup.py test"
10 after_success:
11     coveralls

在列出语言和版本后,咱们告诉Travis如何安装咱们的包。在install这行,确认包含下面这行:

1 - "pip install -r requirements.txt --use-mirrors"

这是pip安装咱们项目的要求(若是有必要的话使用PyPI镜像站点)。另外的两行内容是sandman特有的。它使用一个额外的服务(coveralls.io)来连续监测测试用例的覆盖率,这不是全部项目都须要的。

script:列出能运行该项目测试的命令。与上面同样,sandman还须要作一些额外的工做。你的项目须要的只有Python的setup.py测试,after_success部分也能够一块删掉。

一旦你提交了这个文件并在TravisCI中激活了你的项目的,push到GitHub。一下子后,你会看到一个基于你最近提交的编译结束结果。若是成功 了,你的编译呈现“绿色”和而且状态页会显示编译经过。你能够看到你项目在任什么时候间的编译历史。这对对人开发特别有用,在历史页能够看到特定开发者出错和 编译的频率…

你还会收到一封通知你编译成功的电子邮件。固然你也能够设置只有在出错或错误被修复时才有邮件通知,但编译输出结果相同时也不会发送。这是很是有用的,你在没必要被无用的“编译经过!”邮件淹没的同时在发生改变仍会收到警示。

用ReadTheDocs作持续文档集成

尽管PyPI有一个官方文档站点(pythonhosted.org),可是ReadTheDocs提 供了一个更好的体验。为何?ReadTheDocs有针对GitHub很是棒的集成。当你注册ReadTheDocs的时候,你就会看到你的全部 GitHub 代码库。选择合适的代码库,作一些小幅的配置,那么你的文档就会在你每次提交到GitHub以后自动从新生成。

配置你的项目应该是一个很直观的事情。只有一些事须要记住,尽管,这里有一个配置字段的列表,对应的值可能不必定是你直接用得上的:

  • Repo: https://github.com/github_username/project_name.git
  • Default Branch:develop
  • Default Version:latest
  • Python configuration file: (leave blank)
  • Usevirtualenv: (checked)
  • Requirements file:requirements.txt
  • Documentation Type: Sphinx HTML

DRY 不要重复你本身

如今你已经完成了对于一个现存代码基础的全部艰难的开源工做,你可能不会想在开始一个新项目的时候把这些事重来一遍。幸运的是,你并不须要这么作。有Andrey Roy的Cookiecutter工具(我连接到了Python版本,尽管还有一些不一样语言的版本在the main repo))

Cookiecutter是一个命令行工具可以自动执行新建项目的一些步骤来作这篇文章里提到的一些事情。 Daniel Greenfeld ( @pydanny )写了一篇很好的关于它的博客而且提到了如何与这篇文章里提到的实践联系上。你能够从这里看看这篇文章: Cookiecutter: Project Templates Made Easy .

结论

咱们已经介绍了全部用来开源一个Python包的命令,工具和服务。固然,你能够直接把它扔到GitHub上而且说“本身安装它”,可是没人会这么作。而且你仅仅是开发源代码并不算是真正的开源软件。

另外,你可能不会为你的项目吸引外部贡献者。经过这里列出的方法来设立你的项目,你就已经建立了一个容易维护的Python包而且会鼓励你们来使用和贡献代码。而这,就是开源软件的真正精神,不是吗?

相关文章
相关标签/搜索