Describe the bug
Combining [one|all|any]Of + required generates excessive near-identical models instead of treating them as root validation rules. This makes the output relatively quite verbose and significantly more difficult to debug.
To Reproduce
Example schema:
type: object
properties:
a:
type: string
b:
type: string
c:
type: string
d:
type: string
anyOf:
- required:
- a
- required:
- b
Used commandline:
$ datamodel-codegen --reuse-model --output-model-type pydantic_v2.BaseModel --target-python-version 3.9 --use-subclass-enum --collapse-root-models --allow-extra-fields --input openapi/components/schemas/test.yaml --output test.py
Generated output:
from __future__ import annotations
from typing import Optional, Union
from pydantic import BaseModel, ConfigDict, RootModel
class Model1(BaseModel):
model_config = ConfigDict(
extra='allow',
)
a: str
b: Optional[str] = None
c: Optional[str] = None
d: Optional[str] = None
class Model2(BaseModel):
model_config = ConfigDict(
extra='allow',
)
a: Optional[str] = None
b: str
c: Optional[str] = None
d: Optional[str] = None
class Model(RootModel[Union[Model1, Model2]]):
root: Union[Model1, Model2]
Expected behavior
I believe it would be better in cases like this if the output were as follows with the rules recorded as rules instead of model clones
from pydantic import BaseModel, root_validator, ValidationError
from typing import Optional
class Model(BaseModel):
model_config = ConfigDict(
extra='allow',
)
a: Optional[str] = None
b: Optional[str] = None
c: Optional[str] = None
d: Optional[str] = None
@root_validator(pre=True)
def at_least_one_field(cls, values):
if not any(values.get(field) for field in ['a', 'b']):
raise ValueError('At least one of "a", "b" must be provided')
return values
Keep in mind that the above is a stripped-down compact test. In practice the output is substantially more difficult to evaluate, e.g....
class Model1(BaseModel):
model_config = ConfigDict(
extra='allow',
)
a: str = Field(
...,
description='...',
examples=['...'],
)
b: Optional[str] = Field(
None,
description='...',
examples=['...'],
)
c: Optional[str] = Field(
None, description='...',
)
d:: Optional[str] = Field(
None, description='...',
)
e: Optional[str] = Field(
None, description='...',
)
f: Optional[str] = Field(
None,
description='...',
examples=['...'],
)
g: Optional[str] = Field(
None,
description='...',
examples=['...'],
)
h: Optional[str] = Field(
None,
description='...',
examples=['...'],
)
i: Optional[str] = Field(
None,
description='...',
examples=['...'],
)
j: Optional[str] = Field(
None,
description='...',
examples=['...'],
)
k: Optional[str] = Field(
None,
description='...',
)
l: Optional[bool] = Field(
None,
description='...',
examples=[False],
)
m: Optional[str] = Field(
None, description='...',
)
n: Optional[str] = Field(
None, description='...',
)
class Model2(BaseModel):
model_config = ConfigDict(
extra='allow',
)
a: Optional[str] = Field(
None,
description='...',
examples=['...'],
)
b: str = Field(
...,
description='...',
examples=['...'],
)
c: Optional[str] = Field(
None, description='...',
)
d:: Optional[str] = Field(
None, description='...',
)
e: Optional[str] = Field(
None, description='...',
)
f: Optional[str] = Field(
None,
description='...',
examples=['...'],
)
g: Optional[str] = Field(
None,
description='...',
examples=['...'],
)
h: Optional[str] = Field(
None,
description='...',
examples=['...'],
)
i: Optional[str] = Field(
None,
description='...',
examples=['...'],
)
j: Optional[str] = Field(
None,
description='...',
examples=['...'],
)
k: Optional[str] = Field(
None,
description='...',
)
l: Optional[bool] = Field(
None,
description='...',
examples=[False],
)
m: Optional[str] = Field(
None, description='...',
)
n: Optional[str] = Field(
None, description='...',
)
Version:
Pay now to fund the work behind this issue.
Get updates on progress being made.
Maintainer is rewarded once the issue is completed.
You're funding impactful open source efforts
You want to contribute to this effort
You want to get funding like this too