编者按:《通过 demo 学习 OpenStack 开发》专栏是刘陈泓的系列文章,专栏通过开发一个 demo 的形式来介绍一些参与 OpenStack 项目开发的必要的基础知识,希望帮助大家入门企业级 Python 项目的开发和 OpenStack 项目的开发。刘陈泓主要关注 OpenStack 的身份认证和计费领域。另外,还对云计算、分布式系统应用和开发感兴趣。
为什么写这个系列
OpenStack 是目前我所知的最大最复杂的基于 Python 项目。整个 OpenStack 项目包含了数十个主要的子项目,每个子项目所用到的库也不尽相同。因此,对于 Python 初学者和未接触过 OpenStack 项目的人 Python 来说,入门的难度相当大。
幸运的是,OpenStack 中的项目有很多共同点。比如,它们有相同的代码库结构,都尽可能是用同样的库,配置文件和单元测试的规范也都几乎一样。因此,通过学习这些共通的部分,我们就可以快速掌握多个 OpenStack 项目。但是,万事开头难,学习这些基础知识总是痛苦的。不过,学习的难点并不在于这些知识点本身有多难理解,而是这些基础知识的应用场景和应用效果对初学者来说都是模糊的。这个系列文章的目的就是帮助有需要的人了解 OpenStack 中一些常见的知识点。理解过程就是通过动手做一个 Web application demo 来实现的。
这个系列文章会涉及到以下的知识点:
- 包管理和 pbr
- WSGI, RESTful Service 和 Pecan 框架
- eventlet
- SQLAlchymy
- 单元测试
下面的知识点是不会专门讲的,如果有遇到不会的请自学:
- git
软件包管理
软件包管理是每个 OpenStack 项目的基础,其目的是用来将项目代码打包成源码包或者二进制包进行分发。一个项目的代码可能会被打包放到 PyPI 上,这样你可以通过pip
命令安装这个包;也可能会被打包放到项目的软件仓库里,这样你可以通过apt-get install
或者yum install
来安装这个软件包。
不幸的是,Python 在软件包管理十分混乱,至少历史上十分混乱。原因有两个:
- 一是标准库提供的软件包管理功能十分弱,
- 二是官方没有提供统一的软件包管理标准。
对于这个领域,我曾经也是混乱的,只知道使用 easy_install 和 pip 来安装软件包。不过自从看了 _The Hacker’s Guide to Python_(《Python 高手之路》)之后,算是知道点来龙去脉。
软件打包工具的历史
这里我会讲一下我知道的 Python 的软件打包工具的历史,我们按照历史顺序来叙述。
distutils (before 2000)
disutils 自从 1998 年起就是 Python 标准库的一部分了,不过它在 2000 年就停止了开发。disutils 是最早的 Python 打包工具和标准,也奠定了对 Python 软件进行打包的一个基本工作方式:使用 setup.py 文件。来看一个 setup.py 文件的例子:
<span>from</span> disutils.core <span>import</span> setup setup(name=<span>'webdemo'</span>, description=<span>'A simple web demo.'</span>, author=<span>'author name'</span>, author_email=<span>'author_name@example.com'</span> url=<span>'http://example.com'</span>, packages=[<span>'webdemo'</span>])
setup.py 文件是放在项目根目录下的:
➜ ~/programming/Python/webdemo git:(master) ✗ $ ls LICENSE README.md setup.py webdemo
然后你就可以使用命令 Python setup.py build
来编译包,可以使用 Python setup.py install
来安装这个项目。如果需要帮助,可以通过 Python setup.py --help-commands
来查看支持的命令。
setuptools
disutils 停止开发后,setuptools 成了继任者。setuptools 提供了很多高级功能,包括自动依赖处理、Egg 分发格式以及easy_install
命令。setuptools 的使用方式和 disutils 差不多,也是以一个 setup 函数作为入口,只不过该函数来自于 setuptools 模块,而且支持更多的参数,比如 classifiers, setup_requires 等,参数更多意味着功能更多。
后来有一段时间 setuptools 项目发展开始变得缓慢了,就有人从 setuptools 项目创建了distribute项目。distribute 开始支持 Python 3 等新特性。不过一段时间后,distribute 项目又和 setuptools 项目合并了(2013 年 3 月)。因此,现在已经不存在 distribute 项目了。
到目前为止,setuptools 还是使用最多的打包工具,而且开发很活跃,2015 年 6 月刚刚发布了 18.0 版本。 setuptools 项目的文档。OpenStack 目前也是使用 setuptools 库来执行打包操作,我们下面会详细点介绍 setuptools 工具。
disutils2
在 setuptools 项目发展的过程中,有一个叫 disutils2 的项目也在并行开发中,其目的是全面取代 Python 标准库中的 distutils。disutils2 的最大改进是将 setup 函数的参数单独放到一个 setup.cfg 的文件中(这些成为包的元数据)。不够 disutils2 这个项目缺点很多,而且没有功能上还不如 setuptools 项目,所以在 2012 年的时候,这个项目被废弃了。
distlib
这个是一个新的打包工具,目标也是取代 disutils。不过这个项目的开发进展也不快,到 2015 年才发布了 0.2.0 版本。目前还未能并入到 Python 的标准库中。不过可以保持关注。项目文档地址。
在OpenStack 中使用打包工具
前面已经提到了,OpenStack 也是使用setuptools 工具来进行打包,不过为了满足OpenStack 项目的需求,引入了一个辅助工具 pbr来配合 setuptools 完成打包工作。
pbr (Python Build Reasonableness)
pbr 是一个 setuptools 的扩展工具,被开发出来的主要目的是为了方便使用 setuptools,其项目文档地址也在OpenStack 官网内。
先说一下pbr 如何使用:
import setuptools setuptools.setup(setup_requires=[<span>'pbr'</span>], pbr=True)
按照上面的方式就可以配置 setuptools 工具使用 pbr 来协助完成打包工作。这里的setup_requires
参数意思是 setup 函数在执行之前需要依赖的包的列表。这里的依赖的包的功能可以理解为生成 setup 的实际参数。你可以看到,当使用 pbr 的时候,setup 函数只有两个参数,然而实际上setuptools.setup
函数实际上是disutils.core.setup
函数,会接收任何参数,这些参数可以通过在调用时指定,也可以通过所依赖的扩展来生成(比如 pbr)。
那么 OpenStack 社区为啥要开发 pbr 呢?因为 setuptools 库使用起来还是有点麻烦,参数太多,而且直接通过指定 setup 函数的参数的方法实在太不方便了。pbr 就是为了方便而生的,它带了了如下的改进:
- 使用 setup.cfg 文件来提供包的元数据。这个是从 disutils2 学来的。
- 基于 requirements.txt 文件来实现自动依赖安装。requirements.txt 文件中包含了一个项目所要依赖的库,这个文件的格式是和 pip 兼容的。
- 利用 Sphinx 实现文档自动化。
- 基于 git history 自动生成 AUTHORS 和 ChangeLog 文件。
- 针对 git 自动创建文件列表。
- 基于 git tags 的版本号管理。
pbr 的版本推导
这里重点说明一下基于 git tag 的版本号管理这个功能。当使用 pbr 的时候,版本号有两种方式:postversioning和preversioning,postversioning 是默认方式。要是用 preversioning 的方式,则需要设置 setup.cfg 文件中的 _[metadata]_ 段的version
字段的值。无论采用哪种方式,版本号都是从 git 的历史推理得到的。pbr 使用的版本号标准是 Linux/Python Compatible Semantic Versioning 3.0.0 ,简单的说就是下面这个标准:
Given a version number MAJOR.MINOR.PATCH, increment the:
- MAJOR version when you make incompatible API changes,
- MINOR version when you add functionality in a backwards-compatible manner,
- and PATCH version when you make backwards-compatible bug fixes.
pbr 的版本推导按照如下的步骤进行(注意,最终版本号才是软件包的版本号):
- 如果设置 version 的值为一个给定的版本号,且这个版本号刚好对应一个 tag,则这个值就是最终版本号(注意,这里只有签名的 tag 才有效)。
- 如果不是上面情况,则 pbr 会找到最近的一个 tag,然后为其 MINOR 值加 1 得到一个比它大的最小版本号(注意,这个还不是最终版本号)。
- 然后 pbr 会从最近的一个 tag 开始遍历所有的 git commit,并检查每个提交的 commit message,在 commit message 中查找Sem-Ver:这样的行:
-
如果 Sem-Ver 的值是 _bugfix_,则会增加版本号中 PATCH 部分的值。
-
如果 Sem-Ver 的值是 _feature_ 或者 _deprecation_,则会增加版本号中 MINOR 部分的值。
-
如果 Sem-Ver 的值是 _api-break_,则会增加版本号中 MAJOR 部分的值。
-
如果 Sem-Ver 行不存在,则认为值是 _bugfix_。
-
如果 Sem-Ver 的值不在上面列出的范围内,则会给出警告。
- 如果使用的是 postversioning 的方式,也就是 setup.cfg 中不指定 version 的值,则 pbr 会使用规则 3 推导出来的值作为目标版本号(只是目标版本号,不是最终版本号)。
- 如果使用的是 preversioning 的方式,也就是 setup.cfg 中指定了 version 的值(而且不符合规则 1),则会检查指定的 version 是否高于规则 3 推导出来的版本号,如果没有,则会抛出异常,如果有,则使用指定的版本号作为目标版本号。
- 在得到目标版本号之后,开始计算开发版本号。开发版本号的形式如下:
MAJOR.MINOR.PATCH.devN
。这里要计算的是devN中的N。这个值等于从最近的 git tag 开始的提交数量。计算完开发版本号之后,就得到了最终版本号。
总的来说,从上面的规则计算出来的版本号只有两种形式,一种是发布版本号(对应到某个 tag),另一种是开发版本号。注意:pbr 要求 tag 都是要签名的,也就是打 tag 时要使用git tag -a -s X.Y.Z
的形式。
setup.cfg 和 requirements.txt
setup.cfg
由于 OpenStack 项目都使用了 setuptools 和 pbr 来执行打包工作,因此项目的元数据都放在 setup.cfg 文件中。我们以 keystone 项目的 setup.cfg 文件为例来说明这个文件里一般会包含什么内容。以下是写这篇文章时最新的 keystone 项目的 setup.cfg 文件的内容(以#开头的是我加的注释):
[metadata] name = keystone version = <span>8.0</span>.<span>0</span> summary = OpenStack Identity description-file = README.rst author = OpenStack author-email = OpenStack-dev@lists.OpenStack.org home-page = http://www.OpenStack.org/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: <span>2</span> Programming Language :: Python :: <span>2.7</span> [files] packages = keystone [global] setup-hooks = pbr.hooks.setup_hook [egg_info] tag_build = tag_date = <span>0</span> tag_svn_revision = <span>0</span> [build_sphinx] all_files = <span>1</span> build-dir = doc/build <span>source</span>-dir = doc/<span>source</span> [compile_catalog] directory = keystone/locale domain = keystone [update_catalog] domain = keystone output_dir = keystone/locale input_file = keystone/locale/keystone.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = keystone/locale/keystone.pot copyright_holder = OpenStack Foundation msgid_bugs_address = https://bugs.launchpad.net/keystone [pbr] warnerrors = True autodoc_tree_index_modules = True [entry_points] console_scripts = keystone-all = keystone.cmd.all:main keystone-manage = keystone.cmd.manage:main ...
(上面有些未注释的部分我目前还不太清楚,后续补充,可以先参考 PEP301 )
这里说说一下 classifier 这个参数。这个参数是用来指定一个软件包的分类、许可证、允许运行的操作系统、允许运行的 Python 的版本的信息。这些信息是在一个叫 trove 的项目。关于 Python 和 trove 的关系,请参考。
你可以在PyPI 上找到完整的classifier 值列表,地址。另外,你也可以通过setuptools 的命令来获取这个列表,在项目根目录下执行: Python setup.py register --list-classifiers
。
requirements.txt
这个文件指定了一个项目依赖的包有哪些,并且支出了依赖的包的版本需求,可以看看 keystone 项目的 requirements.txt:
<span># The order of packages is significant, because pip processes them in the order</span> <span># of appearance. Changing the order has an impact on the overall integration</span> <span># process, which may cause wedges in the gate later.</span> pbr<<span>2.0</span>,>=<span>0.11</span> WebOb>=<span>1.2</span><span>.3</span> eventlet>=<span>0.17</span><span>.4</span> greenlet>=<span>0.3</span><span>.2</span> PasteDeploy>=<span>1.5</span><span>.0</span> Paste Routes!=<span>2.0</span>,>=<span>1.12</span><span>.3</span> cryptography>=<span>0.8</span><span>.2</span> <span># Apache-2.0</span> six>=<span>1.9</span><span>.0</span> SQLAlchemy<<span>1.1</span><span>.0</span>,>=<span>0.9</span><span>.7</span> sqlalchemy-migrate>=<span>0.9</span><span>.6</span> stevedore>=<span>1.5</span><span>.0</span> <span># Apache-2.0</span> passlib Python-keystoneclient>=<span>1.6</span><span>.0</span> keystonemiddleware>=<span>1.5</span><span>.0</span> oslo<span>.concurrency</span>>=<span>2.1</span><span>.0</span> <span># Apache-2.0</span> oslo<span>.config</span>>=<span>1.11</span><span>.0</span> <span># Apache-2.0</span> oslo<span>.messaging</span>!=<span>1.12</span><span>.0</span>,>=<span>1.8</span><span>.0</span> <span># Apache-2.0</span> oslo<span>.db</span>>=<span>1.10</span><span>.0</span> <span># Apache-2.0</span> oslo<span>.i</span>18n>=<span>1.5</span><span>.0</span> <span># Apache-2.0</span> oslo<span>.log</span>>=<span>1.2</span><span>.0</span> <span># Apache-2.0</span> oslo<span>.middleware</span>!=<span>2.0</span><span>.0</span>,>=<span>1.2</span><span>.0</span> <span># Apache-2.0</span> oslo<span>.policy</span>>=<span>0.5</span><span>.0</span> <span># Apache-2.0</span> oslo<span>.serialization</span>>=<span>1.4</span><span>.0</span> <span># Apache-2.0</span> oslo<span>.service</span>>=<span>0.1</span><span>.0</span> <span># Apache-2.0</span> oslo<span>.utils</span>>=<span>1.6</span><span>.0</span> <span># Apache-2.0</span> oauthlib>=<span>0.6</span> pysaml2>=<span>2.4</span><span>.0</span> dogpile<span>.cache</span>>=<span>0.5</span><span>.3</span> jsonschema!=<span>2.5</span><span>.0</span>,<<span>3.0</span><span>.0</span>,>=<span>2.0</span><span>.0</span> pycadf>=<span>0.8</span><span>.0</span> msgpack-Python>=<span>0.4</span><span>.0</span>
软件包归档格式
Python 的软件包一开始是没有官方的标准分发格式的。比如 Java 有 jar 包或者 war 包作为分发格式,Python 则什么都没有。后来不同的工具都开始引入一些比较通用的归档格式。比如,setuptools 引入了 Egg 格式。但是,这些都不是官方支持的,存在元数据和包结构彼此不兼容的问题。因此,为了解决这个问题,PEP 427 定义了新的分发包标准,名为 Wheel。目前 pip 和 setuptools 工具都支持 Wheel 格式。这里我们简单总结一下常用的分发格式:
- tar.gz 格式:这个就是标准压缩格式,里面包含了项目元数据和代码,可以使用
Python setup.py sdist
命令生成。 - .egg 格式:这个本质上也是一个压缩文件,只是扩展名换了,里面也包含了项目元数据以及源代码。这个格式由 setuptools 项目引入。可以通过命令
Python setup.py bdist_egg
命令生成。 - .whl 格式:这个是 Wheel 包,也是一个压缩文件,只是扩展名换了,里面也包含了项目元数据和代码,还支持免安装直接运行。whl 分发包内的元数据和 egg 包是有些不同的。这个格式是由 PEP 427 引入的。可以通过命令
Python setup.py bdist_wheel
生成。
.egg-info 和.dist-info 目录
如果你到系统中安装 Python 库的路径下看看,就能看到很多名称以.egg-info 或者以.dist-info 结尾的目录。这些目录的内容就是这个库的元数据,是从库的分发包中拷贝出来的。其中.egg-info 类型的目录来自于 Egg 格式的分发包,.dist-info 类型的目录来自于 Wheel 格式的分发包。
软件包的安装
安装工具
上面已经提到了,setuptools 项目提供了一个软件包安装工具 _esay_install_。easy_install 支持从软件归档文件中或者从 PyPI 上安装软件包,不过这个工具并不好用,比如缺少卸载功能等,因此并不流行,现在更多的都是使用 pip 工具。
pip项目提供了很好的软件包安装方式,并且已经被包含到 Python 3.4 中,可以从 PyPI、tarball 或者 Wheel 归档中安装和卸载软件按包。关于 pip 常见的用法,这里就不赘述了 (pip install, pip uninstall, pip search, …)。
安装路径
软件包的安装路径依赖于操作系统、Python 版本和安装方式。
-
在 Debian 系的系统上(比如 Ubuntu)
-
使用
apt-get install
从系统软件源安装- Python 2.7: /usr/lib/Python2.7/dist-packages
- Python 3.4: /usr/lib/Python3.4/dist-packages
-
使用
pip install
命令安装- Python 2.7: /usr/local/lib/Python2.7/dist-packages
- Python 3.4: /usr/local/lib/Python3.4/dist-packages
-
在 virtualenv 中使用
pip install
安装- Python 2.7: lib/Python2.7/site-packages
- Python 3.4: lib/Python3.4/site-packages
-
-
在 CentOS 系的系统上
-
使用
yum install
命令安装- Python 2.7: /usr/lib/Python2.7/site-packages
-
以开发模式安装
pip 的安装命令可以使用 -e 选项,用来从本地代码目录或者版本库 URL 来安装一个开发版本的库。采用这种方式的时候,在安装目录下只会创建一个包含软件包信息的文件,真正的代码不会安装到系统目录下。
Webdemo 的打包管理
学习过包管理相关的知识后,我们就要以 OpenStack 的方法来创建一个我们自己的项目。这个项目的名称是 Webdemo,就是一个简单的 Web 服务器。这个项目会贯穿这个系列文章。在本文中,我们首先要创建 Webdemo 的项目框架并添加软件包管理相关的内容。
项目目录结构
➜ ~/programming/Python/webdemo git:(master) ✗ $ tree . . ├── LICENSE ├── README.md ├── requirement.txt ├── setup.cfg ├── setup.py └── webdemo └── __init__.py <span>1</span> directory, <span>6</span> files
这个是一个最简单的 Python 项目目录:
- 源代码放在子目录 Webdemo/ 下
- 然后包含了软件包管理的所需的文件:setup.py, setup.cfg, requirements.txt
- LICENSE 和 README
软件包管理相关
首先是 setup.py,就是这么简单:
<span>import</span> setuptools <span>try</span>: <span>import</span> multiprocessing <span>except</span> ImportError: <span>pass</span> setuptools.setup( setup_requires=[<span>'pbr'</span>], pbr=<span>True</span>)
然后是 setup.cfg:
[metadata] name = webdemo version = <span>0.0</span>.<span>1</span> summary = Web Application Demo description-file = README.md author = author author-email = author@example.com classifier = <span> Environment ::</span> Web Environment <span> Intended Audience ::</span> Developers <span> Intended Audience ::</span> Education <span> License :: OSI Approved ::</span> GNU General Public License v2 (GPLv2) <span> Operating System :: POSIX ::</span> Linux <span> Programming Language ::</span> Python <span> Programming Language :: Python ::</span> <span>2</span> <span> Programming Language :: Python ::</span> <span>2.7</span> [global] setup-hooks = pbr.hooks.setup_hook [files] packages = webdemo [entry_points] console_scripts =
只包含最基本的信息。接下来是 requirements.txt 文件:
# The <span>order</span> <span>of</span> packages <span>is</span> significant, because pip processes them <span>in</span> the <span>order</span> # <span>of</span> appearance. Changing the <span>order</span> <span>has</span> an impact <span>on</span> the overall integration # process, which may cause wedges <span>in</span> the gate later. pbr<<span>2.0</span>,>=<span>0.11</span>
目前只依赖于 pbr 库。源代码目录下现在只有一个空的init.py
文件。我们已经搭建好了这个最简单的项目框架了。首先,我们要把这些代码提交到 git 库,然后打上 _tag 0.0.1_:
➜ ~<span>/programming/</span><span>Python</span>/webdemo <span>git:</span>(master) ✗ <span>$ </span>git log --oneline <span>697427</span>c <span>Add</span> packaging information <span>2</span>cbbf4d <span>Initial</span> commit ➜ ~<span>/programming/</span><span>Python</span>/webdemo <span>git:</span>(master) ✗ <span>$ </span>git tag -a -s <span>0</span>.<span>0</span>.<span>1</span> ➜ ~<span>/programming/</span><span>Python</span>/webdemo <span>git:</span>(master) ✗ <span>$ </span>git tag <span>0</span>.<span>0</span>.<span>1</span>
然后就可以使用Python setup.py sdist
命令来生成一个 0.0.1 版本的源码归档了:
➜ ~/programming/Python/webdemo <span>git</span>:(master) ✗ $ Python setup.py sdist running sdist [pbr] Writing ChangeLog [pbr] Generating ChangeLog [pbr] Generating AUTHORS running egg_info writing pbr to webdemo.egg-info/pbr.json writing webdemo.egg-info/PKG-INFO writing top-level names to webdemo.egg-info/top_level.txt writing dependency_links to webdemo.egg-info/dependency_links.txt writing entry points to webdemo.egg-info/entry_points.txt [pbr] Processing SOURCES.txt [pbr] In git context, generating filelist from git <span>warning</span>: <span>no</span> previously-included files found matching <span>'.gitreview'</span> <span>warning</span>: <span>no</span> previously-included files matching <span>'*.pyc'</span> found anywhere <span>in</span> distribution writing manifest file <span>'webdemo.egg-info/SOURCES.txt'</span> <span>warning</span>: <span>sdist</span>: standard file <span>not</span> <span>found</span>: should have one <span>of</span> README, README.rst, README.txt running check <span>warning</span>: <span>check</span>: missing required meta-<span>data</span>: url creating webdemo-<span>0.0</span><span>.1</span> creating webdemo-<span>0.0</span><span>.1</span>/webdemo creating webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info making hard links <span>in</span> webdemo-<span>0.0</span><span>.1</span>... hard linking AUTHORS<span> -></span> webdemo-<span>0.0</span><span>.1</span> hard linking ChangeLog<span> -></span> webdemo-<span>0.0</span><span>.1</span> hard linking LICENSE<span> -></span> webdemo-<span>0.0</span><span>.1</span> hard linking README.md<span> -></span> webdemo-<span>0.0</span><span>.1</span> hard linking requirement.txt<span> -></span> webdemo-<span>0.0</span><span>.1</span> hard linking setup.cfg<span> -></span> webdemo-<span>0.0</span><span>.1</span> hard linking setup.py<span> -></span> webdemo-<span>0.0</span><span>.1</span> hard linking webdemo/__init__.py<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo hard linking webdemo.egg-info/PKG-INFO<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info hard linking webdemo.egg-info/SOURCES.txt<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info hard linking webdemo.egg-info/dependency_links.txt<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info hard linking webdemo.egg-info/entry_points.txt<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info hard linking webdemo.egg-info/<span>not</span>-zip-safe<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info hard linking webdemo.egg-info/pbr.json<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info hard linking webdemo.egg-info/top_level.txt<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info copying setup.cfg<span> -></span> webdemo-<span>0.0</span><span>.1</span> Writing webdemo-<span>0.0</span><span>.1</span>/setup.cfg Creating tar archive removing <span>'webdemo-0.0.1'</span> (<span>and</span> everything under it) ➜ ~/programming/Python/webdemo <span>git</span>:(master) ✗ $ ls dist webdemo-<span>0.0</span><span>.1</span>.tar.gz ➜ ~/programming/Python/webdemo <span>git</span>:(master) ✗ $ ls AUTHORS ChangeLog dist LICENSE README.md requirement.txt setup.cfg setup.py webdemo webdemo.egg-info
验证成功,在 _dist/_ 目录下生成了一个 0.0.1 版本的源码归档,同时生成了如下的文件和目录:AUTHORS, ChangeLog, webdemo.egg-info。
感谢魏星对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群(已满),InfoQ 读者交流群(#2))。
评论