Skip to content

Commit b0573d2

Browse files
Addressed bug where recording requests does not save headers to file. Closes #715 (#720)
1 parent 22fc9e8 commit b0573d2

File tree

3 files changed

+53
-7
lines changed

3 files changed

+53
-7
lines changed

responses/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,11 @@ def add(
814814

815815
if adding_headers is not None:
816816
kwargs.setdefault("headers", adding_headers)
817-
if "content_type" in kwargs and "headers" in kwargs:
817+
if (
818+
"content_type" in kwargs
819+
and "headers" in kwargs
820+
and kwargs["headers"] is not None
821+
):
818822
header_keys = [header.lower() for header in kwargs["headers"]]
819823
if "content-type" in header_keys:
820824
raise RuntimeError(
@@ -852,6 +856,7 @@ def _add_from_file(self, file_path: "Union[str, bytes, os.PathLike[Any]]") -> No
852856
url=rsp["url"],
853857
body=rsp["body"],
854858
status=rsp["status"],
859+
headers=rsp["headers"] if "headers" in rsp else None,
855860
content_type=rsp["content_type"],
856861
auto_calculate_content_length=rsp["auto_calculate_content_length"],
857862
)

responses/_recorder.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,29 @@ def _remove_nones(d: "Any") -> "Any":
3636
return d
3737

3838

39+
def _remove_default_headers(data: "Any") -> "Any":
40+
"""
41+
It would be too verbose to store these headers in the file generated by the
42+
record functionality.
43+
"""
44+
if isinstance(data, dict):
45+
keys_to_remove = [
46+
"Content-Length",
47+
"Content-Type",
48+
"Date",
49+
"Server",
50+
"Connection",
51+
"Content-Encoding",
52+
]
53+
for i, response in enumerate(data["responses"]):
54+
for key in keys_to_remove:
55+
if key in response["response"]["headers"]:
56+
del data["responses"][i]["response"]["headers"][key]
57+
if not response["response"]["headers"]:
58+
del data["responses"][i]["response"]["headers"]
59+
return data
60+
61+
3962
def _dump(
4063
registered: "List[BaseResponse]",
4164
destination: "Union[BinaryIO, TextIOWrapper]",
@@ -63,7 +86,8 @@ def _dump(
6386
"Cannot dump response object."
6487
"Probably you use custom Response object that is missing required attributes"
6588
) from exc
66-
dumper(_remove_nones(data), destination)
89+
90+
dumper(_remove_default_headers(_remove_nones(data)), destination)
6791

6892

6993
class Recorder(RequestsMock):
@@ -116,11 +140,15 @@ def _on_request(
116140
request.params = self._parse_request_params(request.path_url) # type: ignore[attr-defined]
117141
request.req_kwargs = kwargs # type: ignore[attr-defined]
118142
requests_response = _real_send(adapter, request, **kwargs)
143+
headers_values = {
144+
key: value for key, value in requests_response.headers.items()
145+
}
119146
responses_response = Response(
120147
method=str(request.method),
121148
url=str(requests_response.request.url),
122149
status=requests_response.status_code,
123150
body=requests_response.text,
151+
headers=headers_values,
124152
)
125153
self._registry.add(responses_response)
126154
return requests_response

responses/tests/test_recorder.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def get_data(host, port):
2323
"response": {
2424
"method": "GET",
2525
"url": f"http://{host}:{port}/404",
26+
"headers": {"x": "foo"},
2627
"body": "404 Not Found",
2728
"status": 404,
2829
"content_type": "text/plain",
@@ -33,6 +34,7 @@ def get_data(host, port):
3334
"response": {
3435
"method": "GET",
3536
"url": f"http://{host}:{port}/status/wrong",
37+
"headers": {"x": "foo"},
3638
"body": "Invalid status code",
3739
"status": 400,
3840
"content_type": "text/plain",
@@ -43,6 +45,7 @@ def get_data(host, port):
4345
"response": {
4446
"method": "GET",
4547
"url": f"http://{host}:{port}/500",
48+
"headers": {"x": "foo"},
4649
"body": "500 Internal Server Error",
4750
"status": 500,
4851
"content_type": "text/plain",
@@ -89,7 +92,6 @@ def run():
8992

9093
with open(self.out_file) as file:
9194
data = yaml.safe_load(file)
92-
9395
assert data == get_data(httpserver.host, httpserver.port)
9496

9597
def test_recorder_toml(self, httpserver):
@@ -122,16 +124,27 @@ def run():
122124

123125
def prepare_server(self, httpserver):
124126
httpserver.expect_request("/500").respond_with_data(
125-
"500 Internal Server Error", status=500, content_type="text/plain"
127+
"500 Internal Server Error",
128+
status=500,
129+
content_type="text/plain",
130+
headers={"x": "foo"},
126131
)
127132
httpserver.expect_request("/202").respond_with_data(
128-
"OK", status=202, content_type="text/plain"
133+
"OK",
134+
status=202,
135+
content_type="text/plain",
129136
)
130137
httpserver.expect_request("/404").respond_with_data(
131-
"404 Not Found", status=404, content_type="text/plain"
138+
"404 Not Found",
139+
status=404,
140+
content_type="text/plain",
141+
headers={"x": "foo"},
132142
)
133143
httpserver.expect_request("/status/wrong").respond_with_data(
134-
"Invalid status code", status=400, content_type="text/plain"
144+
"Invalid status code",
145+
status=400,
146+
content_type="text/plain",
147+
headers={"x": "foo"},
135148
)
136149
url500 = httpserver.url_for("/500")
137150
url202 = httpserver.url_for("/202")

0 commit comments

Comments
 (0)