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: raise ValueError on 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

License

BSD-3-Clause. See LICENSE for details.

Contributing

Issues & PRs welcome. Please add tests for new behavior and keep doc examples in sync.