pydantic/pydantic · error · TypeError

The core schema type {choice["type"]!r} is not a valid discr

Error message

The core schema type {choice["type"]!r} is not a valid discriminated union variant.

What it means

_handle_choice validates each union variant of a discriminated union. A choice is acceptable only if its core-schema type is in {model, typed-dict, tagged-union, lax-or-strict, dataclass, dataclass-args, definition-ref} or it is a function-with-inner-schema. Any other type (e.g. 'list', 'str', 'int', 'dict') is rejected with a TypeError naming the offending type, with an extra hint when the type is 'list'.

Source

Thrown at pydantic/_internal/_discriminated_union.py:279

            choices_schemas = [v[0] if isinstance(v, tuple) else v for v in choice['choices'][::-1]]
            self._choices_to_handle.extend(choices_schemas)
        elif choice['type'] not in {
            'model',
            'typed-dict',
            'tagged-union',
            'lax-or-strict',
            'dataclass',
            'dataclass-args',
            'definition-ref',
        } and not _core_utils.is_function_with_inner_schema(choice):
            # We should eventually handle 'definition-ref' as well
            err_str = f'The core schema type {choice["type"]!r} is not a valid discriminated union variant.'
            if choice['type'] == 'list':
                err_str += (
                    ' 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=...)]]`).'
                )
            raise TypeError(err_str)
        else:
            if choice['type'] == 'tagged-union' and self._is_discriminator_shared(choice):
                # In this case, this inner tagged-union is compatible with the outer tagged-union,
                # and its choices can be coalesced into the outer TaggedUnionSchema.
                subchoices = [x for x in choice['choices'].values() if not isinstance(x, (str, int))]
                # Reverse the choices list before extending the stack so that they get handled in the order they occur
                self._choices_to_handle.extend(subchoices[::-1])
                return

            inferred_discriminator_values = self._infer_discriminator_values_for_choice(choice, source_name=None)
            self._set_unique_choice_for_values(choice, inferred_discriminator_values)

    def _is_discriminator_shared(self, choice: core_schema.TaggedUnionSchema) -> bool:
        """This method returns a boolean indicating whether the discriminator for the `choice`
        is the same as that being used for the outermost tagged union. This is used to
        determine whether this TaggedUnionSchema choice should be "coalesced" into the top level,
        or whether it should be treated as a separate (nested) choice.
        """

View on GitHub (pinned to 2e5f0e2b42)

Solutions

  1. Ensure every union variant is a BaseModel, TypedDict, or dataclass (or a compatible wrapper).
  2. If you have list[Union[Cat, Dog]], put the discriminator on the inner Union via Annotated: list[Annotated[Union[Cat, Dog], Field(discriminator='kind')]].
  3. Remove primitive variants from the discriminated union or wrap them in a model/typed-dict.

Example fix

# before
list[Union[Cat, Dog]]  with Field(discriminator='kind') on the list field -> error

# after
list[Annotated[Union[Cat, Dog], Field(discriminator='kind')]]
Defensive patterns

Strategy: type-guard

Validate before calling

ALLOWED_VARIANT_TYPES = {'model', 'typed-dict', 'tagged-union', 'lax-or-strict', 'dataclass', 'dataclass-args', 'definition-ref'}

def is_valid_union_variant(schema: dict) -> bool:
    return schema.get('type') in ALLOWED_VARIANT_TYPES

Type guard

def is_discriminatable_variant(tp: type) -> bool:
    from pydantic import BaseModel
    from typing import is_typeddict
    import dataclasses
    return issubclass(tp, BaseModel) or is_typeddict(tp) or dataclasses.is_dataclass(tp)

Try / catch

try:
    class M(BaseModel):
        x: Annotated[Union[Cat, Dog], Field(discriminator='kind')]
except TypeError as e:
    if 'not a valid discriminated union variant' in str(e):
        # ensure every variant is a model/typed-dict/dataclass
        raise
    raise

Prevention

When it happens

Trigger: Annotating a discriminated union whose variant is a bare primitive or container, e.g. Annotated[Union[Cat, Dog, str], Field(discriminator='kind')] where 'str' is not a model/typed-dict/dataclass. Also list[Union[...]] with the discriminator misplaced on the list (the error message hints at this).

Common situations: Adding a fallback primitive type to a discriminated union; misplacing Field(discriminator=...) on a list rather than on the inner union; using a discriminated union over non-model types.

Related errors


AI-assisted analysis of pydantic/pydantic@2e5f0e2b42 (2026-08-04). Data as JSON: /data/errors/1fb90ba5c11eae04.json. Report an issue: GitHub.