From 9751f76186c1c89d71baa571ebd4a5b6be76d117 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 30 Oct 2023 19:13:47 +0000 Subject: [PATCH 1/4] Drop unneccessary `binascii` import (#2909) * Drop unneccessary binascii import * Update httpx/_multipart.py Co-authored-by: T-256 <132141463+T-256@users.noreply.github.com> * boundary is 'bytes' not 'str' --------- Co-authored-by: T-256 <132141463+T-256@users.noreply.github.com> --- httpx/_multipart.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/httpx/_multipart.py b/httpx/_multipart.py index 446f4ad2df..6d5baa8639 100644 --- a/httpx/_multipart.py +++ b/httpx/_multipart.py @@ -1,4 +1,3 @@ -import binascii import io import os import typing @@ -200,7 +199,7 @@ def __init__( boundary: typing.Optional[bytes] = None, ) -> None: if boundary is None: - boundary = binascii.hexlify(os.urandom(16)) + boundary = os.urandom(16).hex().encode("ascii") self.boundary = boundary self.content_type = "multipart/form-data; boundary=%s" % boundary.decode( From ad06741d1e0cfe4f5143c957c54f9a3fb8002e76 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 30 Oct 2023 20:07:42 +0000 Subject: [PATCH 2/4] Lazily import 'netrc' module (#2910) --- httpx/_auth.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/httpx/_auth.py b/httpx/_auth.py index 27dc7f743b..c2c38f3945 100644 --- a/httpx/_auth.py +++ b/httpx/_auth.py @@ -1,5 +1,4 @@ import hashlib -import netrc import os import re import time @@ -148,6 +147,10 @@ class NetRCAuth(Auth): """ def __init__(self, file: typing.Optional[str] = None): + # Lazily import 'netrc'. + # There's no need for us to load this module unless 'NetRCAuth' is being used. + import netrc + self._netrc_info = netrc.netrc(file) def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: From 5f2d62096a99ea8b111236557fd2c788c85908d2 Mon Sep 17 00:00:00 2001 From: Kar Petrosyan <92274156+karpetrosyan@users.noreply.github.com> Date: Tue, 31 Oct 2023 02:57:34 -0400 Subject: [PATCH 3/4] Fix third party package documentation link (#2902) * Fix doc link * Update docs/third_party_packages.md --- docs/third_party_packages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/third_party_packages.md b/docs/third_party_packages.md index 2adb53ae56..3d5f4778ec 100644 --- a/docs/third_party_packages.md +++ b/docs/third_party_packages.md @@ -8,7 +8,7 @@ As HTTPX usage grows, there is an expanding community of developers building too ### Hishel -[GitHub](https://github.com/karosis88/hishel) - [Documentation](https://karosis88.github.io/hishel/) +[GitHub](https://github.com/karpetrosyan/hishel) - [Documentation](https://hishel.com/) An elegant HTTP Cache implementation for HTTPX and HTTP Core. From 1d73150c1f663757d63585aecfcb329d0ec00825 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 31 Oct 2023 10:10:16 +0000 Subject: [PATCH 4/4] Cleanup response.json() method (#2911) Co-authored-by: Kar Petrosyan <92274156+karpetrosyan@users.noreply.github.com> --- httpx/_models.py | 7 +------ httpx/_utils.py | 35 ----------------------------------- tests/test_utils.py | 17 +++++++++++------ 3 files changed, 12 insertions(+), 47 deletions(-) diff --git a/httpx/_models.py b/httpx/_models.py index 8a6bda04bb..4e4162db1a 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -43,7 +43,6 @@ ) from ._urls import URL from ._utils import ( - guess_json_utf, is_known_encoding, normalize_header_key, normalize_header_value, @@ -759,11 +758,7 @@ def raise_for_status(self) -> "Response": raise HTTPStatusError(message, request=request, response=self) def json(self, **kwargs: typing.Any) -> typing.Any: - if self.charset_encoding is None and self.content and len(self.content) > 3: - encoding = guess_json_utf(self.content) - if encoding is not None: - return jsonlib.loads(self.content.decode(encoding), **kwargs) - return jsonlib.loads(self.text, **kwargs) + return jsonlib.loads(self.content, **kwargs) @property def cookies(self) -> "Cookies": diff --git a/httpx/_utils.py b/httpx/_utils.py index 1775b1a1ef..ba5807c048 100644 --- a/httpx/_utils.py +++ b/httpx/_utils.py @@ -91,41 +91,6 @@ def replacer(match: typing.Match[str]) -> str: return f'{name}="{value}"'.encode() -# Null bytes; no need to recreate these on each call to guess_json_utf -_null = b"\x00" -_null2 = _null * 2 -_null3 = _null * 3 - - -def guess_json_utf(data: bytes) -> typing.Optional[str]: - # JSON always starts with two ASCII characters, so detection is as - # easy as counting the nulls and from their location and count - # determine the encoding. Also detect a BOM, if present. - sample = data[:4] - if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): - return "utf-32" # BOM included - if sample[:3] == codecs.BOM_UTF8: - return "utf-8-sig" # BOM included, MS style (discouraged) - if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): - return "utf-16" # BOM included - nullcount = sample.count(_null) - if nullcount == 0: - return "utf-8" - if nullcount == 2: - if sample[::2] == _null2: # 1st and 3rd are null - return "utf-16-be" - if sample[1::2] == _null2: # 2nd and 4th are null - return "utf-16-le" - # Did not detect 2 valid UTF-16 ascii-range characters - if nullcount == 3: - if sample[:3] == _null3: - return "utf-32-be" - if sample[1:] == _null3: - return "utf-32-le" - # Did not detect a valid UTF-32 ascii-range character - return None - - def get_ca_bundle_from_env() -> typing.Optional[str]: if "SSL_CERT_FILE" in os.environ: ssl_file = Path(os.environ["SSL_CERT_FILE"]) diff --git a/tests/test_utils.py b/tests/test_utils.py index ab0fcbecd9..dedb92f7f2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,3 +1,4 @@ +import json import logging import os import random @@ -10,7 +11,6 @@ URLPattern, get_ca_bundle_from_env, get_environment_proxies, - guess_json_utf, is_https_redirect, obfuscate_sensitive_headers, parse_header_links, @@ -34,12 +34,16 @@ ), ) def test_encoded(encoding): - data = "{}".encode(encoding) - assert guess_json_utf(data) == encoding + content = '{"abc": 123}'.encode(encoding) + response = httpx.Response(200, content=content) + assert response.json() == {"abc": 123} def test_bad_utf_like_encoding(): - assert guess_json_utf(b"\x00\x00\x00\x00") is None + content = b"\x00\x00\x00\x00" + response = httpx.Response(200, content=content) + with pytest.raises(json.decoder.JSONDecodeError): + response.json() @pytest.mark.parametrize( @@ -52,8 +56,9 @@ def test_bad_utf_like_encoding(): ), ) def test_guess_by_bom(encoding, expected): - data = "\ufeff{}".encode(encoding) - assert guess_json_utf(data) == expected + content = '\ufeff{"abc": 123}'.encode(encoding) + response = httpx.Response(200, content=content) + assert response.json() == {"abc": 123} @pytest.mark.parametrize(