Repository: pydantic/pydantic

Issue:
[BUG] Validator isn't called with inheritance
Code:
```python
from typing import Optional

import pydantic


class A(pydantic.BaseModel):
    login: str
    password: str


class ClientA(A):
    password: Optional[str] = None

    @pydantic.validator("login")
    def not_called(self):
        return "test"

    @pydantic.validator("password")
    def clean_up(self):
        return None


print(
    ClientA(
        login="login",
        password="password"
    )
)
```
Output:
```python
ClientA login='login' password=None
```

As you can see ```ClientA:not_called``` validator is not called but it will work if I redefine login field in ClientA, like:

Code:
```python
from typing import Optional

import pydantic


class A(pydantic.BaseModel):
    login: str
    password: str


class ClientA(A):
    login: str
    password: Optional[str] = None

    @pydantic.validator("login")
    def not_called(self):
        return "test"

    @pydantic.validator("password")
    def clean_up(self):
        return None


print(
    ClientA(
        login="login",
        password="password"
    )
)
```
Output:
```python
ClientA login='test' password=None
```


Discussion:
Not sure this is a bug, just a current limitation. Isn't that trivial to add validators to fields on inherited classes, but should be possible.
@samuelcolvin yeah, maybe. It was just a little bit of a surprise when I stumbled upon it.
I think we should do two things, raise an exception when a validator is set up wrong (eg fields don't match anything). 

Allow validators to operate on attributes of patent classes. 

Resolving patch:
diff --git a/pydantic/fields.py b/pydantic/fields.py
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -34,29 +34,27 @@ class Field:
     __slots__ = (
         'type_', 'key_type_', 'sub_fields', 'key_field', 'validators', 'whole_pre_validators', 'whole_post_validators',
         'default', 'required', 'model_config', 'name', 'alias', 'description', 'info', 'validate_always',
-        'allow_none', 'shape'
+        'allow_none', 'shape', 'class_validators'
     )
 
     def __init__(
             self, *,
             name: str,
             type_: Type,
+            class_validators: List[Validator],
+            default: Any,
+            required: bool,
+            model_config: Any,
             alias: str=None,
-            class_validators: List[Validator]=None,
-            default: Any=None,
-            required: bool=False,
             allow_none: bool=False,
-            model_config: Any=None,
             description: str=None):
 
         self.name: str = name
         self.alias: str = alias or name
         self.type_: type = type_
         self.key_type_: type = None
-        class_validators = class_validators or []
-        self.validate_always: bool = (
-            getattr(self.type_, 'validate_always', False) or any(v.always for v in class_validators)
-        )
+        self.class_validators = class_validators or []
+        self.validate_always: bool = False
         self.sub_fields: List[Field] = None
         self.key_field: Field = None
         self.validators = []
@@ -69,7 +67,7 @@ def __init__(
         self.allow_none: bool = allow_none
         self.shape: Shape = Shape.SINGLETON
         self.info = {}
-        self._prepare(class_validators)
+        self.prepare()
 
     @classmethod
     def infer(cls, *, name, value, annotation, class_validators, config):
@@ -97,18 +95,22 @@ def set_config(self, config):
     def alt_alias(self):
         return self.name != self.alias
 
-    def _prepare(self, class_validators):
+    def prepare(self):
         if self.default is not None and self.type_ is None:
             self.type_ = type(self.default)
 
         if self.type_ is None:
             raise ConfigError(f'unable to infer type for attribute "{self.name}"')
 
+        self.validate_always: bool = (
+            getattr(self.type_, 'validate_always', False) or any(v.always for v in self.class_validators)
+        )
+
         if not self.required and not self.validate_always and self.default is None:
             self.allow_none = True
 
-        self._populate_sub_fields(class_validators)
-        self._populate_validators(class_validators)
+        self._populate_sub_fields()
+        self._populate_validators()
 
         self.info = {
             'type': type_display(self.type_),
@@ -126,7 +128,7 @@ def _prepare(self, class_validators):
         # if self.description:
         #     self.info['description'] = self.description
 
-    def _populate_sub_fields(self, class_validators):
+    def _populate_sub_fields(self):
         # typing interface is horrible, we have to do some ugly checks
         origin = _get_type_origin(self.type_)
         if origin is None:
@@ -142,7 +144,7 @@ def _populate_sub_fields(self, class_validators):
                     types_.append(type_)
             self.sub_fields = [self.__class__(
                 type_=t,
-                class_validators=class_validators,
+                class_validators=self.class_validators,
                 default=self.default,
                 required=self.required,
                 allow_none=self.allow_none,
@@ -162,7 +164,7 @@ def _populate_sub_fields(self, class_validators):
             self.shape = Shape.MAPPING
             self.key_field = self.__class__(
                 type_=self.key_type_,
-                class_validators=class_validators,
+                class_validators=self.class_validators,
                 default=self.default,
                 required=self.required,
                 allow_none=self.allow_none,
@@ -174,7 +176,7 @@ def _populate_sub_fields(self, class_validators):
             # type_ has been refined eg. as the type of a List and sub_fields needs to be populated
             self.sub_fields = [self.__class__(
                 type_=self.type_,
-                class_validators=class_validators,
+                class_validators=self.class_validators,
                 default=self.default,
                 required=self.required,
                 allow_none=self.allow_none,
@@ -182,19 +184,19 @@ def _populate_sub_fields(self, class_validators):
                 model_config=self.model_config,
             )]
 
-    def _populate_validators(self, class_validators):
+    def _populate_validators(self):
         if not self.sub_fields:
             get_validators = getattr(self.type_, 'get_validators', None)
             v_funcs = (
-                *tuple(v.func for v in class_validators if not v.whole and v.pre),
+                *tuple(v.func for v in self.class_validators if not v.whole and v.pre),
                 *(get_validators() if get_validators else find_validators(self.type_)),
-                *tuple(v.func for v in class_validators if not v.whole and not v.pre),
+                *tuple(v.func for v in self.class_validators if not v.whole and not v.pre),
             )
             self.validators = self._prep_vals(v_funcs)
 
-        if class_validators:
-            self.whole_pre_validators = self._prep_vals(v.func for v in class_validators if v.whole and v.pre)
-            self.whole_post_validators = self._prep_vals(v.func for v in class_validators if v.whole and not v.pre)
+        if self.class_validators:
+            self.whole_pre_validators = self._prep_vals(v.func for v in self.class_validators if v.whole and v.pre)
+            self.whole_post_validators = self._prep_vals(v.func for v in self.class_validators if v.whole and not v.pre)
 
     def _prep_vals(self, v_funcs):
         v = []
diff --git a/pydantic/main.py b/pydantic/main.py
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -77,7 +77,7 @@ def _extract_validators(namespace):
 
 class MetaModel(ABCMeta):
     def __new__(mcs, name, bases, namespace):
-        fields = {}
+        fields: Dict[name, Field] = {}
         config = BaseConfig
         for base in reversed(bases):
             if issubclass(base, BaseModel) and base != BaseModel:
@@ -89,6 +89,11 @@ def __new__(mcs, name, bases, namespace):
 
         for f in fields.values():
             f.set_config(config)
+            extra_validators = vg.get_validators(f.name)
+            if extra_validators:
+                f.class_validators += extra_validators
+                # re-run prepare to add extra validators
+                f.prepare()
 
         annotations = namespace.get('__annotations__', {})
         # annotation only fields need to come first in fields


Test patch:
diff --git a/tests/test_validators.py b/tests/test_validators.py
--- a/tests/test_validators.py
+++ b/tests/test_validators.py
@@ -289,3 +289,39 @@ def check_b(cls, v):
                 return v
     assert str(exc_info.value) == ("Validators defined with incorrect fields: check_b "
                                    "(use check_fields=True if you're inheriting from the model and intended this)")
+
+
+def test_validate_parent():
+    class Parent(BaseModel):
+        a: str
+
+    class Child(Parent):
+        @validator('a')
+        def check_a(cls, v):
+            if 'foobar' not in v:
+                raise ValueError('"foobar" not found in a')
+            return v
+
+    assert Child(a='this is foobar good').a == 'this is foobar good'
+    with pytest.raises(ValidationError):
+        Child(a='snap')
+
+
+def test_validate_parent_extra():
+    class Parent(BaseModel):
+        a: str
+
+        @validator('a')
+        def check_a_one(cls, v):
+            if 'foobar' not in v:
+                raise ValueError('"foobar" not found in a')
+            return v
+
+    class Child(Parent):
+        @validator('a')
+        def check_a_two(cls, v):
+            return v.upper()
+
+    assert Child(a='this is foobar good').a == 'THIS IS FOOBAR GOOD'
+    with pytest.raises(ValidationError):
+        Child(a='snap')