Repository: pydantic/pydantic

Issue:
const validation with list gives unexpected error
# Feature Request | Bug

For bugs/questions:
* OS: Linux
* Python version `import sys; print(sys.version)`: 3.7.3
* Pydantic version `import pydantic; print(pydantic.VERSION)`: 0.29

I expect the following test (not yet in code base) to succeed

```py
def test_const_list_with_wrong_value():
    class SubModel(BaseModel):
        b: int

    class Model(BaseModel):
        a: List[SubModel] = Schema([{"b": 1}, {"b": 2}, {"b": 3}], const=True)

    with pytest.raises(ValidationError) as exc_info:
        Model(a=[{"b": 3}, {"b": 1}, {"b": 2}])

    assert exc_info.value.errors() == [
        {
            'ctx': {'const': SubModel(b=1), 'given': SubModel(b=3)},
            'loc': ('a', 0),
            'msg': "expected constant value {'b': 1}",
            'type': 'value_error.const',
        },
        {
            'ctx': {'const': SubModel(b=2), 'given': SubModel(b=1)},
            'loc': ('a', 1),
            'msg': "expected constant value {'b': 2}",
            'type': 'value_error.const',
        },
        {
            'ctx': {'const': SubModel(b=3), 'given': SubModel(b=2)},
            'loc': ('a', 2),
            'msg': "expected constant value {'b': 3}",
            'type': 'value_error.const',
        },
    ]
```
However it fails with:

```
E       assert [{'ctx': {'co...error.const'}] == [{'ctx': {'con...error.const'}]
E         At index 0 diff: {'loc': ('a', 0), 'msg': "expected constant value [{'b': 1}, {'b': 2}, {'b': 3}]", 'type': 'value_error.const', 'ctx': {'given': <SubModel b=3>, 'const': [{'b': 1}, {'b': 2}, {'b': 3}]}} != {'ctx': {'const': <SubModel b=1>, 'given': <SubModel b=3>}, 'loc': ('a', 0), 'msg': "expected constant value {'b': 1}", 'type': 'value_error.const'}
E         Full diff:
E         - [{'ctx': {'const': [{'b': 1}, {'b': 2}, {'b': 3}], 'given': <SubModel b=3>},
E         + [{'ctx': {'const': <SubModel b=1>, 'given': <SubModel b=3>},
E         'loc': ('a', 0),
E         -   'msg': "expected constant value [{'b': 1}, {'b': 2}, {'b': 3}]",
E         ?                                   -        ---------------------
E         +   'msg': "expected constant value {'b': 1}",
E         'type': 'value_error.const'},
E         -  {'ctx': {'const': [{'b': 1}, {'b': 2}, {'b': 3}], 'given': <SubModel b=1>},
E         +  {'ctx': {'const': <SubModel b=2>, 'given': <SubModel b=1>},
E         'loc': ('a', 1),
E         -   'msg': "expected constant value [{'b': 1}, {'b': 2}, {'b': 3}]",
E         ?                                   -      ----------  -----------
E         +   'msg': "expected constant value {'b': 2}",
E         'type': 'value_error.const'},
E         -  {'ctx': {'const': [{'b': 1}, {'b': 2}, {'b': 3}], 'given': <SubModel b=2>},
E         +  {'ctx': {'const': <SubModel b=3>, 'given': <SubModel b=2>},
E         'loc': ('a', 2),
E         -   'msg': "expected constant value [{'b': 1}, {'b': 2}, {'b': 3}]",
E         ?                                   -      --------------------  -
E         +   'msg': "expected constant value {'b': 3}",
E         'type': 'value_error.const'}]
```

The issue here is that the error contains the complete list value as expected value while the error is per sub field (and the expected value is the sub field only):

```
        {
            'ctx': {'const': [{'b': 1}, {'b': 2}, {'b': 3}], 'given': SubModel(b=3)},
            'loc': ('a', 0),
            'msg': "expected constant value [{'b': 1}, {'b': 2}, {'b': 3}]",
            'type': 'value_error.const',
        },
```


Discussion:
please try master, since this might have been fixed/changed by #582
Same on master. wording has indeed changed but there is still a difference between the given and permitted where the former shows a single item and the latter shows the whole list...
Thanks for checking, PR welcome.

