Skip to content

Add METH_QUERY and treat QUERY as an idempotent method - #13301

Merged
Dreamsorcerer merged 5 commits into
aio-libs:masterfrom
rodrigobnogueira:query-idempotent-methods
Aug 3, 2026
Merged

Add METH_QUERY and treat QUERY as an idempotent method#13301
Dreamsorcerer merged 5 commits into
aio-libs:masterfrom
rodrigobnogueira:query-idempotent-methods

Conversation

@rodrigobnogueira

Copy link
Copy Markdown
Member

What do these changes do?

Follow-up to #13160, as requested in #13160 (comment).

#13174 fixed the parser half of that issue, so QUERY now arrives intact instead of <unknown>. The two remaining pieces (originally part of the closed #13167) never landed:

  1. aiohttp/hdrs.py gains METH_QUERY, alongside the other METH_* constants.
  2. aiohttp/client.py adds "QUERY" to IDEMPOTENT_METHODS, so a QUERY request is replayed when the server closes a keep-alive connection underneath it, the same as GET or PUT. RFC 10008 defines the method as both safe and idempotent (https://www.rfc-editor.org/info/rfc10008/#section-1-12), which is exactly the property that set encodes.

METH_QUERY is deliberately not added to METH_ALL. RouteDef.register() dispatches every METH_ALL member through getattr(router, "add_" + method.lower()), and UrlDispatcher has no add_query(), so adding it there would turn web.route("QUERY", ...) passed to add_routes() into an AttributeError. (METH_CONNECT and METH_TRACE are already in METH_ALL without matching add_* methods, so that path is broken for them today — out of scope here.)

Are there changes in behavior for the user?

Yes, one: a QUERY request that hits a keep-alive connection the server has just closed is now retried once instead of raising ServerDisconnectedError. That is the documented behavior for safe, idempotent methods, and QUERY qualifies. Everything else is additive — a new public constant.

Is it a substantial burden for the maintainers to support this?

No. It is one constant and one set member, with no new code paths; the retry logic itself is untouched.

Related issue number

Follows up #13160 (fixed for the parser by #13174). Reuses the hdrs.py/client.py hunks from the closed #13167, with the RFC URL amended to the form suggested in review there.

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes — N/A, hdrs constants are not individually documented and the retry behavior is described generically
  • If you provide code modification, please add yourself to CONTRIBUTORS.txt — N/A, already listed
  • Add a new news fragment into the CHANGES/ folder
Test evidence

New test test_retry_persistent_connection_query_method sits alongside the existing test_retry_persistent_connection_lowercase_method, and follows the same shape: the handler force-closes the connection on the first request, and the test asserts the request is retried.

Passes with both parsers:

$ AIOHTTP_NO_EXTENSIONS=1 pytest tests/test_test_utils.py -k retry_persistent -v
tests/test_test_utils.py::test_disable_retry_persistent_connection PASSED
tests/test_test_utils.py::test_retry_persistent_connection_lowercase_method PASSED
tests/test_test_utils.py::test_retry_persistent_connection_query_method PASSED

$ pytest tests/test_test_utils.py -k retry_persistent -v   # C extensions built
tests/test_test_utils.py::test_disable_retry_persistent_connection PASSED
tests/test_test_utils.py::test_retry_persistent_connection_lowercase_method PASSED
tests/test_test_utils.py::test_retry_persistent_connection_query_method PASSED

Confirmed the test fails without the production change (reverting IDEMPOTENT_METHODS to its previous value makes it error with the connection dropped, no retry), so it is not vacuous.

Wider run, no regressions:

$ pytest tests/test_test_utils.py tests/test_client_session.py
148 passed, 1 skipped, 1 xfailed

black, isort and mypy are clean on the touched files.

QUERY is defined as safe and idempotent by RFC 10008, so the client can
replay it on a keep-alive connection the server closed underneath us,
the same as GET or PUT. The constant is exported alongside the other
METH_* names.

METH_QUERY stays out of METH_ALL on purpose: RouteDef.register dispatches
every METH_ALL member through getattr(router, "add_" + method.lower()),
and UrlDispatcher has no add_query().
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided There is a change note present in this PR label Aug 2, 2026
@rodrigobnogueira rodrigobnogueira added the backport-3.15 Trigger automatic backporting to the 3.15 release branch by Patchback robot label Aug 2, 2026
@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.98%. Comparing base (073ee84) to head (a031417).
⚠️ Report is 2 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #13301   +/-   ##
=======================================
  Coverage   98.98%   98.98%           
=======================================
  Files         132      132           
  Lines       49023    49070   +47     
  Branches     2551     2553    +2     
=======================================
+ Hits        48526    48573   +47     
  Misses        373      373           
  Partials      124      124           
Flag Coverage Δ
Autobahn 22.13% <23.52%> (+<0.01%) ⬆️
CI-GHA 98.90% <100.00%> (-0.01%) ⬇️
OS-Linux 98.67% <100.00%> (-0.01%) ⬇️
OS-Windows 97.03% <100.00%> (-0.01%) ⬇️
OS-macOS 97.92% <100.00%> (-0.02%) ⬇️
Py-3.10 98.12% <100.00%> (-0.01%) ⬇️
Py-3.11 98.37% <100.00%> (+<0.01%) ⬆️
Py-3.12 98.46% <100.00%> (-0.01%) ⬇️
Py-3.13 98.44% <100.00%> (-0.01%) ⬇️
Py-3.14 98.46% <100.00%> (-0.01%) ⬇️
Py-3.14t 97.55% <100.00%> (-0.01%) ⬇️
Py-pypy-3.11 97.38% <100.00%> (-0.01%) ⬇️
VM-macos 97.92% <100.00%> (-0.02%) ⬇️
VM-ubuntu 98.67% <100.00%> (-0.01%) ⬇️
VM-windows 97.03% <100.00%> (-0.01%) ⬇️
cython-coverage 37.96% <11.76%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@codspeed-hq

codspeed-hq Bot commented Aug 2, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 84 untouched benchmarks
⏩ 83 skipped benchmarks1


Comparing rodrigobnogueira:query-idempotent-methods (a031417) with master (537125a)

Open in CodSpeed

Footnotes

  1. 83 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@rodrigobnogueira
rodrigobnogueira marked this pull request as ready for review August 2, 2026 18:09
Comment thread aiohttp/client.py Outdated
@Dreamsorcerer Dreamsorcerer added the bot:chronographer:skip This PR does not need to include a change note label Aug 3, 2026
@Dreamsorcerer
Dreamsorcerer merged commit 58bae08 into aio-libs:master Aug 3, 2026
51 checks passed
@patchback

patchback Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Backport to 3.15: 💔 cherry-picking failed — conflicts found

❌ Failed to cleanly apply 58bae08 on top of patchback/backports/3.15/58bae08b7e4831c6c184fe22233bfc19941c700b/pr-13301

Backporting merged PR #13301 into master

  1. Ensure you have a local repo clone of your fork. Unless you cloned it
    from the upstream, this would be your origin remote.
  2. Make sure you have an upstream repo added as a remote too. In these
    instructions you'll refer to it by the name upstream. If you don't
    have it, here's how you can add it:
    $ git remote add upstream https://github.com/aio-libs/aiohttp.git
  3. Ensure you have the latest copy of upstream and prepare a branch
    that will hold the backported code:
    $ git fetch upstream
    $ git checkout -b patchback/backports/3.15/58bae08b7e4831c6c184fe22233bfc19941c700b/pr-13301 upstream/3.15
  4. Now, cherry-pick PR Add METH_QUERY and treat QUERY as an idempotent method #13301 contents into that branch:
    $ git cherry-pick -x 58bae08b7e4831c6c184fe22233bfc19941c700b
    If it'll yell at you with something like fatal: Commit 58bae08b7e4831c6c184fe22233bfc19941c700b is a merge but no -m option was given., add -m 1 as follows instead:
    $ git cherry-pick -m1 -x 58bae08b7e4831c6c184fe22233bfc19941c700b
  5. At this point, you'll probably encounter some merge conflicts. You must
    resolve them in to preserve the patch from PR Add METH_QUERY and treat QUERY as an idempotent method #13301 as close to the
    original as possible.
  6. Push this branch to your fork on GitHub:
    $ git push origin patchback/backports/3.15/58bae08b7e4831c6c184fe22233bfc19941c700b/pr-13301
  7. Create a PR, ensure that the CI is green. If it's not — update it so that
    the tests and any other checks pass. This is it!
    Now relax and wait for the maintainers to process your pull request
    when they have some cycles to do reviews. Don't worry — they'll tell you if
    any improvements are necessary when the time comes!

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-3.15 Trigger automatic backporting to the 3.15 release branch by Patchback robot bot:chronographer:provided There is a change note present in this PR bot:chronographer:skip This PR does not need to include a change note

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants