Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question] Combining multiple tags into a single attribute #244

Open
synapticarbors opened this issue Mar 7, 2025 · 2 comments
Open

[Question] Combining multiple tags into a single attribute #244

synapticarbors opened this issue Mar 7, 2025 · 2 comments
Labels
question Further information is requested

Comments

@synapticarbors
Copy link

Digging through the documentation and code, I was unable to figure out if the following was possible, and if so, what the best approach would be. Supposing I had an xml snippet that looked like:

<system>
    <A>
        <row>
            <item>1.0</item>
            <item>0.0</item>
            <item>0.0</item>
        </row>
        <row>
            <item>0.0</item>
            <item>2.2</item>
            <item>3.3</item>
        </row>
        <row>
            <item>0.0</item>
            <item>4.4</item>
            <item>5.5</item>
        </row>
    </A>
</system>

I'm trying to map A to something like:

class Example(BaseXmlModel, tag="system"):
    A: list[list[float]]: element(default_factory=list)

where ex = Example.from_xml(doc.xml) would have

ex.A == [[1.0, 0.0, 0.0], [0.0, 2.2, 3.3], [0.0, 4.4, 5.5]]

The xml_field_validator used in the custom xml serialization example only really seems capable of mapping one tag to a single attribute. Any suggestions on how to do this, as well as the reverse serialization step would be greatly appreciated.

@synapticarbors
Copy link
Author

I'm not sure if there is a better way to solve this, but I just ended up explicitly modeling A, and then keeping a non-exporting field to hold the nested list:

import pydantic_xml as px
from pydantic_xml import BaseXmlModel


class Row(BaseXmlModel, tag='row'):
    items: list[float] = px.element(tag='item')


class Amat(BaseXmlModel, tag='A'):
    rows: list[Row] = px.element(tag='row')


class System(BaseXmlModel, tag="system"):
    A_data: Amat = px.element(tag='A')
    _A: list[list[float]] | None = None

    @property
    def A(self) -> list[list[float]]:
        if self._A is None:
            self._A = [[item for item in row.items] for row in self.A_data.rows]
        return self._A

    @A.setter
    def A(self, value: list[list[float]]):
        self.A_data = Amat(rows=[Row(items=row) for row in value])
        self._A = None   

@dapper91
Copy link
Owner

@synapticarbors Hi,

you can declare Row as a RootXmlModel and define __iter__ and __eq__ methods:

from typing import Iterator

import pydantic_xml as pxml

xml = '''
<system>
    <A>
        <row>
            <item>1.0</item>
            <item>0.0</item>
            <item>0.0</item>
        </row>
        <row>
            <item>0.0</item>
            <item>2.2</item>
            <item>3.3</item>
        </row>
        <row>
            <item>0.0</item>
            <item>4.4</item>
            <item>5.5</item>
        </row>
    </A>
</system>
'''

class Row(pxml.RootXmlModel):
    root: list[float] = pxml.element(tag='item')

    def __iter__(self) -> Iterator[float]:
        return iter(self.root)

    def __getitem__(self, item: int) -> float:
        return self.root[item]

    def __eq__(self, other: list[float]) -> bool:
        return self.root == other



class System(pxml.BaseXmlModel, tag="system"):
    a: list[Row] = pxml.wrapped("A", pxml.element(tag="row", default_factory=list))


sys = System.from_xml(xml)
assert sys.a == [[1.0, 0.0, 0.0], [0.0, 2.2, 3.3], [0.0, 4.4, 5.5]]

@dapper91 dapper91 added the question Further information is requested label Mar 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants