LoginSignup
3
0

urllib.request.urlopenはエラーメッセージを捨てる

Last updated at Posted at 2023-12-05

バージョン

Python 3.11.6

何が起きたか

外部モジュールを使わずにNotion APIを叩こうと以下のようなコードを書いていました。

分かりやすくしてます.py
from json import dumps, loads
from urllib import request

endpoint = "https://api.notion.com/v1/pages/**"
headers = {
    "Authorization": "Bearer **",
    "Notion-Version": "2022-06-28",
    "Content-Type": "application/json",
}
payload = {
    "properties": {
       "リリース日": {
            "date": {
                "start": "2023-12-01"
            }
        }
    }
}
req = request.Request(endpoint, dumps(payload).encode("utf-8"), headers, method="PATCH")
try:
    with request.urlopen(req) as res_:
        res = loads(res_.read().decode("utf-8"))
        ...
except Exception as err:
    ...

このコードはしばらくの間なにごともなく動いていたのですが、ある日ログにこのようなメッセージを見つけます。

HTTP Error 400: Bad Request

なんもわからん。

とりあえずExceptionに何か入ってるだろうと思い中身を漁ってみてもBad Request以上の情報が見つからず…。
image.png

APIリファレンスを見るとどうやらレスポンスのbodyにオブジェクトが入っているらしいということが分かったので、試しにcurlから同様のリクエストを送ってみました。

リクエスト
curl 'https://api.notion.com/v1/pages/**' \
  -X PATCH \
  -H 'Notion-Version: 2022-06-28' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer **' \
  -d '{"properties":{"リリース日":{"date":{"start":"2023-12-01"}}}}'

すると…

レスポンス
{
  "object": "error",
  "status": 400,
  "code": "validation_error",
  "message": "リリース日 is not a property that exists.",
  "request_id": "**"
}

めちゃくちゃ書いてありました。
あとはこれをurlopen()のどこかから見つけることが出来ればOKです。

どうしたのか

結論から言えば無理でした。

urlopenHTTPErrorの仕様を確かめにいったのですが、やはりurlopen()がステータスコード4**や5**を受け取った場合に送出するHTTPErrorにはbodyの内容は渡されていないようです。

というわけで諸々の条件を考えた結果、今回はsubprocess経由でcurlを呼び出すことにしました。

curl使う.py
from json import dumps, loads
from subprocess import run

endpoint = "https://api.notion.com/v1/pages/**"
headers = {
    "Authorization": "Bearer **",
    "Notion-Version": "2022-06-28",
    "Content-Type": "application/json",
}
payload = {
    "properties": {
       "リリース日": {
            "date": {
                "start": "2023-12-01"
            }
        }
    }
}
proc = run(
    ["curl", "-Ssi", endpoint, "-X", "PATCH"]
    + [opt for hd in [f"{k}: {v}" for (k, v) in headers.items()] for opt in ["-H", hd]]
    + ["-d", dumps(payload)],
    capture_output=True,
    encoding="utf-8",
)
h, b = proc.stdout.split("\n\n")
res = {
    headers: h.split("\n")
    body: loads(b)
}

...

当然実行環境にcurlがインストールされている必要があるぶん汎用性は落ちますが選択肢のひとつに持っておいて損はなさそうだなと思います。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0