Pipfile.lockで固定された依存関係を再現するならpipenv syncコマンドを使おう

2020.05.04

こんにちは、CX事業本部の若槻です。

今回は、Pipenvで仮想環境を再現するコマンドを色々と触ってみたところ、「誰かが作成したPipfile.lockをもとに、別のメンバーが同じ依存関係(パッケージバージョン)の環境を再現するならpipenv syncコマンドを使えば良さそう!」という結論に至ったのでご紹介します。

Pipenvとは

Pipenv is a tool that aims to bring the best of all packaging worlds (bundler, composer, npm, cargo, yarn, etc.) to the Python world. Windows is a first-class citizen, in our world.

It automatically creates and manages a virtualenv for your projects, as well as adds/removes packages from your Pipfile as you install/uninstall packages. It also generates the ever-important Pipfile.lock, which is used to produce deterministic builds.

Pipenv is primarily meant to provide users and developers of applications with an easy method to setup a working environment.

Pipenvは、virtualenvのような仮想環境の管理と、Pipのようなパッケージ管理の機能を備えたツールです。

PipenvではPipfilePipfile.lockを利用することにより、Pipでrequirements.txtを用いるよりも強力なパッケージ管理を行うことができます。

  • Pipfile:要求されたパッケージの一覧などが記録されるファイル。
  • Pipfile.lock:実際にインストールされるパッケージの依存関係などが記録されるファイル。

試しにpipenv installコマンドでrequestsのバージョン2.20.0をインストールしてみると、Pipenvの仮想環境にrequestsがインストールされ、同時にPipfilePipfile.lockが作成されました。(Pipenvによるパッケージインストールが初めて行われるディレクトリ内であれば、仮想環境の作成も同時に行われます。)

$ pipenv install requests==2.20.0

Installing requests==2.20.0…
Adding requests to Pipfile's [packages]…
✔ Installation Succeeded 
Pipfile.lock (870504) out of date, updating to (ca72e7)…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
✔ Success! 
Updated Pipfile.lock (870504)!
Installing dependencies from Pipfile.lock (870504)…
     ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 5/5 — 00:00:03
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.

作成されたPipfileです。要求されたパッケージrequests==2.20.0が記録されています。

Pipfile

[source]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
requests = "==2.20.0"

[requires]
python_version = "3.6"

作成されたPipfile.lockです。要求されたrequests==2.20.0に依存してインストールされたパッケージのバージョンが記録されています。(このことをパッケージの依存関係の固定と言います。)

Pipfile.lock

{
    "_meta": {
        "hash": {
            "sha256": "5c2429960803a0b6159d6589fa3dd25d89557b2497344392443403d5cb870504"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.6"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "certifi": {
            "hashes": [
                "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304",
                "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"
            ],
            "version": "==2020.4.5.1"
        },
        "chardet": {
            "hashes": [
                "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
                "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
            ],
            "version": "==3.0.4"
        },
        "idna": {
            "hashes": [
                "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
                "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
            ],
            "version": "==2.7"
        },
        "requests": {
            "hashes": [
                "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c",
                "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279"
            ],
            "index": "pypi",
            "version": "==2.20.0"
        },
        "urllib3": {
            "hashes": [
                "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4",
                "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb"
            ],
            "version": "==1.24.3"
        }
    },
    "develop": {}
}

開発プロジェクトですでに誰かが作成したPipfilePipfile.lockを使えば、他のメンバーも同じPipenv仮想環境を簡単に再現できるようになります。

PipfileやPipfile.lockから環境を再現するコマンド

PipfileやPipfile.lockから環境を再現する場合は、ドキュメント[Pipenv: Python Dev Workflow for Humans]によると以下のようなコマンドが使われるようです。

  • pipenv install
  • pipenv install --deploy
  • pipenv install --ignore-pipfile
  • pipenv sync

しかし、ドキュメントを読んでもこれらコマンドの使い分けまではよく分からなかったため、以下のような状況を想定したPipfilePipfile.lockを用いて、urllib3が未インストールの環境でのコマンド実行時の動作を確認してみました。

  • 想定した状況
    • pipenv install urllib3によりある時点でのurllib3の最新バージョン(1.25)をインストールして以下のPipfileとPipfile.lockを作った。
    • その後urllib3のアップデートがリリースされ、現在の最新バージョンは1.25.9である。

Pipfile

[source]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
urllib3 = "*"

[requires]
python_version = "3.6"

Pipfile.lock

