Repository: pydantic/pydantic

Issue:
Dataclass post_init_post_parse called multiple times.
### Initial Checks

- [X] I have searched GitHub for a duplicate issue and I'm sure this is something new
- [X] I have searched Google & StackOverflow for a solution and couldn't find anything
- [X] I have read and followed [the docs](https://pydantic-docs.helpmanual.io) and still think this is a bug
- [X] I am confident that the issue is with pydantic  (not my code, or another library in the ecosystem like [FastAPI](https://fastapi.tiangolo.com) or [mypy](https://mypy.readthedocs.io/en/stable))


### Description

When using inheritance with `pydantic.dataclasses.dataclass` based objects with `pydantic>=1.10` I seem to have encountered a bug that calls `__post_init_post_parse__` methods multiple times if a `__post_init__` method is called further down an inheritance chain.

Running the below code snippet outputs the print statement three times, whereas if `__post_init__` is placed in `AbstractClass` the `"post init post parse called"` statement is called twice.

The exact same code snippet on `pydantic<1.10` works as expected with the print statement only printed the once.

Including an overridden `__post_init__` method in `ConcreteClass` only prints the statement once - but of course if there is more useful logic contained in `BaseClass.__post_init__` (as would be the case for this pattern) then this is ignored.

Removing the `@dataclass` decorators from the base classes works fine however, except **if** we didn't have access to the code for those underlying base classes.

EDIT: It appears its due to these lines:
https://github.com/pydantic/pydantic/blob/v1.10.2/pydantic/dataclasses.py#L290-L304

### Example Code

```Python
from pydantic.dataclasses import dataclass

@dataclass
class BaseClass:
    def __post_init__(self):
        pass

@dataclass
class AbstractClass(BaseClass):
    pass

@dataclass
class ConcreteClass(AbstractClass):
    def __post_init_post_parse__(self):
        print("post init post parse called")

ConcreteClass()

# prints:
# post init post parse called
# post init post parse called
# post init post parse called


# Example of nested dataclasses from docs: https://pydantic-docs.helpmanual.io/usage/dataclasses/#convert-stdlib-dataclasses-into-pydantic-dataclasses

from datetime import datetime
from typing import Optional

@dataclass
class Meta:
    modified_date: Optional[datetime]
    seen_count: int
    def __post_init__(self):
        print("POST INIT")

@dataclass
class File(Meta):
    filename: str
    def __post_init_post_parse__(self):
        print("POST INIT POST PARSE")

File(filename="file.txt", modified_date=datetime.now(), seen_count=1)

# prints:
# POST INIT
# POST INIT POST PARSE
# POST INIT POST PARSE
```


### Python, Pydantic & OS Version

```Text
pydantic version: 1.10.2
            pydantic compiled: True
                 install path: *****
               python version: 3.8.13 (default, Mar 28 2022, 06:16:26)  [Clang 12.0.0 ]
                     platform: macOS-10.16-x86_64-i386-64bit
     optional deps. installed: ['dotenv', 'typing-extensions']
```


### Affected Components

- [X] [Compatibility between releases](https://pydantic-docs.helpmanual.io/changelog/)
- [X] [Data validation/parsing](https://pydantic-docs.helpmanual.io/usage/models/#basic-model-usage)
- [ ] [Data serialization](https://pydantic-docs.helpmanual.io/usage/exporting_models/) - `.dict()` and `.json()`
- [ ] [JSON Schema](https://pydantic-docs.helpmanual.io/usage/schema/)
- [X] [Dataclasses](https://pydantic-docs.helpmanual.io/usage/dataclasses/)
- [ ] [Model Config](https://pydantic-docs.helpmanual.io/usage/model_config/)
- [ ] [Field Types](https://pydantic-docs.helpmanual.io/usage/types/) - adding or changing a particular data type
- [ ] [Function validation decorator](https://pydantic-docs.helpmanual.io/usage/validation_decorator/)
- [ ] [Generic Models](https://pydantic-docs.helpmanual.io/usage/models/#generic-models)
- [ ] [Other Model behaviour](https://pydantic-docs.helpmanual.io/usage/models/) - `construct()`, pickling, private attributes, ORM mode
- [ ] [Settings Management](https://pydantic-docs.helpmanual.io/usage/settings/)
- [ ] [Plugins](https://pydantic-docs.helpmanual.io/) and integration with other tools - mypy, FastAPI, python-devtools, Hypothesis, VS Code, PyCharm, etc.


Comments:
See #4484.

@PrettyWood, any ideas?
I'll check

Patch:
diff --git a/pydantic/dataclasses.py b/pydantic/dataclasses.py
--- a/pydantic/dataclasses.py
+++ b/pydantic/dataclasses.py
@@ -288,7 +288,10 @@ def handle_extra_init(self: 'Dataclass', *args: Any, **kwargs: Any) -> None:
             init(self, *args, **kwargs)
 
     if hasattr(dc_cls, '__post_init__'):
-        post_init = dc_cls.__post_init__
+        try:
+            post_init = dc_cls.__post_init__.__wrapped__  # type: ignore[attr-defined]
+        except AttributeError:
+            post_init = dc_cls.__post_init__
 
         @wraps(post_init)
         def new_post_init(self: 'Dataclass', *args: Any, **kwargs: Any) -> None:


Test patch:
diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py
--- a/tests/test_dataclasses.py
+++ b/tests/test_dataclasses.py
@@ -1474,6 +1474,31 @@ def __post_init__(self):
     assert C().a == 6  # 1 * 3 + 3
 
 
+def test_inheritance_post_init_2():
+    post_init_calls = 0
+    post_init_post_parse_calls = 0
+
+    @pydantic.dataclasses.dataclass
+    class BaseClass:
+        def __post_init__(self):
+            nonlocal post_init_calls
+            post_init_calls += 1
+
+    @pydantic.dataclasses.dataclass
+    class AbstractClass(BaseClass):
+        pass
+
+    @pydantic.dataclasses.dataclass
+    class ConcreteClass(AbstractClass):
+        def __post_init_post_parse__(self):
+            nonlocal post_init_post_parse_calls
+            post_init_post_parse_calls += 1
+
+    ConcreteClass()
+    assert post_init_calls == 1
+    assert post_init_post_parse_calls == 1
+
+
 def test_dataclass_setattr():
     class Foo:
         bar: str = 'cat'