Python pipenv で pywin32 をインストールするとエラー

11/10 頃から、 Python でpypiwin32 や pywin32 パッケージや、それらに依存しているパッケージを pipenv にてインストールしようとすると、

AttributeError: module 'site' has no attribute 'getsitepackages'

とエラーが出て失敗するようになった。

原因

直接的な原因は、 pipenv ではなく virtualenv にある。

pywin32 v226 で pywin32_bootstrap.py が site.getsitepackages を内部で使うようになった ようなのだが、 pipenv が内部で使っている virtualenv モジュールが作る仮想環境内の site モジュールは getsitepackages 関数を持っていない。
このため、 virtualenv で作成した仮想環境内で pywin32 をインストールさせようとすると、上記のエラーが発生してしまう。

しかも、これは以下のコメントにあるように、 virtualenv の不具合ではなく意図的な仕様であると認識されているようだ。

The problem (as I understand it) is that virtualenv implements its own version of site.py that doesn’t track the core Python version perfectly […]
Fixing this isn’t as simple as “sync up to the core site.py”, because we need to support multiple Python versions, and site.py has changed between them.

2.6 から 3.8 まで幅広い Python バージョンをサポートする virtualenv は、 コアライブラリーの site.py に仕様を追従させるのが難しいとコメントされている。

更に、 virtualenv ではなく、 Python 3.3 以上のコアライブラリーに含まれる venv を使って仮想環境を作成すれば、本問題は発生しない。
(このため、 pipenv と競合の poetry では仮想環境の作成に venv を使っているため、本問題に遭遇しない)

このことも、 virtualenv の方を修正する動機が強くならない要因だろう。

暫定的な回避方法

もし、直接 virtualenv を使って仮想環境を作っていたのであれば、 venv で作り直せばよい。

一方、 pipenv を使っているなど、 virtualenv を使うことが避けられないのであれば、 前述のように、問題の原因となる pywin32 のコード変更は v226 で行われたため、 v225 以下を使用すればよい。

ここで話をややこしくしているのは、過去からの慣習で、 pip などで pywin32 をインストールするとき、 pypiwin32 を参照していることが多い点だ。
せっかく pypiwin32 で使用バージョンを固定していても、その依存関係によってインストールされる pywin32 の実態は、最新版になってしまうのだ。

このため、以下のように、パッケージのバージョンに pywin32 を v225 以下とする条件を加えたり、

 docker = "==4.1.0"
+pywin32 = "<=225"
 docker==4.1.0
+pywin32<=225

pypiwin32 でバージョン指定している部分を pywin32 に書き換えるのがよいだろう。

-pypiwin32 = "==223"
+pywin32 = "==225"
-pypiwin32==223
+pywin32==225

恒久対策

すでに pywin32 のモジュールの法で Issue が立っている。

本来なら、 virtualenv で site.getsitepackages を使えるようにするか、 python 3.3 以上の場合は pipenv 側で (virtualenv ではなく) venv 使うようにしてくれればよいのだが、 多分しばらくはそんな修正は入らないだろうし、 pywin32 が対応してくれることを待つしかないのだ。

pip install in virtualenv breaks virtualenv · Issue #2460 · docker/docker-py · GitHub
https://github.com/docker/docker-py/issues/2460
docker で既に問題になってるし、さっさと直さないと今後あちこちで問題になってきそう。。。

プロキシ環境下で Ansible AWX をインストールする Playbook を作る

11/10 頃から、 Python でpypiwin32 や pywin32 パッケージや、それらに依存しているパッケージを pipenv にてインストールしようとすると、

AttributeError: module 'site' has no attribute 'getsitepackages'

とエラーが出て失敗するようになった。

原因

直接的な原因は、 pipenv ではなく virtualenv にある。

続きを読む

Pydroid で オフライン&セルフホスト の Web アプリを動かす

運用時にネットワークにつながっていない Android タブレットで、 自作のアプリケーションを 簡単に 動かすには、どうしたらよいだろうか?

.apk ビルドしてインストールさせる?いやいや、手軽からはほど遠いだろう。

現在のトレンドは PWA で Service Worker を組み合わせてキャッシュさせる方法だろうか。
PWA 配信するには https のホスティングを用意しなくてはならないし、ブラウザのキャッシュ整理したらデータが吹き飛んでしまう。

ここで私は、 Android 上で Python を動かしてセルフホストさせる方法 を提唱したい。

続きを読む

Flask + jinja でレイアウトの指定 (extends タグ) のテンプレートを ソースコード文字列 で指定する

Python の軽量な Webアプリケーション フレームワーク として有名な Flask。
この Flask を使うと たった一ひとつのソースファイルで簡単に Webアプリケーション を作成できる。

Flask では Jinja2 というテンプレートエンジンを採用している。
通常はテンプレートソースを .html ファイルにテンプレートソースを書き出して ファイル名を指定してそのテンプレートを読み出すのだが、 flask.render_template_string を使えば Python ソースコード上で定義した テンプレート文字列 を使うことができる。

しかし、 extends ステートメントや include ステートメントを使ってレイアウト用などのテンプレートを呼び出す際に、 そのテンプレートを ファイル名 で指定する方法の情報しか出てこない。
これでは、 ひとつのソースファイルで記述できない。
テンプレートソース文字列 を使ってテンプレートの継承を行う方法を、メモがてらまとめておく。

