Python 高手编程系列五十四:你自己的包索引或索引镜像

你可能会想要运行你自己的 Python 包索引,主要有以下 3 个原因。
● 官方的 Python 包索引没有任何可用性保证。它由 Python 软件基金会运行,这要感
谢大量的捐款。因此,它往往意味着网站可能会倒闭。你不希望由于 PyPI 的故障
而中途停止部署或打包过程。
● 即使是不会公开发布的闭源代码,将 Python 编写的可复用组件正确打包也很有用。
它简化了代码库,因为公司内用于不同项目的包不需要供应(vendored)。你可以
从仓库直接安装这些包。这简化了对这些共享代码的维护,如果许多团队在不同项
目上工作,这还可能会降低整个公司的开发成本。
● 使用 setuptools 将整个项目打包是非常好的做法。然后,新应用版本的部署非常简单,只需运行 pip install --update my-application。
PyPI 镜像
如果允许安装工具从 PyPI 的一个镜像下载包,那么就可以缓解 PyPI 故障带来的问题。
事实上,官方的 Python 包索引已经通过内容分发网络(Content Delivery Network,CDN)
提供服务,因此它是自带镜像的。但这无法改变下列事实:似乎不时会有一些糟糕的日子,
下载一个包的任何尝试都会失败。对于这种情况,使用非官方的镜像也不是解决办法,因
为可能会引起一些安全问题。
最好的解决方案是使用你自己的 PyPI 镜像,里面包含所有你需要的包。只有你会使
用这个镜像,才更容易确保其可用性。另一个优点是,每当这项服务停机时,你不需要
依靠其他人来启动它。由 PyPA 维护并推荐的镜像工具是 bandersnatch。它允许你制作
Python 包索引全部内容的镜像,你可以在.pypirc 文件中 repository 区段的 index-url
选项中进行设置(正如上一章所述)。这个镜像不接受上传,并且没有 PyPI 的 web 部分。
无论怎样一定要小心!完整的镜像可能需要数百 GB 的存储,其大小将随着时间的推移而
持续增长。
但如果我们有更好的选择,为什么要止步于简单的镜像呢?你几乎不可能需要整个包索引的镜像。即使对于一个有上百个依赖的项目,也只是所有可用包的一小部分。此外,
无法上传自己的私有包,也是这种简单镜像的一大限制。使用 bandersnatch 的代价这么大,
而附加的价值似乎却非常小。而且对大多数情况来说都是这样。如果只需要为几个项目中
的一个维护包镜像,那么更好的方法是使用 devpi。它是与 PyPI 兼容的包索引实现,可以
提供:
● 上传非公开包的私有索引;
● 索引镜像。
与 bandersnatch 相比,devpi 的主要优点在于它处理镜像的方式。它当然可以对其他索
引制作一般完整的镜像,就像 bandersnatch 所做的那样,但这并不是它的默认做法。它维
护客户端已经请求的包组成的镜像,而不是对整个仓库进行代价高昂的备份。因此,每当
安装工具(pip、setuptools 和 easyinstall)请求一个包时,如果它不在本地镜像
中,那么 devpi 服务器将会尝试从镜像索引(通常是 PyPI)中下载并提供。一旦包下载完
成之后,devpi 将定期检查其更新,以保持镜像的最新状态。
如果你请求一个尚未制作镜像的新包,且上游包索引出现了故障,那么镜像方法有很
小的可能性会失败。不管怎样,由于在大多数部署中,你将会仅依赖在索引中已经存在镜
像的包,因此这一可能性很小。已经请求过的包的镜像状态与 PyPI 保持完全一致,新版本
将会自动下载。这似乎是一个非常合理的权衡。
使用包进行部署
现代 Web 应用都有大量依赖,通常需要很多步骤才能在远程主机上正确安装。例如,
对于远程主机上的新版本应用来说,典型的引导过程包括以下步骤。
● 创建新的虚拟隔离环境。
● 将项目代码移动到执行环境。
● 安装最新的项目需求(通常来自于 requirements.txt 文件)。
● 同步或迁移数据库模式。
● 从项目源代码和外部包中收集静态文件并放在所需位置。
● 为不同语言的应用编译本地化文件。
对于更复杂的网站,可能还有许多额外的任务,主要和前端代码有关:
● 使用 SASS 或 LESS 等预处理器生成 CSS 文件。
● 执行静态文件(JavaScript 和 CSS 文件)的压缩、混淆和/或合并。
● 将 JavaScript 超集语言(CoffeeScript、TypeScript 等)编写的代码编译为原生 JS。
● 预处理响应模板文件(压缩、样式内联等)。
利用 Bash、Fabric 或 Ansible 等工具可以将所有这些步骤轻松自动化,但在应用的安装过程中,在远程主机上完成所有这些步骤并不是一个好主意,其原因如下。
● 有些处理静态资产的常用工具可能会占用大量的 CPU 或内存。在生产环境中运行
这些工具可能会破坏应用运行的稳定性。
● 这些工具通常需要额外的系统依赖,而项目的正常运行可能并不需要它们。它们大
多是额外的运行环境,例如 JVM、Node 或 Ruby。这增加了配置管理的复杂性,
也增加了总的维护成本。
● 如果你要将应用部署到多个服务器(几十、成百、上千),那么你就是在重复大量
工作,而这些工作本来只需要做一次就可以。如果你有自己的基础设施,那么你可
能不会遇到成本的巨大增长,特别是如果你在低流量时段进行部署。但如果你运行
定价模式的云计算服务,并且它对负载峰值或一般的执行时间额外收费的话,那么
额外成本可能会相当高。
● 大多数步骤只是需要大量时间。你在远程服务器上安装代码时,你最不希望的事情就
是由于网络问题导致连接中断。保持部署过程快速完成,可以降低部署中断的概率。
由于显而易见的原因,上述部署步骤的结果不能包含在你的应用代码仓库中。简单来
说,每个版本都有一些必须要做的事情,你不能改变这一点。这显然适合使用自动化,但
应该在正确的地方和正确的时间进行。
类似静态收集和代码/资产预处理之类的大多数事情都可以在本地或专用环境中完成,
所以部署到远程服务器的实际代码只需要最少的现场处理。在构建一个发行版或安装一个
包的过程中,最值得注意的部署步骤如下。
● 安装 Python 依赖,并将静态资产(CSS 文件和 JavaScript)移动到所需位置,这两
个步骤都可以作为 setup.py 脚本 install 命令的一部分来处理。
● 预处理代码(预处理 JavaScript 超集、资产的压缩/混淆/合并、运行 SASS 或 LESS)
与诸如将文本编译本地化(例如 Django 中的 compilemessages)之类的操作,
都可以作为 setup.py 脚本 sdist/bdist 命令的一部分。
利用正确的 MANIFEST.in 文件,可以轻松处理除 Python 之外的预处理代码。当然,
最好在 setuptools 包中 setup()函数调用的 install_requires 参数中给出依赖。
当然,将整个应用打包需要一些额外的工作,例如提供你自己的自定义 setuptools
命令或者覆写现有的命令,但它有许多优点,可以让项目部署变得更加快速、更加可靠。
我们用一个基于 Django 的项目(Django 1.9 版)作为例子。我之所以选择这个框架,
是因为它似乎是同一类型中最流行的 Python 框架,所以你很可能对它已经有所了解。在这
样的项目中,典型的文件结构可能如下所示:
$ tree . -I __pycache __ --dirsfirst
.
├── webxample
│ ├── conf
│ │ ├── __init _.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── locale
│ │ ├── de
│ │ │ └── LC
MESSAGES
│ │ │ └── django.po
│ │ ├── en
│ │ │ └── LC
MESSAGES
│ │ │ └── django.po
│ │ └── pl
│ │ └── LC
_MESSAGES
│ │ └── django.po
│ ├── myapp
│ │ ├── migrations
│ │ │ └── __init _.py
│ │ ├── static
│ │ │ ├── js
│ │ │ │ └── myapp.js
│ │ │ └── sass
│ │ │ └── myapp.scss
│ │ ├── templates
│ │ │ ├── index.html
│ │ │ └── some
_view.html
│ │ ├── __init __.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── models.py
│ │ ├── tests.py
│ │ └── views.py
│ ├── __init __.py
│ └── manage.py
├── MANIFEST.in
├── README.md
└── setup.py
15 directories, 23 files
注意,这与常见的 Django 项目模板略有不同。默认情况下,包含 WSGI 应用、设置模
块和 URL 配置的包名称与项目名称相同。由于我们决定采用打包方法,所以将其命名为webxample。这可能会造成一些混淆,所以最好将其重命名为 conf。
我们不去钻研可能的实现细节,只是做几个简单的假设。
我们的应用示例有一些外部依赖,这里包括两个常用的 Django 包:djangorest
framework 和 django-allauth,还有一个非 Django 包:gunicorn。
● djangorestframework和django-allauth在webexample.webexample.
settings 模块中由 INSTALLED_APPS 给出。
● 应用被本地化为 3 种语言(德语、英语和波兰语),但我们不希望将编译过的
gettext 消息保存在仓库中。
● 我们讨厌普通的 CSS 语法,因此我们决定使用更强大的 SCSS 语言,我们可以用
SASS 将其转换为 CSS。
知道了项目的结构之后,我们可以编写 setup.py 脚本,让 setuptools 处理以下
内容。
● 在 webxample/myapp/static/scss 目录下编译 SCSS 文件。
● 在 webexample/locale 目录下将 gettext 消息由.po 格式编译为.mo 格式。
安装需求:
● 为包提供一个入口点的新脚本,这样我们将使用自定义命令,而不是 manage.py
脚本。
我们这里有一些运气成分。Python 绑定了 libsass(SASS 引擎的 C/C++端口),提
供了一些与 setuptools 和 distutils 的集成。只需要很少的配置,它就可以提供自定
义的 setup.py 命令来运行 SASS 编译,如下所示:
from setuptools import setup
setup(
name=‘webxample’,
setup_requires=[‘libsass >= 0.6.0’],
sass_manifests={
‘webxample.myapp’: (‘static/sass’, ‘static/css’)
},
)
因此,我们可以输入 python setup.py build_scss 将我们的 SCSS 文件编译成
CSS,而不用手动运行 sass 命令或者在 setup.py 脚本中执行子进程。这还不够。它让
我们的生活更轻松,但我们希望整个分发过程能够完全自动化,只需一步就可以创建新版
本。为了实现这个目标,我们不得不覆写一部分现有的 setuptools 分发命令。
setup.py 文件通过打包来处理一些项目准备步骤,其示例可能如下所示:
import os
from setuptools import setup
from setuptools import find_packages
from distutils.cmd import Command
from distutils.command.build import build as _build
try:
from django.core.management.commands.compilemessages
import Command as CompileCommand
except ImportError:

