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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy