pydantic-xml
pydantic-xml copied to clipboard
List of arbitrary elements?
I'm trying to model the <extensions>...</extensions>
element of the gpx schema, which accepts a list of "any elements from a namespace other than this schema's namespace". For example, something like:
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1">
<waypoint lat="..." lon="...">
<extensions>
<foo:name xmlns:foo="https://foo.example.com/">Alice Example</foo:name>
<bar:feature_type xmlns:bar="https://bar.example.com/">House</bar:title>
<foo:display_address valid="1" xmlns:foo="https://foo.example.com/">1 Main St, Sometown, Somewhere 12345</foo:display_address>
</extensions>
</waypoint>
</gpx>
The <extensions>
element needs to accept elements from arbitrary namespaces -- that is, client code may want to add its own extensions, and the module implementing the GPX spec doesn't know anything about them. This is a collection of heterogenous elements of unknown size.
I tried this:
from typing import Literal
from pydantic_xml import BaseXmlModel, attr, element
class GpxExtension(BaseXmlModel):
pass
class FooNameExtension(
GpxExtension, nsmap={"foo": "https://foo.example.com/"}, ns="foo", tag="name"
):
name: str | None = None
class FooDisplayAddressExtension(
GpxExtension,
nsmap={"foo": "https://foo.example.com/"},
ns="foo",
tag="display_address",
):
address: str | None = None
valid: int | None = attr(default=None)
class BarFeatureTypeExtension(
GpxExtension,
nsmap={"foo": "https://foo.example.com/"},
ns="foo",
tag="feature_type",
):
label: str | None = None
class Waypoint(BaseXmlModel):
lat: float | None = attr(default=None)
lon: float | None = attr(default=None)
extensions: list[GpxExtension] = element(default_factory=list)
class GpxFile(
BaseXmlModel,
tag="gpx",
nsmap={
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
"": "http://www.topografix.com/GPX/1/1",
},
):
schemaLocation: Literal[
"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
] = attr(
default="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd",
name="schemaLocation",
ns="xsi",
)
version: Literal["1.1"] = attr(default="1.1")
wpt: list[Waypoint] = []
w = Waypoint()
w.extensions.append(FooNameExtension(name="Alice Example"))
w.extensions.append(
FooDisplayAddressExtension(address="1 Main St., Sometown, Somewhere 12345")
)
w.extensions.append(BarFeatureTypeExtension(label="House"))
g = GpxFile(wpt=[w])
print(g.to_xml(pretty_print=True).decode())
But that gets me:
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" version="1.1">
<wpt lat="" lon="">
<extensions/>
<extensions/>
<extensions/>
</wpt>
</gpx>
Although the individual elements serialize as expected:
>>> print(FooNameExtension(name='Alice Example').to_xml().decode())
<foo:name xmlns:foo="https://foo.example.com/">Alice Example</foo:name>
What's the right way to support a collection of arbitrary elements?