~PyCon USでアメリカにいる期間は日本時間で1日のはじめにブログを公開し、そのあと更新する運用とします。
公開通知で見に来ていただいた方は、後ほど更新版をご確認ください(1日前の記事は更新されていて読めると思います)
はじめに
JST 5/15(金)の分です。nikkie (UUID 28fb3f96-a221-462c-93bd-567b431715b9) です。
メンテ停滞気味な HTTPX を friendly に forkした、HTTPXYZ。
推しの HTTP クライアントですが、HTTPX と同じように使えるのか気になって調べました。
今回はテストコードについてです。
目次
HTTPX をモックする RESPX
HTTP クライアント HTTPX のモックライブラリの1つが RESPX。
Quickstart を元にした例です。
tests/test_example.py
import httpx import respx @respx.mock(assert_all_called=True, assert_all_mocked=True) def test_default(respx_mock): respx_mock.get("https://foo.bar/").mock(return_value=httpx.Response(204)) response = httpx.get("https://foo.bar/") assert response.status_code == 204
リクエストのヘッダやデータの assert もできます。
HTTPXYZ もまた RESPX でモックできる!
ドキュメントに記載がありました。
Using respx in pytest (HTTPX Compatibility)
$ pytest -p httpxyz
試しに httpx -> httpxyz と変更します
import httpxyz import respx @respx.mock(assert_all_called=True, assert_all_mocked=True) def test_default(respx_mock): respx_mock.get("https://foo.bar/").mock(return_value=httpxyz.Response(204)) response = httpxyz.get("https://foo.bar/") assert response.status_code == 204
pytest -p httpxyzでテストは引き続き通ります!
pytestにhttpxyzプラグインを early load するよう指定することで、RESPX で HTTPXYZ もモックできます
仕組み
httpxyz の pytest プラグインの実装のhttpxyz/_pytest_plugin.py1を見ると
https://codeberg.org/httpxyz/httpxyz/src/tag/0.31.1/httpxyz/_pytest_plugin.py#L7
import httpxyz
import httpxyzに秘密があります。
HTTPX Compatibility のドキュメントの冒頭より
>>> import sys >>> import httpxyz >>> sys.modules["httpxyz"] <module 'httpxyz' from '/.../.venv/lib/python3.14/site-packages/httpxyz/__init__.py'> >>> sys.modules["httpx"] <module 'httpxyz' from '/.../.venv/lib/python3.14/site-packages/httpxyz/__init__.py'> >>> sys.modules["httpx"] is sys.modules["httpxyz"] True
importing
httpxyzautomatically registers it under thehttpxname insys.modules
sys.modulesとは
https://docs.python.org/ja/3/library/sys.html#sys.modules
This is a dictionary that maps module names to modules which have already been loaded.
import httpxyzでhttpxyzだけでなくhttpxもsys.modulesに登録され、import された扱いとなります。
その時、httpxモジュールの実態はhttpxyzです。置き換えられている!
https://codeberg.org/httpxyz/httpxyz/src/tag/0.31.1/httpxyz/__init__.py#L120-L121
if "httpx" not in _sys.modules: _sys.modules["httpx"] = _sys.modules[__name__]
import httpxしたときsys.modulesにあればそれを使う2ので、import httpxの裏では httpxyz が使われ続けます
終わりに
HTTPXYZ は pytest plugin も提供していました(pytest -p httpxyz)。
plugin の early load でimport httpxyzしてしまい、sys.modules["httpx"]も指定しています。
以降のimport httpxで import されるのは httpxyz モジュールとして実現していました
HTTPX に比べてパフォーマンス改善した HTTPXYZ を推していくにあたり、これは追い風です!
P.S. pytest-httpx も同様の対処がされている
HTTPX のモックライブラリには pytest-httpx もあります。
こちらについてもpytest -p httpxyzで HTTPXYZ と合わせて使えるようにしたそうです。
https://httpxyz.org/httpx-compatibility/#using-pytest-httpx-in-pytest
- 設定箇所 https://codeberg.org/httpxyz/httpxyz/src/tag/0.31.1/pyproject.toml#L66-L67↩
- 「インポートではモジュール名は sys.modules から探され、存在した場合は、対応する値がインポートされるべきモジュールであり、この処理は完了します。」ref: https://docs.python.org/ja/3/reference/import.html#the-module-cache↩