エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

業務時間内にPythonのコントリビューターになった顛末

これは エムスリー Advent Calendar 2018 5日目の記事です。

こんにちは、エムスリー・エンジニアリングG・基盤開発チーム小本です。

年末ということで、個人的に今年のうれしかったことである「Pythonのコントリビューターになったこと」について書きます。

起きた不具合

社内のバッチ処理サーバーでPythonを使うべく作業中(Python標準のコマンドである)pip や virtualenv を使うと以下のようなエラー1 が起き、異常終了してしまいました。

51_billappxz%pip list
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.
pip (9.0.1)
setuptools (38.2.4)
Traceback (most recent call last):
  File "/data01/billappxz/scripts/ThirdParty/python27/bin/pip", line 11, in <module>
    load_entry_point('pip==9.0.1', 'console_scripts', 'pip')()
  File "/data01/billappxz/scripts/ThirdParty/python27/lib/python2.7/site-packages/pip-9.0.1-py2.7.egg/pip/__init__.py", line 233, in main
    return command.main(cmd_args)
  File "/data01/billappxz/scripts/ThirdParty/python27/lib/python2.7/site-packages/pip-9.0.1-py2.7.egg/pip/basecommand.py", line 251, in main
    timeout=min(5, options.timeout)) as session:
  File "/data01/billappxz/scripts/ThirdParty/python27/lib/python2.7/site-packages/pip-9.0.1-py2.7.egg/pip/basecommand.py", line 72, in _build_session
    insecure_hosts=options.trusted_hosts,
  File "/data01/billappxz/scripts/ThirdParty/python27/lib/python2.7/site-packages/pip-9.0.1-py2.7.egg/pip/download.py", line 329, in __init__
    self.headers["User-Agent"] = user_agent()
  File "/data01/billappxz/scripts/ThirdParty/python27/lib/python2.7/site-packages/pip-9.0.1-py2.7.egg/pip/download.py", line 93, in user_agent
    from pip._vendor import distro
  File "/data01/billappxz/scripts/ThirdParty/python27/lib/python2.7/site-packages/pip-9.0.1-py2.7.egg/pip/_vendor/distro.py", line 1050, in <module>
    _distro = LinuxDistribution()
  File "/data01/billappxz/scripts/ThirdParty/python27/lib/python2.7/site-packages/pip-9.0.1-py2.7.egg/pip/_vendor/distro.py", line 594, in __init__
    if include_lsb else {}
  File "/data01/billappxz/scripts/ThirdParty/python27/lib/python2.7/site-packages/pip-9.0.1-py2.7.egg/pip/_vendor/distro.py", line 922, in _get_lsb_release_info
    stdout, stderr = stdout.decode('utf-8'), stderr.decode('utf-8')
  File "/data01/billappxz/scripts/ThirdParty/python27/lib/python2.7/encodings/utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xd5 in position 24: invalid continuation byte

不具合の原因を探る

不具合が起きたのは古くから稼働しているサーバーで LANG=ja_JP.eucJP になっていました。 発生したのはUnicodeDecodeError なのでいかにも関係ありそうです。LANG= にして実行してみると・・・おや?エラーが起きない。

そこで、スタックトレースで表示された周辺のコードを見てみます。

        props = {}
        for line in lines:
            line = line.decode('utf-8') if isinstance(line, bytes) else line
            kv = line.strip('\n').split(':', 1)
            if len(kv) != 2:
                # Ignore lines without colon.
                continue
            k, v = kv
            props.update({k.replace(' ', '_').lower(): v.strip()})
        return props

https://github.com/pypa/pip/blob/41f09b555651768d1540db7173abe009c10cfe3e/pip/_vendor/distro.py#L953

utf-8 がハードコードされている!

このモジュールでは Linuxのバージョンを得るために lsb_release コマンドを subprocess.Popen で実行して出力をパースしています。lsb_release は ASCII の範囲の文字しか出力しないので "utf-8" でも動作します。しかし、問題が起きたサーバーでは、

  • lsb_release コマンドがインストールされていない
  • LANG=ja_JP.eucJP

という条件が重なっていました。subprocess.Popen 実行時に EUC-JPの bash: lsb_release: コマンドが見つかりません が出力され、それを "utf-8" でデコードしようとしてUnicodeDecodeErrorになっていました。

ところで、Pythonではシステムの文字コードを sys.getfilesystemencoding() などの関数で得られるようになっています。修正するのは容易なはずなので、自分で直して見ることにしました。

Github でプルリクエストを送る&レビュー

今回の不具合は標準ライブラリの pip のものでした。pip はPython本体とは別レポジトリ( pypa/pip)になっています。アクセス制限などはされていないので、とりあえず Issue を作り Pull request を送りました。

しかし、

  • 「これは pip そのものではなく Vendoring しているdistroの不具合だ」→ distro に issue を立てて修正依頼
  • 「news/ にファイルを追加してるけど、不具合修正の時は要らない」→ファイルを削除

など、作業手順の不備があり2すぐにはマージされませんでした。

やったね!

コメントされては直してを繰り返し、1カ月ぐらいかかりましたがプルリクエスト はマージされ Pip 10.0 でリリースされました。最終的に私のコミット内容は実質2行(ライブラリをアップデートしただけ)になってしまっ足し、Pythonインタープリター本体ではなく標準ライブラリの不具合修正ですが、貢献は貢献です。

やったね!

実績解除

ところで、エムスリーでははてなさんペパポさんのを参考に「エンジニア実績システム」を設けています。

OSS へのコミット・プルリクエスト コントリビュートしたGitHubのスター数:1000〜

YESと書けるようになりました!

次は2行より多くのコミットをするべく、今週末は最近話題のresponder のコードを読んでみようかと思います。

補足:エムスリーでは勤務中にOSS活動ができるの?

今回の不具合修正は勤務時間内に行いました。これは、

  • 比較的重要なサーバー(顧客向けレポートを扱う)で起きた不具合だった
  • Pythonを新たに導入する過程で起きた不具合だった(「代わりに他言語を使う」といった選択肢がなかった)
  • 他に緊急のタスクが無かった
  • 短時間で直せる不具合だった

・・・といった状況だったからです。

そのため、業務中ではOSS活動ができるか?(するべきか?)と言えば、それはケースバイケースです。

しかし、上述のようにエンジニア実績システムを作ってOSSへの貢献を社内で共有していますし、有志が毎週 OSSもくもく会を開いています。過去には社員が個人的に開発したOSSを業務で使用したこともあります。

エンジニアを募集しています!

エムスリーでは一緒に働く仲間を募集中です。お気軽にお問い合わせください。

jobs.m3.com


  1. 当時のログが見つからないのでGitHub上の他人のログを貼り付けています。

  2. Contributing に書いてあったんですけどね。ドキュメントをよく読むべきでした。