Repository: marshmallow-code/webargs

Issue:
Deleted fields are always recreated with use_kwargs
If one tries to `load` a json object with Marshmallow so that it validates a field but one does not wish to have it in the final loaded object, one can have it removed using a method decorated with `post_load` so that the field is validated but not exported:

```python
     class GeojsonFeatureCollection(Schema):
         geotype = fields.String(required=True, location='json', load_from='type',
                                 validate=validate.OneOf(choices=['FeatureCollection']))
         features = fields.List(fields.Dict())

         @post_load
         def remove_geotype(self, data):
             data.pop('geotype')
             return data

         class Meta:
             strict = True
```

It works well with Marshmallow.

However, it is not working with webargs. For instance here, it will recreate the geotype member in the dictionary with  the `<marshmallow.missing>` value. Especially, the use of the `use_kwargs` decorator in the view method will force webargs to recreate all the missing fields as one can see here in the parser with the call to the `fill_in_missing_args` function: https://github.com/sloria/webargs/blob/a8640995dbe423d30248a26474f9fb12bad997f6/webargs/core.py#L399-L401

Indeed, the `force_all` argument of the `parse` method is currently always equal to `True` when one uses the `use_kwargs` decorator: https://github.com/sloria/webargs/blob/a8640995dbe423d30248a26474f9fb12bad997f6/webargs/core.py#L464-L475

I think one shoud be able retain in webargs the behaviour that Marshmallow provides so that the deleted fields would not be recreated by default if a flag is passed.

A simple solution would be to export the `force_all` variable as a parameter, maybe with a more explicit name, like `force_recreate_missing_fields` to the `use_args` method so that  one could use it from `use_kwargs`.

Moreover, I am wondering why was it required to recreate all the missing fields if the arguments have to be inserted as keyword arguments ? Was it to avoid raising an error in the views methods in case one argument was missing ? 
If one adds this parameter offering more control over the missing fields, I think it would make sense now not to recreate the missing fields by default, right ? (have `force_recreate_missing_fields=False in `use_args` as parameter)


Discussion:

Resolving patch:
diff --git a/webargs/core.py b/webargs/core.py
--- a/webargs/core.py
+++ b/webargs/core.py
@@ -428,7 +428,13 @@ def get_request_from_view_args(self, view, args, kwargs):
         return None
 
     def use_args(
-        self, argmap, req=None, locations=None, as_kwargs=False, validate=None
+        self,
+        argmap,
+        req=None,
+        locations=None,
+        as_kwargs=False,
+        validate=None,
+        force_all=None,
     ):
         """Decorator that injects parsed arguments into a view function or method.
 
@@ -447,6 +453,10 @@ def greet(args):
         :param callable validate: Validation function that receives the dictionary
             of parsed arguments. If the function returns ``False``, the parser
             will raise a :exc:`ValidationError`.
+        :param bool force_all: If `True`, missing arguments will be included
+            in the parsed arguments dictionary with the ``missing`` value.
+            If `False`, missing values will be omitted. If `None`, fall back
+            to the value of ``as_kwargs``.
         """
         locations = locations or self.locations
         request_obj = req
@@ -457,14 +467,12 @@ def greet(args):
 
         def decorator(func):
             req_ = request_obj
+            force_all_ = force_all if force_all is not None else as_kwargs
 
             @functools.wraps(func)
             def wrapper(*args, **kwargs):
                 req_obj = req_
 
-                # if as_kwargs is passed, must include all args
-                force_all = as_kwargs
-
                 if not req_obj:
                     req_obj = self.get_request_from_view_args(func, args, kwargs)
                 # NOTE: At this point, argmap may be a Schema, or a callable
@@ -473,7 +481,7 @@ def wrapper(*args, **kwargs):
                     req=req_obj,
                     locations=locations,
                     validate=validate,
-                    force_all=force_all,
+                    force_all=force_all_,
                 )
                 if as_kwargs:
                     kwargs.update(parsed_args)


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -605,18 +605,12 @@ def viewfunc(page, username):
     assert viewfunc() == {"json": {"username": "foo"}, "query": {"page": 42}}
 
 
-def test_use_args_doesnt_change_docstring(parser):
-    @parser.use_args({"val": fields.Int()})
-    def viewfunc(args):
-        """View docstring"""
-        pass
-
-    assert viewfunc.__doc__ == "View docstring"
-
+@pytest.mark.parametrize("decorator_name", ["use_args", "use_kwargs"])
+def test_decorators_dont_change_docstring(parser, decorator_name):
+    decorator = getattr(parser, decorator_name)
 
-def test_use_kwargs_doesnt_change_docstring(parser):
-    @parser.use_kwargs({"val": fields.Int()})
-    def viewfunc(val):
+    @decorator({"val": fields.Int()})
+    def viewfunc(*args, **kwargs):
         """View docstring"""
         pass
 
@@ -855,6 +849,33 @@ def viewfunc(username, password):
     assert viewfunc() == {"username": "foo", "password": missing}
 
 
+# https://github.com/sloria/webargs/issues/252
+def test_use_kwargs_force_all_false(web_request, parser):
+    user_args = {"username": fields.Str(required=True), "password": fields.Str()}
+    web_request.json = {"username": "foo"}
+
+    @parser.use_kwargs(user_args, web_request, force_all=False)
+    def viewfunc(username, **kwargs):
+        assert "password" not in kwargs
+        return {"username": username}
+
+    assert viewfunc() == {"username": "foo"}
+
+    class MySchema(Schema):
+        username = fields.Str(required=True)
+        password = fields.Str()
+
+    web_request.json = {"username": "sloria"}
+
+    # https://github.com/sloria/webargs/pull/307#issuecomment-441139909
+    @parser.use_kwargs(MySchema(partial=True), web_request, force_all=False)
+    def viewfunc2(username, **kwargs):
+        assert "password" not in kwargs
+        return {"username": username}
+
+    assert viewfunc2() == {"username": "sloria"}
+
+
 def test_delimited_list_default_delimiter(web_request, parser):
     web_request.json = {"ids": "1,2,3"}
     schema_cls = argmap2schema({"ids": fields.DelimitedList(fields.Int())})