Repository: pydantic/pydantic

Issue:
Env settings in the config for a parent field apparently don`t work
# Bug

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.5.1
            pydantic compiled: True
                 install path: /home/yury/.cache/pypoetry/virtualenvs/chatbot-py3.6/lib/python3.6/site-packages/pydantic
               python version: 3.6.9 (default, Apr 18 2020, 01:56:04)  [GCC 8.4.0]
                     platform: Linux-5.3.0-51-generic-x86_64-with-Ubuntu-18.04-bionic
     optional deps. installed: ['typing-extensions', 'email-validator']
```
If I configure an alternative prefix for a field `foo` in the same class it gets into the field configuration, but whenever I set that in the descendant it doesn't seem to have effect
```py
In [2]: from pydantic import AnyUrl, BaseSettings 
In [4]: %set_env FOO=7                                                                                                                                                                                      
env: FOO=7

In [9]: class A(BaseSettings): 
   ...:     foo : int 
   ...:     class Config: 
   ...:         env_prefix = 'PREFIX_' 
   ...:         fields = { 
   ...:             'foo': {'env': ['FOO', 'PREFIX_FOO'],}, 
   ...:         } 
   ...:          
   ...:                                                                                                                                                                                                     
In [10]: A().foo                                                                                                                                                                                            
Out[10]: 7

In [11]: A.__fields__['foo'].field_info                                                                                                                                                                     
Out[11]: FieldInfo(default=Ellipsis, extra={'env': ['FOO', 'PREFIX_FOO'], 'env_names': ['foo', 'prefix_foo']})

In [12]: class A(BaseSettings): 
    ...:     foo : int 
    ...:      
                                                                                                                                                                                              
In [13]: class B(A): 
    ...:     class Config: 
    ...:         env_prefix = 'PREFIX_' 
    ...:         fields = { 
    ...:             'foo': {'env': ['FOO', 'PREFIX_FOO'],}, 
    ...:         } 
    ...:          
    ...:          
    ...:                                                                                                                                                                                                    

In [14]: B().foo                                                                                                                                                                                            
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
<ipython-input-14-40329edc0228> in <module>
----> 1 B().foo
~/.cache/pypoetry/virtualenvs/chatbot-py3.6/lib/python3.6/site-packages/pydantic/env_settings.cpython-36m-x86_64-linux-gnu.so in pydantic.env_settings.BaseSettings.__init__()
~/.cache/pypoetry/virtualenvs/chatbot-py3.6/lib/python3.6/site-packages/pydantic/main.cpython-36m-x86_64-linux-gnu.so in pydantic.main.BaseModel.__init__()
ValidationError: 1 validation error for B
foo
  field required (type=value_error.missing)

In [15]: A.__fields__['foo']                                                                                                                                                                                
Out[15]: ModelField(name='foo', type=int, required=True)

In [16]: A.__fields__['foo'].field_info                                                                                                                                                                     
Out[16]: FieldInfo(default=Ellipsis, extra={'env_names': {'foo'}})

In [17]: B.__fields__['foo'].field_info                                                                                                                                                                     
Out[17]: FieldInfo(default=Ellipsis, extra={'env_names': {'prefix_foo'}})
```



Discussion:
Related: https://github.com/samuelcolvin/pydantic/issues/1091
Apparently, when calling
https://github.com/samuelcolvin/pydantic/blob/master/pydantic/main.py#L221
and then
https://github.com/samuelcolvin/pydantic/blob/master/pydantic/fields.py#L325 

`info_from_config`, fetched from a config, is not actually a part of the field  yet (`self.field_info`)

and thus on 

https://github.com/samuelcolvin/pydantic/blob/master/pydantic/env_settings.py#L79 

we get a default info
Thanks for pointing this out.

PR welcome to fix it, I've no idea how hard it would be to solve, maybe quite difficult.

Resolving patch:
diff --git a/pydantic/env_settings.py b/pydantic/env_settings.py
--- a/pydantic/env_settings.py
+++ b/pydantic/env_settings.py
@@ -4,7 +4,7 @@
 from typing import AbstractSet, Any, Dict, List, Mapping, Optional, Union
 
 from .fields import ModelField
-from .main import BaseModel, Extra
+from .main import BaseConfig, BaseModel, Extra
 from .typing import display_as_type
 from .utils import deep_update, sequence_like
 
