Skip to content

Commit

Permalink
Go back to treating empty JSON body as {}
Browse files Browse the repository at this point in the history
load_* methods are now expected to return `missing` or {} when data is
missing for the given location. e.g. When a JSON body is "" it should be
`missing`. This is still considered correct.

However, in terms of the external behavior of webargs, go back to the
webargs v5.x stance on marshmallow-code#297 and optional JSON data: no data should be
interpreted as `{}`.

In order to achieve this, the wrapper which calls the various
"loader_func"s to load location data explicitly checks the result for
`missing` and converts it to `{}` if it is missing.

This makes it easy to use webargs to implement an API with optional JSON
data, but internally preserves the ability to detect a missing JSON
payload and distinguish it from a payload containing `{}`.

Revert changes to the regression test for marshmallow-code#297 , and a similar test.
  • Loading branch information
sirosen committed Sep 10, 2019
1 parent 6ca3afc commit 12cec15
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 20 deletions.
11 changes: 9 additions & 2 deletions src/webargs/asyncparser.py
Expand Up @@ -63,9 +63,16 @@ async def _load_location_data(self, schema, req, location):
"""
loader_func = self._get_loader(location)
if asyncio.iscoroutinefunction(loader_func):
return await loader_func(req, schema)
data = await loader_func(req, schema)
else:
return loader_func(req, schema)
data = loader_func(req, schema)

# when the desired location is empty (no data), provide an empty
# dict as the default so that optional arguments in a location
# (e.g. optional JSON body) work smoothly
if data is core.missing:
data = {}
return data

async def _on_validation_error(
self,
Expand Down
8 changes: 7 additions & 1 deletion src/webargs/core.py
Expand Up @@ -162,7 +162,13 @@ def _load_location_data(self, schema, req, location):
lists from multidict objects and `many=True` schemas.
"""
loader_func = self._get_loader(location)
return loader_func(req, schema)
data = loader_func(req, schema)
# when the desired location is empty (no data), provide an empty
# dict as the default so that optional arguments in a location
# (e.g. optional JSON body) work smoothly
if data is missing:
data = {}
return data

def _on_validation_error(
self, error, req, schema, error_status_code, error_headers
Expand Down
5 changes: 4 additions & 1 deletion src/webargs/falconparser.py
Expand Up @@ -52,7 +52,8 @@ def parse_form_body(req):
return parse_query_string(
body, keep_blank_qs_values=req.options.keep_blank_qs_values
)
return {}

return core.missing


class HTTPError(falcon.HTTPError):
Expand Down Expand Up @@ -96,6 +97,8 @@ def load_form(self, req, schema):
form = self._cache.get("form")
if form is None:
self._cache["form"] = form = parse_form_body(req)
if form is core.missing:
return form
return MultiDictProxy(form, schema)

def _raw_load_json(self, req):
Expand Down
24 changes: 8 additions & 16 deletions src/webargs/testing.py
Expand Up @@ -190,35 +190,27 @@ def test_parse_nested_many_missing(self, testapp):
res = testapp.post_json("/echo_nested_many", in_data)
assert res.json == {}

def test_parse_json_if_no_json(self, testapp):
res = testapp.post("/echo_json", expect_errors=True)
assert res.status_code == 422

def test_parse_files(self, testapp):
res = testapp.post(
"/echo_file", {"myfile": webtest.Upload("README.rst", b"data")}
)
assert res.json == {"myfile": "data"}

# https://github.com/sloria/webargs/pull/297
#
# NOTE: although this was originally considered correct behavior for
# webargs under #297 , under the refactor for #419 it is being reconsidered
# as *incorrect*
# the reason is that it makes it impossible to disambiguate "" (not valid
# JSON) from "{}" (valid, empty JSON). This in turn has downstream negative
# impact when you try to build sophisticated nested loaders which traverse
# the various location_load_{locname} methods because you can't tell if the
# user submitted empty data or no data in a location. In particular, this
# would pose serious problems for implementing the "json_or_form" location
def test_empty_json(self, testapp):
res = testapp.post("/echo_json")
assert res.status_code == 200
assert res.json == {"name": "World"}

# https://github.com/sloria/webargs/pull/297
def test_empty_json_with_headers(self, testapp):
res = testapp.post(
"/echo_json",
"",
headers={"Accept": "application/json", "Content-Type": "application/json"},
expect_errors=True,
)
assert res.status_code == 422
assert res.status_code == 200
assert res.json == {"name": "World"}

# https://github.com/sloria/webargs/issues/329
def test_invalid_json(self, testapp):
Expand Down

0 comments on commit 12cec15

Please sign in to comment.