Resolving patch:
diff --git a/pydantic/error_wrappers.py b/pydantic/error_wrappers.py
--- a/pydantic/error_wrappers.py
+++ b/pydantic/error_wrappers.py
@@ -105,7 +105,7 @@ def flatten_errors(
             else:
                 yield error.dict(config, loc_prefix=loc)
         elif isinstance(error, list):
-            yield from flatten_errors(error, config)
+            yield from flatten_errors(error, config, loc=loc)
         else:
             raise RuntimeError(f'Unknown error object: {error}')
 
diff --git a/pydantic/fields.py b/pydantic/fields.py
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -106,8 +106,8 @@ def __init__(
         self.sub_fields: Optional[List[Field]] = None
         self.key_field: Optional[Field] = None
         self.validators: 'ValidatorsList' = []
-        self.whole_pre_validators: Optional['ValidatorsList'] = None
-        self.whole_post_validators: Optional['ValidatorsList'] = None
+        self.whole_pre_validators: 'ValidatorsList' = []
+        self.whole_post_validators: 'ValidatorsList' = []
         self.parse_json: bool = False
         self.shape: int = SHAPE_SINGLETON
         self.prepare()
@@ -253,9 +253,8 @@ def _populate_sub_fields(self) -> None:  # noqa: C901 (ignore complexity)
         else:
             raise TypeError(f'Fields of type "{origin}" are not supported.')
 
-        if getattr(self.type_, '__origin__', None):
-            # type_ has been refined eg. as the type of a List and sub_fields needs to be populated
-            self.sub_fields = [self._create_sub_type(self.type_, '_' + self.name)]
+        # type_ has been refined eg. as the type of a List and sub_fields needs to be populated
+        self.sub_fields = [self._create_sub_type(self.type_, '_' + self.name)]
 
     def _create_sub_type(self, type_: AnyType, name: str, *, for_keys: bool = False) -> 'Field':
         return self.__class__(
@@ -272,13 +271,16 @@ def _populate_validators(self) -> None:
             v_funcs = (
                 *[v.func for v in class_validators_ if not v.whole and v.pre],
                 *(get_validators() if get_validators else list(find_validators(self.type_, self.model_config))),
-                self.schema is not None and self.schema.const and constant_validator,
                 *[v.func for v in class_validators_ if not v.whole and not v.pre],
             )
             self.validators = self._prep_vals(v_funcs)
 
+        # Add const validator
+        if self.schema is not None and self.schema.const:
+            self.whole_pre_validators = self._prep_vals([constant_validator])
+
         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_pre_validators.extend(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)
 
     @staticmethod


Test patch:
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -397,6 +397,112 @@ class Model(BaseModel):
     ]
 
 
+def test_const_list():
+    class SubModel(BaseModel):
+        b: int
+
+    class Model(BaseModel):
+        a: List[SubModel] = Schema([SubModel(b=1), SubModel(b=2), SubModel(b=3)], const=True)
+        b: List[SubModel] = Schema([{'b': 4}, {'b': 5}, {'b': 6}], const=True)
+
+    m = Model()
+    assert m.a == [SubModel(b=1), SubModel(b=2), SubModel(b=3)]
+    assert m.b == [SubModel(b=4), SubModel(b=5), SubModel(b=6)]
+    assert m.schema() == {
+        'definitions': {
+            'SubModel': {
+                'properties': {'b': {'title': 'B', 'type': 'integer'}},
+                'required': ['b'],
+                'title': 'SubModel',
+                'type': 'object',
+            }
+        },
+        'properties': {
+            'a': {
+                'const': [SubModel(b=1), SubModel(b=2), SubModel(b=3)],
+                'items': {'$ref': '#/definitions/SubModel'},
+                'title': 'A',
+                'type': 'array',
+            },
+            'b': {
+                'const': [{'b': 4}, {'b': 5}, {'b': 6}],
+                'items': {'$ref': '#/definitions/SubModel'},
+                'title': 'B',
+                'type': 'array',
+            },
+        },
+        'title': 'Model',
+        'type': 'object',
+    }
+
+
+def test_const_list_with_wrong_value():
+    class SubModel(BaseModel):
+        b: int
+
+    class Model(BaseModel):
+        a: List[SubModel] = Schema([SubModel(b=1), SubModel(b=2), SubModel(b=3)], const=True)
+        b: List[SubModel] = Schema([{'b': 4}, {'b': 5}, {'b': 6}], const=True)
+
+    with pytest.raises(ValidationError) as exc_info:
+        Model(a=[{'b': 3}, {'b': 1}, {'b': 2}], b=[{'b': 6}, {'b': 5}])
+
+    assert exc_info.value.errors() == [
+        {
+            'ctx': {
+                'given': [{'b': 3}, {'b': 1}, {'b': 2}],
+                'permitted': [[SubModel(b=1), SubModel(b=2), SubModel(b=3)]],
+            },
+            'loc': ('a',),
+            'msg': 'unexpected value; permitted: [<SubModel b=1>, <SubModel b=2>, <SubModel b=3>]',
+            'type': 'value_error.const',
+        },
+        {
+            'ctx': {'given': [{'b': 6}, {'b': 5}], 'permitted': [[{'b': 4}, {'b': 5}, {'b': 6}]]},
+            'loc': ('b',),
+            'msg': "unexpected value; permitted: [{'b': 4}, {'b': 5}, {'b': 6}]",
+            'type': 'value_error.const',
+        },
+    ]
+
+    with pytest.raises(ValidationError) as exc_info:
+        Model(a=[SubModel(b=3), SubModel(b=1), SubModel(b=2)], b=[SubModel(b=3), SubModel(b=1)])
+
+    assert exc_info.value.errors() == [
+        {
+            'ctx': {
+                'given': [SubModel(b=3), SubModel(b=1), SubModel(b=2)],
+                'permitted': [[SubModel(b=1), SubModel(b=2), SubModel(b=3)]],
+            },
+            'loc': ('a',),
+            'msg': 'unexpected value; permitted: [<SubModel b=1>, <SubModel b=2>, <SubModel b=3>]',
+            'type': 'value_error.const',
+        },
+        {
+            'ctx': {'given': [SubModel(b=3), SubModel(b=1)], 'permitted': [[{'b': 4}, {'b': 5}, {'b': 6}]]},
+            'loc': ('b',),
+            'msg': "unexpected value; permitted: [{'b': 4}, {'b': 5}, {'b': 6}]",
+            'type': 'value_error.const',
+        },
+    ]
+
+
+def test_const_validation_json_serializable():
+    class SubForm(BaseModel):
+        field: int
+
+    class Form(BaseModel):
+        field1: SubForm = Schema({'field': 2}, const=True)
+        field2: List[SubForm] = Schema([{'field': 2}], const=True)
+
+    with pytest.raises(ValidationError) as exc_info:
+        # Fails
+        Form(field1={'field': 1}, field2=[{'field': 1}])
+
+    # This should not raise an Json error
+    exc_info.value.json()
+
+
 class ValidateAssignmentModel(BaseModel):
     a: int = 2
     b: constr(min_length=1)