diff --git a/yarl/_quoters.py b/yarl/_quoters.py index e0e3f8801..c1d2d7f81 100644 --- a/yarl/_quoters.py +++ b/yarl/_quoters.py @@ -1,5 +1,8 @@ """Quoting and unquoting utilities for URL parts.""" +from typing import Union +from urllib.parse import quote + from ._quoting import _Quoter, _Unquoter QUOTER = _Quoter(requote=False) @@ -16,3 +19,14 @@ PATH_UNQUOTER = _Unquoter(unsafe="+") PATH_SAFE_UNQUOTER = _Unquoter(ignore="/%", unsafe="+") QS_UNQUOTER = _Unquoter(qs=True) + + +def human_quote(s: Union[str, None], unsafe: str) -> Union[str, None]: + if not s: + return s + for c in "%" + unsafe: + if c in s: + s = s.replace(c, f"%{ord(c):02X}") + if s.isprintable(): + return s + return "".join(c if c.isprintable() else quote(c) for c in s) diff --git a/yarl/_url.py b/yarl/_url.py index 4589d801d..77193db08 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -6,7 +6,7 @@ from functools import _CacheInfo, lru_cache from ipaddress import ip_address from typing import TYPE_CHECKING, Any, TypedDict, TypeVar, Union, overload -from urllib.parse import SplitResult, parse_qsl, quote, uses_relative +from urllib.parse import SplitResult, parse_qsl, uses_relative import idna from multidict import MultiDict, MultiDictProxy @@ -34,6 +34,7 @@ QUOTER, REQUOTER, UNQUOTER, + human_quote, ) DEFAULT_PORTS = {"http": 80, "https": 443, "ws": 80, "wss": 443, "ftp": 21} @@ -1334,18 +1335,18 @@ def joinpath(self, *other: str, encoded: bool = False) -> "URL": def human_repr(self) -> str: """Return decoded human readable string for URL representation.""" - user = _human_quote(self.user, "#/:?@[]") - password = _human_quote(self.password, "#/:?@[]") + user = human_quote(self.user, "#/:?@[]") + password = human_quote(self.password, "#/:?@[]") if (host := self.host) and ":" in host: host = f"[{host}]" - path = _human_quote(self.path, "#?") + path = human_quote(self.path, "#?") if TYPE_CHECKING: assert path is not None query_string = "&".join( - "{}={}".format(_human_quote(k, "#&+;="), _human_quote(v, "#&+;=")) + "{}={}".format(human_quote(k, "#&+;="), human_quote(v, "#&+;=")) for k, v in self.query.items() ) - fragment = _human_quote(self.fragment, "") + fragment = human_quote(self.fragment, "") if TYPE_CHECKING: assert fragment is not None netloc = make_netloc(user, password, host, self.explicit_port) @@ -1353,17 +1354,6 @@ def human_repr(self) -> str: return unsplit_result(scheme, netloc, path, query_string, fragment) -def _human_quote(s: Union[str, None], unsafe: str) -> Union[str, None]: - if not s: - return s - for c in "%" + unsafe: - if c in s: - s = s.replace(c, f"%{ord(c):02X}") - if s.isprintable(): - return s - return "".join(c if c.isprintable() else quote(c) for c in s) - - _DEFAULT_IDNA_SIZE = 256 _DEFAULT_ENCODE_SIZE = 512