Repository: marshmallow-code/marshmallow

Issue:
Accessing partial within validates_schema
It does not look like there is a way for a function decorated with `validates_schema` to know if the schema is `load`ing with `partial=True`. This information is important in determining for example if certain validations should be skipped (ie if they involve combinations of fields that are not available on PATCHes).

Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c417279e..c55d7148 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,6 +6,11 @@ Changelog
 
 Features:
 
+- *Backwards-incompatible*: ``many`` is passed as a keyword argument to methods decorated with
+  ``pre_load``, ``post_load``, ``pre_dump``, ``post_dump``,
+  and ``validates_schema``. ``partial`` is passed as a keyword argument to
+  methods decorated with ``pre_load``, ``post_load`` and ``validates_schema``.
+  ``**kwargs`` should be added to all decorated methods.
 - Add ``min_inclusive`` and ``max_exclusive`` parameters to
   ``validate.Range`` (:issue:`1221`). Thanks :user:`kdop` for the PR.
 
diff --git a/docs/extending.rst b/docs/extending.rst
index 4721c56e..6f52c128 100644
--- a/docs/extending.rst
+++ b/docs/extending.rst
@@ -19,7 +19,7 @@ Data pre-processing and post-processing methods can be registered using the `pre
         slug = fields.Str()
 
         @pre_load
-        def slugify_name(self, in_data):
+        def slugify_name(self, in_data, **kwargs):
             in_data['slug'] = in_data['slug'].lower().strip().replace(' ', '-')
             return in_data
 
@@ -36,8 +36,6 @@ By default, pre- and post-processing methods receive one object/datum at a time,
 In cases where your pre- and post-processing methods need to receive the input collection  when ``many=True``, add ``pass_many=True`` to the method decorators. The method will receive the input data (which may be a single datum or a collection) and the boolean value of ``many``.
 
 
-
-
 Example: Enveloping
 +++++++++++++++++++
 
@@ -63,17 +61,17 @@ One common use case is to wrap data in a namespace upon serialization and unwrap
             return key
 
         @pre_load(pass_many=True)
-        def unwrap_envelope(self, data, many):
+        def unwrap_envelope(self, data, many, **kwargs):
             key = self.get_envelope_key(many)
             return data[key]
 
         @post_dump(pass_many=True)
-        def wrap_with_envelope(self, data, many):
+        def wrap_with_envelope(self, data, many, **kwargs):
             key = self.get_envelope_key(many)
             return {key: data}
 
         @post_load
-        def make_object(self, data):
+        def make_object(self, data, **kwargs):
             return self.__model__(**data)
 
     class UserSchema(BaseSchema):
@@ -114,7 +112,7 @@ Pre- and post-processing methods may raise a `ValidationError <marshmallow.excep
         name = fields.Str()
 
         @pre_load
-        def unwrap_envelope(self, data):
+        def unwrap_envelope(self, data, **kwargs):
             if 'data' not in data:
                 raise ValidationError('Input data must have a "data" key.')
             return data['data']
@@ -137,7 +135,7 @@ If you want to store and error on a different key, pass the key name as the seco
         name = fields.Str()
 
         @pre_load
-        def unwrap_envelope(self, data):
+        def unwrap_envelope(self, data, **kwargs):
             if 'data' not in data:
                 raise ValidationError('Input data must have a "data" key.', '_preprocessing')
             return data['data']
@@ -184,7 +182,7 @@ The pipeline for serialization is similar, except that the "pass_many" processor
             field_a = fields.Field()
 
             @pre_load
-            def preprocess(self, data):
+            def preprocess(self, data, **kwargs):
                 step1_data = self.step1(data)
                 step2_data = self.step2(step1_data)
                 return step2_data
@@ -201,12 +199,12 @@ The pipeline for serialization is similar, except that the "pass_many" processor
             field_a = fields.Field()
 
             @pre_load
-            def step1(self, data):
+            def step1(self, data, **kwargs):
                 # ...
 
             # Depends on step1
             @pre_load
-            def step2(self, data):
+            def step2(self, data, **kwargs):
                 # ...
 
 
@@ -254,7 +252,7 @@ You can register schema-level validation functions for a :class:`Schema` using t
         field_b = fields.Integer()
 
         @validates_schema
-        def validate_numbers(self, data):
+        def validate_numbers(self, data, **kwargs):
             if data['field_b'] >= data['field_a']:
                 raise ValidationError('field_a must be greater than field_b')
 
@@ -284,7 +282,7 @@ When multiple schema-leval validator return errors, the error structures are mer
         field_d = fields.Integer()
 
         @validates_schema
-        def validate_lower_bound(self, data):
+        def validate_lower_bound(self, data, **kwargs):
             errors = {}
             if data['field_b'] <= data['field_a']:
                 errors['field_b'] = ['field_b must be greater than field_a']
