Repository: urllib3/urllib3

Issue:
urllib3==1.21: TypeError: unhashable type: 'list'
There seems to be a regression from `1.20` to `1.21`.

```
2017-04-28T04:26:10.152544966Z   File "/usr/local/lib/python3.5/site-packages/steembase/http_client.py", line 146, in exec
2017-04-28T04:26:10.152552486Z     response = self.request(body=body)
2017-04-28T04:26:10.152560726Z   File "/usr/local/lib/python3.5/site-packages/urllib3/poolmanager.py", line 303, in urlopen
2017-04-28T04:26:10.152585508Z     conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme)
2017-04-28T04:26:10.152592399Z   File "/usr/local/lib/python3.5/site-packages/urllib3/poolmanager.py", line 219, in connection_from_host
2017-04-28T04:26:10.152599135Z     return self.connection_from_context(request_context)
2017-04-28T04:26:10.152604508Z   File "/usr/local/lib/python3.5/site-packages/urllib3/poolmanager.py", line 232, in connection_from_context
2017-04-28T04:26:10.152610299Z     return self.connection_from_pool_key(pool_key, request_context=request_context)
2017-04-28T04:26:10.152615790Z   File "/usr/local/lib/python3.5/site-packages/urllib3/poolmanager.py", line 245, in connection_from_pool_key
2017-04-28T04:26:10.152621674Z     pool = self.pools.get(pool_key)
2017-04-28T04:26:10.152626932Z   File "/usr/local/lib/python3.5/_collections_abc.py", line 597, in get
2017-04-28T04:26:10.152632427Z     return self[key]
2017-04-28T04:26:10.152637611Z   File "/usr/local/lib/python3.5/site-packages/urllib3/_collections.py", line 53, in __getitem__
2017-04-28T04:26:10.152643188Z     item = self._container.pop(key)
2017-04-28T04:26:10.152648363Z TypeError: unhashable type: 'list'
```

Code:
https://github.com/Netherdrake/steem-python/blob/master/steembase/http_client.py


Discussion:
Thanks for the report. Could you provide a minimal reproducible example? You link to code but there's no mention of how you're using the code to cause this error.
Any attempt to do a http request will do.

A simple example:
```
pip install -U steem
```

```
from steem import Steem
s = Steem()
s.last_irreversible_block_num
```

Steem in this case wraps `http_client.py` (code link above). 

My speculation is that something changed in PR 1016, which breaks backwards compatibility.
Looks like it's `socket_options` that needs to be converted into a tuple to be used as a part of a `PoolKey` because lists aren't hash-able? It's quite late, but I do believe this is a regression. Nice catch, I will comment again when I've had a chance to sleep. (1 AM currently)
Yeah that's it, here's a minimal verifiable example for maintainers that read this tomorrow:

```python
from urllib3.poolmanager import PoolManager
p = PoolManager(socket_options=[])
p.urlopen('GET', 'https://www.google.com')
```
cc: #1016 @jeremycline

Goodnight :sleeping: 
Yeah, that looks like a correct analysis. We definitely regressed that. Looks like we have a 1.21.1 release in our future folks!

Resolving patch:
diff --git a/urllib3/poolmanager.py b/urllib3/poolmanager.py
--- a/urllib3/poolmanager.py
+++ b/urllib3/poolmanager.py
@@ -82,6 +82,12 @@ def _default_key_normalizer(key_class, request_context):
         if key in context and context[key] is not None:
             context[key] = frozenset(context[key].items())
 
+    # The socket_options key may be a list and needs to be transformed into a
+    # tuple.
+    socket_opts = context.get('socket_options')
+    if socket_opts is not None:
+        context['socket_options'] = tuple(socket_opts)
+
     # Map the kwargs to the names in the namedtuple - this is necessary since
     # namedtuples can't have fields starting with '_'.
     for key in list(context.keys()):


Test patch:
diff --git a/test/test_poolmanager.py b/test/test_poolmanager.py
--- a/test/test_poolmanager.py
+++ b/test/test_poolmanager.py
@@ -1,3 +1,4 @@
+import socket
 import sys
 
 from urllib3.poolmanager import (
@@ -340,6 +341,25 @@ def test_override_pool_kwargs_host(self):
         self.assertEqual(100, override_pool.retries)
         self.assertTrue(override_pool.block)
 
+    def test_pool_kwargs_socket_options(self):
+        """Assert passing socket options works with connection_from_host"""
+        p = PoolManager(socket_options=[])
+        override_opts = [
+            (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1),
+            (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+        ]
+        pool_kwargs = {'socket_options': override_opts}
+
+        default_pool = p.connection_from_host('example.com', scheme='http')
+        override_pool = p.connection_from_host(
+            'example.com', scheme='http', pool_kwargs=pool_kwargs
+        )
+
+        self.assertEqual(default_pool.conn_kw['socket_options'], [])
+        self.assertEqual(
+            override_pool.conn_kw['socket_options'], override_opts
+        )
+
     def test_merge_pool_kwargs(self):
         """Assert _merge_pool_kwargs works in the happy case"""
         p = PoolManager(strict=True)