{
    "_meta": {
        "hash": {
            "sha256": "14f78c3f58762bc3ff8c948eaf6c463fb56183863d6f90d28f2cf48bf6053760"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.6"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "urllib3": {
            "hashes": [
                "sha256:a08afe8b057ba35963364711a1f36d346375da0c118f611f35c0252375338c7c",
                "sha256:f03eeb431c77b88cf8747d47e94233a91d0e0fdae1cf09e0b21405a885700266"
            ],
            "index": "pypi",
            "version": "==1.25"
        }
    },
    "develop": {}
}

なお、コマンド毎にPipenv環境は作り直しています。

pipenv install

pipenv installコマンドでインストールを行った場合は、Pipfileを元にして、urllib3の最新バージョン1.25.9がインストールされ、Pipfile.lockも更新される動作となりました。

$ pipenv install
Pipfile.lock (b8b71b) out of date, updating to (053760)…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
✔ Success! 
Updated Pipfile.lock (b8b71b)!
Installing dependencies from Pipfile.lock (b8b71b)…
     ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 1/1 — 00:00:00
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.
$ pipenv graph
urllib3==1.25.9

$ git diff
diff --git a/Pipfile.lock b/Pipfile.lock
index 065770e..dffb44c 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "14f78c3f58762bc3ff8c948eaf6c463fb56183863d6f90d28f2cf48bf6053760"
+            "sha256": "fe045e48581ffc6cb50af922ec04120b504f96ff6d6917fc535c8a9b76b8b71b"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -18,11 +18,11 @@
     "default": {
         "urllib3": {
             "hashes": [
-                "sha256:a08afe8b057ba35963364711a1f36d346375da0c118f611f35c0252375338c7c",
-                "sha256:f03eeb431c77b88cf8747d47e94233a91d0e0fdae1cf09e0b21405a885700266"
+                "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527",
+                "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"
             ],
             "index": "pypi",
-            "version": "==1.25"
+            "version": "==1.25.9"
         }
     },
     "develop": {}

このときのPipfilePipfile.lock上の記録、および実際に環境上にインストールされたurllib3のバージョンのコマンド実行前後の状態を表であらわすと下記のようになります。

urllib3のバージョン コマンド実行前 コマンド実行後
pipenv install
Pipfile * *
Pipfile.lock 1.25 1.25.9
実際の環境上 なし 1.25.9

pipenv install --deploy

pipenv install --deployコマンドでインストールを行おうとした場合は、「Your Pipfile.lock (053760) is out of date.」というエラーによりurllib3のインストールはされず、PipfileとPipfile.lockの更新もされませんでした。

$ pipenv install --deploy
Your Pipfile.lock (053760) is out of date. Expected: (b8b71b).
Traceback (most recent call last):
  File "/usr/local/bin/pipenv", line 11, in <module>
    sys.exit(cli())
  File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/decorators.py", line 64, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/decorators.py", line 17, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/pipenv/cli/command.py", line 254, in install
    editable_packages=state.installstate.editables,
  File "/usr/local/lib/python3.6/site-packages/pipenv/core.py", line 1874, in do_install
    keep_outdated=keep_outdated
  File "/usr/local/lib/python3.6/site-packages/pipenv/core.py", line 1192, in do_init
    raise exceptions.DeployException
  File "/usr/local/lib/python3.6/site-packages/pipenv/exceptions.py", line 168, in __init__
    PipenvUsageError.__init__(message=fix_utf8(message), extra=extra, **kwargs)
TypeError: __init__() missing 1 required positional argument: 'self'
$ pipenv graph
$

$ git diff
$

このときのコマンド実行前後の状態を表であらわすと下記のようになります。

urllib3のバージョン コマンド実行前 コマンド実行後
pipenv install --deploy
Pipfile * *
Pipfile.lock 1.25 1.25
実際の環境上 なし なし

pipenv install --deployコマンドに関してurllib3ドキュメントの[☤ Using pipenv for Deployments]ページによると、

You can enforce that your Pipfile.lock is up to date using the --deploy flag:

$ pipenv install --deploy

This will fail a build if the Pipfile.lock is out–of–date, instead of generating a new one.

とあり、このコマンドは「Pipfileをもとにパッケージをインストールするが、Pipfile.lockがout-of-dateの場合は失敗する」とのことです。(よって実質的にPipfile.lockの最新化が強制されるようです。)

ここで、pipenv lockコマンドを実行して、PipfileをもとにPipfile.lockを最新化してみます。

