Repository: marshmallow-code/marshmallow

Issue:
Rename pass_many to pass_collection
Follow-up to discussion in https://github.com/marshmallow-code/marshmallow/issues/1368 : `pass_many` is a misleading name and should be improved.

Discussion:
lafrech: I just added a changelog line and fixed the target branch.

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 4997b9cf..bd814803 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -23,6 +23,7 @@ As a consequence of this change:
 - *Backwards-incompatible*: Custom validators must raise a `ValidationError <marshmallow.exceptions.ValidationError>` for invalid values.
   Returning `False` is no longer supported (:issue:`1775`).
 - *Backwards-incompatible*: Rename ``schema`` parameter to ``parent`` in `marshmallow.fields.Field._bind_to_schema` (:issue:`1360`).
+- *Backwards-incompatible*: Rename ``pass_many`` parameter to ``pass_collection`` in pre/post processing methods (:issue:`1369`).
 - *Backwards-incompatible*: `marshmallow.fields.TimeDelta` no longer truncates float values when
   deserializing (:pr:`2654`). This allows microseconds to be preserved, e.g.
 
diff --git a/docs/extending.rst b/docs/extending.rst
index ce736c09..582e07f3 100644
--- a/docs/extending.rst
+++ b/docs/extending.rst
@@ -33,7 +33,7 @@ Passing "many"
 
 By default, pre- and post-processing methods receive one object/datum at a time, transparently handling the ``many`` parameter passed to the ``Schema``'s :func:`~marshmallow.Schema.dump`/:func:`~marshmallow.Schema.load` method at runtime.
 
-In cases where your pre- and post-processing methods needs to handle the input collection when processing multiple objects, add ``pass_many=True`` to the method decorators.
+In cases where your pre- and post-processing methods needs to handle the input collection when processing multiple objects, add ``pass_collection=True`` to the method decorators.
 
 Your method will then receive the input data (which may be a single datum or a collection, depending on the dump/load call).
 
@@ -58,12 +58,12 @@ One common use case is to wrap data in a namespace upon serialization and unwrap
             assert key is not None, "Envelope key undefined"
             return key
 
-        @pre_load(pass_many=True)
+        @pre_load(pass_collection=True)
         def unwrap_envelope(self, data, many, **kwargs):
             key = self.get_envelope_key(many)
             return data[key]
 
-        @post_dump(pass_many=True)
+        @post_dump(pass_collection=True)
         def wrap_with_envelope(self, data, many, **kwargs):
             key = self.get_envelope_key(many)
             return {key: data}
@@ -157,21 +157,21 @@ Pre-/Post-processor Invocation Order
 
 In summary, the processing pipeline for deserialization is as follows:
 
-1. ``@pre_load(pass_many=True)`` methods
-2. ``@pre_load(pass_many=False)`` methods
+1. ``@pre_load(pass_collection=True)`` methods
+2. ``@pre_load(pass_collection=False)`` methods
 3. ``load(in_data, many)`` (validation and deserialization)
 4. ``@validates`` methods (field validators)
 5. ``@validates_schema`` methods (schema validators)
-6. ``@post_load(pass_many=True)`` methods
-7. ``@post_load(pass_many=False)`` methods
+6. ``@post_load(pass_collection=True)`` methods
+7. ``@post_load(pass_collection=False)`` methods
 
-The pipeline for serialization is similar, except that the ``pass_many=True`` processors are invoked *after* the ``pass_many=False`` processors and there are no validators.
+The pipeline for serialization is similar, except that the ``pass_collection=True`` processors are invoked *after* the ``pass_collection=False`` processors and there are no validators.
 
-1. ``@pre_dump(pass_many=False)`` methods
-2. ``@pre_dump(pass_many=True)`` methods
+1. ``@pre_dump(pass_collection=False)`` methods
+2. ``@pre_dump(pass_collection=True)`` methods
 3. ``dump(obj, many)`` (serialization)
-4. ``@post_dump(pass_many=False)`` methods
-5. ``@post_dump(pass_many=True)`` methods
+4. ``@post_dump(pass_collection=False)`` methods
+5. ``@post_dump(pass_collection=True)`` methods
 
 
 .. warning::
