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

PydanticV2 allows extra fields _without_ `--allow-extra-fields` option

Open maximilian-tech opened this issue 1 year ago • 5 comments

Describe the bug It should forbid extra_fields by default. PyDantic_v2 itself ignores extra_fields per default. So I expect datamodel-code-gen to explicitly set allow_extra_fields=False unless --allow-extra-fields is specified

To Reproduce

Example schema:

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "age": {
      "type": "integer",
      "minimum": 0
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "address": {
      "type": "object",
      "properties": {
        "street": {
          "type": "string"
        },
        "city": {
          "type": "string"
        },
        "zipcode": {
          "type": "string",
          "pattern": "^[0-9]{5}(?:-[0-9]{4})?$"
        }
      },
      "required": ["street", "city", "zipcode"]
    },
    "phoneNumbers": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^[0-9]{10}$"
      }
    }
  },
  "required": ["name", "age", "email", "address"]
}

Used commandline:

$ datamodel-codegen --input test_json.json --output test_json.py --output-model-type pydantic_v2.BaseModel

Expected behavior I want the following behavior by default

# generated by datamodel-codegen:
#   filename:  test_json.json
#   timestamp: 2024-05-29T06:01:45+00:00

from __future__ import annotations

from typing import List, Optional

from pydantic import BaseModel, ConfigDict, EmailStr, conint, constr


class Address(BaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    street: str
    city: str
    zipcode: constr(pattern=r'^[0-9]{5}(?:-[0-9]{4})?$')


class Model(BaseModel):
    model_config = ConfigDict(
        extra='forbid',
    )
    name: str
    age: conint(ge=0)
    email: EmailStr
    address: Address
    phoneNumbers: Optional[List[constr(pattern=r'^[0-9]{10}$')]] = None

What I got is this:

# generated by datamodel-codegen:
#   filename:  test_json.json
#   timestamp: 2024-05-29T06:01:45+00:00

from __future__ import annotations

from typing import List, Optional

from pydantic import BaseModel, ConfigDict, EmailStr, conint, constr


class Address(BaseModel):
    street: str
    city: str
    zipcode: constr(pattern=r'^[0-9]{5}(?:-[0-9]{4})?$')


class Model(BaseModel):
    name: str
    age: conint(ge=0)
    email: EmailStr
    address: Address
    phoneNumbers: Optional[List[constr(pattern=r'^[0-9]{10}$')]] = None

Version:

  • OS: Ubuntu 22.04 LTS
  • Python version: 3.10.12
  • datamodel-code-generator version: current master

Additional context Add any other context about the problem here.

maximilian-tech avatar May 29 '24 06:05 maximilian-tech

I encountered the same issue, but found out that setting the additionalProperties property to false in the JSON schema definitions results in a generated model with the extra attribute in the model_config set to "forbid":

model_config = ConfigDict(
    extra='forbid',
)

fkapsahili avatar Jul 10 '24 14:07 fkapsahili

Both the JSON schema and openAPI definitions are extensible by default. See the openAPI spec:

additionalProperties - Value can be boolean or object. Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema. Consistent with JSON Schema, additionalProperties defaults to true.

Therefore datamodel-codegen's only viable options are to ignore or allow extra fields and it goes with the default of ignoring them. I'd rather see it default to "allow", but defaulting to "forbid" would just be wrong.

Emerentius avatar Sep 13 '24 11:09 Emerentius

The ambiguity arises from the different layers, the JSON passes through when being processed here. Per default it does nothing/let's underlying layers decide what is what. In the case of pedanticV1 it once was the default to forbid extra fields, hence an --allow-extra-fields option was added. But PydanticV2 allows extra fields per default, now one would need a --forbid-extra-fields option. This plays a little bit the parsing vs. validating difference. But for me it would be ok, if the documentation would describe the implementation, and an additional flag would be introduced - keeping the status quo of not interfering with 'default' values.

maximilian-tech avatar Sep 13 '24 13:09 maximilian-tech

I have the same problem, when allow_extra_fields = False, the generated model does not have the following definitions (by default, extra=ignore if there are no following definitions) https://docs.pydantic.dev/latest/api/config/#

pydantic.config.ConfigDict.extra
model_config = ConfigDict(
        extra='forbid',
    )
extra instance-attribute [¶](https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.extra)

extra: [ExtraValues](https://docs.pydantic.dev/latest/api/config/#pydantic.config.ExtraValues) | None
**Whether to ignore, allow, or forbid extra attributes during model initialization. Defaults to 'ignore'.**

You can configure how pydantic handles the attributes that are not defined in the model:

allow - Allow any extra attributes.
forbid - Forbid any extra attributes.
ignore - Ignore any extra attributes.

Airpy avatar Mar 14 '25 10:03 Airpy

hi @maximilian-tech

But for me it would be ok, if the documentation would describe the implementation, and an additional flag would be introduced - keeping the status quo of not interfering with 'default' values.

would #2417 work for you?

cosmo-grant avatar Jun 10 '25 22:06 cosmo-grant