$ pipenv lock
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
✔ Success! 
Updated Pipfile.lock (b8b71b)!
$ git diff
diff --git a/Pipfile.lock b/Pipfile.lock
index 065770e..dffb44c 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "14f78c3f58762bc3ff8c948eaf6c463fb56183863d6f90d28f2cf48bf6053760"
+            "sha256": "fe045e48581ffc6cb50af922ec04120b504f96ff6d6917fc535c8a9b76b8b71b"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -18,11 +18,11 @@
     "default": {
         "urllib3": {
             "hashes": [
-                "sha256:a08afe8b057ba35963364711a1f36d346375da0c118f611f35c0252375338c7c",
-                "sha256:f03eeb431c77b88cf8747d47e94233a91d0e0fdae1cf09e0b21405a885700266"
+                "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527",
+                "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"
             ],
             "index": "pypi",
-            "version": "==1.25"
+            "version": "==1.25.9"
         }
     },
     "develop": {}

このときのコマンド実行前後の状態を表であらわすと下記のようになります。

urllib3のバージョン コマンド実行前 コマンド実行後
pipenv lock
Pipfile * *
Pipfile.lock 1.25 1.25.9
実際の環境上 なし なし

そしてこの状態で再度pipenv install --deployコマンドを実行すると、urllib3の最新バージョン1.25.9がインストールされました。

$ pipenv install --deploy
Installing dependencies from Pipfile.lock (b8b71b)…
     ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 1/1 — 00:00:00

このときのpipenv install --deployコマンド実行前後の状態を表であらわすと下記のようになります。

urllib3のバージョン コマンド実行前 コマンド実行後
pipenv install --deploy
Pipfile * *
Pipfile.lock 1.25.9 1.25.9
実際の環境上 なし 1.25.9

pipenv install --ignore-pipfile

pipenv install --ignore-pipfileコマンドでインストールを行った場合は、Pipfile.lockを元にして、urllib3のバージョン1.25がインストールされ、一方でPipfileとPipfile.lockの更新は行われない動作となりました。

$ pipenv graph
urllib3==1.25

$ pipenv install --ignore-pipfile
Installing dependencies from Pipfile.lock (053760)…
     ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 1/1 — 00:00:00

このときのコマンド実行前後の状態を表であらわすと下記のようになります。

urllib3のバージョン コマンド実行前 コマンド実行後
pipenv install --ignore-pipfile
Pipfile * *
Pipfile.lock 1.25 1.25
実際の環境上 なし 1.25

pipenv install --ignore-pipfileコマンドに関してurllib3ドキュメントの[☤ Testing Projects]ページによると、

You might also want to add --ignore-pipfile to pipenv install, as to not accidentally modify the lock-file on each test run. This causes Pipenv to ignore changes to the Pipfile and (more importantly) prevents it from adding the current environment to Pipfile.lock.

とあり、このコマンドは「PipfileではなくPipfile.lockをもとにパッケージをインストールし、また現在の環境をもとにPipfile.lockの更新は行わない」とのことです。

では少し意地悪をして、pipenv install urllib3==1.25.1 --ignore-pipfileのように明示的にバージョンを指定して実行してみると、urllib3==1.25.1がインストールされ、PipfileとPipfile.lockが更新されました。

$ pipenv install urllib3==1.25.1 --ignore-pipfile
Installing urllib3==1.25.1…
Adding urllib3 to Pipfile's [packages]…
✔ Installation Succeeded 
Pipfile.lock (02301d) out of date, updating to (053760)…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
✔ Success! 
Updated Pipfile.lock (02301d)!
Installing dependencies from Pipfile.lock (02301d)…
     ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 1/1 — 00:00:00
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.
$ pipenv graph
urllib3==1.25.1

$ git diff
diff --git a/Pipfile b/Pipfile
index 9b6677f..3fe27a9 100644
--- a/Pipfile
+++ b/Pipfile
@@ -6,7 +6,7 @@ verify_ssl = true
 [dev-packages]

 [packages]
-urllib3 = "*"
+urllib3 = "==1.25.1"

 [requires]
 python_version = "3.6"
