Using Merge Policies¶
Conflict resolution when a key already exists is controlled by MergePolicy.
Available policies:
combine(default): concatenate values → existing first, new afterward (a=1&a=2)replace: last-wins, existing value is overwritten (a=2)keep: first-wins, ignore the new value (a=1)error: raiseValueErroron duplicate key
Specify per request:
from httpx_qs import MergePolicy
r = client.get(
"https://api.example.com/resources",
params={"dup": "original"},
extensions={
"extra_query_params": {"dup": "override"},
"extra_query_params_policy": MergePolicy.REPLACE,
},
)
# Query contains only dup=override
Async Usage¶
SmartQueryStrings works equally for AsyncClient:
import httpx
from httpx_qs.transporters.smart_query_strings import SmartQueryStrings
async def main() -> None:
async with httpx.AsyncClient(transport=SmartQueryStrings(httpx.AsyncHTTPTransport())) as client:
r = await client.get(
"https://example.com/items",
params={"filters": "active"},
extensions={"extra_query_params": {"page": 2}},
)
print(r.request.url)
# Run with: asyncio.run(main())
merge_query Utility¶
You can use the underlying function directly:
from httpx_qs import merge_query, MergePolicy
from qs_codec import EncodeOptions, ListFormat
new_url = merge_query(
"https://example.com?a=1",
{"a": 2, "tags": ["x", "y"]},
options=EncodeOptions(list_format=ListFormat.REPEAT),
policy=MergePolicy.COMBINE,
)
# → https://example.com/?a=1&a=2&tags=x&tags=y
Why ListFormat.REPEAT by Default?¶
qs-codec exposes several list formatting strategies (e.g. repeat, brackets, indices). httpx-qs defaults to
ListFormat.REPEAT because:
It matches common server expectations (
key=value&key=value) without requiring bracket parsing logic.It preserves original ordering while remaining unambiguous and simple for log inspection.
Many API gateways / proxies / caches reliably forward repeated keys whereas bracket syntaxes can be normalized away.
If your API prefers another convention (e.g. tags[]=x&tags[]=y or tags[0]=x) just pass a custom EncodeOptions via
extensions['extra_query_params_options'] or parameter options when calling merge_query directly.
Advanced Per-Request Customization¶
from qs_codec import EncodeOptions, ListFormat
r = client.get(
"https://service.local/search",
params={"q": "test"},
extensions={
"extra_query_params": {"debug": True, "tags": ["alpha", "beta"]},
"extra_query_params_policy": "combine", # also accepts string values
"extra_query_params_options": EncodeOptions(list_format=ListFormat.BRACKETS),
},
)
# Example: ?q=test&debug=true&tags[]=alpha&tags[]=beta
Error Policy Example¶
try:
client.get(
"https://example.com",
params={"token": "abc"},
extensions={
"extra_query_params": {"token": "xyz"},
"extra_query_params_policy": "error",
},
)
except ValueError as exc:
print("Duplicate detected:", exc)
Testing Strategy¶
The project includes unit tests covering policy behaviors, error handling, and transport-level integration. Run them with:
pytest
Further Reading¶
HTTPX documentation: https://www.python-httpx.org
qs-codec documentation: https://techouse.github.io/qs_codec/
License¶
BSD-3-Clause. See LICENSE for details.
Contributing¶
Issues & PRs welcome. Please add tests for new behavior and keep doc examples in sync.