@@ -294,7 +292,7 @@ When multiple schema-leval validator return errors, the error structures are mer
                 raise ValidationError(errors)
 
         @validates_schema
-        def validate_upper_bound(self, data):
+        def validate_upper_bound(self, data, **kwargs):
             errors = {}
             if data['field_b'] >= data['field_d']:
                 errors['field_b'] = ['field_b must be lower than field_d']
@@ -336,7 +334,7 @@ If you want to use the original, unprocessed input, you can add ``pass_original=
         bar = fields.Int()
 
         @post_load(pass_original=True)
-        def add_baz_to_bar(self, data, original_data):
+        def add_baz_to_bar(self, data, original_data, **kwargs):
             baz = original_data.get('baz')
             if baz:
                 data['bar'] = data['bar'] + baz
@@ -422,12 +420,12 @@ Then we create a custom :class:`Schema` that uses our options class.
         OPTIONS_CLASS = NamespaceOpts
 
         @pre_load(pass_many=True)
-        def unwrap_envelope(self, data, many):
+        def unwrap_envelope(self, data, many, **kwargs):
             key = self.opts.plural_name if many else self.opts.name
             return data[key]
 
         @post_dump(pass_many=True)
-        def wrap_with_envelope(self, data, many):
+        def wrap_with_envelope(self, data, many, **kwargs):
             key = self.opts.plural_name if many else self.opts.name
             return {key: data}
 
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index c41f5975..a2b6c615 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -120,7 +120,7 @@ In order to deserialize to an object, define a method of your :class:`Schema` an
         created_at = fields.DateTime()
 
         @post_load
-        def make_user(self, data):
+        def make_user(self, data, **kwargs):
             return User(**data)
 
 Now, the :meth:`load <Schema.load>` method will return a ``User`` object.
diff --git a/docs/upgrading.rst b/docs/upgrading.rst
index 82e7df39..f45c4552 100644
--- a/docs/upgrading.rst
+++ b/docs/upgrading.rst
@@ -79,6 +79,39 @@ will raise a :exc:`TypeError`.
     this change.
 
 
+Decorated methods receive ``many`` and ``partial``
+**************************************************
+
+Methods decorated with
+`pre_load <marshmallow.decorators.pre_load>`, `post_load <marshmallow.decorators.post_load>`,
+`pre_dump <marshmallow.decorators.pre_dump>`, `post_dump <marshmallow.decorators.post_dump>`,
+and `validates_schema <marshmallow.decorators.validates_schema>` receive
+``many`` as a keyword argument. In addition, `pre_load <marshmallow.decorators.pre_load>`, `post_load <marshmallow.decorators.post_load>`,
+and `validates_schema <marshmallow.decorators.validates_schema>` receive
+``partial``. To account for these additional arguments, add ``**kwargs`` to your methods.
+
+.. code-block:: python
+
+    # 2.x
+    class UserSchema(Schema):
+        name = fields.Str()
+        slug = fields.Str()
+
+        @pre_load
+        def slugify_name(self, in_data):
+            in_data['slug'] = in_data['slug'].lower().strip().replace(' ', '-')
+            return in_data
+
+    # 3.x
+    class UserSchema(Schema):
+        name = fields.Str()
+        slug = fields.Str()
+
+        @pre_load
+        def slugify_name(self, in_data, **kwargs):
+            in_data['slug'] = in_data['slug'].lower().strip().replace(' ', '-')
+            return in_data
+
 Deserializing invalid types raises a ``ValidationError``
 ********************************************************
 
@@ -269,7 +302,7 @@ datum.
             unknown = EXCLUDE
 
         @post_load(pass_original=True)
-        def post_load(self, data, original_data):
+        def post_load(self, data, original_data, **kwargs):
             # original_data has 'width' but
             # data does not because it's not
             # in the schema
@@ -636,7 +669,7 @@ Processors that mutate the data should be updated to also return it.
         slug = fields.Str()
 
         @pre_load
-        def slugify_name(self, in_data):
+        def slugify_name(self, in_data, **kwargs):
             # In 3.x, always return the processed data
             in_data['slug'] = in_data['slug'].lower().strip().replace(' ', '-')
             return in_data
diff --git a/examples/flask_example.py b/examples/flask_example.py
index 774270ad..6c17a56a 100644
--- a/examples/flask_example.py
+++ b/examples/flask_example.py
@@ -52,7 +52,7 @@ class QuoteSchema(Schema):
     # Allow client to pass author's full name in request body
     # e.g. {"author': 'Tim Peters"} rather than {"first": "Tim", "last": "Peters"}
     @pre_load
-    def process_author(self, data):
+    def process_author(self, data, **kwargs):
         author_name = data.get('author')
         if author_name:
             first, last = author_name.split(' ')