diff --git a/Pipfile.lock b/Pipfile.lock
index 065770e..e55a1b6 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "14f78c3f58762bc3ff8c948eaf6c463fb56183863d6f90d28f2cf48bf6053760"
+            "sha256": "a83dd596857a98d568970ad6bbd39fbe838378d5b620c1924a0993518402301d"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -18,11 +18,11 @@
     "default": {
         "urllib3": {
             "hashes": [
-                "sha256:a08afe8b057ba35963364711a1f36d346375da0c118f611f35c0252375338c7c",
-                "sha256:f03eeb431c77b88cf8747d47e94233a91d0e0fdae1cf09e0b21405a885700266"
+                "sha256:904bd981d6371bb95a200c0ec9dba5ba7cc980f2d6b125bd793fefe3293be388",
+                "sha256:a9645efd62b9fc1c7cad8ed93e162aad4c6bfd90e143966ddd4099b78cd244be"
             ],
             "index": "pypi",
-            "version": "==1.25"
+            "version": "==1.25.1"
         }
     },
     "develop": {}

このときのコマンド実行前後の状態を表であらわすと下記のようになります。

urllib3のバージョン コマンド実行前 コマンド実行後
pipenv install urllib3==1.25.1 --ignore-pipfile
Pipfile * 1.25.1
Pipfile.lock 1.25 1.25.1
実際の環境上 なし 1.25.1

pipenv sync

pipenv syncコマンドでインストールを行った場合は、Pipfile.lockを元にして、urllib3のバージョン1.25がインストールされ、一方でPipfileとPipfile.lockの更新は行われない動作となりました。

$ pipenv sync
Installing dependencies from Pipfile.lock (053760)…
     ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 1/1 — 00:00:00
$ pipenv graph
urllib3==1.25

$ git diff
$

このときのコマンド実行前後の状態を表であらわすと下記のようになります。

urllib3のバージョン コマンド実行前 コマンド実行後
pipenv sync
Pipfile * *
Pipfile.lock 1.25 1.25
実際の環境上 なし 1.25

pipenv syncコマンドに関してurllib3ドキュメントの[☤ Using pipenv for Deployments]ページによると、

pipenv install --ignore-pipfile is nearly equivalent to pipenv sync, but pipenv sync will never attempt to re-lock your dependencies as it is considered an atomic operation.

とあり、このコマンドはpipenv install --ignore-pipfileのように「Pipfile.lockをもとにインストールを行い、re-lock(再ロック:Pipfile.lock上の依存関係の記録を更新すること)は行わない」とのことです。

ちなみにこのコマンドでもpipenv sync urllib3==1.25.1のようにして実行を試してみましたが、オプションとしてパッケージの指定はできずエラーとなりました。このことからpipenv syncは飽くまでPipfile.lockで固定した依存関係を再現するためのコマンドであるようです。

まとめ

ここまで確認したコマンドごとの実行前後の状態の表をまとめると下記のようになります。

urllib3のバージョン コマンド実行前 コマンド実行後
pipenv install
Pipfile * *
Pipfile.lock 1.25 1.25.9
実際の環境上 なし 1.25.9
pipenv install --deploy
Pipfile * *
Pipfile.lock 1.25 1.25
実際の環境上 なし なし
pipenv lock
Pipfile * *
Pipfile.lock 1.25 1.25.9
実際の環境上 なし なし
pipenv install --deploy
Pipfile * *
Pipfile.lock 1.25.9 1.25.9
実際の環境上 なし 1.25.9
pipenv install --ignore-pipfile
Pipfile * *
Pipfile.lock 1.25 1.25
実際の環境上 なし 1.25
pipenv install urllib3==1.25.1 --ignore-pipfile
Pipfile * 1.25.1
Pipfile.lock 1.25 1.25.1
実際の環境上 なし 1.25.1
pipenv sync
Pipfile * *
Pipfile.lock 1.25 1.25
実際の環境上 なし 1.25

また、上記確認結果やドキュメントをもとに、PipfileやPipfile.lockから環境を再現するコマンドの使い分けをまとめると以下のようになりました。

  • pipenv install
    • Pipfileからインストール
    • 再ロックする
  • pipenv install --deploy
    • Pipfileからインストール
    • 再ロックしない
    • Pipfile.lockがout-of-dateの場合は失敗する(実質的にPipfile.lockの最新化を強制する)
  • pipenv install --ignore-pipfile
    • Pipfile.lockからインストール
    • 再ロックしない(※パッケージを指定した場合を除く)
  • pipenv sync
    • Pipfile.lockからインストール
    • 再ロックしない

以上のまとめより、「誰かが作成したPipfile.lockをもとに、別のメンバーが同じ依存関係(パッケージバージョン)の環境を再現するならpipenv syncコマンドを使えば良さそう!」という結論に至りました。

参考

以上