datamodel-code-generator icon indicating copy to clipboard operation
datamodel-code-generator copied to clipboard

mypy errors due to the way classes are generated with pydantic_v2.BaseModel and --use-annotated

Open Diaoul opened this issue 11 months ago • 5 comments

Describe the bug Mypy (and pyright) are not happy about the generated classes

See https://github.com/pydantic/pydantic/issues/6713

To Reproduce

  • Generate a model with an Optional field and --use-annotated
  • Use it
  • Watch mypy complain with Missing named argument error

Expected behavior

  1. We can use the generated classes without a type checker error
  2. Classes are generated according to the workaround described in the issue

Version:

  • OS: MacOS
  • Python version: 3.11.5
  • datamodel-code-generator version: 0.25.4

Diaoul avatar Feb 29 '24 18:02 Diaoul

Here is some code that reproduce the issue with --use-annotated kind of classes

class SomeModel(BaseModel):
    some_field: Annotated[Optional[str], Field(None)]


class OtherModel(BaseModel):
    other_field: Annotated[Optional[str], Field(default=None)]


some = SomeModel()
other = OtherModel()

🔴 pyright complains about missing parameters for both classes 🔴 mypy complains about missing parameters for both classes

There is a different issue without --use-annotated kind of classes

class SomeModel(BaseModel):
    some_field: Optional[str] = Field(None)

class OtherModel(BaseModel):
    other_field: Optional[str] = Field(default=None)

some = SomeModel()
other = OtherModel()

🔴 pyright says Argument missing for parameter "some_field" but it passes for OtherModel 🤷 🟠 mypy passes only with pydantic.mypy plugin but otherwise same error as pyright

Working version (pydantic v1-like)

from pydantic import BaseModel, Field
from typing import Annotated, Optional


class SomeModel(BaseModel):
    some_field: Annotated[Optional[str], Field(None)] = None


class OtherModel(BaseModel):
    other_field: Annotated[Optional[str], Field(default=None)] = None


some = SomeModel()
other = OtherModel()

🟢 pyright passes 🟢 mypy passes

Diaoul avatar Feb 29 '24 18:02 Diaoul

I'm seeing this too. I'm a first time mypy and pydantic user, so this was a bit confusing 😕

The failure is when instantiating a class Settings:

settings = Settings()

The checks pass when Settings is defined as:

class Settings(BaseSettings):
    debug: bool = False
    prefix_path: Optional[DirectoryPath] = None

but fails when using a `Field:

class Settings(BaseSettings):
    debug: bool = False
    prefix_path: Optional[DirectoryPath] = Field(None)

The workaround for now is to suppress the error

settings = Settings() # type: ignore[call-arg]

sethrj avatar May 09 '24 12:05 sethrj

This change was introduced by https://github.com/koxudaxi/datamodel-code-generator/pull/1498.

@i404788 Could you shed some light into why the change was necessary? I've parsed the pydantic documentation and GitHub issues, and couldn't find where pydantic v2 "assign-style" defaults don't work well with Annotated fields.

Could this change maybe be behind a configuration value, like --set-defaults-in-field?

tcrasset avatar Jul 12 '24 14:07 tcrasset

@tcrasset It's a long time ago so I don't remember the exact case; I believe something like this didn't work at all with pydantic v2:

from pydantic import BaseModel, Field
from typing import Annotated, Optional

class SomeModel(BaseModel):
    some_field: Annotated[Optional[str], Field(description="")] = None

which was the default for datamodel-code-generator. I don't think I found anyone else with this issue at the time either (cause v2 was new, and the model would be trivially re-writable if done manually).

i404788 avatar Jul 12 '24 14:07 i404788

Also running into this. Pyright can't type check when we use Annotated with an optional value; it does not generate the = None to populate the default even for optional values. Using --output-model-type pydantic.BaseModel instead of --output-model-type pydantic_v2.BaseModel seems to fix.

anden-akkio avatar Jul 28 '24 15:07 anden-akkio