dapper91 / pydantic-xml

python xml for humans
https://pydantic-xml.readthedocs.io
The Unlicense
141 stars 14 forks source link

AssertionError: model SubParts is partially initialized #148

Closed SebastianDahlin closed 6 months ago

SebastianDahlin commented 6 months ago

Hi, Loving this library :heart: I was hoping the following would work, but I keep getting an error. I am trying to parse an xml that has a reoccuring nesting such as below:

<part>
  <name>root</name>
  <subparts>
    <subpart>
      <part>
        <name>child</name>
      </part>
    </subpart>
  </subparts>
<part>

So parts can have subparts, which is a list of subpart, a subpart holds a part and so on. My pydantic-xml code is as following.

class SubPart(BaseXmlModel, tag="subpart"):
    part: "Part" = element(tag="part")

class SubParts(BaseXmlModel, tag="subparts"):
    subpart: List[SubPart] = element(tag="subpart")

class Part(BaseXmlModel, tag="part", skip_empty=True):
    name: str = element()
    subparts: Optional[SubParts] = element(tag="subparts", default=None)

I keep getting the error "AssertionError: model SubParts is partially initialized". Tried from future import annotations and ForwardRef. No luck.

Is nesting such as above not possible with pydantic-xml?

dapper91 commented 6 months ago

@SebastianDahlin Hi,

Thanks for your feedback.

When you declare a model containing forward refs it is necessary to call model_rebuild on each model that depends on it because until the forward-referenced model is declared there is no enough information to build the xml serializer for the model. For more information see.

So in your case you should call model_rebuild on SubPart and SubParts like this:

>>> from typing import List, Optional
>>> from pydantic_xml import BaseXmlModel, element
>>>
>>>
>>> xml = '''
... <part>
...   <name>root</name>
...   <subparts>
...     <subpart>
...       <part>
...         <name>child</name>
...       </part>
...     </subpart>
...   </subparts>
... </part>'''
>>>
>>> class SubPart(BaseXmlModel, tag="subpart"):
...     part: "Part" = element(tag="part")
...
>>> class SubParts(BaseXmlModel, tag="subparts"):
...     subpart: List[SubPart] = element(tag="subpart")
...
>>> class Part(BaseXmlModel, tag="part", skip_empty=True):
...     name: str = element()
...     subparts: Optional[SubParts] = element(tag="subparts", default=None)
...
>>> SubPart.model_rebuild()
>>> SubParts.model_rebuild()
>>>
>>> print(Part.from_xml(xml))
name='root' subparts=SubParts(subpart=[SubPart(part=Part(name='child', subparts=None))])
SebastianDahlin commented 6 months ago

That did the trick! Superb. Thank you so much for you swift reply 💯.