CLOVER🍀

That was when it all began.

pipをオフラインで使いたい

これは、なにをしたくて書いたもの?

Pythonのモジュールインストールにはpipを使うことが多いと思いますが、pipをオフライン環境で使う方法はないかな?と。

要するに、どこかでダウンロードしたパッケージを、オフラインの別の環境で使いたい、みたいな話です。

pipについて

そもそも、pipのドキュメントを見たことがありませんでした。こちらみたいですね。

Home - pip documentation v21.1.1

User Guideを見ていると、やりたいことがそのまま書いてありました…。

In some cases, you may want to install from local packages only, with no traffic to PyPI.

Installing from local packages

こちらを見ると、downloadコマンドとinstallコマンドのオプションで実現するようです。

が、ドキュメントに記載されているオプションが変な気が…(特にdownload)。

downloadコマンドのドキュメントは、こちらですね。

pip download does the same resolution and downloading as pip install, but instead of installing the dependencies, it collects the downloaded distributions into the directory provided (defaulting to the current directory). This directory can later be passed as the value to pip install --find-links to facilitate offline or locked down package installation.

pip download - pip documentation v21.1.1

オフライン環境で使えるようにするために、パッケージをディレクトリにダウンロードするコマンドです。

installコマンドは、こちら。

pip install - pip documentation v21.1.1

User Guideによると、--no-index、--find-linksの2つのオプションを使うようです。

意味は、それぞれこのようになっています。

--find-linksでアーカイブの参照先を指定し、--no-indexで--find-linksで指定したディレクトリのみを参照させることで
実現するようです。

このあたりを実際に使っていってみようと思います。

環境

今回の環境は、こちらです。

$ python3 -V
Python 3.8.5


$ pip3 -V
pip 20.0.2 from /usr/lib/python3/dist-packages/pip (python 3.8)

お題

Flaskで簡単なWebアプリを作り、それをGunicornで動かす…という環境一式を、オフライン環境でも動かせるようにしたいと
思います。

確認は、仮想環境を2つ用意して行おうかな、と。

片方はパッケージのダウンロード用。もう片方は、オフラインインストール用ですね。

この2つのディレクトリを使いましょう。

$ mkdir source-project offline-project

プログラムを作成して、ライブラリをダウンロードする

まずは、確認用のプログラムを作成したいと思います。

$ cd source-project

仮想環境を有効にします。

$ python3 -m venv venv
$ . venv/bin/activate

FlaskとGunicornをインストール。

$ pip3 install Flask==2.0.0 gunicorn==20.1.0

ダウンロードの様子。

Collecting Flask==2.0.0
  Downloading Flask-2.0.0-py3-none-any.whl (93 kB)
     |████████████████████████████████| 93 kB 236 kB/s 
Collecting gunicorn==20.1.0
  Downloading gunicorn-20.1.0-py3-none-any.whl (79 kB)
     |████████████████████████████████| 79 kB 1.2 MB/s 
Collecting Werkzeug>=2.0
  Downloading Werkzeug-2.0.0-py3-none-any.whl (288 kB)
     |████████████████████████████████| 288 kB 10.7 MB/s 
Collecting itsdangerous>=2.0
  Downloading itsdangerous-2.0.0-py3-none-any.whl (18 kB)
Collecting click>=7.1.2
  Downloading click-8.0.0-py3-none-any.whl (96 kB)
     |████████████████████████████████| 96 kB 929 kB/s 
Collecting Jinja2>=3.0
  Downloading Jinja2-3.0.0-py3-none-any.whl (133 kB)
     |████████████████████████████████| 133 kB 11.4 MB/s 
Requirement already satisfied: setuptools>=3.0 in ./venv/lib/python3.8/site-packages (from gunicorn==20.1.0) (44.0.0)

簡単なソースコードを作成。

index.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "Hello, World!!"

動作確認しましょう。Gunicornを起動。

$ gunicorn index:app

確認。

$ curl -i localhost:8000
HTTP/1.1 200 OK
Server: gunicorn
Date: Sun, 16 May 2021 15:09:50 GMT
Connection: close
Content-Type: text/html; charset=utf-8
Content-Length: 14

Hello, World!!

Content-Typeがデフォルトのtext/htmlになっているのは、今は気にしないことにします。

動作確認ができたので、いったんGunicornを終了。

freezeして、requirements.txtを作成します。

$ pip3 freeze > requirements.txt

downloadを実行します。ライブラリの保存先は、dependenciesというディレクトリにしました。

$ pip3 download -d dependencies -r requirements.txt

保存先のディレクトリの中身を見てみると、こうなっています。

