Skip to content

Commit 7f694b7

Browse files
committed
Allow str/bytes subclasses to be used as header parts
1 parent e90852d commit 7f694b7

File tree

3 files changed

+48
-13
lines changed

3 files changed

+48
-13
lines changed

requests/_internal_utils.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
_VALID_HEADER_VALUE_RE_BYTE = re.compile(rb"^\S[^\r\n]*$|^$")
1515
_VALID_HEADER_VALUE_RE_STR = re.compile(r"^\S[^\r\n]*$|^$")
1616

17+
_HEADER_VALIDATORS_STR = (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR)
18+
_HEADER_VALIDATORS_BYTE = (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE)
1719
HEADER_VALIDATORS = {
18-
bytes: (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE),
19-
str: (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR),
20+
bytes: _HEADER_VALIDATORS_BYTE,
21+
str: _HEADER_VALIDATORS_STR,
2022
}
2123

2224

requests/utils.py

+19-11
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@
2525
from .__version__ import __version__
2626

2727
# to_native_string is unused here, but imported here for backwards compatibility
28-
from ._internal_utils import HEADER_VALIDATORS, to_native_string # noqa: F401
28+
from ._internal_utils import ( # noqa: F401
29+
_HEADER_VALIDATORS_BYTE,
30+
_HEADER_VALIDATORS_STR,
31+
HEADER_VALIDATORS,
32+
to_native_string,
33+
)
2934
from .compat import (
3035
Mapping,
3136
basestring,
@@ -1031,20 +1036,23 @@ def check_header_validity(header):
10311036
:param header: tuple, in the format (name, value).
10321037
"""
10331038
name, value = header
1039+
_validate_header_part(header, name, 0)
1040+
_validate_header_part(header, value, 1)
10341041

1035-
for part in header:
1036-
if type(part) not in HEADER_VALIDATORS:
1037-
raise InvalidHeader(
1038-
f"Header part ({part!r}) from {{{name!r}: {value!r}}} must be "
1039-
f"of type str or bytes, not {type(part)}"
1040-
)
1041-
1042-
_validate_header_part(name, "name", HEADER_VALIDATORS[type(name)][0])
1043-
_validate_header_part(value, "value", HEADER_VALIDATORS[type(value)][1])
10441042

1043+
def _validate_header_part(header, header_part, header_validator_index):
1044+
if isinstance(header_part, str):
1045+
validator = _HEADER_VALIDATORS_STR[header_validator_index]
1046+
elif isinstance(header_part, bytes):
1047+
validator = _HEADER_VALIDATORS_BYTE[header_validator_index]
1048+
else:
1049+
raise InvalidHeader(
1050+
f"Header part ({header_part!r}) from {header} "
1051+
f"must be of type str or bytes, not {type(header_part)}"
1052+
)
10451053

1046-
def _validate_header_part(header_part, header_kind, validator):
10471054
if not validator.match(header_part):
1055+
header_kind = "name" if header_validator_index == 0 else "value"
10481056
raise InvalidHeader(
10491057
f"Invalid leading whitespace, reserved character(s), or return"
10501058
f"character(s) in header {header_kind}: {header_part!r}"

tests/test_requests.py

+25
Original file line numberDiff line numberDiff line change
@@ -1752,6 +1752,31 @@ def test_header_no_leading_space(self, httpbin, invalid_header):
17521752
with pytest.raises(InvalidHeader):
17531753
requests.get(httpbin("get"), headers=invalid_header)
17541754

1755+
def test_header_with_subclass_types(self, httpbin):
1756+
"""If the subclasses does not behave *exactly* like
1757+
the base bytes/str classes, this is not supported.
1758+
This test is for backwards compatibility.
1759+
"""
1760+
1761+
class MyString(str):
1762+
pass
1763+
1764+
class MyBytes(bytes):
1765+
pass
1766+
1767+
r_str = requests.get(httpbin("get"), headers={MyString("x-custom"): "myheader"})
1768+
assert r_str.request.headers["x-custom"] == "myheader"
1769+
1770+
r_bytes = requests.get(
1771+
httpbin("get"), headers={MyBytes(b"x-custom"): b"myheader"}
1772+
)
1773+
assert r_bytes.request.headers["x-custom"] == b"myheader"
1774+
1775+
r_mixed = requests.get(
1776+
httpbin("get"), headers={MyString("x-custom"): MyBytes(b"myheader")}
1777+
)
1778+
assert r_mixed.request.headers["x-custom"] == b"myheader"
1779+
17551780
@pytest.mark.parametrize("files", ("foo", b"foo", bytearray(b"foo")))
17561781
def test_can_send_objects_with_files(self, httpbin, files):
17571782
data = {"a": "this is a string"}

0 commit comments

Comments
 (0)