Query String Multimap Parser

Parse repeated query parameters into a multimap, rebuild a stable query string, and read typed single-value flags safely.

PythonData Integrity

Code

snippet
from collections import defaultdict
from collections.abc import Iterable
from urllib.parse import parse_qsl, urlencode

Params = dict[str, list[str]]


def parse_multimap(query: str) -> Params:
    grouped: dict[str, list[str]] = defaultdict(list)
    for key, value in parse_qsl(query, keep_blank_values=True):
        grouped[key].append(value)
    return dict(grouped)


def build_multimap(params: dict[str, Iterable[str]], *, sort_keys: bool = True) -> str:
    pairs: list[tuple[str, str]] = []
    keys = sorted(params) if sort_keys else list(params)
    for key in keys:
        for value in params[key]:
            pairs.append((key, value))
    return urlencode(pairs)


def get_one(params: Params, key: str, default: str | None = None) -> str | None:
    values = params.get(key)
    if not values:
        return default
    if len(values) > 1:
        raise ValueError(f"{key} must not repeat")
    return values[0]


def get_bool(params: Params, key: str, default: bool = False) -> bool:
    value = get_one(params, key)
    if value is None:
        return default
    normalized = value.strip().lower()
    if normalized in {"1", "true", "yes", "on"}:
        return True
    if normalized in {"0", "false", "no", "off"}:
        return False
    raise ValueError(f"{key} is not a boolean")


def main() -> None:
    query = "tag=python&tag=go&page=2&debug=1&empty="
    params = parse_multimap(query)
    print(params)
    print("tags:", params["tag"])
    print("page:", get_one(params, "page"))
    print("debug:", get_bool(params, "debug"))
    print("empty:", repr(get_one(params, "empty")))
    print(build_multimap(params))
    try:
        get_one({"tag": ["a", "b"]}, "tag")
    except ValueError as error:
        print("rejected:", error)


if __name__ == "__main__":
    main()