HTTPX transport leveraging qs-codec for advanced query string encoding and decoding.
Project description
Smart, policy-driven query string merging & encoding for httpx powered by qs-codec.
Overview
httpx-qs provides:
A transport wrapper SmartQueryStrings that merges existing URL query parameters with additional ones supplied via request.extensions.
A flexible merge_query utility with selectable conflict resolution policies.
Consistent, standards-aware encoding via qs-codec (RFC3986 percent-encoding, structured arrays, nested objects, etc.).
Why?
HTTPX already lets you pass params= when making requests, but sometimes you need to:
Inject additional query parameters from middleware/transport layers (e.g., auth tags, tracing IDs, feature flags) without losing the caller’s original intent.
Combine repeated keys or treat them deterministically (replace / keep / error) rather than always flattening.
Support nested data or list semantics consistent across clients and services.
qs-codec supplies the primitives (decoding & encoding with configurable ListFormat). httpx-qs stitches that into HTTPX’s transport pipeline so you can declaratively extend queries at request dispatch time.
Installation
pip install httpx-qs
Minimal Example
import httpx
from httpx_qs.transporters.smart_query_strings import SmartQueryStrings
client = httpx.Client(transport=SmartQueryStrings(httpx.HTTPTransport()))
response = client.get(
"https://www.google.com",
params={"a": "b", "c": "d"},
extensions={"extra_query_params": {"c": "D", "tags": ["x", "y"]}},
)
print(str(response.request.url))
# Example (order may vary): https://www.google.com/?a=b&c=d&c=D&tags=x&tags=y
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
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.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file httpx_qs-0.1.2.tar.gz.
File metadata
- Download URL: httpx_qs-0.1.2.tar.gz
- Upload date:
- Size: 11.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
61b23a9c6beaafa571e7a2bbfd28967f92a37aea624fa121b6769abb9de52581
|
|
| MD5 |
015692c3da49b9cb6b13869c74a4d042
|
|
| BLAKE2b-256 |
bc3d1c73461c58d02a0927345ffb26dcbe88d32e0695425e9371f1a7014da815
|
Provenance
The following attestation bundles were made for httpx_qs-0.1.2.tar.gz:
Publisher:
publish.yml on techouse/httpx_qs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
httpx_qs-0.1.2.tar.gz -
Subject digest:
61b23a9c6beaafa571e7a2bbfd28967f92a37aea624fa121b6769abb9de52581 - Sigstore transparency entry: 565497356
- Sigstore integration time:
-
Permalink:
techouse/httpx_qs@813721455b4caa035e041cbe957bfcda354e78d7 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/techouse
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@813721455b4caa035e041cbe957bfcda354e78d7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file httpx_qs-0.1.2-py3-none-any.whl.
File metadata
- Download URL: httpx_qs-0.1.2-py3-none-any.whl
- Upload date:
- Size: 8.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed5c93a3949582fa25e029a84cdfd73c8d6923a7e24932196aba08ec879b0a35
|
|
| MD5 |
210c1dc4a01258dc54e4c3aaec08095b
|
|
| BLAKE2b-256 |
31235ada264ade5b0fe65ffaddf75bc7d6f69eddda1385ffcda96f15ffd3dff5
|
Provenance
The following attestation bundles were made for httpx_qs-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on techouse/httpx_qs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
httpx_qs-0.1.2-py3-none-any.whl -
Subject digest:
ed5c93a3949582fa25e029a84cdfd73c8d6923a7e24932196aba08ec879b0a35 - Sigstore transparency entry: 565497364
- Sigstore integration time:
-
Permalink:
techouse/httpx_qs@813721455b4caa035e041cbe957bfcda354e78d7 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/techouse
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@813721455b4caa035e041cbe957bfcda354e78d7 -
Trigger Event:
push
-
Statement type: