Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Order of Final annotation changes behaviour of field #9904

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

Closed
1 task done
TristanSpeakEasy opened this issue Jul 16, 2024 · 2 comments · Fixed by #11479
Closed
1 task done

Order of Final annotation changes behaviour of field #9904

TristanSpeakEasy opened this issue Jul 16, 2024 · 2 comments · Fixed by #11479
Assignees
Labels
bug V2 Bug related to Pydantic V2
Milestone

Comments

@TristanSpeakEasy
Copy link

TristanSpeakEasy commented Jul 16, 2024

Initial Checks

  • I confirm that I'm using Pydantic V2

Description

For a class defined like so:

class TestModel1(BaseModel):
    model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True)

    normal_field: Annotated[str, pydantic.Field(alias="normalField")]
    SOME_CONST: Annotated[
        Final[int],
        pydantic.Field(alias="someConst"),
    ] = 9007199254740991

when dumping the model the someConst field will be present in the dumped dict.

But Mypy complains that the Final annotation should be the last in the chain.

error: Final can be only used as an outermost qualifier in a variable annotation [valid-type]

So when modifying it to this:

class TestModel2(BaseModel):
    model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True)

    normal_field: Annotated[str, pydantic.Field(alias="normalField")]
    SOME_CONST: Final[
        Annotated[
            int,
            pydantic.Field(alias="someConst"),
        ]
    ] = 9007199254740991

and dumping the model the someConst value is not longer present in the dumped dict.

I would expect that change of order here to have no material impact on behaviour and would expect it to behave as the first example.

Example Code

from typing import Final
from typing_extensions import Annotated
from pydantic import BaseModel, ConfigDict
import pydantic


class TestModel1(BaseModel):
    model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True)

    normal_field: Annotated[str, pydantic.Field(alias="normalField")]
    SOME_CONST: Annotated[
        Final[int],
        pydantic.Field(alias="someConst"),
    ] = 9007199254740991


class TestModel2(BaseModel):
    model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True)

    normal_field: Annotated[str, pydantic.Field(alias="normalField")]
    SOME_CONST: Final[
        Annotated[
            int,
            pydantic.Field(alias="someConst"),
        ]
    ] = 9007199254740991


t1 = TestModel1(normal_field="test")
t2 = TestModel2(normal_field="test")

d1 = t1.model_dump(by_alias=True)
d2 = t2.model_dump(by_alias=True)

print(d1)
print(d2)

assert d1["someConst"] == 9007199254740991
assert d2["someConst"] == 9007199254740991

Python, Pydantic & OS Version

pydantic version: 2.8.2
        pydantic-core version: 2.20.1
          pydantic-core build: profile=release pgo=true
                 install path: /home/trist/.cache/pypoetry/virtualenvs/scratch-4Ve8dmKK-py3.8/lib/python3.8/site-packages/pydantic
               python version: 3.8.19 (default, Mar 19 2024, 16:05:24)  [GCC 11.4.0]
                     platform: Linux-5.15.153.1-microsoft-standard-WSL2-x86_64-with-glibc2.34
             related packages: typing_extensions-4.12.2
                       commit: unknown
@TristanSpeakEasy TristanSpeakEasy added bug V2 Bug related to Pydantic V2 pending Is unconfirmed labels Jul 16, 2024
@TristanSpeakEasy TristanSpeakEasy changed the title Order a Final annotation changes behaviour of field Order of Final annotation changes behaviour of field Jul 16, 2024
@Viicos
Copy link
Member

Viicos commented Jul 16, 2024

This is a known mypy bug: python/mypy#12061.

I think the best path to take here is to keep using the first example an use a type: ignore comment until mypy fixes the issue.

@Viicos
Copy link
Member

Viicos commented Feb 25, 2025

So actually I went too fast when reading this. Currently, Pydantic considers final fields with a default value to be class variables. This is why in TestModel2, the field value is actually not present in the model dump (because class variables aren't serialized). This decision predates the typing specification update that specified the meaning of Final in dataclasses and dataclass-like models (see #11119).

The logic to determine if a field is final was incomplete, and that's why TestModel1 did not suffer from this issue. In 2.11, both cases will be considered as class variables, but a deprecation warning will be emitted, stating that starting in V3, they will be considered as normal fields.

In the meanwhile, you can omit the Final qualifier, and specify frozen on the Field() function instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug V2 Bug related to Pydantic V2
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants