From 0a45a712a7ef84e3370df1cb18ead008f5ea2990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9F=C4=B1l=20Ulu=C5=9Fahin?= Date: Fri, 23 Oct 2026 14:14:44 +0100 Subject: [PATCH] Move QueryParams into _queryparams.py --- src/hip/__init__.py | 3 +- src/hip/_queryparams.py | 221 ++++++++++++++++++++++++++++++++++++++++ src/hip/_urls.py | 219 +-------------------------------------- 3 files changed, 226 insertions(+), 217 deletions(-) create mode 100644 src/hip/_queryparams.py diff --git a/src/hip/__init__.py b/src/hip/__init__.py index 9e4d7bf..b4a5af6 100644 --- a/src/hip/__init__.py +++ b/src/hip/__init__.py @@ -2,12 +2,13 @@ from ._client import Client from ._content import HTML, JSON, Binary, Content, File, Files, Form, MultiPart, Text from ._headers import Headers +from ._queryparams import QueryParams from ._request import Method, Request from ._response import Response, StatusCode from ._server import Server from ._streams import Stream from ._timeouts import sleep, timeout -from ._urls import URL, QueryParams +from ._urls import URL __all__ = [ "HTML", diff --git a/src/hip/_queryparams.py b/src/hip/_queryparams.py new file mode 100644 index 0000000..4d3dd97 --- /dev/null +++ b/src/hip/_queryparams.py @@ -0,0 +1,221 @@ +from __future__ import annotations + +import typing + +from ._urlencode import urldecode, urlencode + +__all__ = ["QueryParams"] + + +class QueryParams(typing.Mapping[str, str]): + """ + URL query parameters, as a multi-dict. + """ + + def __init__( + self, + params: ( + QueryParams + | dict[str, str | list[str]] + | list[tuple[str, str]] + | str + | None + ) = None, + ) -> None: + d: dict[str, list[str]] = {} + + if params is None: + d = {} + elif isinstance(params, str): + d = urldecode(params) + elif isinstance(params, QueryParams): + d = params.multi_dict() + elif isinstance(params, dict): + # Convert dict inputs like: + # {"a": "123", "b": ["456", "789"]} + # To dict inputs where values are always lists, like: + # {"a": ["123"], "b": ["456", "789"]} + d = {k: [v] if isinstance(v, str) else list(v) for k, v in params.items()} + else: + # Convert list inputs like: + # [("a", "123"), ("a", "456"), ("b", "789")] + # To a dict representation, like: + # {"a": ["123", "456"], "b": ["789"]} + for k, v in params: + d.setdefault(k, []).append(v) + + self._dict = d + + def keys(self) -> typing.KeysView[str]: + """ + Return all the keys in the query params. + + Usage: + + q = hip.QueryParams("a=123&a=456&b=789") + assert list(q.keys()) == ["a", "b"] + """ + return self._dict.keys() + + def values(self) -> typing.ValuesView[str]: + """ + Return all the values in the query params. If a key occurs more than once + only the first item for that key is returned. + + Usage: + + q = hip.QueryParams("a=123&a=456&b=789") + assert list(q.values()) == ["123", "789"] + """ + return {k: v[0] for k, v in self._dict.items()}.values() + + def items(self) -> typing.ItemsView[str, str]: + """ + Return all items in the query params. If a key occurs more than once + only the first item for that key is returned. + + Usage: + + q = hip.QueryParams("a=123&a=456&b=789") + assert list(q.items()) == [("a", "123"), ("b", "789")] + """ + return {k: v[0] for k, v in self._dict.items()}.items() + + def multi_items(self) -> list[tuple[str, str]]: + """ + Return all items in the query params. Allow duplicate keys to occur. + + Usage: + + q = hip.QueryParams("a=123&a=456&b=789") + assert list(q.multi_items()) == [("a", "123"), ("a", "456"), ("b", "789")] + """ + multi_items: list[tuple[str, str]] = [] + for k, v in self._dict.items(): + multi_items.extend([(k, i) for i in v]) + return multi_items + + def multi_dict(self) -> dict[str, list[str]]: + return {k: list(v) for k, v in self._dict.items()} + + def get(self, key: str, default: typing.Any = None) -> typing.Any: + """ + Get a value from the query param for a given key. If the key occurs + more than once, then only the first value is returned. + + Usage: + + q = hip.QueryParams("a=123&a=456&b=789") + assert q.get("a") == "123" + """ + if key in self._dict: + return self._dict[key][0] + return default + + def get_list(self, key: str) -> list[str]: + """ + Get all values from the query param for a given key. + + Usage: + + q = hip.QueryParams("a=123&a=456&b=789") + assert q.get_list("a") == ["123", "456"] + """ + return list(self._dict.get(key, [])) + + def copy_set(self, key: str, value: str) -> QueryParams: + """ + Return a new QueryParams instance, setting the value of a key. + + Usage: + + q = hip.QueryParams("a=123") + q = q.set("a", "456") + assert q == hip.QueryParams("a=456") + """ + q = QueryParams() + q._dict = dict(self._dict) + q._dict[key] = [value] + return q + + def copy_append(self, key: str, value: str) -> QueryParams: + """ + Return a new QueryParams instance, setting or appending the value of a key. + + Usage: + + q = hip.QueryParams("a=123") + q = q.append("a", "456") + assert q == hip.QueryParams("a=123&a=456") + """ + q = QueryParams() + q._dict = dict(self._dict) + q._dict[key] = q.get_list(key) + [value] + return q + + def copy_remove(self, key: str) -> QueryParams: + """ + Return a new QueryParams instance, removing the value of a key. + + Usage: + + q = hip.QueryParams("a=123") + q = q.remove("a") + assert q == hip.QueryParams("") + """ + q = QueryParams() + q._dict = dict(self._dict) + q._dict.pop(str(key), None) + return q + + def copy_update( + self, + params: ( + QueryParams | dict[str, str | list[str]] | list[tuple[str, str]] | None + ) = None, + ) -> QueryParams: + """ + Return a new QueryParams instance, updated with. + + Usage: + + q = hip.QueryParams("a=123") + q = q.copy_update({"b": "456"}) + assert q == hip.QueryParams("a=123&b=456") + + q = hip.QueryParams("a=123") + q = q.copy_update({"a": "456", "b": "789"}) + assert q == hip.QueryParams("a=456&b=789") + """ + q = QueryParams(params) + q._dict = {**self._dict, **q._dict} + return q + + def __getitem__(self, key: str) -> str: + return self._dict[key][0] + + def __contains__(self, key: typing.Any) -> bool: + return key in self._dict + + def __iter__(self) -> typing.Iterator[str]: + return iter(self.keys()) + + def __len__(self) -> int: + return len(self._dict) + + def __bool__(self) -> bool: + return bool(self._dict) + + def __hash__(self) -> int: + return hash(str(self)) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return False + return sorted(self.multi_items()) == sorted(other.multi_items()) + + def __str__(self) -> str: + return urlencode(self.multi_dict()) + + def __repr__(self) -> str: + return f"" diff --git a/src/hip/_urls.py b/src/hip/_urls.py index 29aec0c..62e2039 100644 --- a/src/hip/_urls.py +++ b/src/hip/_urls.py @@ -2,10 +2,11 @@ import typing -from ._urlencode import unquote, urldecode, urlencode +from ._queryparams import QueryParams +from ._urlencode import unquote from ._urlparse import urlparse -__all__ = ["URL", "QueryParams"] +__all__ = ["URL"] class URL: @@ -325,217 +326,3 @@ def __str__(self) -> str: def __repr__(self) -> str: return f"" - - -class QueryParams(typing.Mapping[str, str]): - """ - URL query parameters, as a multi-dict. - """ - - def __init__( - self, - params: ( - QueryParams - | dict[str, str | list[str]] - | list[tuple[str, str]] - | str - | None - ) = None, - ) -> None: - d: dict[str, list[str]] = {} - - if params is None: - d = {} - elif isinstance(params, str): - d = urldecode(params) - elif isinstance(params, QueryParams): - d = params.multi_dict() - elif isinstance(params, dict): - # Convert dict inputs like: - # {"a": "123", "b": ["456", "789"]} - # To dict inputs where values are always lists, like: - # {"a": ["123"], "b": ["456", "789"]} - d = {k: [v] if isinstance(v, str) else list(v) for k, v in params.items()} - else: - # Convert list inputs like: - # [("a", "123"), ("a", "456"), ("b", "789")] - # To a dict representation, like: - # {"a": ["123", "456"], "b": ["789"]} - for k, v in params: - d.setdefault(k, []).append(v) - - self._dict = d - - def keys(self) -> typing.KeysView[str]: - """ - Return all the keys in the query params. - - Usage: - - q = hip.QueryParams("a=123&a=456&b=789") - assert list(q.keys()) == ["a", "b"] - """ - return self._dict.keys() - - def values(self) -> typing.ValuesView[str]: - """ - Return all the values in the query params. If a key occurs more than once - only the first item for that key is returned. - - Usage: - - q = hip.QueryParams("a=123&a=456&b=789") - assert list(q.values()) == ["123", "789"] - """ - return {k: v[0] for k, v in self._dict.items()}.values() - - def items(self) -> typing.ItemsView[str, str]: - """ - Return all items in the query params. If a key occurs more than once - only the first item for that key is returned. - - Usage: - - q = hip.QueryParams("a=123&a=456&b=789") - assert list(q.items()) == [("a", "123"), ("b", "789")] - """ - return {k: v[0] for k, v in self._dict.items()}.items() - - def multi_items(self) -> list[tuple[str, str]]: - """ - Return all items in the query params. Allow duplicate keys to occur. - - Usage: - - q = hip.QueryParams("a=123&a=456&b=789") - assert list(q.multi_items()) == [("a", "123"), ("a", "456"), ("b", "789")] - """ - multi_items: list[tuple[str, str]] = [] - for k, v in self._dict.items(): - multi_items.extend([(k, i) for i in v]) - return multi_items - - def multi_dict(self) -> dict[str, list[str]]: - return {k: list(v) for k, v in self._dict.items()} - - def get(self, key: str, default: typing.Any = None) -> typing.Any: - """ - Get a value from the query param for a given key. If the key occurs - more than once, then only the first value is returned. - - Usage: - - q = hip.QueryParams("a=123&a=456&b=789") - assert q.get("a") == "123" - """ - if key in self._dict: - return self._dict[key][0] - return default - - def get_list(self, key: str) -> list[str]: - """ - Get all values from the query param for a given key. - - Usage: - - q = hip.QueryParams("a=123&a=456&b=789") - assert q.get_list("a") == ["123", "456"] - """ - return list(self._dict.get(key, [])) - - def copy_set(self, key: str, value: str) -> QueryParams: - """ - Return a new QueryParams instance, setting the value of a key. - - Usage: - - q = hip.QueryParams("a=123") - q = q.set("a", "456") - assert q == hip.QueryParams("a=456") - """ - q = QueryParams() - q._dict = dict(self._dict) - q._dict[key] = [value] - return q - - def copy_append(self, key: str, value: str) -> QueryParams: - """ - Return a new QueryParams instance, setting or appending the value of a key. - - Usage: - - q = hip.QueryParams("a=123") - q = q.append("a", "456") - assert q == hip.QueryParams("a=123&a=456") - """ - q = QueryParams() - q._dict = dict(self._dict) - q._dict[key] = q.get_list(key) + [value] - return q - - def copy_remove(self, key: str) -> QueryParams: - """ - Return a new QueryParams instance, removing the value of a key. - - Usage: - - q = hip.QueryParams("a=123") - q = q.remove("a") - assert q == hip.QueryParams("") - """ - q = QueryParams() - q._dict = dict(self._dict) - q._dict.pop(str(key), None) - return q - - def copy_update( - self, - params: ( - QueryParams | dict[str, str | list[str]] | list[tuple[str, str]] | None - ) = None, - ) -> QueryParams: - """ - Return a new QueryParams instance, updated with. - - Usage: - - q = hip.QueryParams("a=123") - q = q.copy_update({"b": "456"}) - assert q == hip.QueryParams("a=123&b=456") - - q = hip.QueryParams("a=123") - q = q.copy_update({"a": "456", "b": "789"}) - assert q == hip.QueryParams("a=456&b=789") - """ - q = QueryParams(params) - q._dict = {**self._dict, **q._dict} - return q - - def __getitem__(self, key: str) -> str: - return self._dict[key][0] - - def __contains__(self, key: typing.Any) -> bool: - return key in self._dict - - def __iter__(self) -> typing.Iterator[str]: - return iter(self.keys()) - - def __len__(self) -> int: - return len(self._dict) - - def __bool__(self) -> bool: - return bool(self._dict) - - def __hash__(self) -> int: - return hash(str(self)) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, self.__class__): - return False - return sorted(self.multi_items()) == sorted(other.multi_items()) - - def __str__(self) -> str: - return urlencode(self.multi_dict()) - - def __repr__(self) -> str: - return f""