nikkie-ftnextの日記

イベントレポートや読書メモを発信

httpxでPOSTやPUTリクエストを送る時のdata引数とjson引数はどう使い分ける? 〜互換なAPIのrequestsのドキュメントやhttpxの実装を読む〜

最近の学びのエントリです(挨拶省略した簡略版)

目次

nikkieはhttpxを使いたい

PythonのHTTPクライアントライブラリとして使用者が多いのはrequestsだと思います。
しかしながら私としては2024年となってはあまりrequestsを採用したくなく、いまはhttpxを選んでいます(一石を投じていきたい!)
httpxはrequestsのAPIを踏襲しつつ1、(requestsにはできない2)イベントループによる非同期処理も書けるのです!

そんなhttpxを使っていての学びのアウトプットです。
Web APIJSONPythonのコード中では辞書)を送りたかったのですが、data引数なのかjson引数なのかがパッと分かりませんでした。

まとめ:data引数とjson引数

※httpxのドキュメントは記載が少なめだったので、同一のAPIのrequestsのドキュメントも参照しました。
httpxのソースコードも簡単に確認し、httpxにも当てはまると考えて記載していますが、誤解がありましたらお知らせください。

どちらも辞書を渡しますが、

  • data引数はform-encoded dataとして送る
    • 単純な辞書(dict[str, str])を渡す
    • 辞書をjson.dumpsした文字列も渡せる
    • Content-Typeヘッダはapplication/x-www-form-urlencodedに設定
  • json引数はJSON-encoded dataとして送る
    • 込み入った辞書を渡す(辞書の値が、数値や真理値、配列)
    • Content-Typeヘッダはapplication/jsonに設定

今回利用するWeb APIapplication/jsonのリクエストを受け付けたため、json引数を使って事なきを得ました。

HTTPメソッドのdata引数とjson引数

httpxのドキュメント QuickStartより

Sending Form Encoded Data

One common way of including that is as form-encoded data, which is used for HTML forms.

r = httpx.post("https://httpbin.org/post", data={'key1': 'value1', 'key2': 'value2'})

POSTやPUTリクエストでリクエストボディにデータを含めるやり方の一つが、form-encoded data
data引数を使います。
辞書を渡していますね

Sending JSON Encoded Data

For more complicated data structures you'll often want to use JSON encoding instead.

r = httpx.post("https://httpbin.org/post", json={'integer': 123, 'boolean': True, 'list': ['a', 'b', 'c']})

POSTやPUTリクエストのボディにデータを含める別のやり方がJSON encoded data
先のform-encoded dataと比べて、込み入ったデータが送れるそうです。
文字列以外の値や、配列が含まれていますね

互換なAPIのrequestsのドキュメントも確認

Quickstartの「More complicated POST requests

data引数

Typically, you want to send some form-encoded data — much like an HTML form. To do this, simply pass a dictionary to the data argument.

r = requests.post('https://httpbin.org/post', data={'key1': 'value1', 'key2': 'value2'})

There are times that you may want to send data that is not form-encoded. If you pass in a string instead of a dict, that data will be posted directly.

r = requests.post(url, data=json.dumps({'some': 'data'}))

json.dumps()で辞書はJSON形式の文字列となります。

>>> import json
>>> json.dumps({'some': 'data'})
'{"some": "data"}'

data引数に辞書の代わりに文字列を渡すと、その文字列がそのままPOSTされると記載されています。

json引数

If you need that header set and you don’t want to encode the dict yourself, you can also pass it directly using the json parameter (added in version 2.4.2) and it will be encoded automatically:

r = requests.post(url, json={'some': 'data'})
  • json引数を指定したとき、Content-Typeヘッダをapplication/jsonに設定
    • data引数ではヘッダは設定されない
  • json引数には辞書を渡す
    • data引数には辞書、または自身でエンコードjson.dumps(辞書))した文字列を渡す

mdn「フォームデータの送信」

「form-encodedって説明できないかも」と手を伸ばしました。

クライアント側の「POST メソッド」より

フォームをが POST メソッドで送信されると、URL にはデータが追加されず、HTTP リクエストは次のように、リクエスト本文にデータが含まれた形になります。(引用ママ)

say=Hi&to=Mom

深く理解した感覚はないのですが、この形式は明らかにJSONではないですし、JSONとは別の形式があると認識しました(宿題事項)

P.S. httpxのソースコードを覗く

https://github.com/encode/httpx/blob/0.27.0/httpx/_content.py#L182-L214

    if data is not None and not isinstance(data, Mapping):
        return encode_content(data)

    if content is not None:
        return encode_content(content)
    elif files:
        return encode_multipart_data(data or {}, files, boundary)
    elif data:
        return encode_urlencoded_data(data)
    elif json is not None:
        return encode_json(json)

data引数に辞書を指定した時のencode_urlencoded_data()関数
https://github.com/encode/httpx/blob/0.27.0/httpx/_content.py#L134-L147

json引数を指定した時のencode_json()関数
https://github.com/encode/httpx/blob/0.27.0/httpx/_content.py#L174-L179

  • json.dumps()で辞書を文字列にしてからencode()でbytesに変換
  • Content-Typeapplication/jsonに指定

httpx関連エントリ


  1. A broadly requests-compatible API.」 ref: https://www.python-httpx.org/#features
  2. 見逃していたらお知らせください。requestsの拡張ライブラリでは対応しているようです。ref: https://requests.readthedocs.io/en/latest/community/recommended/#requests-threads