2
Help us understand the problem. What are the problem?
Organization

型情報がないこと由来のエラーに耐えられなかったので、`typeshed`にコントリビュートしたお話

きっかけ

私が参画しているプロジェクトではCOMをPythonから操作しているスクリプトがあります。

そこで発生するエラーハンドリングを行うため、よく次のようなコードを書いています。

from _ctypes import COMError

try:
    ...
except COMError as e:
    ...

ですが、_ctypesには型スタブがなかったので、importした時点でVSCodeのpylance(pyright)がエラーを出していて、それを抑制したとしてもCOMErrorUnknown扱いになっていました。
comerror.png

これによって型チェッカーに例外である情報が伝わっていなかったり、COMErrorがあると型チェッカーから警告が出てやりづらい状況でした。

この状態を長らくしょうがないものとして放置していましたが、そのプロジェクトに参画してから2年近くがたち、いよいよ我慢ならなくなりました。

そこでPythonライブラリの型スタブをまとめているtypeshedへのコントリビュートを決意しました。

typeshedについて

PEP484に役割が書いてあるのでご参照ください。
https://peps.python.org/pep-0484/#the-typeshed-repo

翻訳はこちら

issueでの議論

さて、いきなりモジュールのスタブを追加する、まして標準ライブラリにあるものを他のコントリビューターへの情報共有もなしにいきなりPRすることは流石にためらわれました。
そこで、issueを立ててでまず私がやりたいことを伝えて、それに関連するものはどういう状況なのかを聞きました。

私のプロジェクトでは、いくつかの COM-ffi パッケージを使用しています。

_ctypes.COMError_ctypes.CopyComPointer も使っていますが、これらは型スタブが定義されていないため、 mypy タイプチェッカーは Cannot find implementation or library stub for module named "_ctypes" エラーを発生させることがあります。

このような型スタブを追加したいです。

# githubのissueカンバンにあるスニペット

しかし、これら以外のAPIはよくわかりません。

  • そして、if sys.platform == "win32": が必要な場所はどこなのか。

何かご意見があればお願いします。

その後、typeshedのコントリビューターから様々なアドバイスが寄せられました。
下記が要約です。

  • お気軽にPRしていいですよ :smile: stdlib/以下に_foo.pyiみたいなモジュールがすでにたくさんあります

    • CONTRIBUTING.mdを読んで、_ctypesがincompleteであることをマークしてください
    • COMの要素はLinux環境では存在していないことを確認したから、COM関係はif sys.platform == "win32":の下になります
  • 今までstdlib/ctypes/__init__.pyiに定義してきた要素は、実際ランタイムでは_ctypesに定義されています。もし_ctypesのスタブが追加されたらランタイムに追随するように移動したほうがいいと思います(とはいっても最初のPRは最小限でOKです)

  • stdlib/VERSIONSにも_ctypesの情報を入れてください

PR mergeまでの顛末

アドバイスを元にスタブを作り、PRしました。

スタブ中のdocstringが不要だったのでそれを取り除くようにレビューを受けて修正されました。

だがしかし、修正後もCIが通らない!

CIのエラーメッセージを見ていると、本来ランタイムで定義されている_ctypes._Pointerなどがないことを怒られていました。

「incompleteなスタブを作ったときには、モジュールレベルでdef __getattr__(name: str) -> Any: ... # incompleteを定義する」とCONTRIBUTING.mdには書いてあったのにこれはおかしい!

途方にくれていると、レビュアーが諸々修正箇所を指摘してくれました。

stubtestallowlistの存在

stubtestmypyなどの型チェッカーにバンドルされているテストで、ランタイム実装の名前空間と型スタブを比べて、型スタブに不足があれば失敗するテストです。

下記issueの"1) Use stubtest to find things missing from or problematic with stubs."に概要が書かれていますが、CONTRIBUTING.mdには書いていなかったので見落としていました。

そんなこんなでレビュアーによってallowlistにまだスタブに追加していない各オブジェクトが追加されて、CIが通るようになりmergeされました。

現在の状況

mypypylanceにスタブの更新が取り込まれ、Unknownではなくなりました!

image.png

その後

スタブをランタイムの定義に追随させるべく、コントリビュートを続けています。

その中でどうしても循環importが発生するのでissueを立てて相談しました。

型スタブでは循環importしていてもランタイムとは違ってエラーにならず問題ないと回答をもらったので安心して、少しづつ定義を移動させるPRを出そうとしています。

ただし現在(2022/09/20時点)では_ctypesに定義を書いてctypesへimportするスタブを書いても、pytypeを使うテストが失敗してCIが通りません
これに関してはpytype側にissueを立ててあり、そのうち修正してもらう予定になっています。

(上記が解決しだい)みなさんのコントリビュートもお待ちしております。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
2
Help us understand the problem. What are the problem?