Query String Multimap Parser
Parse repeated query parameters into a multimap, rebuild a stable query string, and read typed single-value flags safely.
Code
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()Report a problem with this exampleOpens a pre-filled issue on GitHub.