[インデックス 14114] ファイルの概要
このコミットは、lib/codereview/codereview.py
ファイルにおける認証エラーハンドリング時のクラッシュを修正するものです。具体的には、Pythonのバージョンアップに伴い、urllib2.HTTPError
の reason
プロパティが読み取り専用になったことによる問題を解決しています。
コミット
commit df7b720708e9da9e1bd00fe2424d765095427831
Author: Uriel Mangado <uriel@berlinblue.org>
Date: Thu Oct 11 00:23:53 2012 +0800
codereview.py: Fix crash on auth error handling.
In recent Python versions .reason is a read-only property that simply gives you the msg value.
Fixes #4024
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/6545052
---
lib/codereview/codereview.py | 20 +++++++++++---------\n 1 file changed, 11 insertions(+), 9 deletions(-)\n
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/df7b720708e9da9e1bd00fe2424d765095427831
元コミット内容
codereview.py
における認証エラーハンドリング時のクラッシュを修正します。最近のPythonバージョンでは、.reason
は読み取り専用のプロパティであり、単に msg
の値を提供するだけです。
変更の背景
この変更は、Go言語のコードレビューシステムで使用されるPythonスクリプト codereview.py
が、特定のPythonバージョン(おそらくPython 2.7以降、または特定のパッチレベル)で認証エラーを適切に処理できなくなり、クラッシュする問題(Issue #4024)に対応するために行われました。
問題の根源は、Pythonの標準ライブラリである urllib2
の HTTPError
クラスの内部的な変更にありました。以前のバージョンでは HTTPError
オブジェクトの reason
属性にエラーの具体的な理由(例: "BadAuthentication")を直接設定することが可能であったか、あるいはそのように振る舞うことが期待されていました。しかし、新しいPythonバージョンでは、reason
属性が msg
属性の読み取り専用のエイリアス、またはそれに準ずる振る舞いをするようになりました。これにより、ClientLoginError
クラスが reason
属性に値を代入しようとすると、エラーが発生するか、意図しない動作を引き起こすようになりました。
この変更は、GoプロジェクトのコードレビューツールがGoogleアカウントの認証(ClientLogin APIを使用していた可能性が高い)を行う際に、認証エラーが発生した場合に適切にエラーの種類を判別できなくなるという直接的な影響を及ぼしました。
前提知識の解説
urllib2.HTTPError
と reason
/ msg
属性
urllib2
はPython 2.xでHTTPリクエストを扱うためのモジュールです。HTTPError
は、HTTPリクエストがサーバーからエラーレスポンス(例: 401 Unauthorized, 404 Not Found)を受け取った際に発生する例外です。
msg
属性: HTTPステータスコードに対応する人間が読める形式の理由フレーズ(例: "Unauthorized", "Not Found")を含みます。reason
属性:urllib.error.URLError
(HTTPErrorの親クラス) にはreason
属性がありますが、urllib2.HTTPError
では歴史的にmsg
属性がその役割を果たしていました。Python 3.xでは、urllib.error.HTTPError
にreason
属性が追加され、これはmsg
属性のエイリアスとして機能するようになりました。
このコミットが行われた2012年頃は、Python 2.xが主流であり、Python 3.xへの移行期でもありました。この時期に urllib2.HTTPError
の内部的な振る舞いや属性の扱いが変更されたことが、この問題の直接的な原因となっています。特に、reason
属性が読み取り専用になったことで、カスタム例外クラス ClientLoginError
がこの属性に値を設定しようとすると問題が発生しました。
ClientLoginError
このコードベースにおける ClientLoginError
は、GoogleのClientLogin認証システム(現在は非推奨)からのエラーを処理するためにカスタムで定義された例外クラスです。これは urllib2.HTTPError
を継承しており、認証プロセス中に発生する様々な特定のエラー(例: "BadAuthentication", "CaptchaRequired" など)を捕捉し、それに応じて適切なメッセージをユーザーに表示することを目的としていました。
Pythonのバージョン互換性
Pythonはバージョン間で互換性のない変更が導入されることがあります。特にPython 2.xから3.xへの移行期には、多くの標準ライブラリのAPIが変更されました。このコミットは、そのようなAPIの変更が既存のコードに与える影響の一例を示しています。
技術的詳細
この修正の技術的な核心は、urllib2.HTTPError
の reason
属性が読み取り専用になったという事実に対応することです。
元のコードでは、ClientLoginError
のコンストラクタ内で self.reason = args["Error"]
という行がありました。これは、Google ClientLogin APIから返されるエラー情報(args
ディクショナリの "Error" キーに含まれる文字列)を、ClientLoginError
オブジェクトの reason
属性に設定しようとしていました。
しかし、Pythonの新しいバージョンでは、urllib2.HTTPError
の reason
属性は、その親クラスである urllib.error.URLError
の reason
属性と同様に、msg
属性の値を反映する読み取り専用のプロパティとして扱われるようになりました。このため、self.reason = ...
のような代入操作は許可されなくなり、エラーが発生するか、代入が無視されるようになりました。
この問題を解決するため、コミットでは以下の変更が行われました。
ClientLoginError.__init__
メソッド内で、self.reason = args["Error"]
の行を削除し、代わりにself.msg = args["Error"]
としました。これにより、エラーの具体的な理由がmsg
属性に設定されるようになります。コメントで「.reason
は現在.msg
に基づく読み取り専用プロパティである」と明記されており、msg
を設定することでreason
も間接的に設定されることを示唆しています。- コードベース全体で、
ClientLoginError
オブジェクトのreason
属性を参照していた箇所をすべてmsg
属性を参照するように変更しました。具体的には、AbstractRpcServer._GetAuthToken
メソッド内のexcept ClientLoginError, e:
ブロック内で、e.reason == "..."
となっていた条件分岐をすべてe.msg == "..."
に変更しました。
この修正により、ClientLoginError
はPythonの新しいバージョンでも正しくエラー情報を保持し、その情報に基づいて適切なエラーメッセージをユーザーに表示できるようになりました。
コアとなるコードの変更箇所
--- a/lib/codereview/codereview.py
+++ b/lib/codereview/codereview.py
@@ -2738,7 +2738,9 @@ class ClientLoginError(urllib2.HTTPError):
def __init__(self, url, code, msg, headers, args):
urllib2.HTTPError.__init__(self, url, code, msg, headers, None)
self.args = args
- self.reason = args["Error"]
+ # .reason is now a read-only property based on .msg
+ # this means we ignore 'msg', but that seems to work fine.
+ self.msg = args["Error"]
class AbstractRpcServer(object):\n@@ -2871,31 +2873,31 @@ class AbstractRpcServer(object):\n \t\t\ttry:\n \t\t\t\tauth_token = self._GetAuthToken(credentials[0], credentials[1])\n \t\t\texcept ClientLoginError, e:\n-\t\t\t\tif e.reason == "BadAuthentication":\n+\t\t\t\tif e.msg == "BadAuthentication":\n \t\t\t\t\tprint >>sys.stderr, "Invalid username or password."\n \t\t\t\t\tcontinue\n-\t\t\t\tif e.reason == "CaptchaRequired":\n+\t\t\t\tif e.msg == "CaptchaRequired":\n \t\t\t\t\tprint >>sys.stderr, (\n \t\t\t\t\t\t"Please go to\\n"\n \t\t\t\t\t\t"https://www.google.com/accounts/DisplayUnlockCaptcha\\n"\n \t\t\t\t\t\t"and verify you are a human. Then try again.")\n \t\t\t\t\tbreak\n-\t\t\t\tif e.reason == "NotVerified":\n+\t\t\t\tif e.msg == "NotVerified":\n \t\t\t\t\tprint >>sys.stderr, "Account not verified."\n \t\t\t\t\tbreak\n-\t\t\t\tif e.reason == "TermsNotAgreed":\n+\t\t\t\tif e.msg == "TermsNotAgreed":\n \t\t\t\t\tprint >>sys.stderr, "User has not agreed to TOS."\n \t\t\t\t\tbreak\n-\t\t\t\tif e.reason == "AccountDeleted":\n+\t\t\t\tif e.msg == "AccountDeleted":\n \t\t\t\t\tprint >>sys.stderr, "The user account has been deleted."\n \t\t\t\t\tbreak\n-\t\t\t\tif e.reason == "AccountDisabled":\n+\t\t\t\tif e.msg == "AccountDisabled":\n \t\t\t\t\tprint >>sys.stderr, "The user account has been disabled."\n \t\t\t\t\tbreak\n-\t\t\t\tif e.reason == "ServiceDisabled":\n+\t\t\t\tif e.msg == "ServiceDisabled":\n \t\t\t\t\tprint >>sys.stderr, "The user's access to the service has been disabled."\n \t\t\t\t\tbreak\n-\t\t\t\tif e.reason == "ServiceUnavailable":\n+\t\t\t\tif e.msg == "ServiceUnavailable":\n \t\t\t\t\tprint >>sys.stderr, "The service is not available; try again later."\n \t\t\t\t\tbreak\n \t\t\t\traise\n```
## コアとなるコードの解説
### `ClientLoginError` クラスの変更
* **変更前**:
```python
self.reason = args["Error"]
```
`ClientLoginError` のインスタンス化時に、`args` ディクショナリから取得したエラー文字列を `self.reason` に直接代入していました。これが、新しいPythonバージョンで `reason` が読み取り専用になったために問題を引き起こしました。
* **変更後**:
```python
# .reason is now a read-only property based on .msg
# this means we ignore 'msg', but that seems to work fine.
self.msg = args["Error"]
```
`self.reason` への直接代入を削除し、代わりに `self.msg = args["Error"]` としています。これにより、エラーの具体的な理由が `msg` 属性に設定されます。コメントにあるように、`reason` が `msg` に基づく読み取り専用プロパティであるため、`msg` を設定することで `reason` も適切に反映されることが期待されます。
### `AbstractRpcServer` クラスの変更
`AbstractRpcServer` クラス内の `_GetAuthToken` メソッドの `try...except ClientLoginError, e:` ブロック内で、認証エラーの種類を判別するために `e.reason` を使用していた箇所がすべて `e.msg` に変更されています。
* **変更前**:
```python
if e.reason == "BadAuthentication":
# ...
if e.reason == "CaptchaRequired":
# ... (同様の変更が複数箇所)
```
* **変更後**:
```python
if e.msg == "BadAuthentication":
# ...
if e.msg == "CaptchaRequired":
# ... (同様の変更が複数箇所)
```
これにより、`ClientLoginError` オブジェクトが保持するエラー情報(`msg` 属性に格納されている)に基づいて、正しいエラーメッセージがユーザーに表示されるようになります。
これらの変更は、PythonのバージョンアップによるAPIの振る舞いの変化にコードを適応させ、認証エラーハンドリングの堅牢性を確保することを目的としています。
## 関連リンク
* Go Gerrit Change-Id: `6545052` - [https://golang.org/cl/6545052](https://golang.org/cl/6545052)
* Go Issue: `4024` - [https://code.google.com/p/go/issues/detail?id=4024](https://code.google.com/p/go/issues/detail?id=4024) (元のIssueトラッカーはGoogle Codeであり、現在はアーカイブされている可能性があります)
## 参考にした情報源リンク
* Python `urllib2.HTTPError` の `reason` および `msg` 属性に関する情報:
* [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG98GG3RuFv2_pkEQwiGnDAWzLnzIHytkDJe-yl7QeM0XubDLKZ_Pbwgja1cpYUAL8a9mMOUUFosoDhKyEnAO_EEgycODz-916OwT2W1e5OCMj2k-MrNYkBYkp2poHRi-OBLlhCZuLOQQCHSbE6VA==](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG98GG3RuFv2_pkEQwiGnDAWzLnzIHytkDJe-yl7QeM0XubDLKZ_Pbwgja1cpYUAL8a9mMOUUFosoDhKyEnAO_EEgycODz-916OwT2W1e5OCMj2k-MrNYkBYkp2poHRi-OBLlhCZuLOQQCHSbE6VA==)
* [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE6mJhb3WyEkNs7f7vf_dcHUKkti3VB_QaL1lwPlVPEoK5GEEl1-hXtvNFvpdiIy8LPTDTHUj0wAbSqUVcoyXOMB67zxLVhwsLj8oUctfmUH5la0Au_lIyZXq0cikY=](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE6mJhb3WyEkNs7f7vf_dcHUKkti3VB_QaL1lwPlVPEoK5GEEl1-hCtvNFvpdiIy8LPTDTHUj0wAbSqUVcoyXOMB67zxLVhwsLj8oUctfmUH5la0Au_lIyZXq0cikY=)