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.

Primitive types#

Union can be applied to text, attributes or elements. The type declaration order matters since the first type matched wins.

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. The type declaration order matters since the first model matched wins.

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
    }
}