注意:安装期间 django 可能不可用

CompileCommand = None

这个环境是必需的

os.environ.setdefault(
“DJANGO_SETTINGS_MODULE”, “webxample.conf.settings”
)
class build_messages(Command):
“”" 自定义命令,用于在构建 Django 中的 gettext 消息。
“”"
description = “”“compile gettext messages”“”
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
if CompileCommand:
CompileCommand().handle(
verbosity=2, locales=[], exclude=[]
)
else:
raise RuntimeError(“could not build translations”)
class build(_build):
“”" 覆写了添加额外构建步骤的构建命令。
“”"
sub_commands = [
(‘build_messages’, None),
(‘build_sass’, None),
] + _build.sub_commands
setup(
name=‘webxample’,
setup_requires=[
‘libsass >= 0.6.0’,
‘django >= 1.9.2’,
],
install_requires=[
‘django >= 1.9.2’,
‘gunicorn == 19.4.5’,
‘djangorestframework == 3.3.2’,
‘django-allauth == 0.24.1’,
],
packages=find_packages(‘.’),
sass_manifests={
‘webxample.myapp’: (‘static/sass’, ‘static/css’)
},
cmdclass={
‘build_messages’: build_messages,
‘build’: build,
},
entry_points={
‘console_scripts’: {
‘webxample = webxample.manage:main’,
}
}
)
利用这样的实现,我们用下面这一个终端命令就可以构建所有资产并为 webxample
项目创建包的源代码发行版,代码如下:
$ python setup.py build sdist
如果你已经有了自己的包索引(用 devpi 创建的),你可以添加 install 子命令,
或者使用 twine 让组织内可以使用 pip 来安装这个包。如果仔细观察利用 setup.py 脚
本创建的源代码发行版的结构,我们可以发现它包含编译过的 gettext 消息和从 SCSS 文件生成的 CSS 样式表,如下所示:
$ tar -xvzf dist/webxample-0.0.0.tar.gz 2> /dev/null
$ tree webxample-0.0.0/ -I __pycache __ --dirsfirst
webxample-0.0.0/
├── webxample
│ ├── conf
│ │ ├── __init _.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── locale
│ │ ├── de
│ │ │ └── LC
MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── en
│ │ │ └── LC
MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ └── pl
│ │ └── LC
_MESSAGES
│ │ ├── django.mo
│ │ └── django.po
│ ├── myapp
│ │ ├── migrations
│ │ │ └── __init _.py
│ │ ├── static
│ │ │ ├── css
│ │ │ │ └── myapp.scss.css
│ │ │ └── js
│ │ │ └── myapp.js
│ │ ├── templates
│ │ │ ├── index.html
│ │ │ └── some
_view.html
│ │ ├── __init __.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── models.py
│ │ ├── tests.py
│ │ └── views.py
│ ├── __init __.py
│ └── manage.py

