Repository: pydantic/pydantic

Issue:
root_validator and context
So in v2 `@field_validator` have context passed to a callback but `@root_validator` is not 

can we add as well context to `@root_validator` ?



Discussion:
I think the plan is to create a new validator called `@model_validator` or similar, which will be like `@root_validator` but follow more of the v2 api conventions (and, in particular, have `context`). And `@root_validator` will get deprecated and just remain for v1 backwards compatibility.

@adriangb may have more detail and/or be able to correct this statement.
`@model_validator` already exists. We obviously need better docs, but here are the tests: https://github.com/pydantic/pydantic/blob/8f855b060aae63ea3c06dd17b3e5b50edf5f7bd0/tests/test_model_validator.py
@adriangb got it, thank you

will import stay like this 

`from pydantic.decorators import model_validator` 

or you will make it as well 

`from pydantic import model_validator` ?
The second should work (or be added).
@samuelcolvin @adriangb 

there is just one issue with `model_validator`- so `root_validator` worked with classmethods, while `model_validator` works with staticmethod:

```Python
class Shape(BaseModel):
    ...
    @root_validator(pre=True)
    def run_root_validator(cls, values):
        # ------------------^
        print('run_root_validator:', cls, values)
        ...

    @model_validator(mode='before')
    def run_model_validator(values, info: ValidationInfo):
        print('run_model_validator', info)
        ...
```

can we make model_validator for classmethod as well ? or pass the class in ValidationInfo ?
This works:

```python
from typing import Any, Dict

from pydantic import BaseModel
from pydantic.decorators import model_validator


class Model(BaseModel):
    x: int

    @model_validator(mode='before')
    @classmethod
    def val_x(cls, values: Dict[str, Any]) -> Dict[str, Any]:
        values['x'] += 1
        return values


assert Model(x=1).x == 2
```

We can add support for not specifying `@classmethod`, but you probably should anyway.
@adriangb ok, but anyway behaviour of root_validator and model_validator are different when you inherit models:

```Python
class Human(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    @root_validator(pre=True)
    def run_root_validator(cls, values):
        print('run_root_validator:', cls, values)
        return values

    @model_validator(mode='before')
    def run_model_validator(values, info: ValidationInfo):
        print('run_model_validator', info)
        return values


class Person(Human):
    id: int
    name: str
    age: int = None


data = Person.model_validate(obj, context={'request': 'some'})
```
**model_validator is not executing** in this case, while **root_validator is fine**:

```
run_root_validator: <class '__main__.Person'> <Mock object at 0x1033eec70>
```

Resolving patch:
diff --git a/pydantic/_internal/_decorators.py b/pydantic/_internal/_decorators.py
--- a/pydantic/_internal/_decorators.py
+++ b/pydantic/_internal/_decorators.py
@@ -248,6 +248,7 @@ def build(model_dc: type[Any]) -> DecoratorInfos:  # noqa: C901 (ignore complexi
                 res.root_validator.update({k: v.bind_to_cls(model_dc) for k, v in existing.root_validator.items()})
                 res.field_serializer.update({k: v.bind_to_cls(model_dc) for k, v in existing.field_serializer.items()})
                 res.model_serializer.update({k: v.bind_to_cls(model_dc) for k, v in existing.model_serializer.items()})
+                res.model_validator.update({k: v.bind_to_cls(model_dc) for k, v in existing.model_validator.items()})
                 res.computed_fields.update({k: v.bind_to_cls(model_dc) for k, v in existing.computed_fields.items()})
 
         for var_name, var_value in vars(model_dc).items():
diff --git a/pydantic/decorators.py b/pydantic/decorators.py
--- a/pydantic/decorators.py
+++ b/pydantic/decorators.py
@@ -329,7 +329,7 @@ def root_validator(
     def dec(f: Callable[..., Any] | classmethod[Any, Any, Any] | staticmethod[Any, Any]) -> Any:
         if _decorators.is_instance_method_from_sig(f):
             raise TypeError('`@root_validator` cannot be applied to instance methods')
-        # auto apply the @classmethod decorator and warn users if we had to do so
+        # auto apply the @classmethod decorator
         res = _decorators.ensure_classmethod_based_on_signature(f)
         dec_info = _decorators.RootValidatorDecoratorInfo(mode=mode)
         return _decorators.PydanticDescriptorProxy(res, dec_info, shim=wrap)
@@ -565,6 +565,8 @@ def model_validator(
     mode: Literal['wrap', 'before', 'after'],
 ) -> Any:
     def dec(f: Any) -> _decorators.PydanticDescriptorProxy[Any]:
+        # auto apply the @classmethod decorator
+        f = _decorators.ensure_classmethod_based_on_signature(f)
         dec_info = _decorators.ModelValidatorDecoratorInfo(mode=mode)
         return _decorators.PydanticDescriptorProxy(f, dec_info)
 


Test patch:
diff --git a/tests/test_model_validator.py b/tests/test_model_validator.py
--- a/tests/test_model_validator.py
+++ b/tests/test_model_validator.py
@@ -2,6 +2,8 @@
 
 from typing import Any, Dict, cast
 
+import pytest
+
 from pydantic import BaseModel, ValidationInfo
 from pydantic.decorators import ModelWrapValidatorHandler, model_validator
 
@@ -29,13 +31,14 @@ def val_model(cls, values: Any, handler: ModelWrapValidatorHandler[Model], info:
     assert Model.model_validate(Model(x=1, y=2)).model_dump() == {'x': 3, 'y': 4}
 
 
-def test_model_validator_before() -> None:
+@pytest.mark.parametrize('classmethod_decorator', [classmethod, lambda x: x])
+def test_model_validator_before(classmethod_decorator: Any) -> None:
     class Model(BaseModel):
         x: int
         y: int
 
         @model_validator(mode='before')
-        @classmethod
+        @classmethod_decorator
         def val_model(cls, values: Any, info: ValidationInfo) -> dict[str, Any] | Model:
             assert not info.context
             if isinstance(values, dict):
@@ -66,3 +69,17 @@ def val_model(self, info: ValidationInfo) -> Model:
 
     assert Model(x=1, y=2).model_dump() == {'x': 2, 'y': 3}
     assert Model.model_validate(Model(x=1, y=2)).model_dump() == {'x': 3, 'y': 4}
+
+
+def test_subclass() -> None:
+    class Human(BaseModel):
+        @model_validator(mode='before')
+        @classmethod
+        def run_model_validator(cls, values: dict[str, Any]) -> dict[str, Any]:
+            values['age'] *= 2
+            return values
+
+    class Person(Human):
+        age: int
+
+    assert Person(age=28).age == 56