diff --git a/changes/1228-paul-ilyin.md b/changes/1228-paul-ilyin.md new file mode 100644 index 0000000..76cbfc3 --- /dev/null +++ b/changes/1228-paul-ilyin.md @@ -0,0 +1 @@ +`update_forward_refs()` method of BaseModel now copies `__dict__` of class module instead of modyfying it \ No newline at end of file diff --git a/pydantic/main.py b/pydantic/main.py index 3ace6d4..17fb874 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -646,7 +646,7 @@ class BaseModel(metaclass=ModelMetaclass): """ 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) diff --git a/tests/test_aliases.py b/tests/test_aliases.py index 751cf5e..5e0fce2 100644 --- a/tests/test_aliases.py +++ b/tests/test_aliases.py @@ -330,10 +330,7 @@ def test_alias_priority(): 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 index 3da5807..33acd4f 100644 --- a/tests/test_decorator.py +++ b/tests/test_decorator.py @@ -35,21 +35,21 @@ def test_args(): 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 test_kwargs(): 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 test_args_name(): 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 @@ def test_async(): 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 index 1121ce6..3f69ff3 100644 --- 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 @@ def test_custom_init_subclass_params(): 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$'):