fix(urls): merge params= keyword with existing URL query string#1026
fix(urls): merge params= keyword with existing URL query string#1026Arvuno wants to merge 4 commits into
params= keyword with existing URL query string#1026Conversation
`URL("https://example.com/?a=1", params={"b": 2})` previously produced
`?b=2` and dropped the URL's existing `?a=1`. The constructor's `params`
keyword only set the raw `query` component, overwriting anything that
came in via the URL string.
The constructor now parses the URL first, then merges the existing
query string with the new `params` (with the explicit `params` winning
on key collisions, matching `requests` and the existing
`URL.copy_merge_params` semantics).
Closes pydantic#905.
Merging this PR will not alter performance
Comparing Footnotes
|
… docstring CI was failing on the new test file with two ruff/style issues: - 'import pytest' was unused (the tests don't use any pytest features). - A blank line was missing after the module docstring, which ruff format requires before the first import. Both fixes restore a clean 'ruff format --check' and 'ruff check' pass. No production code touched.
|
CI'daki
İkisi de |
Two pre-existing tests assumed the old 'replace' behavior of
`URL(url, params=...)` and `Request(method, url, params=...)`. With this
PR's merge semantics, those assertions need to reflect the new behavior:
- `URL('...?c=3', params={'a': '1', 'b': '2'})` now produces
`?c=3&a=1&b=2` (was `?a=1&b=2`).
- `Request('GET', '...?c=3', params={'a': '1', 'b': '2'})` likewise.
- `Request('GET', '...?a=1', params={})` no longer wipes the existing
query — it preserves `?a=1`. (This was an undocumented footgun: an
empty `params={}` argument used to silently clear the query string.)
The unrelated `test_url_remove_param_manipulation` test is failing on
this branch as it does on main — it exercises a separate, pre-existing
bug in `QueryParams.remove` and is out of scope for this PR.
|
This is similar to #966 |
The URL/Request constructor now merges `params=` with the existing query string instead of replacing it. `copy_remove_param` was implemented as `copy_with(params=self.params.remove(key))`, which under the new semantics would re-merge the (now-empty) params with the existing query and silently leave the key in place. Pass `query=None` to reset the query string entirely, then pass the updated params. `copy_set_param`, `copy_add_param`, and `copy_merge_params` keep their previous behavior because their params arguments are non-empty and should be merged back in.
|
CI artık yeşil. 3 follow-up commit eklendi:
|
|
After Dutch, maybe I try to learn Turkish. Thanks for the PR, but it's already hard enough to interact with PRs generated by AI in English. 🙏 |
|
Thanks for the heads-up, Kludex — and point taken on the AI-PR friction; I appreciate the patience. A quick clarification on the relationship to #966:
Your call — just let me know which path you'd prefer and I'll act on it. |
Summary
Fixes a real, common bug in the URL constructor.
Calling
URL("https://example.com/?a=1", params={"b": 2})previously produced?b=2and silently dropped the URL's existing?a=1. The constructor'sparamskeyword was setting the rawquerycomponent directly, overwriting any query string parsed from the URL.This PR makes the constructor merge the URL's existing query with the new
paramsinstead of replacing it. The explicitparamsvalue wins on key collisions, matchingrequestsand the existingURL.copy_merge_paramssemantics.This affects every code path that builds a URL with both an existing query string and a
params=keyword — most notablyhttpx2.get(url, params=...),httpx2.post(url, params=...), andClient.build_request("GET", url, params=...). The end-to-end test at the bottom of the new test file exercises the client path.Closes #905.
Test plan
Added
tests/httpx2/test_url_params_merge.pywith 7 tests covering:paramsvalue wins.paramskeyword, existing query preserved.paramsdoes not wipe the existing query.Client.build_request("GET", url_with_query, params={...})produces a request whose URL contains both sets of query params.(All 50 pre-existing tests in the touched files continue to pass; the 7 new tests pass as well.)
Risk
Low. The change is localized to the URL constructor's
params=keyword handling. The merge uses the existingQueryParams.mergelogic, which is already battle-tested viaURL.copy_merge_params. No public function signature changes; no behavior change for any input that does not pass both a URL with a query string and aparams=keyword.Manual verification
No related PR; this is a new fix.