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

Explicitly raise an error if field names clashes with types #8243

Merged
merged 9 commits into from Jan 15, 2024
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
26 changes: 26 additions & 0 deletions docs/errors/usage_errors.md
Expand Up @@ -1018,5 +1018,31 @@ except PydanticUserError as exc_info:
assert exc_info.code == 'root-model-extra'
```

## Cannot evaluate type annotation {#unevaluable-type-annotation}

Because type annotations are evaluated *after* assignments, you might get unexpected results when using a type annotation name
that clashes with one of your fields. We raise an error in the following case:

```py
from datetime import date

from pydantic import BaseModel

class Model(BaseModel):
date: date = Field(description="A date")
sydney-runkle marked this conversation as resolved.
Show resolved Hide resolved
```

As a workaround, you can either use an alias or change your import:

```py
import datetime
# Or `from datetime import date as _date`

from pydantic import BaseModel

class Model(BaseModel):
date: datetime.date = Field(description="A date")

sydney-runkle marked this conversation as resolved.
Show resolved Hide resolved
```

{% endraw %}
1 change: 1 addition & 0 deletions pydantic/errors.py
Expand Up @@ -58,6 +58,7 @@
'invalid_annotated_type',
'type-adapter-config-unused',
'root-model-extra',
'unevaluable-type-annotation',
]


Expand Down
7 changes: 7 additions & 0 deletions pydantic/fields.py
Expand Up @@ -321,6 +321,13 @@ class MyModel(pydantic.BaseModel):
Returns:
A field object with the passed values.
"""
if annotation is default:
raise PydanticUserError(
'Error when building FieldInfo from annotated attribute. '
"Make sure you don't have any field name clashing with a type annotation ",
code='unevaluable-type-annotation',
)

final = False
if _typing_extra.is_finalvar(annotation):
final = True
Expand Down
18 changes: 8 additions & 10 deletions tests/test_edge_cases.py
Expand Up @@ -1375,19 +1375,17 @@ class FooBar:
pass

class Model(BaseModel):
a: int = int
b: Type[int]
c: Type[int] = int
d: FooBar = FooBar
e: Type[FooBar]
f: Type[FooBar] = FooBar
g: Sequence[Type[FooBar]] = [FooBar]
h: Union[Type[FooBar], Sequence[Type[FooBar]]] = FooBar
i: Union[Type[FooBar], Sequence[Type[FooBar]]] = [FooBar]
Comment on lines 1377 to -1386
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As said here: I just removed the now invalid cases

a: Type[int]
b: Type[int] = int
c: Type[FooBar]
d: Type[FooBar] = FooBar
e: Sequence[Type[FooBar]] = [FooBar]
f: Union[Type[FooBar], Sequence[Type[FooBar]]] = FooBar
g: Union[Type[FooBar], Sequence[Type[FooBar]]] = [FooBar]

model_config = dict(arbitrary_types_allowed=True)

assert Model.model_fields.keys() == set('abcdefghi')
assert Model.model_fields.keys() == set('abcdefg')


def test_assign_type():
Expand Down
16 changes: 15 additions & 1 deletion tests/test_fields.py
@@ -1,7 +1,7 @@
import pytest

import pydantic.dataclasses
from pydantic import BaseModel, Field, RootModel, ValidationError, fields
from pydantic import BaseModel, Field, PydanticUserError, RootModel, ValidationError, fields


def test_field_info_annotation_keyword_argument():
Expand All @@ -17,6 +17,20 @@ def test_field_info_annotation_keyword_argument():
assert e.value.args == ('"annotation" is not permitted as a Field keyword argument',)


def test_field_info_annotated_attribute_name_clashing():
"""This tests that `FieldInfo.from_annotated_attribute` will raise a `PydanticUserError` if attribute names clashes
with a type.
"""

with pytest.raises(PydanticUserError):

class SubModel(BaseModel):
a: int = 1

class Model(BaseModel):
SubModel: SubModel = Field()


def test_init_var_field():
@pydantic.dataclasses.dataclass
class Foo:
Expand Down