@@ -432,12 +432,12 @@ Then we create a custom :class:`Schema` that uses our options class.
     class NamespacedSchema(Schema):
         OPTIONS_CLASS = NamespaceOpts
 
-        @pre_load(pass_many=True)
+        @pre_load(pass_collection=True)
         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)
+        @post_dump(pass_collection=True)
         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/examples/peewee_example.py b/examples/peewee_example.py
index c2c4d37a..772414a1 100644
--- a/examples/peewee_example.py
+++ b/examples/peewee_example.py
@@ -66,7 +66,7 @@ class UserSchema(Schema):
         return data
 
     # We add a post_dump hook to add an envelope to responses
-    @post_dump(pass_many=True)
+    @post_dump(pass_collection=True)
     def wrap(self, data, many, **kwargs):
         key = "users" if many else "user"
         return {key: data}
@@ -80,7 +80,7 @@ class TodoSchema(Schema):
     posted_on = fields.DateTime(dump_only=True)
 
     # Again, add an envelope to responses
-    @post_dump(pass_many=True)
+    @post_dump(pass_collection=True)
     def wrap(self, data, many, **kwargs):
         key = "todos" if many else "todo"
         return {key: data}
diff --git a/src/marshmallow/decorators.py b/src/marshmallow/decorators.py
index 965edb68..45d3355f 100644
--- a/src/marshmallow/decorators.py
+++ b/src/marshmallow/decorators.py
@@ -35,12 +35,12 @@ Example: ::
             item["email"] = item["email"].lower().strip()
             return item
 
-        @pre_load(pass_many=True)
+        @pre_load(pass_collection=True)
         def remove_envelope(self, data, many, **kwargs):
             namespace = "results" if many else "result"
             return data[namespace]
 
-        @post_dump(pass_many=True)
+        @post_dump(pass_collection=True)
         def add_envelope(self, data, many, **kwargs):
             namespace = "results" if many else "result"
             return {namespace: data}
@@ -93,7 +93,7 @@ def validates(field_name: str) -> Callable[..., Any]:
 
 def validates_schema(
     fn: Callable[..., Any] | None = None,
-    pass_many: bool = False,
+    pass_collection: bool = False,
     pass_original: bool = False,
     skip_on_field_errors: bool = True,
 ) -> Callable[..., Any]:
