Skip to main content

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.

PyPI version PyPI - Status PyPI - Downloads Supported Python versions PyPy support status PyPI - Format Tests CodeQL License Codecov Codacy Quality GitHub Sponsors GitHub Repo stars Contributor Covenant mypy pylint isort Security Status

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.

Requirements

  • CPython 3.8-3.14, including free-threaded 3.14t, or PyPy 3.8-3.11

  • httpx>=0.28.1,<1.0.0

  • qs-codec>=1.5.0

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

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

httpx_qs-0.2.2.tar.gz (12.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

httpx_qs-0.2.2-py3-none-any.whl (8.8 kB view details)

Uploaded Python 3

File details

Details for the file httpx_qs-0.2.2.tar.gz.

File metadata

  • Download URL: httpx_qs-0.2.2.tar.gz
  • Upload date:
  • Size: 12.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for httpx_qs-0.2.2.tar.gz
Algorithm Hash digest
SHA256 2049c04ba17dcdd71ff0e193c5cd8142ff7988776d54ab0576623c46e5e035c7
MD5 c296ec60bf78243ce76eb591aac65726
BLAKE2b-256 fbadc8c73dbe411b5daa56286ba4f47ce1545572e1fbf01ae27a433899b577d2

See more details on using hashes here.

Provenance

The following attestation bundles were made for httpx_qs-0.2.2.tar.gz:

Publisher: publish.yml on techouse/httpx_qs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file httpx_qs-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: httpx_qs-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 8.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for httpx_qs-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 fbb1dce4f1c807daa69b656bd54cff81416ec3fefaa8c5fb3c160fa670c3a1ca
MD5 c099f50ce4a9fce9d4bcc69c4ae138b6
BLAKE2b-256 9167262d1a73b59f63c2e529a343efd55c1c2d172259d8564b12d4ae6ec3adfc

See more details on using hashes here.

Provenance

The following attestation bundles were made for httpx_qs-0.2.2-py3-none-any.whl:

Publisher: publish.yml on techouse/httpx_qs

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page