0x00 前言
PyCon 2018 有很多精彩的演讲,今天的文章里,介绍一下 K 神的演讲 『Python 未来的包管理工具 pipenv』
Kenneth Reitz 出品,必属精品。
0x01 Python packaging 进化历史
『上古时代』的 Pythonist 是这样安装依赖包的。
curl http://pypi.python.org/packages/alsdasdl/requests.tar.gz | tar zxf
cd requests/
python setup.py install
复制代码
这个问题初看起来不是问题,但是随着你安装程序的增多就知道有多么痛苦了。
- 有的依赖库依赖别的库你怎么解决?比如 pandas 需要安装 numpy
- 有的依赖库依赖 c 库怎么办?比如 LXML
- 在 python2.6.5 下,如果我需要安装两个不同版本的 Django 开发不同的软件怎么办?难道只能动态复制文件到 site-packages 里面?
后来,我们是这样安装包的。
easy_install requests
复制代码
我们可以直接从 pypi 进行安装了。但尼玛,为什么 easy_install 安装很 easy, 但是没有 easy_uninstall?
好,2010 年后,我们继续前进:
- 可以通过 pip 替代 easy_install 了。
- 可以通过 virtualenv 管理项目的依赖库了。虽然说,还是不能像 ruby gem 一样同时把多个版本的的软件装在同一个系统里。
- 可以通过 requirements 锁依赖了。
但,同期的其他编程语言社区分别出现了如下的包管理工具:
- node -> yarn && npm , 有 lockfile
- php -> composer , 有 lockfile
- rust -> cargo , 有 lockfile
- ruby -> bundler , 有 lockfile
而我大 Python 居然没跟上潮流
python -> pip && virtualenv/venv , 无 lockfile
PS: Python3.3 之后,默认可以直接使用 venv 模块,不需要再安装 virtualenv 了。但还是需要手动,并且用起来比较反直觉。
有人说,我可以通过 requirements 来🔐版本呀
那我们聊聊 requirements.txt
- 如果你使用 pip freeze 来形成这个文件,则不直观,完全看不出来哪个依赖库依赖哪个依赖。
- 如果你直接手动指定你所需要的库,比如 flask 的话,似乎又有些太直观了。
- 如果能有一个东西,既可以表示 freeze 的结果 (what you want),又可以表示你需要的库 (what you need). 就好了。
这当然可以考虑用两份 requirements 来解决。先安装 what you need 用来开发,然后 freeze 为 what you want 去部署。也就是最后生成 requirements-dev.txt , 然后最后 freeze 成 requirements-prod.txt
这其实还是有一些可以优化的地方的,铺垫了这么多 K 神肯定是来介绍他的 pipenv 的。
比如说,我想查看,本项目的依赖库,直接 pipenv graph
coverage==4.5.1
fabric==2.0.1
- cryptography [required: >=1.1, installed: 2.2.2]
- asn1crypto [required: >=0.21.0, installed: 0.24.0]
- cffi [required: >=1.7, installed: 1.11.5]
- pycparser [required: Any, installed: 2.18]
- idna [required: >=2.1, installed: 2.6]
- six [required: >=1.4.1, installed: 1.11.0]
- invoke [required: <2.0,>=1.0, installed: 1.0.0]
- paramiko [required: >=2.4, installed: 2.4.1]
- bcrypt [required: >=3.1.3, installed: 3.1.4]
- cffi [required: >=1.1, installed: 1.11.5]
- pycparser [required: Any, installed: 2.18]
- six [required: >=1.4.1, installed: 1.11.0]
- cryptography [required: >=1.5, installed: 2.2.2]
- asn1crypto [required: >=0.21.0, installed: 0.24.0]
- cffi [required: >=1.7, installed: 1.11.5]
- pycparser [required: Any, installed: 2.18]
- idna [required: >=2.1, installed: 2.6]
- six [required: >=1.4.1, installed: 1.11.0]
- pyasn1 [required: >=0.1.7, installed: 0.4.2]
- pynacl [required: >=1.0.1, installed: 1.2.1]
- cffi [required: >=1.4.1, installed: 1.11.5]
- pycparser [required: Any, installed: 2.18]
- six [required: Any, installed: 1.11.0]
flake8==3.5.0
- mccabe [required: >=0.6.0,<0.7.0, installed: 0.6.1]
- pycodestyle [required: <2.4.0,>=2.0.0, installed: 2.3.1]
- pyflakes [required: >=1.5.0,<1.7.0, installed: 1.6.0]
# 其他省略
复制代码
假如我要检查我当前开发环境的依赖是不是有些小问题。直接执行 check
$ pipenv check
Checking PEP 508 requirements…
Passed!
Checking installed package safety…
33075: django >=1.10,<1.10.3 resolved (1.10.1 installed)!
Django before 1.8.x before 1.8.16, 1.9.x before 1.9.11, and 1.10.x before 1.10.3, when settings.DEBUG is True, allow remote attackers to conduct DNS rebinding attacks by leveraging failure to validate the HTTP Host header against settings.ALLOWED_HOSTS.
33076: django >=1.10,<1.10.3 resolved (1.10.1 installed)!
Django 1.8.x before 1.8.16, 1.9.x before 1.9.11, and 1.10.x before 1.10.3 use a hardcoded password for a temporary database user created when running tests with an Oracle database, which makes it easier for remote attackers to obtain access to the database server by leveraging failure to manually specify a password in the database settings TEST dictionary.
33300: django >=1.10,<1.10.7 resolved (1.10.1 installed)!
CVE-2017-7233: Open redirect and possible XSS attack via user-supplied numeric redirect URLs
============================================================================================
Django relies on user input in some cases (e.g.
:func:`django.contrib.auth.views.login` and :doc:`i18n </topics/i18n/index>`)
to redirect the user to an "on success" URL. The security check for these
redirects (namely ``django.utils.http.is_safe_url()``) considered some numeric
URLs (e.g. ``http:999999999``) "safe" when they shouldn't be. Also, if a developer relies on ``is_safe_url()`` to provide safe redirect targets and puts such a URL into a link, they could suffer from an XSS attack. CVE-2017-7234: Open redirect vulnerability in ``django.views.static.serve()`` ============================================================================= A maliciously crafted URL to a Django site using the :func:`~django.views.static.serve` view could redirect to any other domain. The view no longer does any redirects as they don't provide any known, useful
functionality.
Note, however, that this view has always carried a warning that it is not
hardened for production use and should be used only as a development aid.
复制代码
如何尝鲜?我最近更新到了之前写的一个库(代码写的惨不忍赌,最近准备重构,勿喷)
git clone git@github.com:twocucao/YaPyLib.git
cd YaPyLib/
brew install pipenv
pipenv --three
pipenv install --dev
pipenv shell
复制代码
记住几个命令
pipenv --venv # 查看 venv 位置
pipenv --python 3.6.5
exit 退出 pipenv shell
复制代码
当进入项目的时候,就执行 pipenv shell 自动 source 一下环境。嗯,但总觉得假如我维护了 30 个项目,每次开一个终端 cd 到对应的目录,然后执行 pipenv shell 真的很麻烦啊。
嗯,没错,要是能将这个过程自动化就好了。
本人假设你用的 zsh , 添加下面的配置到 zsh 中。
function auto_pipenv_shell {
if [ ! -n "${PIPENV_ACTIVE+1}" ]; then
if [ -f "Pipfile" ] ; then
pipenv shell
fi
fi
}
function cd {
builtin cd "$@"
auto_pipenv_shell
}
auto_pipenv_shell
复制代码
至于其他的功能,参考官网自己摸索吧。
FAQ 环节
FAQ 环节有一个问题非常有趣,应该把 lockfile 放在 git 仓库里面吗?
k 神是这么回答的 yes。 这个问题很久之前就在 issue 上回答过了
https://github.com/pypa/pipenv/issues/598
我刚开始觉得不提交会好一些,后来觉得 track 一下也无妨。
写在最后
在用 npm / yarn 写 node 程序 /vuejs 应用的时候,特别希望 python 圈子里面能出一个类似于包管理工具。后来果然有了 pipenv, 今年 2 月份的时候把自己的项目迁移过来,发现 pipenv 用起来很挺舒服的。
pipenv 是 Python 未来的依赖管理工具。火速用上吧。