@@ -101,7 +101,7 @@ def validates_schema(
 
     By default it receives a single object at a time, transparently handling the ``many``
     argument passed to the `Schema`'s :func:`~marshmallow.Schema.validate` call.
-    If ``pass_many=True``, the raw data (which may be a collection) is passed.
+    If ``pass_collection=True``, the raw data (which may be a collection) is passed.
 
     If ``pass_original=True``, the original data (before unmarshalling) will be passed as
     an additional argument to the method.
@@ -119,31 +119,31 @@ def validates_schema(
     return set_hook(
         fn,
         VALIDATES_SCHEMA,
-        many=pass_many,
+        many=pass_collection,
         pass_original=pass_original,
         skip_on_field_errors=skip_on_field_errors,
     )
 
 
 def pre_dump(
-    fn: Callable[..., Any] | None = None, pass_many: bool = False
+    fn: Callable[..., Any] | None = None, pass_collection: bool = False
 ) -> Callable[..., Any]:
     """Register a method to invoke before serializing an object. The method
     receives the object to be serialized and returns the processed object.
 
     By default it receives a single object at a time, transparently handling the ``many``
     argument passed to the `Schema`'s :func:`~marshmallow.Schema.dump` call.
-    If ``pass_many=True``, the raw data (which may be a collection) is passed.
+    If ``pass_collection=True``, the raw data (which may be a collection) is passed.
 
     .. versionchanged:: 3.0.0
         ``many`` is always passed as a keyword arguments to the decorated method.
     """
-    return set_hook(fn, PRE_DUMP, many=pass_many)
+    return set_hook(fn, PRE_DUMP, many=pass_collection)
 
 
 def post_dump(
     fn: Callable[..., Any] | None = None,
-    pass_many: bool = False,
+    pass_collection: bool = False,
     pass_original: bool = False,
 ) -> Callable[..., Any]:
     """Register a method to invoke after serializing an object. The method
@@ -151,7 +151,7 @@ def post_dump(
 
     By default it receives a single object at a time, transparently handling the ``many``
     argument passed to the `Schema`'s :func:`~marshmallow.Schema.dump` call.
-    If ``pass_many=True``, the raw data (which may be a collection) is passed.
+    If ``pass_collection=True``, the raw data (which may be a collection) is passed.
 
     If ``pass_original=True``, the original data (before serializing) will be passed as
     an additional argument to the method.
@@ -159,29 +159,29 @@ def post_dump(
     .. versionchanged:: 3.0.0
         ``many`` is always passed as a keyword arguments to the decorated method.
     """
-    return set_hook(fn, POST_DUMP, many=pass_many, pass_original=pass_original)
+    return set_hook(fn, POST_DUMP, many=pass_collection, pass_original=pass_original)
 
 
 def pre_load(
-    fn: Callable[..., Any] | None = None, pass_many: bool = False
+    fn: Callable[..., Any] | None = None, pass_collection: bool = False
 ) -> Callable[..., Any]:
     """Register a method to invoke before deserializing an object. The method
     receives the data to be deserialized and returns the processed data.
 
     By default it receives a single object at a time, transparently handling the ``many``
     argument passed to the `Schema`'s :func:`~marshmallow.Schema.load` call.
-    If ``pass_many=True``, the raw data (which may be a collection) is passed.
+    If ``pass_collection=True``, the raw data (which may be a collection) 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, many=pass_many)
+    return set_hook(fn, PRE_LOAD, many=pass_collection)
 
 
 def post_load(
     fn: Callable[..., Any] | None = None,
-    pass_many: bool = False,
+    pass_collection: bool = False,
     pass_original: bool = False,
 ) -> Callable[..., Any]:
     """Register a method to invoke after deserializing an object. The method
@@ -189,7 +189,7 @@ def post_load(
 
     By default it receives a single object at a time, transparently handling the ``many``
     argument passed to the `Schema`'s :func:`~marshmallow.Schema.load` call.
-    If ``pass_many=True``, the raw data (which may be a collection) is passed.
+    If ``pass_collection=True``, the raw data (which may be a collection) is passed.
 
     If ``pass_original=True``, the original data (before deserializing) will be passed as
     an additional argument to the method.
@@ -198,7 +198,7 @@ def post_load(
         ``partial`` and ``many`` are always passed as keyword arguments to
         the decorated method.
     """
-    return set_hook(fn, POST_LOAD, many=pass_many, pass_original=pass_original)
+    return set_hook(fn, POST_LOAD, many=pass_collection, pass_original=pass_original)
 
 
 def set_hook(
diff --git a/src/marshmallow/schema.py b/src/marshmallow/schema.py
index ba2a56f7..0d24b520 100644
--- a/src/marshmallow/schema.py
+++ b/src/marshmallow/schema.py
@@ -817,7 +817,7 @@ class Schema(base.SchemaABC, metaclass=SchemaMeta):
                 field_errors = bool(error_store.errors)
                 self._invoke_schema_validators(
                     error_store=error_store,
-                    pass_many=True,
+                    pass_collection=True,
                     data=result,
                     original_data=data,
                     many=many,
@@ -826,7 +826,7 @@ class Schema(base.SchemaABC, metaclass=SchemaMeta):
                 )
                 self._invoke_schema_validators(
                     error_store=error_store,
-                    pass_many=False,
+                    pass_collection=False,
                     data=result,
                     original_data=data,
                     many=many,
@@ -999,14 +999,18 @@ class Schema(base.SchemaABC, metaclass=SchemaMeta):
     def _invoke_dump_processors(
         self, tag: str, data, *, many: bool, original_data=None
     ):
-        # The pass_many post-dump processors may do things like add an envelope, so
-        # invoke those after invoking the non-pass_many processors which will expect
+        # The pass_collection post-dump processors may do things like add an envelope, so
+        # invoke those after invoking the non-pass_collection processors which will expect
         # to get a list of items.
         data = self._invoke_processors(
-            tag, pass_many=False, data=data, many=many, original_data=original_data
+            tag,
+            pass_collection=False,
+            data=data,
+            many=many,
+            original_data=original_data,
         )
         data = self._invoke_processors(
-            tag, pass_many=True, data=data, many=many, original_data=original_data
+            tag, pass_collection=True, data=data, many=many, original_data=original_data
         )
         return data
 
@@ -1019,11 +1023,11 @@ class Schema(base.SchemaABC, metaclass=SchemaMeta):
         original_data,
         partial: bool | types.StrSequenceOrSet | None,
     ):
-        # This has to invert the order of the dump processors, so run the pass_many
+        # This has to invert the order of the dump processors, so run the pass_collection
         # processors first.
         data = self._invoke_processors(
             tag,
-            pass_many=True,
+            pass_collection=True,
             data=data,
             many=many,
             original_data=original_data,
@@ -1031,7 +1035,7 @@ class Schema(base.SchemaABC, metaclass=SchemaMeta):
         )
         data = self._invoke_processors(
             tag,
-            pass_many=False,
+            pass_collection=False,
             data=data,
             many=many,
             original_data=original_data,
@@ -1089,7 +1093,7 @@ class Schema(base.SchemaABC, metaclass=SchemaMeta):
         self,
         *,
         error_store: ErrorStore,
-        pass_many: bool,
+        pass_collection: bool,
         data,
         original_data,
         many: bool,
@@ -1097,14 +1101,14 @@ class Schema(base.SchemaABC, metaclass=SchemaMeta):
         field_errors: bool = False,
     ):
         for attr_name, hook_many, validator_kwargs in self._hooks[VALIDATES_SCHEMA]:
-            if hook_many != pass_many:
+            if hook_many != pass_collection:
                 continue
             validator = getattr(self, attr_name)
             if field_errors and validator_kwargs["skip_on_field_errors"]:
                 continue
             pass_original = validator_kwargs.get("pass_original", False)
 
-            if many and not pass_many:
+            if many and not pass_collection:
                 for idx, (item, orig) in enumerate(zip(data, original_data)):
                     self._run_validator(
                         validator,
@@ -1131,20 +1135,20 @@ class Schema(base.SchemaABC, metaclass=SchemaMeta):
         self,
         tag: str,
         *,
-        pass_many: bool,
+        pass_collection: bool,
         data,
         many: bool,
         original_data=None,
         **kwargs,
     ):
         for attr_name, hook_many, processor_kwargs in self._hooks[tag]:
-            if hook_many != pass_many:
+            if hook_many != pass_collection:
                 continue
             # This will be a bound method.
             processor = getattr(self, attr_name)
             pass_original = processor_kwargs.get("pass_original", False)
 
-            if many and not pass_many:
+            if many and not pass_collection:
                 if pass_original:
                     data = [
                         processor(item, original, many=many, **kwargs)


Test patch:
diff --git a/tests/test_decorators.py b/tests/test_decorators.py
index 854f87fc..4c310bd6 100644
--- a/tests/test_decorators.py
+++ b/tests/test_decorators.py
@@ -42,13 +42,13 @@ def test_decorated_processors(partial_val):
             return item
 
         # Explicitly raw, post dump, instance method.
-        @post_dump(pass_many=True)
+        @post_dump(pass_collection=True)
         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)
+        @pre_load(pass_collection=True)
         def remove_envelope(self, data, many, partial, **kwargs):
             assert partial is partial_val
             key = self.get_envelope_key(many)
@@ -59,7 +59,7 @@ def test_decorated_processors(partial_val):
             return "data" if many else "datum"
 
         # Explicitly not raw, pre load, instance method.
-        @pre_load(pass_many=False)
+        @pre_load(pass_collection=False)
         def remove_tag(self, item, partial, **kwargs):
             assert partial is partial_val
             assert "many" in kwargs
@@ -158,7 +158,7 @@ class TestPassOriginal:
         class MySchema(Schema):
             foo = fields.Field()
 
-            @post_load(pass_many=True, pass_original=True)
+            @post_load(pass_collection=True, pass_original=True)
             def post_load(self, data, original, many, **kwargs):
                 if many:
                     ret = []
@@ -170,7 +170,7 @@ class TestPassOriginal:
                     ret["_post_load"] = original["sentinel"]
                 return ret
 
-            @post_dump(pass_many=True, pass_original=True)
+            @post_dump(pass_collection=True, pass_original=True)
             def post_dump(self, data, original, many, **kwargs):
                 if many:
                     ret = []
@@ -434,19 +434,19 @@ class TestValidatesSchemaDecorator:
 
     @pytest.mark.parametrize("data", ([{"foo": 1, "bar": 2}],))
     @pytest.mark.parametrize(
-        "pass_many,expected_data,expected_original_data",
+        "pass_collection,expected_data,expected_original_data",
         (
             [True, [{"foo": 1}], [{"foo": 1, "bar": 2}]],
             [False, {"foo": 1}, {"foo": 1, "bar": 2}],
         ),
     )
-    def test_validator_nested_many_pass_original_and_pass_many(
-        self, pass_many, data, expected_data, expected_original_data
+    def test_validator_nested_many_pass_original_and_pass_collection(
+        self, pass_collection, data, expected_data, expected_original_data
     ):
         class NestedSchema(Schema):
             foo = fields.Int(required=True)
 
-            @validates_schema(pass_many=pass_many, pass_original=True)
+            @validates_schema(pass_collection=pass_collection, pass_original=True)
             def validate_schema(self, data, original_data, many, **kwargs):
                 assert data == expected_data
                 assert original_data == expected_original_data
@@ -460,7 +460,7 @@ class TestValidatesSchemaDecorator:
 
         schema = MySchema()
         errors = schema.validate({"nested": data})
-        error = errors["nested"] if pass_many else errors["nested"][0]
+        error = errors["nested"] if pass_collection else errors["nested"][0]
         assert error["_schema"][0] == "Method called"
 
     def test_decorated_validators(self):
@@ -473,7 +473,7 @@ class TestValidatesSchemaDecorator:
                 if data["foo"] <= 3:
                     raise ValidationError("Must be greater than 3")
 
-            @validates_schema(pass_many=True)
+            @validates_schema(pass_collection=True)
             def validate_raw(self, data, many, **kwargs):
                 if many:
                     assert type(data) is list
@@ -574,7 +574,7 @@ class TestValidatesSchemaDecorator:
                 ):
                     raise ValidationError("foo cannot be a string")
 
-            @validates_schema(pass_many=True, pass_original=True)
+            @validates_schema(pass_collection=True, pass_original=True)
             def validate_original_bar(self, data, original_data, many, **kwargs):
                 def check(datum):
                     if isinstance(datum, dict) and isinstance(datum["bar"], str):
@@ -641,7 +641,7 @@ class TestValidatesSchemaDecorator:
                 if data["foo"] != data["bar"]:
                     raise ValidationError("Foo and bar must be equal.")
 
-            @validates_schema(skip_on_field_errors=True, pass_many=True)
+            @validates_schema(skip_on_field_errors=True, pass_collection=True)
             def validate_many(self, data, many, **kwargs):
                 if many:
                     assert type(data) is list
@@ -817,22 +817,22 @@ example = Example(nested=[Nested(x) for x in range(1)])
     "data,expected_data,expected_original_data",
     ([example, {"foo": 0}, example.nested[0]],),
 )
-def test_decorator_post_dump_with_nested_original_and_pass_many(
+def test_decorator_post_dump_with_nested_original_and_pass_collection(
     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(
+        @post_dump(pass_collection=False, pass_original=True)
+        def check_pass_original_when_pass_collection_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(
+        @post_dump(pass_collection=True, pass_original=True)
+        def check_pass_original_when_pass_collection_true(
             self, data, original_data, many, **kwargs
         ):
             assert many is True
@@ -851,22 +851,22 @@ def test_decorator_post_dump_with_nested_original_and_pass_many(
     "data,expected_data,expected_original_data",
     ([{"nested": [{"foo": 0}]}, {"foo": 0}, {"foo": 0}],),
 )
-def test_decorator_post_load_with_nested_original_and_pass_many(
+def test_decorator_post_load_with_nested_original_and_pass_collection(
     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(
+        @post_load(pass_collection=False, pass_original=True)
+        def check_pass_original_when_pass_collection_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(
+        @post_load(pass_collection=True, pass_original=True)
+        def check_pass_original_when_pass_collection_true(
             self, data, original_data, many, **kwargs
         ):
             assert many is True