koxudaxi / datamodel-code-generator

Pydantic model and dataclasses.dataclass generator for easy conversion of JSON, OpenAPI, JSON Schema, and YAML data sources.
https://koxudaxi.github.io/datamodel-code-generator/
MIT License
2.73k stars 298 forks source link

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

Open Diaoul opened 8 months ago

Diaoul commented 8 months ago

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

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

To Reproduce

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:

Diaoul commented 8 months ago

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

sethrj commented 5 months ago

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]
tcrasset commented 3 months ago

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?

i404788 commented 3 months ago

@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).

anden-akkio commented 3 months ago

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.