We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Errors while validating arguments in headers result in a flask crash
If you make a view with header arguments `@bp.arguments(someschema, location='headers')`
Then feed it headers that are not defined in the schema, it will (rightfully) cause a schema validation error, however the error created includes the entire header tuple as a dictionary key, instead of just the 'key' (tuple position 0). This causes flask to error out while trying to convert the response to a valid JSON response.

This is the response returned (grabbed this with a pydb)
```python
{
    "code": 422,
    "status": "Unprocessable Entity",
    "errors": {"headers": {("Someheader", "someval"): ["Unknown field."]}},
}
```
This is the stack trace flask produces, which I have included so people searching it will hopefully find their way here.

```python
../../../../miniconda3/lib/python3.7/site-packages/werkzeug/test.py:1006: in get
    return self.open(*args, **kw)
nomitall/api/tests/_client.py:37: in open
    return super().open(*args, **kwargs)
../../../../miniconda3/lib/python3.7/site-packages/flask/testing.py:227: in open
    follow_redirects=follow_redirects,
../../../../miniconda3/lib/python3.7/site-packages/werkzeug/test.py:970: in open
    response = self.run_wsgi_app(environ.copy(), buffered=buffered)
../../../../miniconda3/lib/python3.7/site-packages/werkzeug/test.py:861: in run_wsgi_app
    rv = run_wsgi_app(self.application, environ, buffered=buffered)
../../../../miniconda3/lib/python3.7/site-packages/werkzeug/test.py:1096: in run_wsgi_app
    app_rv = app(environ, start_response)
../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:2463: in __call__
    return self.wsgi_app(environ, start_response)
../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:2449: in wsgi_app
    response = self.handle_exception(e)
../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:1866: in handle_exception
    reraise(exc_type, exc_value, tb)
../../../../miniconda3/lib/python3.7/site-packages/flask/_compat.py:39: in reraise
    raise value
../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:2446: in wsgi_app
    response = self.full_dispatch_request()
../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:1952: in full_dispatch_request
    return self.finalize_request(rv)
../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:1967: in finalize_request
    response = self.make_response(rv)
../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:2111: in make_response
    rv = jsonify(rv)
../../../../miniconda3/lib/python3.7/site-packages/flask/json/__init__.py:370: in jsonify
    dumps(data, indent=indent, separators=separators) + "\n",
../../../../miniconda3/lib/python3.7/site-packages/flask/json/__init__.py:211: in dumps
    rv = _json.dumps(obj, **kwargs)
../../../../miniconda3/lib/python3.7/site-packages/simplejson/__init__.py:412: in dumps
    **kw).encode(obj)
../../../../miniconda3/lib/python3.7/site-packages/simplejson/encoder.py:298: in encode
    chunks = list(chunks)
../../../../miniconda3/lib/python3.7/site-packages/simplejson/encoder.py:696: in _iterencode
    for chunk in _iterencode_dict(o, _current_indent_level):
../../../../miniconda3/lib/python3.7/site-packages/simplejson/encoder.py:652: in _iterencode_dict
    for chunk in chunks:
../../../../miniconda3/lib/python3.7/site-packages/simplejson/encoder.py:652: in _iterencode_dict
    for chunk in chunks:
../../../../miniconda3/lib/python3.7/site-packages/simplejson/encoder.py:598: in _iterencode_dict
    k = _stringify_key(k)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

key = ("Someheader", "someval")

    def _stringify_key(key):
        if isinstance(key, string_types): # pragma: no cover
            pass
        elif _PY3 and isinstance(key, bytes) and _encoding is not None:
            key = str(key, _encoding)
        elif isinstance(key, float):
            key = _floatstr(key)
        elif key is True:
            key = 'true'
        elif key is False:
            key = 'false'
        elif key is None:
            key = 'null'
        elif isinstance(key, integer_types):
            if type(key) not in integer_types:
                # See marshmallow-code/flask-smorest#118, do not trust custom str/repr
                key = int(key)
            key = str(key)
        elif _use_decimal and isinstance(key, Decimal):
            key = str(key)
        elif _skipkeys:
            key = None
        else:
            raise TypeError('keys must be str, int, float, bool or None, '
>                           'not %s' % key.__class__.__name__)
E           TypeError: keys must be str, int, float, bool or None, not tuple

../../../../miniconda3/lib/python3.7/site-packages/simplejson/encoder.py:568: TypeError
```

I'm not sure there is a good workaround apart from disabling schema validation.

