Repository: pydantic/pydantic

Issue:
update_forward_refs modifies __dict__ of a module
# Bug

```
OS: macOS Catalina 10.15.3  
python: 3.7.3
pydantic version: 1.1
```
<!-- or if you're using pydantic prior to v1.3, manually include: OS, python version and pydantic version -->

<!-- Please read the [docs](https://pydantic-docs.helpmanual.io/) and search through issues to
confirm your bug hasn't already been reported. -->

<!-- Where possible please include a self-contained code snippet describing your bug: -->
After creating a BaseModel subclass and calling `update_forward_ref()` classmethod, pydantic can modify __dict__ of a module in which the model was created if the model was declared inside some function of this module. 
```py

from typing import Optional, ForwardRef
from pydantic import BaseModel

def first_func():
    class Mod(BaseModel):
        field: Optional[ForwardRef("Mod")]
    Mod.update_forward_refs()
    return Mod

def second_func():
    class Mod(BaseModel):
        another_field: Optional[ForwardRef("Mod")]

    Mod.update_forward_refs()
    
    return Mod

first_model = first_func()
second_model = second_func()

print(second_model.__fields__["another_field"].type_)
# Output: <class '__main__.first_func.<locals>.Mod'>
# Expected: <class '__main__.second_func.<locals>.Mod'>
```

It happens because of these two lines: https://github.com/samuelcolvin/pydantic/blob/645e5fe6a0c74c95c24410571e9bb804af3eb677/pydantic/main.py#L632-L633  
They can be fixed that way:  
```py
        globalns = sys.modules[cls.__module__].__dict__.copy()
        globalns.setdefault(cls.__name__, cls)
```

This bug can lead to errors when two different models with the same names are defined in two different test cases or some other errors which can be hard to debug.


Discussion:
thanks for reporting, PR welcome.

Resolving patch:
diff --git a/pydantic/main.py b/pydantic/main.py
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -644,7 +644,7 @@ def update_forward_refs(cls, **localns: Any) -> None:
         """
         Try to update ForwardRefs on fields based on this Model, globalns and localns.
         """
-        globalns = sys.modules[cls.__module__].__dict__
+        globalns = sys.modules[cls.__module__].__dict__.copy()
         globalns.setdefault(cls.__name__, cls)
         for f in cls.__fields__.values():
             update_field_forward_refs(f, globalns=globalns, localns=localns)


Test patch:
diff --git a/tests/test_aliases.py b/tests/test_aliases.py
--- a/tests/test_aliases.py
+++ b/tests/test_aliases.py
@@ -330,10 +330,7 @@ class Child(Parent):
         a: str = Field(..., alias='a_field_child')
 
         class Config:
-            fields = {
-                'a': dict(alias='a_config_child'),
-                'b': dict(alias='b_config_child'),
-            }
+            fields = {'a': dict(alias='a_config_child'), 'b': dict(alias='b_config_child')}
 
             @staticmethod
             def alias_generator(x):
diff --git a/tests/test_decorator.py b/tests/test_decorator.py
--- a/tests/test_decorator.py
+++ b/tests/test_decorator.py
@@ -35,21 +35,21 @@ def foo(a: int, b: int):
         foo(1, 'x')
 
     assert exc_info.value.errors() == [
-        {'loc': ('b',), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'},
+        {'loc': ('b',), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}
     ]
 
     with pytest.raises(ValidationError) as exc_info:
         foo(1, 2, 3)
 
     assert exc_info.value.errors() == [
-        {'loc': ('args',), 'msg': '2 positional arguments expected but 3 given', 'type': 'type_error'},
+        {'loc': ('args',), 'msg': '2 positional arguments expected but 3 given', 'type': 'type_error'}
     ]
 
     with pytest.raises(ValidationError) as exc_info:
         foo(1, 2, apple=3)
 
     assert exc_info.value.errors() == [
-        {'loc': ('kwargs',), 'msg': "unexpected keyword argument: 'apple'", 'type': 'type_error'},
+        {'loc': ('kwargs',), 'msg': "unexpected keyword argument: 'apple'", 'type': 'type_error'}
     ]
 
 
@@ -87,7 +87,7 @@ def foo(*, a: int, b: int):
         foo(a=1, b='x')
 
     assert exc_info.value.errors() == [
-        {'loc': ('b',), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'},
+        {'loc': ('b',), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}
     ]
 
     with pytest.raises(ValidationError) as exc_info:
@@ -141,7 +141,7 @@ def foo(a, b, /, c=None):
             'loc': ('v__positional_only',),
             'msg': "positional-only argument passed as keyword argument: 'b'",
             'type': 'type_error',
-        },
+        }
     ]
     with pytest.raises(ValidationError) as exc_info:
         module.foo(a=1, b=2)
@@ -150,7 +150,7 @@ def foo(a, b, /, c=None):
             'loc': ('v__positional_only',),
             'msg': "positional-only arguments passed as keyword arguments: 'a', 'b'",
             'type': 'type_error',
-        },
+        }
     ]
 
 
@@ -165,19 +165,19 @@ def foo(args: int, kwargs: int):
     with pytest.raises(ValidationError) as exc_info:
         foo(1, 2, apple=4)
     assert exc_info.value.errors() == [
-        {'loc': ('v__kwargs',), 'msg': "unexpected keyword argument: 'apple'", 'type': 'type_error'},
+        {'loc': ('v__kwargs',), 'msg': "unexpected keyword argument: 'apple'", 'type': 'type_error'}
     ]
 
     with pytest.raises(ValidationError) as exc_info:
         foo(1, 2, apple=4, banana=5)
     assert exc_info.value.errors() == [
-        {'loc': ('v__kwargs',), 'msg': "unexpected keyword arguments: 'apple', 'banana'", 'type': 'type_error'},
+        {'loc': ('v__kwargs',), 'msg': "unexpected keyword arguments: 'apple', 'banana'", 'type': 'type_error'}
     ]
 
     with pytest.raises(ValidationError) as exc_info:
         foo(1, 2, 3)
     assert exc_info.value.errors() == [
-        {'loc': ('v__args',), 'msg': '2 positional arguments expected but 3 given', 'type': 'type_error'},
+        {'loc': ('v__args',), 'msg': '2 positional arguments expected but 3 given', 'type': 'type_error'}
     ]
 
 
@@ -202,9 +202,7 @@ async def run():
     loop.run_until_complete(run())
     with pytest.raises(ValidationError) as exc_info:
         loop.run_until_complete(foo('x'))
-    assert exc_info.value.errors() == [
-        {'loc': ('b',), 'msg': 'field required', 'type': 'value_error.missing'},
-    ]
+    assert exc_info.value.errors() == [{'loc': ('b',), 'msg': 'field required', 'type': 'value_error.missing'}]
 
 
 def test_string_annotation():
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -1,5 +1,6 @@
+import sys
 from enum import Enum
-from typing import Any, Callable, ClassVar, List, Mapping, Type
+from typing import Any, Callable, ClassVar, List, Mapping, Optional, Type
 from uuid import UUID, uuid4
 
 import pytest
@@ -995,6 +996,15 @@ class NewModel(DerivedModel, something=2):
     assert NewModel.something == 2
 
 
+def test_update_forward_refs_does_not_modify_module_dict():
+    class MyModel(BaseModel):
+        field: Optional['MyModel']
+
+    MyModel.update_forward_refs()
+
+    assert 'MyModel' not in sys.modules[MyModel.__module__].__dict__
+
+
 def test_two_defaults():
     with pytest.raises(ValueError, match='^cannot specify both default and default_factory$'):