$ ll dependencies
合計 1512
drwxrwxr-x 2 xxxxx xxxxx   4096  5月 17 00:12 ./
drwxrwxr-x 5 xxxxx xxxxx   4096  5月 17 00:12 ../
-rw-rw-r-- 1 xxxxx xxxxx  93174  5月 17 00:12 Flask-2.0.0-py3-none-any.whl
-rw-rw-r-- 1 xxxxx xxxxx 133357  5月 17 00:12 Jinja2-3.0.0-py3-none-any.whl
-rw-rw-r-- 1 xxxxx xxxxx  30551  5月 17 00:12 MarkupSafe-2.0.0-cp38-cp38-manylinux2010_x86_64.whl
-rw-rw-r-- 1 xxxxx xxxxx 288104  5月 17 00:12 Werkzeug-2.0.0-py3-none-any.whl
-rw-rw-r-- 1 xxxxx xxxxx  96881  5月 17 00:12 click-8.0.0-py3-none-any.whl
-rw-rw-r-- 1 xxxxx xxxxx  79531  5月 17 00:12 gunicorn-20.1.0-py3-none-any.whl
-rw-rw-r-- 1 xxxxx xxxxx  18240  5月 17 00:12 itsdangerous-2.0.0-py3-none-any.whl
-rw-rw-r-- 1 xxxxx xxxxx 785278  5月 17 00:12 setuptools-56.2.0-py3-none-any.whl

ちょっとLinux依存と思われるものが含まれていますね。コマンドを実行しているプラットフォームの情報に依存している
みたいです。

これをなんとかしたかったら、もうちょっとオプションに踏み込む必要がありそうです。

pip download with the --platform, --python-version, --implementation, and --abi options provides the ability to fetch dependencies for an interpreter and system other than the ones that pip is running on.

pip download / Overview

--platformや--python-versionあたりはポイントかもです。

--platform

--python-version <python_version>

今回は、このまま使うとしましょう。

ディレクトリをtar.gzに固めて

$ tar czf dependencies.tar.gz dependencies

仮想環境を終了します。

$ deactivate

ダウンロードしたライブラリを使って、依存関係をインストールする

では、先ほどダウンロードしたライブラリとプログラムを使って、ライブラリのオフラインインストールと実行を試して
みましょう。

オフライン用のプロジェクト側に移動。

$ cd ../offline-project

仮想環境を作成して、有効化します。

$ python3 -m venv venv
$ . venv/bin/activate

一応、pipのキャッシュも削除しておきましょう。

$ rm -rf ~/.cache/pip

先ほどのプロジェクトから、ライブラリとソースコード、そしてrequirements.txtをコピー。

$ cp ../source-project/dependencies.tar.gz ../source-project/index.py ../source-project/requirements.txt ./.

tar.gzファイルを展開して

$ tar xf dependencies.tar.gz

--no-indexと--find-linksを指定して実行。

$ pip3 install --no-index --find-links=dependencies -r requirements.txt

実行時のログ。

Looking in links: dependencies
Processing ./dependencies/click-8.0.0-py3-none-any.whl
Processing ./dependencies/Flask-2.0.0-py3-none-any.whl
Processing ./dependencies/gunicorn-20.1.0-py3-none-any.whl
Processing ./dependencies/itsdangerous-2.0.0-py3-none-any.whl
Processing ./dependencies/Jinja2-3.0.0-py3-none-any.whl
Processing ./dependencies/MarkupSafe-2.0.0-cp38-cp38-manylinux2010_x86_64.whl
Processing ./dependencies/Werkzeug-2.0.0-py3-none-any.whl
Requirement already satisfied: setuptools>=3.0 in ./venv/lib/python3.8/site-packages (from gunicorn==20.1.0->-r requirements.txt (line 3)) (44.0.0)
Installing collected packages: click, MarkupSafe, Jinja2, itsdangerous, Werkzeug, Flask, gunicorn
Successfully installed Flask-2.0.0 Jinja2-3.0.0 MarkupSafe-2.0.0 Werkzeug-2.0.0 click-8.0.0 gunicorn-20.1.0 itsdangerous-2.0.0

インターネットからダウンロードしているような様子はなくなりました。

プログラムをGunicornで実行してみましょう。

$ gunicorn index:app
[2021-05-17 00:25:02 +0900] [7586] [INFO] Starting gunicorn 20.1.0
[2021-05-17 00:25:02 +0900] [7586] [INFO] Listening at: http://127.0.0.1:8000 (7586)
[2021-05-17 00:25:02 +0900] [7586] [INFO] Using worker: sync
[2021-05-17 00:25:02 +0900] [7588] [INFO] Booting worker with pid: 7588

なにも問題なく起動します。

確認。

$ curl -i localhost:8000
HTTP/1.1 200 OK
Server: gunicorn
Date: Sun, 16 May 2021 15:25:32 GMT
Connection: close
Content-Type: text/html; charset=utf-8
Content-Length: 14

Hello, World!!

OKですね。

使用するライブラリによっては、オプションをもうちょっと調整しなくてはいけない気もしますが、だいたいやり方は
わかったのではないでしょうか。