Skip to content

WrapValidator annotation changes smart union score #11752

Open
pydantic/pydantic-core
#1700
@weiliddat

Description

@weiliddat

Initial Checks

  • I confirm that I'm using Pydantic V2

Description

When Pydantic matches against a union type (in the default smart mode), adding a WrapValidator seems to change its preferred union member type.

Validating a field with a union of two similar models (see code below) without WrapValidator will use the leftmost union member. However, adding a "dummy" WrapValidator causes Pydantic to choose the right union member instead.

Using other validators like AfterValidator doesn't seem to show this issue.

Example Code

from typing import Annotated, Literal

from pydantic import BaseModel, Field, WrapValidator


class ObjectPayload(BaseModel):
    name: str


class AddMutation(BaseModel):
    type: Literal["add"] = Field("add")
    # passes if you replace WrapValidator with AfterValidator or remove it
    payload: Annotated[ObjectPayload, WrapValidator(lambda v, nxt: nxt(v))] = Field()


class UpdateMutation(BaseModel):
    type: Literal["update"] = Field("update")
    payload: ObjectPayload = Field()


class ObjectUpdate(BaseModel):
    # passes if you use Field(union_mode="left_to_right")
    mutation: AddMutation | UpdateMutation = Field()


def test_union():
    obj = ObjectUpdate.model_validate(
        {
            "mutation": {
                "type": "add",
                "payload": {"name": "John Doe"},
            }
        }
    )
    assert obj.mutation.type == "add"
    assert obj.mutation.payload.name == "John Doe"

    obj = ObjectUpdate.model_validate(
        {
            "mutation": {
                "type": "update",
                "payload": {"name": "John Doe"},
            }
        }
    )
    assert obj.mutation.type == "update"
    assert obj.mutation.payload.name == "John Doe"

    obj = ObjectUpdate.model_validate(
        {
            "mutation": {
                "payload": {"name": "John Doe"},
            }
        }
    )
    assert obj.mutation.type == "add"
    assert obj.mutation.payload.name == "John Doe"

Python, Pydantic & OS Version

pydantic version: 2.11.2
        pydantic-core version: 2.33.1
          pydantic-core build: profile=release pgo=false
                 install path: /Users/chiawei.ong/dev/pL/product-api/.direnv/python-3.12.10/lib/python3.12/site-packages/pydantic
               python version: 3.12.10 (main, Apr  8 2025, 11:35:47) [Clang 16.0.0 (clang-1600.0.26.6)]
                     platform: macOS-15.4-arm64-arm-64bit
             related packages: mypy-1.15.0 pydantic-extra-types-2.10.3 typing_extensions-4.13.1
                       commit: unknown

Metadata

Metadata

Assignees

Labels

bug V2Bug related to Pydantic V2

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions