Generic models#

pydantic library supports generic-models. Generic xml model can be declared the same way:

Model
AuthType = TypeVar('AuthType')


class Request(BaseXmlModel, Generic[AuthType], tag='request'):
    request_id: UUID = attr(name='id')
    timestamp: float = attr()
    auth: AuthType


class BasicAuth(BaseXmlModel):
    user: str = attr()
    password: SecretStr = attr()


class TokenAuth(BaseXmlModel):
    token: UUID = element()


BasicRequest = Request[BasicAuth]
TokenRequest = Request[TokenAuth]
Document
<request id="27765d90-f3ef-426f-be9d-8da2b405b4a9"
         timestamp="1674976874.291046">
    <auth user="root" password="secret"/>
</request>
{
    "request_id": "27765d90-f3ef-426f-be9d-8da2b405b4a9",
    "timestamp": 1674976874.291046,
    "auth": {
        "user": "root",
        "password": "secret"
    }
}
<request id="27765d90-f3ef-426f-be9d-8da2b405b4a9"
         timestamp="1674976874.291046">
    <auth>
        <token>7de9e375-84c1-441f-a628-dbaf5017e94f</token>
    </auth>
</request>
{
    "request_id": "27765d90-f3ef-426f-be9d-8da2b405b4a9",
    "timestamp": 1674976874.291046,
    "auth": {
        "token": "7de9e375-84c1-441f-a628-dbaf5017e94f"
    }
}

A generic model can be of one or more types and organized in a recursive structure. The following example illustrate how to describes a flexable SOAP request model:

model.py:

import datetime as dt
import pathlib
from typing import Generic, TypeVar

from pydantic import HttpUrl

from pydantic_xml import BaseXmlModel, element

AuthType = TypeVar('AuthType')


class SoapHeader(
    BaseXmlModel, Generic[AuthType],
    tag='Header',
    ns='soap',
):
    auth: AuthType


class SoapMethod(BaseXmlModel):
    pass


MethodType = TypeVar('MethodType', bound=SoapMethod)


class SoapBody(
    BaseXmlModel, Generic[MethodType],
    tag='Body',
    ns='soap',
):
    call: MethodType


HeaderType = TypeVar('HeaderType', bound=SoapHeader)
BodyType = TypeVar('BodyType', bound=SoapBody)


class SoapEnvelope(
    BaseXmlModel,
    Generic[HeaderType, BodyType],
    tag='Envelope',
    ns='soap',
    nsmap={
        'soap': 'http://www.w3.org/2003/05/soap-envelope/',
    },
):
    header: HeaderType
    body: BodyType


class BasicAuth(
    BaseXmlModel,
    tag='BasicAuth',
    ns='auth',
    nsmap={
        'auth': 'http://www.company.com/auth',
    },
):
    user: str = element(tag='Username')
    password: str = element(tag='Password')


class CreateCompanyMethod(
    SoapMethod,
    tag='CreateCompany',
    ns='co',
    nsmap={
        'co': 'https://www.company.com/co',
    },
):
    trade_name: str = element(tag='TradeName')
    founded: dt.date = element(tag='Founded')
    website: HttpUrl = element(tag='WebSite')


CreateCompanyRequest = SoapEnvelope[
    SoapHeader[
        BasicAuth
    ],
    SoapBody[
        CreateCompanyMethod
    ],
]

xml_doc = pathlib.Path('./doc.xml').read_text()

request = CreateCompanyRequest.from_xml(xml_doc)

assert request == CreateCompanyRequest.parse_file('./doc.json')

doc.xml:

<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope/">
    <soap:Header>
        <auth:BasicAuth xmlns:auth="http://www.company.com/auth">
            <auth:Username>admin</auth:Username>
            <auth:Password>secret</auth:Password>
        </auth:BasicAuth>
    </soap:Header>
    <soap:Body>
        <co:CreateCompany xmlns:co="https://www.company.com/co">
            <co:TradeName>SpaceX</co:TradeName>
            <co:Founded>2002-03-14</co:Founded>
            <co:WebSite>https://www.spacex.com</co:WebSite>
        </co:CreateCompany>
    </soap:Body>
</soap:Envelope>

doc.json:

{
    "header": {
        "auth": {
            "user": "admin",
            "password": "secret"
        }
    },
    "body": {
        "call": {
            "trade_name": "SpaceX",
            "founded": "2002-03-14",
            "website": "https://www.spacex.com"
        }
    }
}