├── webxample.egg-info
│ ├── PKG-INFO
│ ├── SOURCES.txt
│ ├── dependency_ links.txt
│ ├── requires.txt
│ └── top
_level.txt
├── MANIFEST.in
├── PKG-INFO
├── README.md
├── setup.cfg
└── setup.py
16 directories, 33 files
使用这种方法还有额外的好处,就是我们能够为项目提供自己的入口点来代替 Django
默认的 manage.py 脚本。现在我们可以利用这个入口点运行所有 Django 管理命令,例如:
$ webxample migrate
$ webxample collectstatic
$ webxample runserver
这需要稍稍修改 manage.py 脚本,以保持与 setup()中 entry_points 参数的兼
容,这样其代码的主要部分被 main()函数调用所包装,如下所示:
#!/usr/bin/env python3
import os
import sys
def main():
os.environ.setdefault(
“DJANGO_SETTINGS_MODULE”, “webxample.conf.settings”
)
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
if name == “main”:
main()
不幸的是,许多框架(包括 Django)并没有按照这样打包项目的想法来设计。也就是
说,根据你项目的进展,将其转换为一个包可能需要大量更改。对于 Django 来说,这通常意味着重新编写许多隐式导入并在设置文件中修改许多配置变量。
另一个问题在于利用 Python 打包创建的版本的一致性。如果授权不同的团队成员来创建
应用发行版,那么确保这个过程发生在同样的可复制环境中是非常重要的,尤其是你做了许
多资产预处理时。即使用相同的代码库来创建包,在两个不同环境中创建的包可能也会看起
来不一样。这可能是因为在构建过程中使用了不同版本的工具。最佳做法是将分发职责转移
给一个持续集成/交付系统,例如 Jenkins 或 Buildbot。额外的优点是你可以确定一个包在分
发前通过了所有必需的测试。你甚至可以将自动部署作为这种持续交付系统的一部分。
尽管如此,使用 setuptools 将代码作为 Python 包分发也并不简单和轻松。它会大
大简化你的部署,因此是非常值得尝试的。注意,这也符合十二要素应用中第 6 条规则的
详细建议:以一个或多个无状态进程运行应用。

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值