From 68c18e383ce93b9e4fbe2923b80a834fe3e06923 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 28 Oct 2024 17:33:24 +0000 Subject: [PATCH 1/3] Just use default safe=... characters for urlescape --- httpx/_urls.py | 16 +--------------- tests/models/test_url.py | 4 ++-- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/httpx/_urls.py b/httpx/_urls.py index bfc0e9e698..7976cb1837 100644 --- a/httpx/_urls.py +++ b/httpx/_urls.py @@ -12,20 +12,6 @@ __all__ = ["URL", "QueryParams"] -# To urlencode query parameters, we use the whatwg query percent-encode set -# and additionally escape U+0025 (%), U+0026 (&), U+002B (+) and U+003D (=). - -# https://url.spec.whatwg.org/#percent-encoded-bytes - -URLENCODE_SAFE = "".join( - [ - chr(i) - for i in range(0x20, 0x7F) - if i not in (0x20, 0x22, 0x23, 0x25, 0x26, 0x2B, 0x3C, 0x3D, 0x3E) - ] -) - - class URL: """ url = httpx.URL("HTTPS://jo%40email.com:a%20secret@müller.de:1234/pa%20th?search=ab#anchorlink") @@ -619,7 +605,7 @@ def __eq__(self, other: typing.Any) -> bool: return sorted(self.multi_items()) == sorted(other.multi_items()) def __str__(self) -> str: - return urlencode(self.multi_items(), safe=URLENCODE_SAFE) + return urlencode(self.multi_items()) def __repr__(self) -> str: class_name = self.__class__.__name__ diff --git a/tests/models/test_url.py b/tests/models/test_url.py index d32ed52169..03072e8f5c 100644 --- a/tests/models/test_url.py +++ b/tests/models/test_url.py @@ -148,7 +148,7 @@ def test_url_query_encoding(): assert url.raw_path == b"/?a=b+c&d=e/f" url = httpx.URL("https://www.example.com/", params={"a": "b c", "d": "e/f"}) - assert url.raw_path == b"/?a=b+c&d=e/f" + assert url.raw_path == b"/?a=b+c&d=e%2Ff" def test_url_params(): @@ -309,7 +309,7 @@ def test_param_with_existing_escape_requires_encoding(): # even if they include a valid escape sequence. # We want to match browser form behaviour here. url = httpx.URL("http://webservice", params={"u": "http://example.com?q=foo%2Fa"}) - assert str(url) == "http://webservice?u=http://example.com?q%3Dfoo%252Fa" + assert str(url) == "http://webservice?u=http%3A%2F%2Fexample.com%3Fq%3Dfoo%252Fa" # Tests for query parameter percent encoding. From d7c91f941918816f9b0f985667834a17fe36f5ac Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 29 Oct 2024 13:48:29 +0000 Subject: [PATCH 2/3] Cleanup Request method signature --- httpx/_models.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/httpx/_models.py b/httpx/_models.py index 2ff22b4876..c59f0196d3 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -310,7 +310,7 @@ def __repr__(self) -> str: class Request: def __init__( self, - method: str | bytes, + method: str, url: URL | str, *, params: QueryParamTypes | None = None, @@ -323,11 +323,7 @@ def __init__( stream: SyncByteStream | AsyncByteStream | None = None, extensions: RequestExtensions | None = None, ) -> None: - self.method = ( - method.decode("ascii").upper() - if isinstance(method, bytes) - else method.upper() - ) + self.method = method.upper() self.url = URL(url) if params is None else URL(url, params=params) self.headers = Headers(headers) self.extensions = {} if extensions is None else extensions From e099909136b78b187a7dfe026a7cef61d818c085 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 29 Oct 2024 16:45:01 +0000 Subject: [PATCH 3/3] Fix extensions annotations --- httpx/_models.py | 4 ++-- httpx/_types.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/httpx/_models.py b/httpx/_models.py index c59f0196d3..c0ef30cd1b 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -326,7 +326,7 @@ def __init__( self.method = method.upper() self.url = URL(url) if params is None else URL(url, params=params) self.headers = Headers(headers) - self.extensions = {} if extensions is None else extensions + self.extensions = {} if extensions is None else dict(extensions) if cookies: Cookies(cookies).set_cookie_header(self) @@ -465,7 +465,7 @@ def __init__( # the client will set `response.next_request`. self.next_request: Request | None = None - self.extensions: ResponseExtensions = {} if extensions is None else extensions + self.extensions = {} if extensions is None else dict(extensions) self.history = [] if history is None else list(history) self.is_closed = False diff --git a/httpx/_types.py b/httpx/_types.py index edd00da1bc..4f0eab96a2 100644 --- a/httpx/_types.py +++ b/httpx/_types.py @@ -15,7 +15,6 @@ Iterator, List, Mapping, - MutableMapping, Optional, Sequence, Tuple, @@ -67,7 +66,7 @@ RequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] ResponseContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] -ResponseExtensions = MutableMapping[str, Any] +ResponseExtensions = Mapping[str, Any] RequestData = Mapping[str, Any] @@ -84,7 +83,7 @@ ] RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]] -RequestExtensions = MutableMapping[str, Any] +RequestExtensions = Mapping[str, Any] __all__ = ["AsyncByteStream", "SyncByteStream"]