Repository: encode/httpx

Issue:
Deletion of non-existent keys from `Headers` should raise KeyError.
Prompted by  #310

To replicate:

```python
headers = httpx.Headers()
del headers['DOES_NOT_EXIST']  # Should raise a KeyError, but doesn't
```

To resolve this issue we'll need to:

* Ensure that deletion of non-existant keys raises a KeyError.
* Ensure that we have a `.pop(key, default)` that we can use instead for removal without KeyErrors.
* Replace instances of `del headers[key]` in our codebase with `headers.pop(key, None)` where appropriate.



Discussion:
I would like to work on this.
Sounds good to me! :) Thanks!

You can start working on any issues without me assigning you, a comment in the issue should be enough to signal to others that you're working on it.

Resolving patch:
diff --git a/httpx/dispatch/http2.py b/httpx/dispatch/http2.py
--- a/httpx/dispatch/http2.py
+++ b/httpx/dispatch/http2.py
@@ -4,7 +4,7 @@
 import h2.connection
 import h2.events
 
-from ..concurrency.base import BaseStream, ConcurrencyBackend, TimeoutFlag, BaseEvent
+from ..concurrency.base import BaseEvent, BaseStream, ConcurrencyBackend, TimeoutFlag
 from ..config import TimeoutConfig, TimeoutTypes
 from ..models import AsyncRequest, AsyncResponse
 from ..utils import get_logger
diff --git a/httpx/middleware.py b/httpx/middleware.py
--- a/httpx/middleware.py
+++ b/httpx/middleware.py
@@ -147,15 +147,14 @@ def redirect_headers(self, request: AsyncRequest, url: URL, method: str) -> Head
         if url.origin != request.url.origin:
             # Strip Authorization headers when responses are redirected away from
             # the origin.
-            del headers["Authorization"]
+            headers.pop("Authorization", None)
             headers["Host"] = url.authority
 
         if method != request.method and method == "GET":
             # If we've switch to a 'GET' request, then strip any headers which
             # are only relevant to the request body.
-            del headers["Content-Length"]
-            del headers["Transfer-Encoding"]
-
+            headers.pop("Content-Length", None)
+            headers.pop("Transfer-Encoding", None)
         return headers
 
     def redirect_content(self, request: AsyncRequest, method: str) -> bytes:
diff --git a/httpx/models.py b/httpx/models.py
--- a/httpx/models.py
+++ b/httpx/models.py
@@ -473,6 +473,8 @@ def __delitem__(self, key: str) -> None:
         for idx, (item_key, _) in enumerate(self._list):
             if item_key == del_key:
                 pop_indexes.append(idx)
+        if not pop_indexes:
+            raise KeyError(key)
 
         for idx in reversed(pop_indexes):
             del self._list[idx]
@@ -624,7 +626,6 @@ def __init__(
             assert hasattr(data, "__aiter__")
             self.is_streaming = True
             self.content_aiter = data
-
         self.prepare()
 
     async def read(self) -> bytes:


Test patch:
diff --git a/tests/client/test_headers.py b/tests/client/test_headers.py
--- a/tests/client/test_headers.py
+++ b/tests/client/test_headers.py
@@ -2,6 +2,8 @@
 
 import json
 
+import pytest
+
 from httpx import (
     AsyncDispatcher,
     AsyncRequest,
@@ -11,6 +13,7 @@
     TimeoutTypes,
     VerifyTypes,
     __version__,
+    models,
 )
 
 
@@ -122,3 +125,9 @@ def test_header_update():
             "user-agent": "python-myclient/0.2.1",
         }
     }
+
+
+def test_header_does_not_exist():
+    headers = models.Headers({"foo": "bar"})
+    with pytest.raises(KeyError):
+        del headers["baz"]