diff --git a/HISTORY.md b/HISTORY.md index dbdcca824a..efd9844b72 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,15 @@ Release History =============== +3.2.4 (2023-11-15) +------------------ + +**Fixed** +- Compatibility with some third-party mock tools. + +**Changed** +- Relax IllegalHeader constraint when value is an integer, or float. + 3.2.3 (2023-11-11) ------------------ diff --git a/src/niquests/__version__.py b/src/niquests/__version__.py index 8eebe9af93..802c034dd0 100644 --- a/src/niquests/__version__.py +++ b/src/niquests/__version__.py @@ -9,9 +9,9 @@ __url__: str = "https://niquests.readthedocs.io" __version__: str -__version__ = "3.2.3" +__version__ = "3.2.4" -__build__: int = 0x030203 +__build__: int = 0x030204 __author__: str = "Kenneth Reitz" __author_email__: str = "me@kennethreitz.org" __license__: str = "Apache-2.0" diff --git a/src/niquests/adapters.py b/src/niquests/adapters.py index ae4b209ff7..8ec205a74f 100644 --- a/src/niquests/adapters.py +++ b/src/niquests/adapters.py @@ -442,7 +442,7 @@ def build_response( """ response = Response() - if isinstance(resp, BaseHTTPResponse): + if isinstance(resp, ResponsePromise) is False: # Fallback to None if there's no status_code, for whatever reason. response.status_code = getattr(resp, "status", None) @@ -451,8 +451,8 @@ def build_response( # Set encoding. response.encoding = get_encoding_from_headers(response.headers) - response.raw = resp - response.reason = response.raw.reason + response.raw = resp # type: ignore[assignment] + response.reason = response.raw.reason # type: ignore[union-attr] if isinstance(req.url, bytes): response.url = req.url.decode("utf-8") @@ -460,10 +460,10 @@ def build_response( response.url = req.url # Add new cookies from the server. - extract_cookies_to_jar(response.cookies, req, resp) + extract_cookies_to_jar(response.cookies, req, resp) # type: ignore[arg-type] else: with self._promise_lock: - self._promises[resp.uid] = response + self._promises[resp.uid] = response # type: ignore[union-attr] # Give the Response some context. response.request = req diff --git a/src/niquests/models.py b/src/niquests/models.py index f9424ca858..8bc8a7c622 100644 --- a/src/niquests/models.py +++ b/src/niquests/models.py @@ -832,7 +832,7 @@ class Response: server's response to an HTTP request. """ - __attrs__ = { + __attrs__ = [ "_content", "status_code", "headers", @@ -843,7 +843,7 @@ class Response: "cookies", "elapsed", "request", - } + ] __lazy_attrs__ = { "json", @@ -934,7 +934,7 @@ def lazy(self) -> bool: Determine if response isn't received and is actually a placeholder. Only significant if request was sent through a multiplexed connection. """ - return self.raw is None and hasattr(self, "_gather") + return hasattr(self, "raw") and self.raw is None and hasattr(self, "_gather") def __getattribute__(self, item): if item in Response.__lazy_attrs__ and self.lazy: diff --git a/src/niquests/structures.py b/src/niquests/structures.py index c975c47101..f3542b696f 100644 --- a/src/niquests/structures.py +++ b/src/niquests/structures.py @@ -15,11 +15,24 @@ from .exceptions import InvalidHeader -def _ensure_str_or_bytes(key: typing.Any, value: typing.Any) -> None: +def _ensure_str_or_bytes( + key: typing.Any, value: typing.Any +) -> tuple[bytes | str, bytes | str]: + if isinstance( + value, + ( + float, + int, + ), + ): + if isinstance(key, bytes): + key = key.decode() + value = str(value) if isinstance(key, (bytes, str)) is False or ( value is not None and isinstance(value, (bytes, str)) is False ): raise InvalidHeader(f"Illegal header name or value {key}") + return key, value class CaseInsensitiveDict(MutableMapping): @@ -55,14 +68,15 @@ def __init__(self, data=None, **kwargs) -> None: ] = OrderedDict() if data is None: data = {} + normalized_items = [] for k, v in data.items() if hasattr(data, "items") else data: - _ensure_str_or_bytes(k, v) - self.update(data, **kwargs) + normalized_items.append(_ensure_str_or_bytes(k, v)) + self.update(normalized_items, **kwargs) def __setitem__(self, key: str | bytes, value: str | bytes) -> None: # Use the lowercased key for lookups, but store the actual # key alongside the value. - _ensure_str_or_bytes(key, value) + key, value = _ensure_str_or_bytes(key, value) self._store[key.lower()] = (key, value) def __getitem__(self, key) -> bytes | str: diff --git a/tests/test_requests.py b/tests/test_requests.py index c6ccf104fc..86cf85811d 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -1666,7 +1666,7 @@ def test_header_validation(self, httpbin): @pytest.mark.parametrize( "invalid_header, key", ( - ({"foo": 3}, "foo"), + ({"foo": ...}, "foo"), ({"bar": {"foo": "bar"}}, "bar"), ({"baz": ["foo", "bar"]}, "baz"), ),