Repository: encode/httpx

Issue:
Support header comparisons with dict / list.
We should support direct header comparisons with dicts, or with lists of two-tuples.
This would be nice because it would help folks clean up test cases, eg...

```python
response = httpx.get(...)
assert response.status_code == 200
assert response.text == "Hello, world!"
assert response.headers == {"Content-Length": "13"}
```

The fix will be something like changing `Headers.__eq__` from this...

```python
def __eq__(self, other: typing.Any) -> bool:
    if not isinstance(other, Headers):
        return False
    return sorted(self._list) == sorted(other._list)
```

To this...

```python
def __eq__(self, other: typing.Any) -> bool:
    try:
        other_headers = Headers(other)
    except ValueError:
        return False
    return sorted(self._list) == sorted(other_headers._list)
```

A pull request resolving this should:

* Make the fix in `Headers.__eq__`.
* Update all our tests to use the simpler comparison style wherever possible.

This would be a nice contributor friendly issue, so if anyone's keen then feel free to jump in.


Discussion:

Resolving patch:
diff --git a/httpx/_models.py b/httpx/_models.py
--- a/httpx/_models.py
+++ b/httpx/_models.py
@@ -724,9 +724,11 @@ def __len__(self) -> int:
         return len(self._list)
 
     def __eq__(self, other: typing.Any) -> bool:
-        if not isinstance(other, Headers):
+        try:
+            other_headers = Headers(other)
+        except ValueError:
             return False
-        return sorted(self._list) == sorted(other._list)
+        return sorted(self._list) == sorted(other_headers._list)
 
     def __repr__(self) -> str:
         class_name = self.__class__.__name__


Test patch:
diff --git a/tests/models/test_headers.py b/tests/models/test_headers.py
--- a/tests/models/test_headers.py
+++ b/tests/models/test_headers.py
@@ -22,8 +22,10 @@ def test_headers():
     assert list(h) == ["a", "b"]
     assert dict(h) == {"a": "123, 456", "b": "789"}
     assert repr(h) == "Headers([('a', '123'), ('a', '456'), ('b', '789')])"
-    assert h == httpx.Headers([("a", "123"), ("b", "789"), ("a", "456")])
-    assert h != [("a", "123"), ("A", "456"), ("b", "789")]
+    assert h == [("a", "123"), ("b", "789"), ("a", "456")]
+    assert h == [("a", "123"), ("A", "456"), ("b", "789")]
+    assert h == {"a": "123", "A": "456", "b": "789"}
+    assert h != "a: 123\nA: 456\nb: 789"
 
     h = httpx.Headers({"a": "123", "b": "789"})
     assert h["A"] == "123"
diff --git a/tests/models/test_requests.py b/tests/models/test_requests.py
--- a/tests/models/test_requests.py
+++ b/tests/models/test_requests.py
@@ -26,9 +26,7 @@ def __iter__(self):
             yield b"test 123"  # pragma: nocover
 
     request = httpx.Request("POST", "http://example.org", content=Content())
-    assert request.headers == httpx.Headers(
-        {"Host": "example.org", "Transfer-Encoding": "chunked"}
-    )
+    assert request.headers == {"Host": "example.org", "Transfer-Encoding": "chunked"}
 
 
 def test_generator_with_transfer_encoding_header():
@@ -36,9 +34,7 @@ def content():
         yield b"test 123"  # pragma: nocover
 
     request = httpx.Request("POST", "http://example.org", content=content())
-    assert request.headers == httpx.Headers(
-        {"Host": "example.org", "Transfer-Encoding": "chunked"}
-    )
+    assert request.headers == {"Host": "example.org", "Transfer-Encoding": "chunked"}
 
 
 def test_generator_with_content_length_header():
@@ -49,9 +45,7 @@ def content():
     request = httpx.Request(
         "POST", "http://example.org", content=content(), headers=headers
     )
-    assert request.headers == httpx.Headers(
-        {"Host": "example.org", "Content-Length": "8"}
-    )
+    assert request.headers == {"Host": "example.org", "Content-Length": "8"}
 
 
 def test_url_encoded_data():
@@ -73,13 +67,11 @@ def test_json_encoded_data():
 def test_headers():
     request = httpx.Request("POST", "http://example.org", json={"test": 123})
 
-    assert request.headers == httpx.Headers(
-        {
-            "Host": "example.org",
-            "Content-Type": "application/json",
-            "Content-Length": "13",
-        }
-    )
+    assert request.headers == {
+        "Host": "example.org",
+        "Content-Type": "application/json",
+        "Content-Length": "13",
+    }
 
 
 def test_read_and_stream_data():
diff --git a/tests/models/test_responses.py b/tests/models/test_responses.py
--- a/tests/models/test_responses.py
+++ b/tests/models/test_responses.py
@@ -44,11 +44,7 @@ def test_response_content():
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
     assert response.text == "Hello, world!"
-    assert response.headers == httpx.Headers(
-        {
-            "Content-Length": "13",
-        }
-    )
+    assert response.headers == {"Content-Length": "13"}
 
 
 def test_response_text():
@@ -57,12 +53,10 @@ def test_response_text():
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
     assert response.text == "Hello, world!"
-    assert response.headers == httpx.Headers(
-        {
-            "Content-Length": "13",
-            "Content-Type": "text/plain; charset=utf-8",
-        }
-    )
+    assert response.headers == {
+        "Content-Length": "13",
+        "Content-Type": "text/plain; charset=utf-8",
+    }
 
 
 def test_response_html():
@@ -71,12 +65,10 @@ def test_response_html():
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
     assert response.text == "<html><body>Hello, world!</html></body>"
-    assert response.headers == httpx.Headers(
-        {
-            "Content-Length": "39",
-            "Content-Type": "text/html; charset=utf-8",
-        }
-    )
+    assert response.headers == {
+        "Content-Length": "39",
+        "Content-Type": "text/html; charset=utf-8",
+    }
 
 
 def test_response_json():
@@ -85,12 +77,10 @@ def test_response_json():
     assert response.status_code == 200
     assert response.reason_phrase == "OK"
     assert response.json() == {"hello": "world"}
-    assert response.headers == httpx.Headers(
-        {
-            "Content-Length": "18",
-            "Content-Type": "application/json",
-        }
-    )
+    assert response.headers == {
+        "Content-Length": "18",
+        "Content-Type": "application/json",
+    }
 
 
 def test_raise_for_status():
@@ -742,7 +732,7 @@ def content():
         yield b"test 123"  # pragma: nocover
 
     response = httpx.Response(200, content=content())
-    assert response.headers == httpx.Headers({"Transfer-Encoding": "chunked"})
+    assert response.headers == {"Transfer-Encoding": "chunked"}
 
 
 def test_generator_with_content_length_header():
@@ -751,4 +741,4 @@ def content():
 
     headers = {"Content-Length": "8"}
     response = httpx.Response(200, content=content(), headers=headers)
-    assert response.headers == httpx.Headers({"Content-Length": "8"})
+    assert response.headers == {"Content-Length": "8"}