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

Discriminated union with a List: annotation is on the list, not the union

Open colmanhumphrey opened this issue 1 year ago • 1 comments

Describe the bug

Generation of list/array of discriminated union annotates the list, not the union. Same problem with = Field(..., discriminator) too if you don't use annotated.

To Reproduce

Example schema:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "SomeTest",
  "type": "object",
  "required": [
    "some_array"
  ],
  "properties": {
    "some_array": {
      "type": "array",
      "items": {
        "discriminator": {
          "propertyName": "discrim",
          "mapping": {
            "Type1": "#/definitions/FeatureType1",
            "Type2": "#/definitions/FeatureType2"
          }
        },
        "oneOf": [
          {
          "$ref": "#/definitions/FeatureType1"
        },
          {
          "$ref": "#/definitions/FeatureType2"
        }
        ]
      }
    }
  },
  "definitions": {
    "FeatureType1": {
      "type": "object",
      "properties": {
        "discrim": {
          "type": "string"
        },
        "id": {
          "type": "string"
        }
      }
    },
    "FeatureType2": {
      "type": "object",
      "properties": {
        "discrim": {
          "type": "string"
        },
        "id": {
          "type": "string"
        },
        "something_else": {
          "type": "string"
        }
      }
    }
  }
}

Used commandline:

$ datamodel-codegen --disable-timestamp --use-annotated --collapse-root-models --target-python-version '3.11' --input ../schema.json --input-file-type jsonschema --output whatever.py

This gives:

# generated by datamodel-codegen:
#   filename:  schema.json

from __future__ import annotations

from typing import Annotated, List, Literal, Optional, Union

from pydantic import BaseModel, Field


class FeatureType1(BaseModel):
    discrim: Literal['Type1']
    id: Optional[str] = None


class FeatureType2(BaseModel):
    discrim: Literal['Type2']
    id: Optional[str] = None
    something_else: Optional[str] = None


class SomeTest(BaseModel):
    some_array: Annotated[
        List[Union[FeatureType1, FeatureType2]], Field(discriminator='discrim')
    ]

This doesn't work:

TypeError: `discriminator` can only be used with `Union` type with more than one variant`

Expected behavior

The problem is that the Annotation should be on the Union, not on the list, so the SomeTest class should be:

class SomeTest(BaseModel):
    some_array: List[
        Annotated[Union[FeatureType1, FeatureType2], Field(discriminator='discrim')]
    ]

And now it works. Note that we get more or less the same problem if you remove --use-annotated

Version:

  • OS: Windows 11
  • Python version: 3.11
  • datamodel-code-generator version: 0.25.4

Additional context

Thanks!

colmanhumphrey avatar Apr 27 '24 00:04 colmanhumphrey

That behavior was ignored by Pydantic until 2.8.2, but starting 2.9.0 it end up with the error on generated model:

TypeError: 'list' is not a valid discriminated union variant; should be a BaseModelordataclass``

Maybe there is workaround to bypass it?

quizmoon avatar Sep 19 '24 16:09 quizmoon

This is blocking us from upgrading pydantic, does anyone have any workaround?

pcavalar avatar Oct 21 '24 12:10 pcavalar

This is blocking us from upgrading pydantic, does anyone have any workaround?

the same issue :/

quizmoon avatar Oct 21 '24 13:10 quizmoon

this is huge

hanayashiki avatar Dec 25 '24 06:12 hanayashiki

We just wrote scripts that processed the generated file into a working one and added them to the build/make files. Not ideal, but gets the job done

colmanhumphrey avatar Jan 16 '25 22:01 colmanhumphrey

Same issue here

egerard-ARC avatar Jan 21 '25 10:01 egerard-ARC

PR welcome.

gaborbernat avatar Feb 06 '25 20:02 gaborbernat

I have found that this problem only occurs if you use --collapse-root-models option. Without this option, the generated models seem to import fine with newer pydantic2.

gjcarneiro avatar Apr 14 '25 15:04 gjcarneiro

I have found that this problem only occurs if you use --collapse-root-models option. Without this option, the generated models seem to import fine with newer pydantic2.

That's what I was missing when investigating thank you! I've opened a PR with a small change that fixes my case but I'll need maintainer input on whether or not it's a good fix for the general case.

rcarriga avatar Apr 15 '25 13:04 rcarriga