Union types#

To declare a field that can be of one type or anther typing.Union is used. It works for primitive types and models as well but not combined together.

The type declaration order matters. Currently, pydantic has two validation modes:

  • left_to_right, where the first successful validation is accepted,

  • smart (default), the first type that matches (without coercion) wins.

You can read more about it in the pydantic docs.

Primitive types#

Union can be applied to a text, attributes or elements.

Model
class Message(BaseXmlModel, tag='Message'):
    timestamp: Union[float, dt.datetime] = attr()
    text: Optional[str] = None


class Messages(BaseXmlModel):
    messages: List[Message]
Document
<Messages>
    <Message timestamp="1674995230.295639">hello world</Message>
    <Message timestamp="2023-01-29T17:30:38.762166"/>
</Messages>
{
    "messages": [
        {
            "timestamp": 1674995230.295639,
            "text": "hello world"
        },
        {
            "timestamp": "2023-01-29T17:30:38.762166"
        }
    ]
}

Model types#

Union can be applied to model types either.

Model
class Event(BaseXmlModel):
    timestamp: float = attr()


class KeyboardEvent(Event, tag='keyboard'):
    type: str = attr()
    key: str = element()


class MouseEvent(Event, tag='mouse'):
    position: Dict[str, int] = element()


class Log(BaseXmlModel, tag='log'):
    events: List[Union[KeyboardEvent, MouseEvent]]
Document
<log>
    <mouse timestamp="1674999183.5486422">
        <position x="234" y="345"/>
    </mouse>
    <keyboard timestamp="1674999184.227246"
              type="KEYDOWN">
        <key>CTRL</key>
    </keyboard>
    <keyboard timestamp="1674999185.6342669"
              type="KEYDOWN">
        <key>C</key>
    </keyboard>
    <mouse timestamp="1674999186.270716">
        <position x="236" y="211"/>
    </mouse>
</log>
{
    "events": [
        {
            "timestamp": 1674999183.5486422,
            "position": {"x": 234, "y": 345}
        },
        {
            "timestamp": 1674999184.227246,
            "type": "KEYDOWN",
            "key": "CTRL"
        },
        {
            "timestamp": 1674999185.6342669,
            "type": "KEYDOWN",
            "key": "C"
        },
        {
            "timestamp": 1674999186.270716,
            "position": {"x": 236, "y": 211}
        }
    ]
}

Discriminated unions#

Pydantic supports so called discriminated unions - the unions where the sub-model type is selected based on its field value.

pydantic-xml supports the similar mechanism to distinguish one sub-model from another by its xml attribute value:

Model
class Device(BaseXmlModel, tag='device'):
    type: str


class CPU(Device):
    type: Literal['CPU'] = attr()
    cores: int = element()


class GPU(Device):
    type: Literal['GPU'] = attr()
    cores: int = element()
    cuda: bool = attr(default=False)


class Hardware(BaseXmlModel, tag='hardware'):
    accelerator: Union[CPU, GPU] = Field(..., discriminator='type')
Document
<hardware>
    <device type="GPU">
        <cores>4096</cores>
    </device>
</hardware>
{
    "accelerator": {
        "type": "GPU",
        "cores": 4096,
        "cuda": false
    }
}