摘要:不幸的是,在软件包管理十分混乱,至少历史上十分混乱。的最大改进是将函数的参数多带带放到一个的文件中这些成为包的元数据。基于的版本号管理。的版本推导这里重点说明一下基于的版本号管理这个功能。开发版本号的形式如下。
为什么写这个系列
OpenStack是目前我所知的最大最复杂的基于Python项目。整个OpenStack项目包含了数十个主要的子项目,每个子项目所用到的库也不尽相同。因此,对于Python初学者和未接触过OpenStack项目的人来说,入门的难度相当大。
幸运的是,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文件的例子:
#!/usr/bin/env python # -*- coding: utf-8 -*- from disutils.core import setup setup(name="webdemo", description="A simple web demo.", author="author name", author_email="author_name@example.com" url="http://example.com", packages=["webdemo"])
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来查看支持的命令。
setuptoolsdisutils停止开发后,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项目的文档在:http://pythonhosted.org/setuptools/。OpenStack目前也是使用setuptools库来执行打包操作,我们下面会详细点介绍setuptools工具。
disutils2在setuptools项目发展的过程中,有一个叫disutils2的项目也在并行开发中,其目的是全面取代Python标准库中的distutils。disutils2的最大改进是将setup函数的参数多带带放到一个setup.cfg的文件中(这些成为包的元数据)。不够disutils2这个项目缺点很多,而且没有功能上还不如setuptools项目,所以在2012年的时候,这个项目被废弃了。
distlib这个是一个新的打包工具,目标也是取代disutils。不过这个项目的开发进展也不快,到2015年才发布了0.2.0版本。目前还未能并入到Python的标准库中。不过可以保持关注。项目文档地址:https://readthedocs.org/projects/distlib/
在OpenStack中使用打包工具前面已经提到了,OpenStack也是使用setuptools工具来进行打包,不过为了满足OpenStack项目的需求,引入了一个辅助工具pbr来配合setuptools完成打包工作。
pbr (Python Build Reasonableness)pbr是一个setuptools的扩展工具,被开发出来的主要目的是为了方便使用setuptools,其项目文档地址也在OpenStack官网内:http://docs.openstack.org/developer/pbr/。
先说一下pbr如何使用:
import setuptools setuptools.setup(setup_requires=["pbr"], 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 = 8.0.0 # 软件包版本号,还可以指定preversoining, postversioning等值,具体的作用看pbr的文档。 summary = OpenStack Identity # 简介 description-file = # 指定README文件 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 :: 2 Programming Language :: Python :: 2.7 [files] # 文件段 packages = # 包名称 keystone [global] # 全局段 setup-hooks = # 指定安装hook pbr.hooks.setup_hook [egg_info] # 指定egg信息 tag_build = tag_date = 0 tag_svn_revision = 0 [build_sphinx] # 文档build相关信息 all_files = 1 build-dir = doc/build source-dir = doc/source [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 # NOTE(dstanek): Uncomment the [pbr] section below and remove the ext.apidoc # Sphinx extension when https://launchpad.net/bugs/1260495 is fixed. [pbr] # pbr本身的配置 warnerrors = True autodoc_tree_index_modules = True [entry_points] # 指定入口点 console_scripts = # 指定要生成的可执行文件 keystone-all = keystone.cmd.all:main keystone-manage = keystone.cmd.manage:main # 下面是其他entry_points内容,主要用于指定不同功能的扩展,和打包无关。 ...
(上面有些未注释的部分我目前还不太清楚,后续补充,可以先参考PEP301)
这里说说一下classifier这个参数。这个参数是用来指定一个软件包的分类、许可证、允许运行的操作系统、允许运行的Python的版本的信息。这些信息是在一个叫trove的项目。关于Python和trove的关系,请参考http://stackoverflow.com/questions/9094220/trove-classifiers-definition。
你可以在PyPI上找到完整的classifier值列表,地址是:https://pypi.python.org/pypi?%3Aaction=list_classifiers。另外,你也可以通过setuptools的命令来获取这个列表,在项目根目录下执行:python setup.py register --list-classifiers。
requirements.txt这个文件指定了一个项目依赖的包有哪些,并且支出了依赖的包的版本需求,可以看看keystone项目的requirements.txt:
# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr<2.0,>=0.11 WebOb>=1.2.3 eventlet>=0.17.4 greenlet>=0.3.2 PasteDeploy>=1.5.0 Paste Routes!=2.0,>=1.12.3 cryptography>=0.8.2 # Apache-2.0 six>=1.9.0 SQLAlchemy<1.1.0,>=0.9.7 sqlalchemy-migrate>=0.9.6 stevedore>=1.5.0 # Apache-2.0 passlib python-keystoneclient>=1.6.0 keystonemiddleware>=1.5.0 oslo.concurrency>=2.1.0 # Apache-2.0 oslo.config>=1.11.0 # Apache-2.0 oslo.messaging!=1.12.0,>=1.8.0 # Apache-2.0 oslo.db>=1.10.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 oslo.log>=1.2.0 # Apache-2.0 oslo.middleware!=2.0.0,>=1.2.0 # Apache-2.0 oslo.policy>=0.5.0 # Apache-2.0 oslo.serialization>=1.4.0 # Apache-2.0 oslo.service>=0.1.0 # Apache-2.0 oslo.utils>=1.6.0 # Apache-2.0 oauthlib>=0.6 pysaml2>=2.4.0 dogpile.cache>=0.5.3 jsonschema!=2.5.0,<3.0.0,>=2.0.0 pycadf>=0.8.0 msgpack-python>=0.4.0软件包归档格式
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 1 directory, 6 files
这个是一个最简单的Python项目目录:
源代码放在子目录webdemo/下
然后包含了软件包管理的所需的文件:setup.py, setup.cfg, requirements.txt
LICENSE和README
软件包管理相关首先是setup.py,就是这么简单:
#!/usr/bin/env python # -*- coding: utf-8 -*- import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=["pbr"], pbr=True)
然后是setup.cfg:
[metadata] name = webdemo version = 0.0.1 summary = Web Application Demo description-file = README.md author = author author-email = author@example.com classifier = Environment :: Web Environment Intended Audience :: Developers Intended Audience :: Education License :: OSI Approved :: GNU General Public License v2 (GPLv2) Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 [global] setup-hooks = pbr.hooks.setup_hook [files] packages = webdemo [entry_points] console_scripts =
只包含最基本的信息。接下来是requirements.txt文件:
# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr<2.0,>=0.11
目前只依赖于pbr库。源代码目录下现在只有一个空的__init__.py文件。我们已经搭建好了这个最简单的项目框架了。首先,我们要把这些代码提交到git库,然后打上tag 0.0.1:
➜ ~/programming/python/webdemo git:(master) ✗ $ git log --oneline 697427c Add packaging information 2cbbf4d Initial commit ➜ ~/programming/python/webdemo git:(master) ✗ $ git tag -a -s 0.0.1 ➜ ~/programming/python/webdemo git:(master) ✗ $ git tag 0.0.1
然后就可以使用python setup.py sdist命令来生成一个0.0.1版本的源码归档了:
➜ ~/programming/python/webdemo git:(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 warning: no previously-included files found matching ".gitreview" warning: no previously-included files matching "*.pyc" found anywhere in distribution writing manifest file "webdemo.egg-info/SOURCES.txt" warning: sdist: standard file not found: should have one of README, README.rst, README.txt running check warning: check: missing required meta-data: url creating webdemo-0.0.1 creating webdemo-0.0.1/webdemo creating webdemo-0.0.1/webdemo.egg-info making hard links in webdemo-0.0.1... hard linking AUTHORS -> webdemo-0.0.1 hard linking ChangeLog -> webdemo-0.0.1 hard linking LICENSE -> webdemo-0.0.1 hard linking README.md -> webdemo-0.0.1 hard linking requirement.txt -> webdemo-0.0.1 hard linking setup.cfg -> webdemo-0.0.1 hard linking setup.py -> webdemo-0.0.1 hard linking webdemo/__init__.py -> webdemo-0.0.1/webdemo hard linking webdemo.egg-info/PKG-INFO -> webdemo-0.0.1/webdemo.egg-info hard linking webdemo.egg-info/SOURCES.txt -> webdemo-0.0.1/webdemo.egg-info hard linking webdemo.egg-info/dependency_links.txt -> webdemo-0.0.1/webdemo.egg-info hard linking webdemo.egg-info/entry_points.txt -> webdemo-0.0.1/webdemo.egg-info hard linking webdemo.egg-info/not-zip-safe -> webdemo-0.0.1/webdemo.egg-info hard linking webdemo.egg-info/pbr.json -> webdemo-0.0.1/webdemo.egg-info hard linking webdemo.egg-info/top_level.txt -> webdemo-0.0.1/webdemo.egg-info copying setup.cfg -> webdemo-0.0.1 Writing webdemo-0.0.1/setup.cfg Creating tar archive removing "webdemo-0.0.1" (and everything under it) ➜ ~/programming/python/webdemo git:(master) ✗ $ ls dist webdemo-0.0.1.tar.gz ➜ ~/programming/python/webdemo git:(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。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/45387.html
摘要:通过,也就是通过各个项目提供的来使用各个服务的功能。通过使用的方式是由各个服务自己实现的,比如负责计算的项目实现了计算相关的,负责认证的项目实现了认证和授权相关的。的服务都是使用的方式来部署的。 使用OpenStack服务的方式 OpenStack项目作为一个IaaS平台,提供了三种使用方式: 通过Web界面,也就是通过Dashboard(面板)来使用平台上的功能。 通过命令行,也就...
摘要:到这里,我们的服务的框架已经搭建完成,并且测试服务器也跑起来了。上面的代码也就可以修改为再次运行我们的测试服务器,就可以返现返回值为格式了。我们先来完成利用来检查返回值的代码方法的第一个参数表示返回值的类型这样就完成了的返回值检查了。 上一篇文章说到,我们将以实例的形式来继续讲述这个API服务的开发知识,这里会使用Pecan和WSME两个库。 设计REST API 要开发REST AP...
摘要:本文将进入单元测试的部分,这也是基础知识中最后一个大块。本文将重点讲述和中的单元测试的生态环境。另外,在中指定要运行的单元测试用例的完整语法是。中使用模块管理单元测试用例。每个项目的单元测试代码结构可 本文将进入单元测试的部分,这也是基础知识中最后一个大块。本文将重点讲述Python和OpenStack中的单元测试的生态环境。 单元测试的重要性 github上有个人画了一些不同语言的学...
摘要:在实际项目中,这么做肯定是不行的实际项目中不会使用内存数据库,这种数据库一般只是在单元测试中使用。接下来,我们将会了解中单元测试的相关知识。 在上一篇文章,我们介绍了SQLAlchemy的基本概念,也介绍了基本的使用流程。本文我们结合webdemo这个项目来介绍如何在项目中使用SQLAlchemy。另外,我们还会介绍数据库版本管理的概念和实践,这也是OpenStack每个项目都需要做的...
摘要:另外,项目在单元测试中使用的是的内存数据库,这样开发者运行单元测试的时候不需要安装和配置复杂的数据库,只要安装好就可以了。而且,数据库是保存在内存中的,会提高单元测试的速度。是实现层的基础。项目一般会使用数据库来运行单元测试。 OpenStack中的关系型数据库应用 OpenStack中的数据库应用主要是关系型数据库,主要使用的是MySQL数据库。当然也有一些NoSQL的应用,比如Ce...
阅读 3841·2021-11-16 11:44
阅读 3097·2021-11-12 10:36
阅读 3353·2021-10-08 10:04
阅读 1227·2021-09-03 10:29
阅读 371·2019-08-30 13:50
阅读 2571·2019-08-29 17:14
阅读 1719·2019-08-29 15:32
阅读 1050·2019-08-29 11:27