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 3 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
28 changes: 28 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,33 @@ 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():
warnings.simplefilter('error')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I misunderstood the simplefilter() docs - I have adjusted it now.

Essentially follows this example from the docs, to ensure that all warnings are captured, then run the code, then check the recorded warnings list is empty.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh I see, looks good.


class ChildWithoutRedefinedField(BaseModel, Parent):
pass

# 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