Skip to content

Commit 00a2524

Browse files
committed
Add headers.get and headers.getlist
1 parent 31e626c commit 00a2524

File tree

3 files changed

+66
-5
lines changed

3 files changed

+66
-5
lines changed

docs/source/api.rst

+12
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,18 @@ have a newline inside a header value, and ``Content-Length: hello`` is
128128
an error because `Content-Length` should always be an integer. We may
129129
add additional checks in the future.
130130

131+
It is possible to get the first or all headers for a given name.
132+
133+
.. ipython:: python
134+
135+
res = h11.Response(status_code=204, headers=[
136+
("Date", b"Thu, 09 Jan 2025 18:37:23 GMT"),
137+
("Set-Cookie", b"sid=1234"),
138+
("Set-Cookie", b"lang=en_US"),
139+
])
140+
res.headers.get(b"date")
141+
res.headers.getlist(b"set-cookie")
142+
131143
While we make sure to expose header names as lowercased bytes, we also
132144
preserve the original header casing that is used. Compliant HTTP
133145
agents should always treat headers in a case insensitive manner, but

h11/_headers.py

+38-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import re
2-
from typing import AnyStr, cast, List, overload, Sequence, Tuple, TYPE_CHECKING, Union
2+
from typing import List, overload, Sequence, Tuple, TYPE_CHECKING, TypeVar, Union
33

44
from ._abnf import field_name, field_value
55
from ._util import bytesify, LocalProtocolError, validate
@@ -13,6 +13,8 @@
1313
from typing_extensions import Literal # type: ignore
1414

1515

16+
T = TypeVar("T")
17+
1618
# Facts
1719
# -----
1820
#
@@ -84,19 +86,29 @@ class Headers(Sequence[Tuple[bytes, bytes]]):
8486
r = Request(
8587
method="GET",
8688
target="/",
87-
headers=[("Host", "example.org"), ("Connection", "keep-alive")],
89+
headers=[
90+
("Host", "example.org"),
91+
("Connection", "keep-alive"),
92+
("Cookie", "session=1234"),
93+
("Cookie", "lang=en_US"),
94+
],
8895
http_version="1.1",
8996
)
9097
assert r.headers == [
9198
(b"host", b"example.org"),
92-
(b"connection", b"keep-alive")
99+
(b"connection", b"keep-alive"),
100+
(b"cookie", b"session=1234"),
101+
(b"cookie", b"lang=en_US"),
93102
]
94103
assert r.headers.raw_items() == [
95104
(b"Host", b"example.org"),
96-
(b"Connection", b"keep-alive")
105+
(b"Connection", b"keep-alive"),
106+
(b"Cookie", b"session=1234"),
107+
(b"Cookie", b"lang=en_US"),
97108
]
109+
assert r.headers.get(b"host") == b"example.org"
110+
assert r.headers.getlist(b"cookie") == [b"session=1234", b"lang=en_US"]
98111
"""
99-
100112
__slots__ = "_full_items"
101113

102114
def __init__(self, full_items: List[Tuple[bytes, bytes, bytes]]) -> None:
@@ -118,6 +130,27 @@ def __getitem__(self, idx: int) -> Tuple[bytes, bytes]: # type: ignore[override
118130
_, name, value = self._full_items[idx]
119131
return (name, value)
120132

133+
def get(self, key: bytes, default: T = None) -> Union[bytes, T]:
134+
"""Find the first header with lowercased-name :param:`key`, it returns
135+
its value when found, and :param:`default` otherwise.
136+
137+
Args:
138+
key (bytes): The lowercased header name to find.
139+
140+
default: The value to return when the header is not found.
141+
"""
142+
return next((value for name, value in self if name == key), default)
143+
144+
def getlist(self, key: bytes) -> List[bytes]:
145+
"""Find the all the headers with lowercased-name :param:`key`,
146+
it returns their values in a list. It returns an empty list when
147+
no header matched.
148+
149+
Args:
150+
key (bytes): The lowercased header name to find.
151+
"""
152+
return [value for name, value in self if name == key]
153+
121154
def raw_items(self) -> List[Tuple[bytes, bytes]]:
122155
return [(raw_name, value) for raw_name, _, value in self._full_items]
123156

h11/tests/test_events.py

+16
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,19 @@ def test_header_casing() -> None:
146146
(b"Host", b"example.org"),
147147
(b"Connection", b"keep-alive"),
148148
]
149+
150+
def test_header_get() -> None:
151+
r = Response(status_code=204, headers=[
152+
("Date", b"Thu, 09 Jan 2025 18:37:23 GMT"),
153+
("Set-Cookie", b"sid=1234"),
154+
("Set-Cookie", b"lang=en_US"),
155+
])
156+
157+
assert r.headers.get(b"date") == b"Thu, 09 Jan 2025 18:37:23 GMT"
158+
assert r.headers.get(b"set-cookie") == b"sid=1234"
159+
assert r.headers.get(b"content-length") is None
160+
assert r.headers.get(b"content-length", b"0") == b"0"
161+
162+
assert r.headers.getlist(b"date") == [b"Thu, 09 Jan 2025 18:37:23 GMT"]
163+
assert r.headers.getlist(b"set-cookie") == [b"sid=1234", b"lang=en_US"]
164+
assert r.headers.getlist(b"content-length") == []

0 commit comments

Comments
 (0)