@@ -65,7 +65,7 @@ def _build_environ(self, _env_file: Union[Path, str, None] = None) -> Dict[str,
             d[field.alias] = env_val
         return d
 
-    class Config:
+    class Config(BaseConfig):
         env_prefix = ''
         env_file = None
         validate_all = True
@@ -76,7 +76,9 @@ class Config:
         @classmethod
         def prepare_field(cls, field: ModelField) -> None:
             env_names: Union[List[str], AbstractSet[str]]
-            env = field.field_info.extra.get('env')
+            field_info_from_config = cls.get_field_info(field.name)
+
+            env = field_info_from_config.get('env') or field.field_info.extra.get('env')
             if env is None:
                 if field.has_alias:
                     warnings.warn(


Test patch:
diff --git a/tests/test_settings.py b/tests/test_settings.py
--- a/tests/test_settings.py
+++ b/tests/test_settings.py
@@ -1,5 +1,5 @@
 import os
-from typing import Dict, List, Set
+from typing import Dict, List, Optional, Set
 
 import pytest
 
@@ -221,6 +221,107 @@ class SettingsChild(SettingsParent):
     assert SettingsChild(foobar='abc').foobar == 'abc'
 
 
+def test_env_prefix_inheritance_config(env):
+    env.set('foobar', 'foobar')
+    env.set('prefix_foobar', 'prefix_foobar')
+
+    env.set('foobar_parent_from_field', 'foobar_parent_from_field')
+    env.set('foobar_child_from_field', 'foobar_child_from_field')
+
+    env.set('foobar_parent_from_config', 'foobar_parent_from_config')
+    env.set('foobar_child_from_config', 'foobar_child_from_config')
+
+    # . Child prefix does not override explicit parent field config
+    class Parent(BaseSettings):
+        foobar: str = Field(None, env='foobar_parent_from_field')
+
+    class Child(Parent):
+        class Config:
+            env_prefix = 'prefix_'
+
+    assert Child().foobar == 'foobar_parent_from_field'
+
+    # c. Child prefix does not override explicit parent class config
+    class Parent(BaseSettings):
+        foobar: str = None
+
+        class Config:
+            fields = {
+                'foobar': {'env': ['foobar_parent_from_config']},
+            }
+
+    class Child(Parent):
+        class Config:
+            env_prefix = 'prefix_'
+
+    assert Child().foobar == 'foobar_parent_from_config'
+
+    # d. Child prefix overrides parent with implicit config
+    class Parent(BaseSettings):
+        foobar: str = None
+
+    class Child(Parent):
+        class Config:
+            env_prefix = 'prefix_'
+
+    assert Child().foobar == 'prefix_foobar'
+
+
+def test_env_inheritance_config(env):
+    env.set('foobar', 'foobar')
+    env.set('prefix_foobar', 'prefix_foobar')
+
+    env.set('foobar_parent_from_field', 'foobar_parent_from_field')
+    env.set('foobar_child_from_field', 'foobar_child_from_field')
+
+    env.set('foobar_parent_from_config', 'foobar_parent_from_config')
+    env.set('foobar_child_from_config', 'foobar_child_from_config')
+
+    # a. Child class config overrides prefix and parent field config
+    class Parent(BaseSettings):
+        foobar: str = Field(None, env='foobar_parent_from_field')
+
+    class Child(Parent):
+        class Config:
+            env_prefix = 'prefix_'
+            fields = {
+                'foobar': {'env': ['foobar_child_from_config']},
+            }
+
+    assert Child().foobar == 'foobar_child_from_config'
+
+    # b. Child class config overrides prefix and parent class config
+    class Parent(BaseSettings):
+        foobar: str = None
+
+        class Config:
+            fields = {
+                'foobar': {'env': ['foobar_parent_from_config']},
+            }
+
+    class Child(Parent):
+        class Config:
+            env_prefix = 'prefix_'
+            fields = {
+                'foobar': {'env': ['foobar_child_from_config']},
+            }
+
+    assert Child().foobar == 'foobar_child_from_config'
+
+    # . Child class config overrides prefix and parent with implicit config
+    class Parent(BaseSettings):
+        foobar: Optional[str]
+
+    class Child(Parent):
+        class Config:
+            env_prefix = 'prefix_'
+            fields = {
+                'foobar': {'env': ['foobar_child_from_field']},
+            }
+
+    assert Child().foobar == 'foobar_child_from_field'
+
+
 def test_env_invalid(env):
     with pytest.raises(TypeError, match=r'invalid field env: 123 \(int\); should be string, list or set'):