Repository: marshmallow-code/webargs

Issue:
aiohttp parser breaks with more than one argument
Nothing seems to work once I add more than one field. Here's my minimal example.

I'm validating with:
```
programs_actions_args = {
    'type': webargs.fields.Str(required=True),
    'chain': webargs.fields.Integer(required=True),
}
```
and here's what happens when I POST:
```
POST /v1/programs/8137474d19926b0aa8efd4f1d3944131d59269d97a7bd8dab8e79d667eb314df/actions HTTP/1.1
Accept: application/json, */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 34
Content-Type: application/json
Host: localhost:8080
User-Agent: HTTPie/0.9.9

{
    "chain": "3",
    "type": "sampling"
}

HTTP/1.1 422 Unprocessable Entity
Content-Length: 46
Content-Type: application/json
Date: Fri, 05 May 2017 21:20:42 GMT
Server: Python/3.6 aiohttp/2.0.7

{
    "type": [
        "Missing data for required field."
    ]
}
```
Things work fine if there's only one argument.


Discussion:
Things seem to work fine if I just use Marshmallow directly.
Thanks @ariddell for reporting this. I'll try to look at this over the next few days. I'd gladly review and merge a PR fixing this. =)
I wish I knew more about marshmallow and aiohttp internals. I'll probably take a stab at it eventually. Thanks for webargs (and everything else)!
Same here.

After quite some digging, found out that in 
```python
    def parse_json(self, req, name, field):
        """Pull a json value from the request."""
        if not (req.has_body and is_json_request(req)):
            return core.missing
        json_data = self._cache.get('json')
        if json_data is None:
            self._cache['json'] = json_data = yield from req.json()
        return core.get_value(json_data, name, field, allow_many_nested=True)
```

req.has_body is true only on the first invocation, and false for the rest. In my case it's 3 arguments and only the first one gets parsed, in the following two it is false, and the parameter is considered missing.

Resolving patch:
diff --git a/tasks.py b/tasks.py
--- a/tasks.py
+++ b/tasks.py
@@ -27,8 +27,9 @@ def test(ctx, coverage=False, browse=False):
 def flake(ctx):
     """Run flake8 on codebase."""
     cmd = 'flake8 .'
+    excludes = ['.direnv']
     if sys.version_info < (3, 4, 1):
-        excludes = [
+        excludes += [
             os.path.join('tests', 'apps', 'aiohttp_app.py'),
             os.path.join('tests', 'test_aiohttparser.py'),
             os.path.join('webargs', 'async.py'),
@@ -36,7 +37,7 @@ def flake(ctx):
             os.path.join('examples', 'annotations_example.py'),
             'build',
         ]
-        cmd += ' --exclude={0}'.format(','.join(excludes))
+    cmd += ' --exclude={0}'.format(','.join(excludes))
     ctx.run(cmd, echo=True)
 
 @task
diff --git a/webargs/aiohttpparser.py b/webargs/aiohttpparser.py
--- a/webargs/aiohttpparser.py
+++ b/webargs/aiohttpparser.py
@@ -87,10 +87,10 @@ def parse_form(self, req, name, field):
     @asyncio.coroutine
     def parse_json(self, req, name, field):
         """Pull a json value from the request."""
-        if not (req.has_body and is_json_request(req)):
-            return core.missing
         json_data = self._cache.get('json')
         if json_data is None:
+            if not (req.has_body and is_json_request(req)):
+                return core.missing
             self._cache['json'] = json_data = yield from req.json()
         return core.get_value(json_data, name, field, allow_many_nested=True)
 


Test patch:
diff --git a/tests/apps/aiohttp_app.py b/tests/apps/aiohttp_app.py
--- a/tests/apps/aiohttp_app.py
+++ b/tests/apps/aiohttp_app.py
@@ -109,6 +109,16 @@ def echo_nested(request):
     parsed = yield from parser.parse(args, request)
     return json_response(parsed)
 
+@asyncio.coroutine
+def echo_multiple_args(request):
+    args = {
+        'first': fields.Str(),
+        'last': fields.Str(),
+    }
+    parsed = yield from parser.parse(args, request)
+    return json_response(parsed)
+
+
 @asyncio.coroutine
 def echo_nested_many(request):
     args = {
@@ -165,6 +175,7 @@ def create_app():
     add_route(app, ['GET'], '/echo_headers', echo_headers)
     add_route(app, ['GET'], '/echo_cookie', echo_cookie)
     add_route(app, ['POST'], '/echo_nested', echo_nested)
+    add_route(app, ['POST'], '/echo_multiple_args', echo_multiple_args)
     add_route(app, ['POST'], '/echo_nested_many', echo_nested_many)
     add_route(app, ['POST'], '/echo_nested_many_load_from', echo_nested_many_load_from)
     add_route(app, ['GET'], '/echo_match_info/{mymatch}', echo_match_info)
diff --git a/tests/test_aiohttpparser.py b/tests/test_aiohttpparser.py
--- a/tests/test_aiohttpparser.py
+++ b/tests/test_aiohttpparser.py
@@ -30,6 +30,11 @@ def test_invalid_status_code_passed_to_validation_error(self, testapp):
             testapp.get('/error_invalid?text=foo')
         assert excinfo.value.args[0] == 'No exception for 12345'
 
+    # regression test for https://github.com/sloria/webargs/issues/165
+    def test_multiple_args(self, testapp):
+        res = testapp.post_json('/echo_multiple_args', {'first': '1', 'last': '2', '_ignore': 0})
+        assert res.json == {'first': '1', 'last': '2'}
+
     # regression test for https://github.com/sloria/webargs/issues/145
     def test_nested_many_with_load_from(self, testapp):
         res = testapp.post_json('/echo_nested_many_load_from', {'x_field': [{'id': 42}]})