Repository: pydantic/pydantic

Issue:
AttributeError: 'NoneType' object has no attribute 'min_items'
# Bug

Please complete:
* OS:  Windows 10
* Python version `import sys; print(sys.version)`: 3.6.9 |Anaconda, Inc.| (default, Jul 30 2019, 14:00:49) [MSC v.1915 64 bit (AMD64)]
* Pydantic version `import pydantic; print(pydantic.VERSION)`: 1.0

I get an AttributeError while trying to generate a schema for a dictionary with values of type list.

Example:
```py
from typing import Dict, List
from pydantic import BaseModel

class SomeSchema(BaseModel):
    entries: Dict[str, List[int]]

SomeSchema.schema()
```
Which results in `AttributeError: 'NoneType' object has no attribute 'min_items'`

Is this a bug or do I need to define the schema in a different way?


Discussion:

Resolving patch:
diff --git a/pydantic/env_settings.py b/pydantic/env_settings.py
--- a/pydantic/env_settings.py
+++ b/pydantic/env_settings.py
@@ -40,7 +40,7 @@ def _build_environ(self) -> Dict[str, Optional[str]]:
 
         for field in self.__fields__.values():
             env_val: Optional[str] = None
-            for env_name in field.field_info.extra['env_names']:  # type: ignore
+            for env_name in field.field_info.extra['env_names']:
                 env_val = env_vars.get(env_name)
                 if env_val is not None:
                     break
@@ -65,9 +65,6 @@ class Config:
 
         @classmethod
         def prepare_field(cls, field: ModelField) -> None:
-            if not field.field_info:
-                return
-
             env_names: Iterable[str]
             env = field.field_info.extra.pop('env', None)
             if env is None:
diff --git a/pydantic/fields.py b/pydantic/fields.py
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -221,7 +221,7 @@ def __init__(
         self.default: Any = default
         self.required: bool = required
         self.model_config = model_config
-        self.field_info: Optional[FieldInfo] = field_info
+        self.field_info: FieldInfo = field_info or FieldInfo(default)
 
         self.allow_none: bool = False
         self.validate_always: bool = False
@@ -271,7 +271,6 @@ def set_config(self, config: Type['BaseConfig']) -> None:
         self.model_config = config
         info_from_config = config.get_field_info(self.name)
         if info_from_config:
-            self.field_info = cast(FieldInfo, self.field_info)
             self.field_info.alias = info_from_config.get('alias') or self.field_info.alias or self.name
             self.alias = cast(str, self.field_info.alias)
 
diff --git a/pydantic/schema.py b/pydantic/schema.py
--- a/pydantic/schema.py
+++ b/pydantic/schema.py
@@ -179,20 +179,15 @@ def field_schema(
     """
     ref_prefix = ref_prefix or default_prefix
     schema_overrides = False
-    field_info = cast(FieldInfo, field.field_info)
-    s = dict(title=field_info.title or field.alias.title().replace('_', ' '))
-    if field_info.title:
+    s = dict(title=field.field_info.title or field.alias.title().replace('_', ' '))
+    if field.field_info.title:
         schema_overrides = True
 
-    if field_info.description:
-        s['description'] = field_info.description
+    if field.field_info.description:
+        s['description'] = field.field_info.description
         schema_overrides = True
 
-    if (
-        not field.required
-        and not (field.field_info is not None and field.field_info.const)
-        and field.default is not None
-    ):
+    if not field.required and not field.field_info.const and field.default is not None:
         s['default'] = encode_default(field.default)
         schema_overrides = True
 
@@ -251,9 +246,8 @@ def get_field_schema_validations(field: ModelField) -> Dict[str, Any]:
                 f_schema[keyword] = attr
     if field.field_info is not None and field.field_info.const:
         f_schema['const'] = field.default
-    field_info = cast(FieldInfo, field.field_info)
-    if field_info.extra:
-        f_schema.update(field_info.extra)
+    if field.field_info.extra:
+        f_schema.update(field.field_info.extra)
     return f_schema
 
 
@@ -396,11 +390,10 @@ def field_type_schema(
         s: Dict[str, Any] = {'type': 'array', 'items': f_schema}
         if field.shape in {SHAPE_SET, SHAPE_FROZENSET}:
             s['uniqueItems'] = True
-        field_info = cast(FieldInfo, field.field_info)
-        if field_info.min_items is not None:
-            s['minItems'] = field_info.min_items
-        if field_info.max_items is not None:
-            s['maxItems'] = field_info.max_items
+        if field.field_info.min_items is not None:
+            s['minItems'] = field.field_info.min_items
+        if field.field_info.max_items is not None:
+            s['maxItems'] = field.field_info.max_items
         return s, definitions, nested_models
     elif field.shape == SHAPE_MAPPING:
         dict_schema: Dict[str, Any] = {'type': 'object'}


Test patch:
diff --git a/tests/test_schema.py b/tests/test_schema.py
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -1627,3 +1627,21 @@ class Model(BaseModel):
     with pytest.raises(ValidationError) as exc_info:
         Model(foo=1)
     assert exc_info.value.errors() == [{'loc': ('foo',), 'msg': 'value is not a valid list', 'type': 'type_error.list'}]
+
+
+def test_subfield_field_info():
+    class MyModel(BaseModel):
+        entries: Dict[str, List[int]]
+
+    assert MyModel.schema() == {
+        'title': 'MyModel',
+        'type': 'object',
+        'properties': {
+            'entries': {
+                'title': 'Entries',
+                'type': 'object',
+                'additionalProperties': {'type': 'array', 'items': {'type': 'integer'}},
+            }
+        },
+        'required': ['entries'],
+    }