Skip to content

Discriminator in array properties - wrong pydantic code generator #2904

@fanick1

Description

@fanick1

Describe the bug
Arrays + polymorphism via discriminator probably lead to invalid pydantic model.
I am getting

TL DR: the generated code:

external_attributes: Optional[
       List[Union[ExternalAttributeString, ExternalAttributeNumber]]
   ] = Field(None, alias='externalAttributes', discriminator='type')

should be

external_attributes: Optional[
        List[Annotated[Union[ExternalAttributeString, ExternalAttributeNumber], Field(discriminator='type')]]
    ] = Field(None, alias='externalAttributes')

I am getting

    class External(BaseModel):
.venv/lib/python3.12/site-packages/pydantic/_internal/_model_construction.py:255: in __new__
    complete_model_class(
.venv/lib/python3.12/site-packages/pydantic/_internal/_model_construction.py:648: in complete_model_class
    schema = gen_schema.generate_schema(cls)
.venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:729: in generate_schema
    schema = self._generate_schema_inner(obj)
.venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:1023: in _generate_schema_inner
    return self._model_schema(obj)
.venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:856: in _model_schema
    {k: self._generate_md_field_schema(k, v, decorators) for k, v in fields.items()},
.venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:1228: in _generate_md_field_schema
    schema, metadata = self._common_field_schema(name, field_info, decorators)
.venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:1278: in _common_field_schema
    schema = self._apply_annotations(
.venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:2227: in _apply_annotations
    schema = get_inner_schema(source_type)
.venv/lib/python3.12/site-packages/pydantic/_internal/_schema_generation_shared.py:83: in __call__
    schema = self._handler(source_type)
.venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:2213: in inner_handler
    return transform_inner_schema(schema)
.venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:1267: in set_discriminator
    schema = self._apply_discriminator_to_union(schema, field_info.discriminator)
.venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:675: in _apply_discriminator_to_union
    return _discriminated_union.apply_discriminator(
.venv/lib/python3.12/site-packages/pydantic/_internal/_discriminated_union.py:70: in apply_discriminator
    return _ApplyInferredDiscriminator(discriminator, definitions or {}).apply(schema)
.venv/lib/python3.12/site-packages/pydantic/_internal/_discriminated_union.py:164: in apply
    schema = self._apply_to_root(schema)
.venv/lib/python3.12/site-packages/pydantic/_internal/_discriminated_union.py:177: in _apply_to_root
    wrapped = self._apply_to_root(schema['schema'])
.venv/lib/python3.12/site-packages/pydantic/_internal/_discriminated_union.py:200: in _apply_to_root
    self._handle_choice(choice)
.venv/lib/python3.12/site-packages/pydantic/_internal/_discriminated_union.py:268: in _handle_choice
    raise TypeError(err_str)
E   TypeError: The core schema type 'list' is not a valid discriminated union variant. If you are making use of a list of union types, make sure the discriminator is applied to the union type and not the list (e.g. `list[Annotated[<T> | <U>, Field(discriminator=...)]]`).

To Reproduce

Example schema:

components:
 schemas:
  External:
      type: object
      properties:
        externalAttributes:
          type: array
          items:
            $ref: '#/components/schemas/ExternalAttribute'
    ExternalAttributeBase:
      type: object
      title: ExternalAttributeBase
      properties:
        key:
          description: "Attribute key"
          type: string
        type:
          description: "Attribute type (e.g. string, number, date, etc.)"
          enum:
            - string
            - number
          type: string
      required:
        - key
        - type

    ExternalAttributeString:
      allOf:
        - $ref: '#/components/schemas/ExternalAttributeBase'
        - type: object
          title: ExternalAttributeString
          properties:
            value:
              description: "Attribute value"
              type: string
          required:
            - value
    ExternalAttributeNumber:
      allOf:
        - $ref: '#/components/schemas/ExternalAttributeBase'
        - type: object
          title: ExternalAttributeNumber
          properties:
            value:
              description: "Attribute value"
              type: number
          required:
            - value

ExternalAttribute:
      oneOf:
        - $ref: '#/components/schemas/ExternalAttributeString'
        - $ref: '#/components/schemas/ExternalAttributeNumber'
      discriminator:
        propertyName: type
        mapping:
          string: '#/components/schemas/ExternalAttributeString'
          number: '#/components/schemas/ExternalAttributeNumber'

Used commandline:

$ poetry run datamodel-codegen --input src/common/openapi/codegen/domain_types.part.openapi.yaml --input-file-type openapi --openapi-scopes schemas paths --capitalise-enum-members --snake-case-field --use-field-description --allow-population-by-field-name --collapse-root-models --output-model-type=pydantic_v2.BaseModel --use-operation-id-as-name --enum-field-as-literal all --output src/common/openapi/codegen/domain_types.part_model.py

Expected behavior
The generated module (domain_types.part_model.py in this case) to contain:

List[Annotated[Union[<T1>, <T2>....], Field(discriminator=<discriminator>)]
    ] = Field(None, alias=<whatever>)

instead of:

List[Union[<T1>, <T2>....]]
    ] = Field(None, alias=<whatever>, discriminator=<discriminator>)

Version:

  • OS: [e.g. iOS] macOS 15.7.2
  • Python version: 3.12.7
  • datamodel-code-generator version: 0.25.9 - latest version 0.51.0 doesn't generate the discriminator in the Field spec for the list at all (I tried updating to the latest version after I wrote all of this and just found out)

Additional context

  • none

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions