Repository: lovasoa/marshmallow_dataclass

Issue:
Behavior of marshmallow.post_load is dependent on function name
I discovered a discrepancy with how the `post_load` decorator behaves within a marshmallow dataclass. In [Schema._invoke_processors](https://github.com/marshmallow-code/marshmallow/blob/76196abf35ff9ec58f3dc2377ea7a8a9bf23712a/src/marshmallow/schema.py#L1192) the post loads `make_data_class` processor runs before or after the post_load defined in the dataclass based on whichever comes first alphabetically. Some example below illustrate this: 

```python
import marshmallow
import marshmallow_dataclass

@marshmallow_dataclass.dataclass
class Person:
	name: str

	@marshmallow.post_load
	def a(schema, data, **kwargs):
		data.name = data.name.capitalize()  # works if: data['name'] = data['name'].capitalize()
		return data

Person.Schema().load({'name': 'matt'})
>> AttributeError: 'dict' object has no attribute 'name'
```

```python
import marshmallow
import marshmallow_dataclass

@marshmallow_dataclass.dataclass
class Person:
	name: str

	@marshmallow.post_load
	def z(schema, data, **kwargs):
		data.name = data.name.capitalize()
		return data

Person.Schema().load({'name': 'matt'})
>> Person(name='Matt')
```

The post_load in the first example does not create an instance of the dataclass itself before being invoked and feels more so like a `pre_load` because of this. The validation checks still seem to work properly, but it just feels a little weird to work with. I know the documentation does not include anything about pre/post loading, but I figured I'd document this in case anyone else runs into the same issue.

Comments:

Patch:
diff --git a/marshmallow_dataclass/__init__.py b/marshmallow_dataclass/__init__.py
index 67e7d77..403da98 100644
--- a/marshmallow_dataclass/__init__.py
+++ b/marshmallow_dataclass/__init__.py
@@ -34,10 +34,9 @@ Full example::
       })
       Schema: ClassVar[Type[Schema]] = Schema # For the type checker
 """
-import dataclasses
 import inspect
-from functools import lru_cache
 from enum import EnumMeta
+from functools import lru_cache
 from typing import (
     overload,
     Dict,
@@ -54,6 +53,7 @@ from typing import (
     Set,
 )
 
+import dataclasses
 import marshmallow
 import typing_inspect
 
@@ -457,12 +457,17 @@ def _base_schema(
     Base schema factory that creates a schema for `clazz` derived either from `base_schema`
     or `BaseSchema`
     """
+
     # Remove `type: ignore` when mypy handles dynamic base classes
     # https://github.com/python/mypy/issues/2813
     class BaseSchema(base_schema or marshmallow.Schema):  # type: ignore
-        @marshmallow.post_load
-        def make_data_class(self, data, **_):
-            return clazz(**data)
+        def load(self, data: Mapping, *, many: bool = None, **kwargs):
+            all_loaded = super().load(data, many=many, **kwargs)
+            many = self.many if many is None else bool(many)
+            if many:
+                return [clazz(**loaded) for loaded in all_loaded]
+            else:
+                return clazz(**all_loaded)
 
     return BaseSchema
 


Test patch:
diff --git a/tests/test_post_load.py b/tests/test_post_load.py
new file mode 100644
index 0000000..2e53c2c
--- /dev/null
+++ b/tests/test_post_load.py
@@ -0,0 +1,44 @@
+import unittest
+
+import marshmallow
+
+import marshmallow_dataclass
+
+
+# Regression test for https://github.com/lovasoa/marshmallow_dataclass/issues/75
+class TestPostLoad(unittest.TestCase):
+    @marshmallow_dataclass.dataclass
+    class Named:
+        first_name: str
+        last_name: str
+
+        @marshmallow.post_load
+        def a(self, data, **_kwargs):
+            data["first_name"] = data["first_name"].capitalize()
+            return data
+
+        @marshmallow.post_load
+        def z(self, data, **_kwargs):
+            data["last_name"] = data["last_name"].capitalize()
+            return data
+
+    def test_post_load_method_naming_does_not_affect_data(self):
+        actual = self.Named.Schema().load(
+            {"first_name": "matt", "last_name": "groening"}
+        )
+        expected = self.Named(first_name="Matt", last_name="Groening")
+        self.assertEqual(actual, expected)
+
+    def test_load_many(self):
+        actual = self.Named.Schema().load(
+            [
+                {"first_name": "matt", "last_name": "groening"},
+                {"first_name": "bart", "last_name": "simpson"},
+            ],
+            many=True,
+        )
+        expected = [
+            self.Named(first_name="Matt", last_name="Groening"),
+            self.Named(first_name="Bart", last_name="Simpson"),
+        ]
+        self.assertEqual(actual, expected)