Skip to content
This repository was archived by the owner on Jan 13, 2021. It is now read-only.

Commit 721a239

Browse files
committed
Switch to storing headers in 'raw' form
1 parent bab718f commit 721a239

File tree

2 files changed

+53
-20
lines changed

2 files changed

+53
-20
lines changed

hyper/common/headers.py

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,35 @@ def __init__(self, *args, **kwargs):
2929
# The meat of the structure. In practice, headers are an ordered list
3030
# of tuples. This early version of the data structure simply uses this
3131
# directly under the covers.
32+
#
33+
# An important curiosity here is that the headers are not stored in
34+
# 'canonical form', but are instead stored in the form they were
35+
# provided in. This is to ensure that it is always possible to
36+
# reproduce the original header structure if necessary. This leads to
37+
# some unfortunate performance costs on structure access where it is
38+
# often necessary to transform the data into canonical form on access.
39+
# This cost is judged acceptable in low-level code like `hyper`, but
40+
# higher-level abstractions should consider if they really require this
41+
# logic.
3242
self._items = []
3343

3444
for arg in args:
35-
for item in arg:
36-
self._items.extend(canonical_form(*item))
45+
self._items.extend(arg)
3746

3847
for k, v in kwargs.items():
39-
self._items.extend(canonical_form(k, v))
48+
self._items.append((k, v))
4049

4150
def __getitem__(self, key):
4251
"""
4352
Unlike the dict __getitem__, this returns a list of items in the order
44-
they were added.
53+
they were added. These items are returned in 'canonical form', meaning
54+
that comma-separated values are split into multiple values.
4555
"""
4656
values = []
4757

4858
for k, v in self._items:
4959
if _keys_equal(k, key):
50-
values.append(v)
60+
values.extend(x[1] for x in canonical_form(k, v))
5161

5262
if not values:
5363
raise KeyError("Nonexistent header key: {}".format(key))
@@ -56,10 +66,9 @@ def __getitem__(self, key):
5666

5767
def __setitem__(self, key, value):
5868
"""
59-
Unlike the dict __setitem__, this appends to the list of items. It also
60-
splits out headers that can be split on the comma.
69+
Unlike the dict __setitem__, this appends to the list of items.
6170
"""
62-
self._items.extend(canonical_form(key, value))
71+
self._items.append((key, value))
6372

6473
def __delitem__(self, key):
6574
"""
@@ -80,16 +89,23 @@ def __delitem__(self, key):
8089

8190
def __iter__(self):
8291
"""
83-
This mapping iterates like the list of tuples it is.
92+
This mapping iterates like the list of tuples it is. The headers are
93+
returned in canonical form.
8494
"""
8595
for pair in self._items:
86-
yield pair
96+
for value in canonical_form(*pair):
97+
yield value
8798

8899
def __len__(self):
89100
"""
90-
The length of this mapping is the number of individual headers.
101+
The length of this mapping is the number of individual headers in
102+
canonical form. Sadly, this is a somewhat expensive operation.
91103
"""
92-
return len(self._items)
104+
size = 0
105+
for _ in self:
106+
size += 1
107+
108+
return size
93109

94110
def __contains__(self, key):
95111
"""
@@ -103,22 +119,21 @@ def keys(self):
103119
does not filter duplicates, ensuring that it's the same length as
104120
len().
105121
"""
106-
for n, _ in self._items:
122+
for n, _ in self:
107123
yield n
108124

109125
def items(self):
110126
"""
111127
This mapping iterates like the list of tuples it is.
112128
"""
113-
for item in self:
114-
yield item
129+
return self.__iter__()
115130

116131
def values(self):
117132
"""
118133
This is an almost nonsensical query on a header dictionary, but we
119134
satisfy it in the exact same way we satisfy 'keys'.
120135
"""
121-
for _, v in self._items:
136+
for _, v in self:
122137
yield v
123138

124139
def get(self, name, default=None):

test/test_headers.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,26 @@ def test_equality(self):
128128
h1['k2'] = 'v3'
129129
h1['k1'] = 'v4'
130130

131+
h2 = HTTPHeaderMap()
132+
h2['k1'] = 'v1, v2'
133+
h2['k2'] = 'v3'
134+
h2['k1'] = 'v4'
135+
136+
assert h1 == h2
137+
138+
def test_inequality_of_raw_ordering(self):
139+
h1 = HTTPHeaderMap()
140+
h1['k1'] = 'v1, v2'
141+
h1['k2'] = 'v3'
142+
h1['k1'] = 'v4'
143+
131144
h2 = HTTPHeaderMap()
132145
h2['k1'] = 'v1'
133146
h2['k1'] = 'v2'
134147
h2['k2'] = 'v3'
135148
h2['k1'] = 'v4'
136149

137-
assert h1 == h2
150+
assert h1 != h2
138151

139152
def test_inequality(self):
140153
h1 = HTTPHeaderMap()
@@ -182,6 +195,11 @@ def test_create_from_iterables_and_kwargs(self):
182195
('k2', 'v2'),
183196
('k2', 'v3'),
184197
]
185-
h = HTTPHeaderMap(items, k3='v4', k4='v5')
186-
187-
assert list(h) == items + [('k3', 'v4'), ('k4', 'v5')]
198+
h = list(HTTPHeaderMap(items, k3='v4', k4='v5'))
199+
200+
# kwargs are an unordered dictionary, so allow for both possible
201+
# iteration orders.
202+
assert (
203+
h == items + [('k3', 'v4'), ('k4', 'v5')] or
204+
h == items + [('k4', 'v5'), ('k3', 'v4')]
205+
)

0 commit comments

Comments
 (0)