If this issue is unclear please ask for further explanation and i'll sink some time into creating some self contained reproduction code.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/webargs/multidictproxy.py`
Content:
```
1 from collections.abc import Mapping
2 
3 from webargs.compat import MARSHMALLOW_VERSION_INFO
4 from webargs.core import missing, is_multiple
5 
6 
7 class MultiDictProxy(Mapping):
8     """
9     A proxy object which wraps multidict types along with a matching schema
10     Whenever a value is looked up, it is checked against the schema to see if
11     there is a matching field where `is_multiple` is True. If there is, then
12     the data should be loaded as a list or tuple.
13 
14     In all other cases, __getitem__ proxies directly to the input multidict.
15     """
16 
17     def __init__(self, multidict, schema):
18         self.data = multidict
19         self.multiple_keys = self._collect_multiple_keys(schema)
20 
21     @staticmethod
22     def _collect_multiple_keys(schema):
23         result = set()
24         for name, field in schema.fields.items():
25             if not is_multiple(field):
26                 continue
27             if MARSHMALLOW_VERSION_INFO[0] < 3:
28                 result.add(field.load_from if field.load_from is not None else name)
29             else:
30                 result.add(field.data_key if field.data_key is not None else name)
31         return result
32 
33     def __getitem__(self, key):
34         val = self.data.get(key, missing)
35         if val is missing or key not in self.multiple_keys:
36             return val
37         if hasattr(self.data, "getlist"):
38             return self.data.getlist(key)
39         if hasattr(self.data, "getall"):
40             return self.data.getall(key)
41         if isinstance(val, (list, tuple)):
42             return val
43         if val is None:
44             return None
45         return [val]
46 
47     def __str__(self):  # str(proxy) proxies to str(proxy.data)
48         return str(self.data)
49 
50     def __repr__(self):
51         return "MultiDictProxy(data={!r}, multiple_keys={!r})".format(
52             self.data, self.multiple_keys
53         )
54 
55     def __delitem__(self, key):
56         del self.data[key]
57 
58     def __setitem__(self, key, value):
59         self.data[key] = value
60 
61     def __getattr__(self, name):
62         return getattr(self.data, name)
63 
64     def __iter__(self):
65         return iter(self.data)
66 
67     def __contains__(self, x):
68         return x in self.data
69 
70     def __len__(self):
71         return len(self.data)
72 
73     def __eq__(self, other):
74         return self.data == other
75 
76     def __ne__(self, other):
77         return self.data != other
78
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/webargs/multidictproxy.py b/src/webargs/multidictproxy.py
--- a/src/webargs/multidictproxy.py
+++ b/src/webargs/multidictproxy.py
@@ -62,7 +62,13 @@
         return getattr(self.data, name)
 
     def __iter__(self):
-        return iter(self.data)
+        for x in iter(self.data):
+            # special case for header dicts which produce an iterator of tuples
+            # instead of an iterator of strings
+            if isinstance(x, tuple):
+                yield x[0]
+            else:
+                yield x
 
     def __contains__(self, x):
         return x in self.data

{"golden_diff": "diff --git a/src/webargs/multidictproxy.py b/src/webargs/multidictproxy.py\n--- a/src/webargs/multidictproxy.py\n+++ b/src/webargs/multidictproxy.py\n@@ -62,7 +62,13 @@\n         return getattr(self.data, name)\n \n     def __iter__(self):\n-        return iter(self.data)\n+        for x in iter(self.data):\n+            # special case for header dicts which produce an iterator of tuples\n+            # instead of an iterator of strings\n+            if isinstance(x, tuple):\n+                yield x[0]\n+            else:\n+                yield x\n \n     def __contains__(self, x):\n         return x in self.data\n", "issue": "Errors while validating arguments in headers result in a flask crash\nIf you make a view with header arguments `@bp.arguments(someschema, location='headers')`\r\nThen feed it headers that are not defined in the schema, it will (rightfully) cause a schema validation error, however the error created includes the entire header tuple as a dictionary key, instead of just the 'key' (tuple position 0). This causes flask to error out while trying to convert the response to a valid JSON response.\r\n\r\nThis is the response returned (grabbed this with a pydb)\r\n```python\r\n{\r\n    \"code\": 422,\r\n    \"status\": \"Unprocessable Entity\",\r\n    \"errors\": {\"headers\": {(\"Someheader\", \"someval\"): [\"Unknown field.\"]}},\r\n}\r\n```\r\nThis is the stack trace flask produces, which I have included so people searching it will hopefully find their way here.\r\n\r\n```python\r\n../../../../miniconda3/lib/python3.7/site-packages/werkzeug/test.py:1006: in get\r\n    return self.open(*args, **kw)\r\nnomitall/api/tests/_client.py:37: in open\r\n    return super().open(*args, **kwargs)\r\n../../../../miniconda3/lib/python3.7/site-packages/flask/testing.py:227: in open\r\n    follow_redirects=follow_redirects,\r\n../../../../miniconda3/lib/python3.7/site-packages/werkzeug/test.py:970: in open\r\n    response = self.run_wsgi_app(environ.copy(), buffered=buffered)\r\n../../../../miniconda3/lib/python3.7/site-packages/werkzeug/test.py:861: in run_wsgi_app\r\n    rv = run_wsgi_app(self.application, environ, buffered=buffered)\r\n../../../../miniconda3/lib/python3.7/site-packages/werkzeug/test.py:1096: in run_wsgi_app\r\n    app_rv = app(environ, start_response)\r\n../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:2463: in __call__\r\n    return self.wsgi_app(environ, start_response)\r\n../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:2449: in wsgi_app\r\n    response = self.handle_exception(e)\r\n../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:1866: in handle_exception\r\n    reraise(exc_type, exc_value, tb)\r\n../../../../miniconda3/lib/python3.7/site-packages/flask/_compat.py:39: in reraise\r\n    raise value\r\n../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:2446: in wsgi_app\r\n    response = self.full_dispatch_request()\r\n../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:1952: in full_dispatch_request\r\n    return self.finalize_request(rv)\r\n../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:1967: in finalize_request\r\n    response = self.make_response(rv)\r\n../../../../miniconda3/lib/python3.7/site-packages/flask/app.py:2111: in make_response\r\n    rv = jsonify(rv)\r\n../../../../miniconda3/lib/python3.7/site-packages/flask/json/__init__.py:370: in jsonify\r\n    dumps(data, indent=indent, separators=separators) + \"\\n\",\r\n../../../../miniconda3/lib/python3.7/site-packages/flask/json/__init__.py:211: in dumps\r\n    rv = _json.dumps(obj, **kwargs)\r\n../../../../miniconda3/lib/python3.7/site-packages/simplejson/__init__.py:412: in dumps\r\n    **kw).encode(obj)\r\n../../../../miniconda3/lib/python3.7/site-packages/simplejson/encoder.py:298: in encode\r\n    chunks = list(chunks)\r\n../../../../miniconda3/lib/python3.7/site-packages/simplejson/encoder.py:696: in _iterencode\r\n    for chunk in _iterencode_dict(o, _current_indent_level):\r\n../../../../miniconda3/lib/python3.7/site-packages/simplejson/encoder.py:652: in _iterencode_dict\r\n    for chunk in chunks:\r\n../../../../miniconda3/lib/python3.7/site-packages/simplejson/encoder.py:652: in _iterencode_dict\r\n    for chunk in chunks:\r\n../../../../miniconda3/lib/python3.7/site-packages/simplejson/encoder.py:598: in _iterencode_dict\r\n    k = _stringify_key(k)\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nkey = (\"Someheader\", \"someval\")\r\n\r\n    def _stringify_key(key):\r\n        if isinstance(key, string_types): # pragma: no cover\r\n            pass\r\n        elif _PY3 and isinstance(key, bytes) and _encoding is not None:\r\n            key = str(key, _encoding)\r\n        elif isinstance(key, float):\r\n            key = _floatstr(key)\r\n        elif key is True:\r\n            key = 'true'\r\n        elif key is False:\r\n            key = 'false'\r\n        elif key is None:\r\n            key = 'null'\r\n        elif isinstance(key, integer_types):\r\n            if type(key) not in integer_types:\r\n                # See marshmallow-code/flask-smorest#118, do not trust custom str/repr\r\n                key = int(key)\r\n            key = str(key)\r\n        elif _use_decimal and isinstance(key, Decimal):\r\n            key = str(key)\r\n        elif _skipkeys:\r\n            key = None\r\n        else:\r\n            raise TypeError('keys must be str, int, float, bool or None, '\r\n>                           'not %s' % key.__class__.__name__)\r\nE           TypeError: keys must be str, int, float, bool or None, not tuple\r\n\r\n../../../../miniconda3/lib/python3.7/site-packages/simplejson/encoder.py:568: TypeError\r\n```\r\n\r\nI'm not sure there is a good workaround apart from disabling schema validation.\r\n\r\nIf this issue is unclear please ask for further explanation and i'll sink some time into creating some self contained reproduction code.\n", "before_files": [{"content": "from collections.abc import Mapping\n\nfrom webargs.compat import MARSHMALLOW_VERSION_INFO\nfrom webargs.core import missing, is_multiple\n\n\nclass MultiDictProxy(Mapping):\n    \"\"\"\n    A proxy object which wraps multidict types along with a matching schema\n    Whenever a value is looked up, it is checked against the schema to see if\n    there is a matching field where `is_multiple` is True. If there is, then\n    the data should be loaded as a list or tuple.\n\n    In all other cases, __getitem__ proxies directly to the input multidict.\n    \"\"\"\n\n    def __init__(self, multidict, schema):\n        self.data = multidict\n        self.multiple_keys = self._collect_multiple_keys(schema)\n\n    @staticmethod\n    def _collect_multiple_keys(schema):\n        result = set()\n        for name, field in schema.fields.items():\n            if not is_multiple(field):\n                continue\n            if MARSHMALLOW_VERSION_INFO[0] < 3:\n                result.add(field.load_from if field.load_from is not None else name)\n            else:\n                result.add(field.data_key if field.data_key is not None else name)\n        return result\n\n    def __getitem__(self, key):\n        val = self.data.get(key, missing)\n        if val is missing or key not in self.multiple_keys:\n            return val\n        if hasattr(self.data, \"getlist\"):\n            return self.data.getlist(key)\n        if hasattr(self.data, \"getall\"):\n            return self.data.getall(key)\n        if isinstance(val, (list, tuple)):\n            return val\n        if val is None:\n            return None\n        return [val]\n\n    def __str__(self):  # str(proxy) proxies to str(proxy.data)\n        return str(self.data)\n\n    def __repr__(self):\n        return \"MultiDictProxy(data={!r}, multiple_keys={!r})\".format(\n            self.data, self.multiple_keys\n        )\n\n    def __delitem__(self, key):\n        del self.data[key]\n\n    def __setitem__(self, key, value):\n        self.data[key] = value\n\n    def __getattr__(self, name):\n        return getattr(self.data, name)\n\n    def __iter__(self):\n        return iter(self.data)\n\n    def __contains__(self, x):\n        return x in self.data\n\n    def __len__(self):\n        return len(self.data)\n\n    def __eq__(self, other):\n        return self.data == other\n\n    def __ne__(self, other):\n        return self.data != other\n", "path": "src/webargs/multidictproxy.py"}], "after_files": [{"content": "from collections.abc import Mapping\n\nfrom webargs.compat import MARSHMALLOW_VERSION_INFO\nfrom webargs.core import missing, is_multiple\n\n\nclass MultiDictProxy(Mapping):\n    \"\"\"\n    A proxy object which wraps multidict types along with a matching schema\n    Whenever a value is looked up, it is checked against the schema to see if\n    there is a matching field where `is_multiple` is True. If there is, then\n    the data should be loaded as a list or tuple.\n\n    In all other cases, __getitem__ proxies directly to the input multidict.\n    \"\"\"\n\n    def __init__(self, multidict, schema):\n        self.data = multidict\n        self.multiple_keys = self._collect_multiple_keys(schema)\n\n    @staticmethod\n    def _collect_multiple_keys(schema):\n        result = set()\n        for name, field in schema.fields.items():\n            if not is_multiple(field):\n                continue\n            if MARSHMALLOW_VERSION_INFO[0] < 3:\n                result.add(field.load_from if field.load_from is not None else name)\n            else:\n                result.add(field.data_key if field.data_key is not None else name)\n        return result\n\n    def __getitem__(self, key):\n        val = self.data.get(key, missing)\n        if val is missing or key not in self.multiple_keys:\n            return val\n        if hasattr(self.data, \"getlist\"):\n            return self.data.getlist(key)\n        if hasattr(self.data, \"getall\"):\n            return self.data.getall(key)\n        if isinstance(val, (list, tuple)):\n            return val\n        if val is None:\n            return None\n        return [val]\n\n    def __str__(self):  # str(proxy) proxies to str(proxy.data)\n        return str(self.data)\n\n    def __repr__(self):\n        return \"MultiDictProxy(data={!r}, multiple_keys={!r})\".format(\n            self.data, self.multiple_keys\n        )\n\n    def __delitem__(self, key):\n        del self.data[key]\n\n    def __setitem__(self, key, value):\n        self.data[key] = value\n\n    def __getattr__(self, name):\n        return getattr(self.data, name)\n\n    def __iter__(self):\n        for x in iter(self.data):\n            # special case for header dicts which produce an iterator of tuples\n            # instead of an iterator of strings\n            if isinstance(x, tuple):\n                yield x[0]\n            else:\n                yield x\n\n    def __contains__(self, x):\n        return x in self.data\n\n    def __len__(self):\n        return len(self.data)\n\n    def __eq__(self, other):\n        return self.data == other\n\n    def __ne__(self, other):\n        return self.data != other\n", "path": "src/webargs/multidictproxy.py"}]}