Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not warn about shadowed fields if they are not redefined in a child class #9111

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 8 additions & 3 deletions pydantic/_internal/_fields.py
Expand Up @@ -177,21 +177,26 @@ def collect_model_fields( # noqa: C901
)

# when building a generic model with `MyModel[int]`, the generic_origin check makes sure we don't get
# "... shadows an attribute" errors
# "... shadows an attribute" warnings
generic_origin = getattr(cls, '__pydantic_generic_metadata__', {}).get('origin')
for base in bases:
dataclass_fields = {
field.name for field in (dataclasses.fields(base) if dataclasses.is_dataclass(base) else ())
}
if hasattr(base, ann_name):
if base is generic_origin:
# Don't error when "shadowing" of attributes in parametrized generics
# Don't warn when "shadowing" of attributes in parametrized generics
continue

if ann_name in dataclass_fields:
# Don't error when inheriting stdlib dataclasses whose fields are "shadowed" by defaults being set
# Don't warn when inheriting stdlib dataclasses whose fields are "shadowed" by defaults being set
# on the class instance.
continue

if ann_name not in annotations:
# Don't warn when a field exists in a parent class but has not been defined in the current class
continue

warnings.warn(
f'Field name "{ann_name}" in "{cls.__qualname__}" shadows an attribute in parent '
f'"{base.__qualname__}"',
Expand Down
32 changes: 32 additions & 0 deletions tests/test_main.py
@@ -1,6 +1,7 @@
import json
import platform
import re
import warnings
from collections import defaultdict
from copy import deepcopy
from dataclasses import dataclass
Expand Down Expand Up @@ -3151,6 +3152,37 @@ class Three(One):
assert getattr(Three, 'foo', None) == ' edited! edited!'


def test_shadow_attribute_warn_for_redefined_fields() -> None:
"""https://github.com/pydantic/pydantic/issues/9107"""

# A simple class which defines a field
class Parent:
foo: bool = False

# When inheriting from the parent class, as long as the field is not defined at all, there should be no warning
# about shadowed fields.
with warnings.catch_warnings(record=True) as captured_warnings:
# Start capturing all warnings
warnings.simplefilter('always')

class ChildWithoutRedefinedField(BaseModel, Parent):
pass

# Check that no warnings were captured
assert len(captured_warnings) == 0

# But when inheriting from the parent class and a parent field is redefined, a warning should be raised about
# shadowed fields irrespective of whether it is defined with a type that is still compatible or narrower, or
# with a different default that is still compatible with the type definition.
with pytest.warns(
UserWarning,
match=r'"foo" in ".*ChildWithRedefinedField" shadows an attribute in parent ".*Parent"',
):

class ChildWithRedefinedField(BaseModel, Parent):
foo: bool = True


def test_eval_type_backport():
class Model(BaseModel):
foo: 'list[int | str]'
Expand Down