diff --git a/examples/peewee_example.py b/examples/peewee_example.py
index 555af84b..a1fc8c97 100644
--- a/examples/peewee_example.py
+++ b/examples/peewee_example.py
@@ -51,13 +51,13 @@ class UserSchema(Schema):
 
     # Clean up data
     @pre_load
-    def process_input(self, data):
+    def process_input(self, data, **kwargs):
         data['email'] = data['email'].lower().strip()
         return data
 
     # We add a post_dump hook to add an envelope to responses
     @post_dump(pass_many=True)
-    def wrap(self, data, many):
+    def wrap(self, data, many, **kwargs):
         key = 'users' if many else 'user'
         return {
             key: data,
@@ -73,7 +73,7 @@ class TodoSchema(Schema):
 
     # Again, add an envelope to responses
     @post_dump(pass_many=True)
-    def wrap(self, data, many):
+    def wrap(self, data, many, **kwargs):
         key = 'todos' if many else 'todo'
         return {
             key: data,
@@ -81,7 +81,7 @@ class TodoSchema(Schema):
 
     # We use make_object to create a new Todo from validated data
     @post_load
-    def make_object(self, data):
+    def make_object(self, data, **kwargs):
         if not data:
             return None
         return Todo(
diff --git a/performance/benchmark.py b/performance/benchmark.py
index 80fc432d..396450f9 100644
--- a/performance/benchmark.py
+++ b/performance/benchmark.py
@@ -11,7 +11,7 @@ import gc
 import timeit
 import time
 
-from marshmallow import Schema, fields, ValidationError, pre_load
+from marshmallow import Schema, fields, ValidationError, post_dump
 
 
 # Custom validator
@@ -46,18 +46,9 @@ class QuoteSchema(Schema):
     line_number = fields.Float()
     col_number = fields.Float()
 
-    # Allow client to pass author's full name in request body
-    # e.g. {"author': 'Tim Peters"} rather than {"first": "Tim", "last": "Peters"}
-    @pre_load
-    def process_author(self, data):
-        author_name = data.get('author')
-        if author_name:
-            first, last = author_name.split(' ')
-            author_dict = dict(first=first, last=last)
-        else:
-            author_dict = {}
-        data['author'] = author_dict
-        return data
+    @post_dump
+    def add_full_name(self, data, **kwargs):
+        data['author_full'] = '{}, {}'.format(data['author']['last'], data['author']['first'])
 
 
 class Author:
diff --git a/src/marshmallow/decorators.py b/src/marshmallow/decorators.py
index 69a93130..a16456ee 100644
--- a/src/marshmallow/decorators.py
+++ b/src/marshmallow/decorators.py
@@ -14,27 +14,27 @@ Example: ::
         age = fields.Integer(required=True)
 
         @post_load
-        def lowerstrip_email(self, item):
+        def lowerstrip_email(self, item, **kwargs):
             item['email'] = item['email'].lower().strip()
             return item
 
         @pre_load(pass_many=True)
-        def remove_envelope(self, data, many):
+        def remove_envelope(self, data, many, **kwargs):
             namespace = 'results' if many else 'result'
             return data[namespace]
 
         @post_dump(pass_many=True)
-        def add_envelope(self, data, many):
+        def add_envelope(self, data, many, **kwargs):
             namespace = 'results' if many else 'result'
             return {namespace: data}
 
         @validates_schema
-        def validate_email(self, data):
+        def validate_email(self, data, **kwargs):
             if len(data['email']) < 3:
                 raise ValidationError('Email must be more than 3 characters', 'email')
 
         @validates('age')
-        def validate_age(self, data):
+        def validate_age(self, data, **kwargs):
             if data < 14:
                 raise ValidationError('Too young!')
 
@@ -86,6 +86,10 @@ def validates_schema(
 
     .. versionchanged:: 3.0.0b1
         ``skip_on_field_errors`` defaults to `True`.
+
+    .. versionchanged:: 3.0.0
+        ``partial`` and ``many`` are always passed as keyword arguments to
+        the decorated method.
     """
     return set_hook(
         fn,
@@ -102,6 +106,9 @@ def pre_dump(fn=None, pass_many=False):
     By default, receives a single object at a time, regardless of whether ``many=True``
     is passed to the `Schema`. If ``pass_many=True``, the raw data (which may be a collection)
     and the value for ``many`` is passed.
+
+    .. versionchanged:: 3.0.0
+        ``many`` is always passed as a keyword arguments to the decorated method.
     """
     return set_hook(fn, (PRE_DUMP, pass_many))
 
@@ -116,6 +123,10 @@ def post_dump(fn=None, pass_many=False, pass_original=False):
 
     If ``pass_original=True``, the original data (before serializing) will be passed as
     an additional argument to the method.
+
+    .. versionchanged:: 3.0.0
+        ``partial`` and ``many`` are always passed as keyword arguments to
+        the decorated method.
     """
     return set_hook(fn, (POST_DUMP, pass_many), pass_original=pass_original)
 
@@ -127,6 +138,10 @@ def pre_load(fn=None, pass_many=False):
     By default, receives a single datum at a time, transparently handling the ``many``
     argument passed to the Schema. If ``pass_many=True``, the raw data
     (which may be a collection) and the value for ``many`` is passed.
+
+    .. versionchanged:: 3.0.0
+        ``partial`` and ``many`` are always passed as keyword arguments to
+        the decorated method.
     """
     return set_hook(fn, (PRE_LOAD, pass_many))
 
@@ -141,6 +156,10 @@ def post_load(fn=None, pass_many=False, pass_original=False):
 
     If ``pass_original=True``, the original data (before deserializing) will be passed as
     an additional argument to the method.
+
+    .. versionchanged:: 3.0.0
+        ``partial`` and ``many`` are always passed as keyword arguments to
+        the decorated method.
     """
     return set_hook(fn, (POST_LOAD, pass_many), pass_original=pass_original)
 
diff --git a/src/marshmallow/schema.py b/src/marshmallow/schema.py
index 18099dae..41b4d16e 100644
--- a/src/marshmallow/schema.py
+++ b/src/marshmallow/schema.py
@@ -3,7 +3,6 @@ from collections import defaultdict, OrderedDict
 import datetime as dt
 import uuid
 import decimal
-import functools
 import copy
 import inspect
 import json
@@ -737,14 +736,14 @@ class BaseSchema(base.SchemaABC):
 
     def _run_validator(
         self, validator_func, output, *,
-        original_data, fields_dict, error_store, index=None,
-        many=False, pass_original=False
+        original_data, fields_dict, error_store,
+        many, partial, pass_original, index=None
     ):
         try:
             if pass_original:  # Pass original, raw data (before unmarshalling)
-                validator_func(output, original_data)
+                validator_func(output, original_data, partial=partial, many=many)
             else:
-                validator_func(output)
+                validator_func(output, partial=partial, many=many)
         except ValidationError as err:
             error_store.store_error(err.messages, err.field_name, index=index)
 
@@ -806,6 +805,7 @@ class BaseSchema(base.SchemaABC):
                     data,
                     many=many,
                     original_data=data,
+                    partial=partial,
                 )
             except ValidationError as err:
                 errors = err.normalized_messages()
@@ -835,6 +835,7 @@ class BaseSchema(base.SchemaABC):
                     data=result,
                     original_data=data,
                     many=many,
+                    partial=partial,
                     field_errors=field_errors,
                 )
                 self._invoke_schema_validators(
@@ -843,6 +844,7 @@ class BaseSchema(base.SchemaABC):
                     data=result,
                     original_data=data,
                     many=many,
+                    partial=partial,
                     field_errors=field_errors,
                 )
             errors = error_store.errors
@@ -854,6 +856,7 @@ class BaseSchema(base.SchemaABC):
                         result,
                         many=many,
                         original_data=data,
+                        partial=partial,
                     )
                 except ValidationError as err:
                     errors = err.normalized_messages()
@@ -1027,16 +1030,18 @@ class BaseSchema(base.SchemaABC):
         )
         return data
 
-    def _invoke_load_processors(self, tag, data, *, many, original_data=None):
+    def _invoke_load_processors(self, tag, data, *, many, original_data, partial):
         # This has to invert the order of the dump processors, so run the pass_many
         # processors first.
         data = self._invoke_processors(
             tag, pass_many=True,
             data=data, many=many, original_data=original_data,
+            partial=partial,
         )
         data = self._invoke_processors(
             tag, pass_many=False,
             data=data, many=many, original_data=original_data,
+            partial=partial,
         )
         return data
 
@@ -1092,6 +1097,7 @@ class BaseSchema(base.SchemaABC):
         data,
         original_data,
         many,
+        partial,
         field_errors=False
     ):
         for attr_name in self._hooks[(VALIDATES_SCHEMA, pass_many)]:
@@ -1101,8 +1107,6 @@ class BaseSchema(base.SchemaABC):
                 continue
             pass_original = validator_kwargs.get('pass_original', False)
 
-            if pass_many:
-                validator = functools.partial(validator, many=many)
             if many and not pass_many:
                 for idx, (item, orig) in enumerate(zip(data, original_data)):
                     self._run_validator(
@@ -1112,6 +1116,7 @@ class BaseSchema(base.SchemaABC):
                         fields_dict=self.fields,
                         error_store=error_store,
                         many=many,
+                        partial=partial,
                         index=idx,
                         pass_original=pass_original,
                     )
@@ -1124,6 +1129,7 @@ class BaseSchema(base.SchemaABC):
                     error_store=error_store,
                     many=many,
                     pass_original=pass_original,
+                    partial=partial,
                 )
 
     def _invoke_processors(
@@ -1133,7 +1139,8 @@ class BaseSchema(base.SchemaABC):
         pass_many,
         data,
         many,
-        original_data=None
+        original_data=None,
+        **kwargs
     ):
         key = (tag, pass_many)
         for attr_name in self._hooks[key]:
@@ -1145,22 +1152,22 @@ class BaseSchema(base.SchemaABC):
 
             if pass_many:
                 if pass_original:
-                    data = processor(data, many, original_data)
+                    data = processor(data, original_data, many=many, **kwargs)
                 else:
-                    data = processor(data, many)
+                    data = processor(data, many=many, **kwargs)
             elif many:
                 if pass_original:
                     data = [
-                        processor(item, original)
+                        processor(item, original, many=many, **kwargs)
                         for item, original in zip(data, original_data)
                     ]
                 else:
-                    data = [processor(item) for item in data]
+                    data = [processor(item, many=many, **kwargs) for item in data]
             else:
                 if pass_original:
-                    data = processor(data, original_data)
+                    data = processor(data, original_data, many=many, **kwargs)
                 else:
-                    data = processor(data)
+                    data = processor(data, many=many, **kwargs)
         return data
 
 


Test patch:
diff --git a/tests/base.py b/tests/base.py
index e50e8b4b..321469a8 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -189,7 +189,7 @@ class UserSchema(Schema):
             raise ValidationError(str(te))
 
     @post_load
-    def make_user(self, data):
+    def make_user(self, data, **kwargs):
         return User(**data)
 
 class UserMetaSchema(Schema):
diff --git a/tests/test_decorators.py b/tests/test_decorators.py
index 4e9f9ef1..a93c5e2a 100644
--- a/tests/test_decorators.py
+++ b/tests/test_decorators.py
@@ -16,7 +16,8 @@ from marshmallow import (
 )
 
 
-def test_decorated_processors():
+@pytest.mark.parametrize('partial_val', (True, False))
+def test_decorated_processors(partial_val):
     class ExampleSchema(Schema):
         """Includes different ways to invoke decorators and set up methods"""
 
@@ -26,25 +27,28 @@ def test_decorated_processors():
 
         # Implicit default raw, pre dump, static method.
         @pre_dump
-        def increment_value(self, item):
+        def increment_value(self, item, **kwargs):
+            assert 'many' in kwargs
             item['value'] += 1
             return item
 
         # Implicit default raw, post dump, class method.
         @post_dump
-        def add_tag(self, item):
+        def add_tag(self, item, **kwargs):
+            assert 'many' in kwargs
             item['value'] = self.TAG + item['value']
             return item
 
         # Explicitly raw, post dump, instance method.
         @post_dump(pass_many=True)
-        def add_envelope(self, data, many):
+        def add_envelope(self, data, many, **kwargs):
             key = self.get_envelope_key(many)
             return {key: data}
 
         # Explicitly raw, pre load, instance method.
         @pre_load(pass_many=True)
-        def remove_envelope(self, data, many):
+        def remove_envelope(self, data, many, partial, **kwargs):
+            assert partial is partial_val
             key = self.get_envelope_key(many)
             return data[key]
 
@@ -54,17 +58,21 @@ def test_decorated_processors():
 
         # Explicitly not raw, pre load, instance method.
         @pre_load(pass_many=False)
-        def remove_tag(self, item):
+        def remove_tag(self, item, partial, **kwargs):
+            assert partial is partial_val
+            assert 'many' in kwargs
             item['value'] = item['value'][len(self.TAG):]
             return item
 
         # Explicit default raw, post load, instance method.
         @post_load()
-        def decrement_value(self, item):
+        def decrement_value(self, item, partial, **kwargs):
+            assert partial is partial_val
+            assert 'many' in kwargs
             item['value'] -= 1
             return item
 
-    schema = ExampleSchema()
+    schema = ExampleSchema(partial=partial_val)
 
     # Need to re-create these because the processors will modify in place.
     make_item = lambda: {'value': 3}
@@ -88,22 +96,22 @@ def test_decorated_processor_returning_none(unknown):
         value = fields.Integer()
 
         @post_load
-        def load_none(self, item):
+        def load_none(self, item, **kwargs):
             return None
 
         @post_dump
-        def dump_none(self, item):
+        def dump_none(self, item, **kwargs):
             return None
 
     class PreSchema(Schema):
         value = fields.Integer()
 
         @pre_load
-        def load_none(self, item):
+        def load_none(self, item, **kwargs):
             return None
 
         @pre_dump
-        def dump_none(self, item):
+        def dump_none(self, item, **kwargs):
             return None
 
     schema = PostSchema(unknown=unknown)
@@ -123,13 +131,13 @@ class TestPassOriginal:
             foo = fields.Field()
 
             @post_load(pass_original=True)
-            def post_load(self, data, input_data):
+            def post_load(self, data, original_data, **kwargs):
                 ret = data.copy()
-                ret['_post_load'] = input_data['sentinel']
+                ret['_post_load'] = original_data['sentinel']
                 return ret
 
             @post_dump(pass_original=True)
-            def post_dump(self, data, obj):
+            def post_dump(self, data, obj, **kwargs):
                 ret = data.copy()
                 ret['_post_dump'] = obj['sentinel']
                 return ret
@@ -150,7 +158,7 @@ class TestPassOriginal:
             foo = fields.Field()
 
             @post_load(pass_many=True, pass_original=True)
-            def post_load(self, data, many, original):
+            def post_load(self, data, original, many, **kwargs):
                 if many:
                     ret = []
                     for item, orig_item in zip(data, original):
@@ -162,7 +170,7 @@ class TestPassOriginal:
                 return ret
 
             @post_dump(pass_many=True, pass_original=True)
-            def post_dump(self, data, many, original):
+            def post_dump(self, data, original, many, **kwargs):
                 if many:
                     ret = []
                     for item, orig_item in zip(data, original):
@@ -202,24 +210,24 @@ def test_decorated_processor_inheritance():
     class ParentSchema(Schema):
 
         @post_dump
-        def inherited(self, item):
+        def inherited(self, item, **kwargs):
             item['inherited'] = 'inherited'
             return item
 
         @post_dump
-        def overridden(self, item):
+        def overridden(self, item, **kwargs):
             item['overridden'] = 'base'
             return item
 
         @post_dump
-        def deleted(self, item):
+        def deleted(self, item, **kwargs):
             item['deleted'] = 'retained'
             return item
 
     class ChildSchema(ParentSchema):
 
         @post_dump
-        def overridden(self, item):
+        def overridden(self, item, **kwargs):
             item['overridden'] = 'overridden'
             return item
 
@@ -244,7 +252,7 @@ def test_pre_dump_is_invoked_before_implicit_field_generation():
         field = fields.Integer()
 
         @pre_dump
-        def hook(s, data):
+        def hook(s, data, **kwargs):
             data['generated_field'] = 7
             return data
 
@@ -415,7 +423,7 @@ class TestValidatesSchemaDecorator:
             foo = fields.Int(required=True)
 
             @validates_schema
-            def validate_schema(self, data):
+            def validate_schema(self, data, **kwargs):
                 raise ValidationError('This will never work.')
 
         class MySchema(Schema):
@@ -434,7 +442,7 @@ class TestValidatesSchemaDecorator:
             foo = fields.Int(required=True)
 
             @validates_schema
-            def validate_schema(self, data):
+            def validate_schema(self, data, **kwargs):
                 raise ValidationError('This will never work.', 'foo')
 
         class MySchema(Schema):
@@ -456,17 +464,17 @@ class TestValidatesSchemaDecorator:
         ),
     )
     def test_validator_nested_many_pass_original_and_pass_many(
-            self, pass_many, data, expected_data, expected_original_data,
+        self, pass_many, data, expected_data, expected_original_data,
     ):
 
         class NestedSchema(Schema):
             foo = fields.Int(required=True)
 
             @validates_schema(pass_many=pass_many, pass_original=True)
-            def validate_schema(self, data, original_data, many=False):
+            def validate_schema(self, data, original_data, many, **kwargs):
                 assert data == expected_data
                 assert original_data == expected_original_data
-                assert many is pass_many
+                assert many is True
                 raise ValidationError('Method called')
 
         class MySchema(Schema):
@@ -484,19 +492,19 @@ class TestValidatesSchemaDecorator:
             bar = fields.Int()
 
             @validates_schema
-            def validate_schema(self, data):
+            def validate_schema(self, data, **kwargs):
                 if data['foo'] <= 3:
                     raise ValidationError('Must be greater than 3')
 
             @validates_schema(pass_many=True)
-            def validate_raw(self, data, many):
+            def validate_raw(self, data, many, **kwargs):
                 if many:
                     assert type(data) is list
                     if len(data) < 2:
                         raise ValidationError('Must provide at least 2 items')
 
             @validates_schema
-            def validate_bar(self, data):
+            def validate_bar(self, data, **kwargs):
                 if 'bar' in data and data['bar'] < 0:
                     raise ValidationError('bar must not be negative', 'bar')
 
@@ -522,12 +530,12 @@ class TestValidatesSchemaDecorator:
             bar = fields.Int()
 
             @validates_schema
-            def validate_schema(self, data):
+            def validate_schema(self, data, **kwargs):
                 if data['foo'] <= 3:
                     raise ValidationError('Must be greater than 3')
 
             @validates_schema
-            def validate_bar(self, data):
+            def validate_bar(self, data, **kwargs):
                 if 'bar' in data and data['bar'] < 0:
                     raise ValidationError('bar must not be negative')
 
@@ -557,15 +565,15 @@ class TestValidatesSchemaDecorator:
             nested = fields.Nested(NestedSchema)
 
             @validates_schema
-            def validate_nested_foo(self, data):
+            def validate_nested_foo(self, data, **kwargs):
                 raise ValidationError({'nested': {'foo': ['Invalid foo']}})
 
             @validates_schema
-            def validate_nested_bar_1(self, data):
+            def validate_nested_bar_1(self, data, **kwargs):
                 raise ValidationError({'nested': {'bar': ['Invalid bar 1']}})
 
             @validates_schema
-            def validate_nested_bar_2(self, data):
+            def validate_nested_bar_2(self, data, **kwargs):
                 raise ValidationError({'nested': {'bar': ['Invalid bar 2']}})
 
         with pytest.raises(ValidationError) as excinfo:
@@ -582,12 +590,12 @@ class TestValidatesSchemaDecorator:
             bar = fields.Int()
 
             @validates_schema(pass_original=True)
-            def validate_original_foo(self, data, original_data):
+            def validate_original(self, data, original_data, partial, **kwargs):
                 if isinstance(original_data, dict) and isinstance(original_data['foo'], str):
                     raise ValidationError('foo cannot be a string')
 
             @validates_schema(pass_many=True, pass_original=True)
-            def validate_original_bar(self, data, original_data, many):
+            def validate_original_bar(self, data, original_data, many, **kwargs):
                 def check(datum):
                     if isinstance(datum, dict) and isinstance(datum['bar'], str):
                         raise ValidationError('bar cannot be a string')
@@ -619,7 +627,7 @@ class TestValidatesSchemaDecorator:
             bam = fields.Int(required=True)
 
             @validates_schema(skip_on_field_errors=True)
-            def consistency_validation(self, data):
+            def consistency_validation(self, data, **kwargs):
                 errors = {}
                 if data['bar']['baz'] != data['foo']:
                     errors['bar'] = {'baz': 'Non-matching value'}
@@ -639,7 +647,7 @@ class TestValidatesSchemaDecorator:
         class MySchema(Schema):
 
             @validates_schema
-            def validator(self, data):
+            def validator(self, data, **kwargs):
                 raise ValidationError('Error message', 'arbitrary_key')
 
         errors = MySchema().validate({})
@@ -652,12 +660,12 @@ class TestValidatesSchemaDecorator:
             bar = fields.Int(required=True)
 
             @validates_schema(skip_on_field_errors=True)
-            def validate_schema(self, data):
+            def validate_schema(self, data, **kwargs):
                 if data['foo'] != data['bar']:
                     raise ValidationError('Foo and bar must be equal.')
 
             @validates_schema(skip_on_field_errors=True, pass_many=True)
-            def validate_many(self, data, many):
+            def validate_many(self, data, many, **kwargs):
                 if many:
                     assert type(data) is list
                     if len(data) < 2:
@@ -692,7 +700,7 @@ def test_decorator_error_handling():
         bar = fields.Int()
 
         @pre_load()
-        def pre_load_error1(self, item):
+        def pre_load_error1(self, item, **kwargs):
             if item['foo'] != 0:
                 return item
             errors = {
@@ -702,19 +710,19 @@ def test_decorator_error_handling():
             raise ValidationError(errors)
 
         @pre_load()
-        def pre_load_error2(self, item):
+        def pre_load_error2(self, item, **kwargs):
             if item['foo'] != 4:
                 return item
             raise ValidationError('preloadmsg1', 'foo')
 
         @pre_load()
-        def pre_load_error3(self, item):
+        def pre_load_error3(self, item, **kwargs):
             if item['foo'] != 8:
                 return item
             raise ValidationError('preloadmsg1')
 
         @post_load()
-        def post_load_error1(self, item):
+        def post_load_error1(self, item, **kwargs):
             if item['foo'] != 1:
                 return item
             errors = {
@@ -724,13 +732,13 @@ def test_decorator_error_handling():
             raise ValidationError(errors)
 
         @post_load()
-        def post_load_error2(self, item):
+        def post_load_error2(self, item, **kwargs):
             if item['foo'] != 5:
                 return item
             raise ValidationError('postloadmsg1', 'foo')
 
         @pre_dump()
-        def pre_dump_error1(self, item):
+        def pre_dump_error1(self, item, **kwargs):
             if item['foo'] != 2:
                 return item
             errors = {
@@ -740,13 +748,13 @@ def test_decorator_error_handling():
             raise ValidationError(errors)
 
         @pre_dump()
-        def pre_dump_error2(self, item):
+        def pre_dump_error2(self, item, **kwargs):
             if item['foo'] != 6:
                 return item
             raise ValidationError('predumpmsg1', 'foo')
 
         @post_dump()
-        def post_dump_error1(self, item):
+        def post_dump_error1(self, item, **kwargs):
             if item['foo'] != 3:
                 return item
             errors = {
@@ -756,7 +764,7 @@ def test_decorator_error_handling():
             raise ValidationError(errors)
 
         @post_dump()
-        def post_dump_error2(self, item):
+        def post_dump_error2(self, item, **kwargs):
             if item['foo'] != 7:
                 return item
             raise ValidationError('postdumpmsg1', 'foo')
@@ -845,7 +853,7 @@ def test_decorator_error_handling():
 def test_decorator_error_handling_with_load(decorator):
     class ExampleSchema(Schema):
         @decorator
-        def raise_value_error(self, item):
+        def raise_value_error(self, item, **kwargs):
             raise ValidationError({'foo': 'error'})
 
     schema = ExampleSchema()
@@ -858,7 +866,7 @@ def test_decorator_error_handling_with_load(decorator):
 def test_decorator_error_handling_with_load_dict_error(decorator):
     class ExampleSchema(Schema):
         @decorator
-        def raise_value_error(self, item):
+        def raise_value_error(self, item, **kwargs):
             raise ValidationError({'foo': 'error'}, 'nested_field')
 
     schema = ExampleSchema()
@@ -871,7 +879,7 @@ def test_decorator_error_handling_with_load_dict_error(decorator):
 def test_decorator_error_handling_with_dump(decorator):
     class ExampleSchema(Schema):
         @decorator
-        def raise_value_error(self, item):
+        def raise_value_error(self, item, **kwargs):
             raise ValidationError({'foo': 'error'})
 
     schema = ExampleSchema()
@@ -884,7 +892,7 @@ def test_decorator_error_handling_with_dump(decorator):
 def test_decorator_error_handling_with_dump_dict_error(decorator):
     class ExampleSchema(Schema):
         @decorator
-        def raise_value_error(self, item):
+        def raise_value_error(self, item, **kwargs):
             raise ValidationError({'foo': 'error'}, 'nested')
 
     schema = ExampleSchema()
@@ -913,21 +921,21 @@ example = Example(nested=[Nested(x) for x in range(1)])
         [example, {'foo': 0}, example.nested[0]],
     ),
 )
-def test_decorator_post_dump_with_nested_pass_original_and_pass_many(
-        data, expected_data, expected_original_data,
+def test_decorator_post_dump_with_nested_original_and_pass_many(
+    data, expected_data, expected_original_data,
 ):
 
     class NestedSchema(Schema):
         foo = fields.Int(required=True)
 
         @post_dump(pass_many=False, pass_original=True)
-        def check_pass_original_when_pass_many_false(self, data, original_data):
+        def check_pass_original_when_pass_many_false(self, data, original_data, **kwargs):
             assert data == expected_data
             assert original_data == expected_original_data
             return data
 
         @post_dump(pass_many=True, pass_original=True)
-        def check_pass_original_when_pass_many_true(self, data, many, original_data):
+        def check_pass_original_when_pass_many_true(self, data, original_data, many, **kwargs):
             assert many is True
             assert data == [expected_data]
             assert original_data == [expected_original_data]
@@ -946,21 +954,21 @@ def test_decorator_post_dump_with_nested_pass_original_and_pass_many(
         [{'nested': [{'foo': 0}]}, {'foo': 0}, {'foo': 0}],
     ),
 )
-def test_decorator_post_load_with_nested_pass_original_and_pass_many(
-        data, expected_data, expected_original_data,
+def test_decorator_post_load_with_nested_original_and_pass_many(
+    data, expected_data, expected_original_data,
 ):
 
     class NestedSchema(Schema):
         foo = fields.Int(required=True)
 
         @post_load(pass_many=False, pass_original=True)
-        def check_pass_original_when_pass_many_false(self, data, original_data):
+        def check_pass_original_when_pass_many_false(self, data, original_data, **kwargs):
             assert data == expected_data
             assert original_data == expected_original_data
             return data
 
         @post_load(pass_many=True, pass_original=True)
-        def check_pass_original_when_pass_many_true(self, data, many, original_data):
+        def check_pass_original_when_pass_many_true(self, data, original_data, many, **kwargs):
             assert many is True
             assert data == [expected_data]
             assert original_data == [expected_original_data]
diff --git a/tests/test_schema.py b/tests/test_schema.py
index edbb5a40..aab36d75 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -188,7 +188,6 @@ def test_load_resets_error_fields():
     with pytest.raises(ValidationError) as excinfo:
         schema.load({'name': 12, 'email': 'mick@stones.com'})
     exc = excinfo.value
-    assert len(exc.messages.keys()) == 1
 
 def test_errored_fields_do_not_appear_in_output():
 
@@ -1761,7 +1760,7 @@ class TestHandleError:
             num = fields.Int()
 
             @validates_schema
-            def validates_schema(self, data):
+            def validates_schema(self, data, **kwargs):
                 raise ValidationError('Invalid schema!')
 
             def handle_error(self, error, data):