From 5e295e060388d216efae4807ceee86660cbc4588 Mon Sep 17 00:00:00 2001 From: Kevin McCarthy Date: Sat, 3 May 2014 17:32:49 -1000 Subject: [PATCH] Serialize dict of lists, use dicts internally There is a weird quirk in HTTP. You can send the same header twice. For this reason, headers are represented by a dict, with lists as the values. However, it appears that HTTPlib is completely incapable of sending the same header twice. This puts me in a weird position: I want to be able to accurately represent HTTP headers in cassettes, but I don't want the extra step of always having to do [0] in the general case, i.e. request.headers['key'][0] In addition, some servers sometimes send the same header more than once, and httplib *can* deal with this situation. Futhermore, I wanted to keep the request and response cassette format as similar as possible. For this reason, in cassettes I keep a dict with lists as keys, but once deserialized into VCR, I keep them as plain, naked dicts. --- tests/unit/test_filters.py | 4 ++-- tests/unit/test_request.py | 10 ++-------- vcr/request.py | 32 ++++++++++++++++++++++++++------ vcr/stubs/__init__.py | 2 +- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/tests/unit/test_filters.py b/tests/unit/test_filters.py index f274279..ae1f7a4 100644 --- a/tests/unit/test_filters.py +++ b/tests/unit/test_filters.py @@ -6,11 +6,11 @@ def test_remove_headers(): headers = {'hello': ['goodbye'], 'secret': ['header']} request = Request('GET', 'http://google.com', '', headers) _remove_headers(request, ['secret']) - assert request.headers == {'hello': ['goodbye']} + assert request.headers == {'hello': 'goodbye'} def test_remove_headers_empty(): - headers = {'hello': ['goodbye'], 'secret': ['header']} + headers = {'hello': 'goodbye', 'secret': 'header'} request = Request('GET', 'http://google.com', '', headers) _remove_headers(request, []) assert request.headers == headers diff --git a/tests/unit/test_request.py b/tests/unit/test_request.py index ae0685e..28b695d 100644 --- a/tests/unit/test_request.py +++ b/tests/unit/test_request.py @@ -11,16 +11,10 @@ def test_str(): def test_headers(): headers = {'X-Header1': ['h1'], 'X-Header2': 'h2'} req = Request('GET', 'http://go.com/', '', headers) - assert req.headers == {'X-Header1': ['h1'], 'X-Header2': ['h2']} + assert req.headers == {'X-Header1': 'h1', 'X-Header2': 'h2'} req.add_header('X-Header1', 'h11') - assert req.headers == {'X-Header1': ['h1', 'h11'], 'X-Header2': ['h2']} - - -def test_flat_headers_dict(): - headers = {'X-Header1': ['h1', 'h11'], 'X-Header2': ['h2']} - req = Request('GET', 'http://go.com/', '', headers) - assert req.flat_headers_dict() == {'X-Header1': 'h1', 'X-Header2': 'h2'} + assert req.headers == {'X-Header1': 'h11', 'X-Header2': 'h2'} @pytest.mark.parametrize("uri, expected_port", [ diff --git a/vcr/request.py b/vcr/request.py index 417c40c..30c052d 100644 --- a/vcr/request.py +++ b/vcr/request.py @@ -2,6 +2,26 @@ from six.moves.urllib.parse import urlparse, parse_qsl class Request(object): + """ + VCR's representation of a request. + + There is a weird quirk in HTTP. You can send the same header twice. For + this reason, headers are represented by a dict, with lists as the values. + However, it appears that HTTPlib is completely incapable of sending the + same header twice. This puts me in a weird position: I want to be able to + accurately represent HTTP headers in cassettes, but I don't want the extra + step of always having to do [0] in the general case, i.e. + request.headers['key'][0] + + In addition, some servers sometimes send the same header more than once, + and httplib *can* deal with this situation. + + Futhermore, I wanted to keep the request and response cassette format as + similar as possible. + + For this reason, in cassettes I keep a dict with lists as keys, but once + deserialized into VCR, I keep them as plain, naked dicts. + """ def __init__(self, method, uri, body, headers): self.method = method @@ -12,11 +32,11 @@ class Request(object): self.add_header(key, headers[key]) def add_header(self, key, value): - value = list(value) if isinstance(value, (tuple, list)) else [value] - self.headers.setdefault(key, []).extend(value) - - def flat_headers_dict(self): - return dict((key, self.headers[key][0]) for key in self.headers) + # see class docstring for an explanation + if isinstance(value, (tuple, list)): + self.headers[key] = value[0] + else: + self.headers[key] = value @property def scheme(self): @@ -64,7 +84,7 @@ class Request(object): 'method': self.method, 'uri': self.uri, 'body': self.body, - 'headers': self.headers, + 'headers': dict(((k, [v]) for k, v in self.headers.items())), } @classmethod diff --git a/vcr/stubs/__init__.py b/vcr/stubs/__init__.py index 2218786..2d88da2 100644 --- a/vcr/stubs/__init__.py +++ b/vcr/stubs/__init__.py @@ -234,7 +234,7 @@ class VCRConnection: method=self._vcr_request.method, url=self._url(self._vcr_request.uri), body=self._vcr_request.body, - headers=self._vcr_request.flat_headers_dict(), + headers=self._vcr_request.headers, ) # get the response