以下のサンプルは Flask を使ったものだが、 Django で Jinja2 を使用した場合も同じように対応できると思われる。


サンプル実装

extends タグinclude ステートメント では、 与えられた文字列のテンプレートファイルをテンプレートローダーが探し出して、 テンプレートの継承関係を解決する仕組みになっている。
Jinja のドキュメントには書かれていないが、 文字列ではなく Jinja の Template オブジェクト を与えると、そのテンプレートをそのまま使ってくれるようだ。

つまり、 あらかじめ Template オブジェクトを作成しておいて、 それを [cci]jinja_env.globals[/cci] や [cci]render_template_string[/cci] のキーワード引数によってテンプレート内の引数に渡し、 最終的に extends タグに渡るようにすればよいのだ。

サンプルコードは以下の通り。
続きを読む

Selenium でページ全体のスクリーンショットを撮る (Python)

02/25 更新: Chrome で水平スクロールバーがスクリーンショットに写ってしまう問題を修正。

Selenium を使って自動テストを行っていると、表示された結果のスクリーンショットを撮って保存したい時がままある。

ところが、 Chrome や Firefox で Selenium のスクリーンショット機能を使うと、 ウィンドウに表示されている内容だけしか取得できない。
(ブラウザやそのバージョンによって動作が異なる)


この記事によると、これは Selenium の仕様らしい。

ブラウザごとの挙動の差はさておき、 ウィンドウサイズでの取得となってしまう Chrome と Firefox で、何とかページ全体のスクリーンショットを保存したい。
ここでは、 Selenium の Python Binding を使って、実現する方法を考えてみる。

続きを読む

pipenv の仮想環境のフォルダ名のルール

Python におけるパッケージ管理と言えば、 以前は virtualenv/venv で仮想環境を作成し、 pip を使って その仮想環境にパッケージをインストールするのが、おなじみの方法だった。

node.js の npm や .NET の [cci]dotnet restore[/cci] などに慣れている人々にとっては、 環境の管理やパッケージの管理が別々だったり、 ひとつのファイル+ひとつのコマンド で環境を復元できないのは、 ソースコード管理がスマートにできず、非常に面倒に感じる。

このため、 Python にも 仮想環境・パッケージ管理を統合するツールがいくつも生まれては消えていった。
そして、最近になってついに、 Python.org 公式が推奨する Python パッケージ管理ツールとして、 pipenv というものが登場したようだ。 (→ Python.org)

pipenv の仮想環境の場所

[cci]pipenv install[/cci] コマンドを使ってパッケージをインストールすると、 npm の [cci]package.json[/cci] にあたる [cci]Pipfile[/cci] というファイルがカレントディレクトリにできあがるが、 npm の [cci]node_modules[/cci] にあたるようなパッケージがインストールされた仮想環境のフォルダは、カレントディレクトリは(標準の設定だと)作成されない。 1

その仮想環境のフォルダの場所は [cci]pipenv –venv[/cci] コマンドで調べることができる。
Windows の場合は、 [cci]%USERPROFILE%/.virtualenvs/[/cci] に、環境毎のサブディレクトリが作成されているはずだ。

その仮想環境ディレクトリの名前の前半は、 Pipfile が存在するフォルダの名前になるが、 最後の9文字はなにやらランダムのような名前がつけられている。
例えば、 [cci]D:/pipenv/[/cci] で pipenv を初期化した場合、 [cci]%USERPROFILE%/.virtualenvs/pipenv-LAdtM08T/[/cci] が作成されるだろう。

はたして、この文字は何なのか?

続きを読む

Python の類似画像ライブラリ ImageHash を Windows で使う

sha1 や md5 等で知られるファイルハッシュは、ファイルの1ビットでも異なると、全く別のダイジェスト値を返すように作られている。

一方で、 画像の情報をハッシュ化する際に、 画像の大きさや微妙な違いには目を瞑って同じような画像は同じダイジェスト値、似たような画像は似たようなダイジェスト値を得たい場合もある。
例えば、大きさの違う画像や、 jpeg, png の形式が異なる画像を 同じ画像として扱うようにしたい場合だ。

そのようなハッシュ関数はいくつか知られている。

  • average hashing (aHash)
  • perception hashing (pHash)
  • difference hashing (dHash)
  • wavelet hashing (wHash)

そのうち、上記の 4つ の計算を行えるのが、 Python の ImageHash ライブラリだ。

このライブラリ自体は ピュアな Python ライブラリなのだが、 依存しているパッケージが総じて C言語拡張モジュールなので Windows で動作させるにはすこし手間がかかる。

そこで、 cygwin 上の python にインストールする場合と、 Windows 上の CPython にインストールする方法をそれぞれ紹介しよう。

以下は virtualenv を使って仮想環境上にインストールする手順とするが、 直接 Python のシステム環境に入れてしまっても問題はない。

メモ: 2017年現在、 pypi でプリコンパイル済みの依存モジュールがダウンロードできるので、以下の方法を使わなくても [cci]pip[/cci] や [cci]pipenv[/cci] コマンドだけでインストールが完了するはずだ。

cygwin を使う場合

続きを読む