项目地址: https://pypi.org/project/pip-tools/

pip-tools 是一个命令行工具集合,主要有两个命令: pip-compilepip-sync,现在主要用的是 pip-compile

在写python项目的时候,我们通常会依赖到其他的包,比如写一个后端的服务,我们可能会依赖到flask/pymysql这些基础的包,随着开发的进行,我们还可能会引入更多的依赖。

这些python依赖包构成了一个完整的服务,开发完成后,我们将这些服务部署到服务器上时,只需要安装好对应的依赖,便可以直接运行服务。

但是,如果我们不将生产环境的依赖固定下来的话,整个服务可能会因为某一次服务更新,其中某个的更新带来的不兼容导致整个服务崩溃。比如pyjwt,在1.7.1版本之前生成的jwt-token是一个bytes类型,我们将其转换成为字符串需要使用到decode()方法。但是在更新到2.0之后,生成的jwt-token是一个字符串类型,如果我们对其再调用decode方法的话,就会发生错误。

这就显现出了固定依赖的重要性了。

通常,固定依赖,我们可以在开发环境使用 pip freeze命令,直接将开发环境的所有版本固定下来到一个 requirements.txt 文件里面,这是懒人做法,当然也很有效。但是文件里面会带有一些系统的依赖版本,比如urllib 之类的,这些我们是不需要再次安装的,所以,如果这些依赖出现在 requirements.txt 文件里面,就会显得不那么干净

这时,我们可以使用 pip-tools里面的 pip-compile 工具,对依赖进行编译,生成一份漂亮的依赖文件。

直接生成最新依赖

  1. 首先,我们将项目中直接依赖的包写到requirements.in文件里面

requirements.in

pymysql
flask
  1. 使用 pip-compile 自动使用最新依赖版本生成依赖文件

命令: pip-compile, 输出requirements.txt文件:

requirements.txt

#
# This file is autogenerated by pip-compile with python 3.8
# To update, run:
#
#    pip-compile
#
--index-url https://pypi.doubanio.com/simple
--trusted-host pypi.doubanio.com

click==8.1.3
    # via flask
flask==2.1.2
    # via -r requirements.in
importlib-metadata==4.11.4
    # via flask
itsdangerous==2.1.2
    # via flask
jinja2==3.1.2
    # via flask
markupsafe==2.1.1
    # via jinja2
pymysql==1.0.2
    # via -r requirements.in
werkzeug==2.1.2
    # via flask
zipp==3.8.0
    # via importlib-metadata

这样,我们就得到了一份漂亮的依赖文件,我们还可以清晰的看到某一个依赖包是哪个包的依赖。

从本地已有依赖生成文件

有时候,我们的服务已经部署上云了,这时候,我们想要使用已有的(本地已安装的)依赖版本来生成依赖文件,而不是从pypi上查找最新的,因为现有正在运行的相对稳定,而更新后不确定会引入哪些不兼容问题。

我们可以这样做:

  1. 还是先将我们的直接依赖写到 requirements.in 文件里面
  2. 到生产环境中,使用 pip freeze --exclude-editable > requirements.txt 命令,将当前环境的所有包版本写到requirements.txt 文件里面
  3. 使用命令 pip-compile --no-upgrade,只用本地依赖生成一份漂亮的依赖文件。

重新生成的依赖文件:

requirements.txt

#
# This file is autogenerated by pip-compile with python 3.8
# To update, run:
#
#    pip-compile
#
--index-url https://pypi.doubanio.com/simple
--trusted-host pypi.doubanio.com

click==7.1.2
    # via flask
flask==1.1.4
    # via -r requirements.in
itsdangerous==1.1.0
    # via flask
jinja2==2.11.3
    # via flask
markupsafe==2.1.1
    # via jinja2
pymysql==0.10.1
    # via -r requirements.in
werkzeug==1.0.1
    # via flask

加上参数 --no-upgrade,pip-compile 就会优先使用requirements.txt里面,我们freeze的依赖版本,而不是到pypi中寻找最新的版本。

可以看到,重新生成的 requirements.txt 文件的各个版本都相对落后一些,依赖总数也少一点。