[one|any|all]Of+required schema rules produce numerous near-identical models which are then quite hard to differentiate
fiendish opened this issue · 0 comments
fiendish commented
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:
- OS: Linux
- Python version: 3.9.12
- datamodel-code-generator version: 0.25.8