PyXB1.2.3 UnboundElementError when doing toxml

Help
John Weng
2014-02-18
2014-02-19
  • John Weng

    John Weng - 2014-02-18

    Hi,

    Just got a bug whenever I try to convert a PyXB object to xml via .toxml().

    I generated a simple example below in sample.xsd.

    <?xml version="1.0" encoding="UTF-8" ?>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    
    <xs:element name="shiporder">
        <xs:complexType>
        <xs:sequence>
            <xs:element name="orderperson" type="xs:string"/>
        </xs:sequence>
        </xs:complexType>
    </xs:element>
    
    <xs:complexType name="programType">
        <xs:sequence>
            <xs:element name="programId" type="xs:integer"/>
        </xs:sequence>
    </xs:complexType>
    <xs:element name="program" type="programType" />
    
    </xs:schema>
    

    When I use pyxbgen -u sample.xsd -m sample, then I do below and let's call this case 1

    import sample
    pt = sample.programType()
    pt.programId=123
    pt.toxml()
    

    ---------------------------------------------------------------------------.
    UnboundElementError Traceback (most recent call last)
    <ipython-input-10-a0d3de3d064f> in <module>()
    ----> 1 c.toxml()

    /home/john/bin/pyenv-pyxb-1.2.3/lib/python3.3/site-packages/pyxb/binding/basis.py in >toxml(self, encoding, bds, root_only)
    537 created.
    538 """
    --> 539 dom = self.toDOM(bds)
    540 if root_only:
    541 dom = dom.documentElement

    /home/john/bin/pyenv-pyxb-1.2.3/lib/python3.3/site-packages/pyxb/binding/basis.py in >toDOM(self, bds, parent, element_name)
    509 need_xsi_type = need_xsi_type or >element_binding.typeDefinition()._RequireXSIType(type(self))
    510 if element_name is None:
    --> 511 raise pyxb.UnboundElementError(self)
    512 element = bds.createChildElement(element_name, parent)
    513 if need_xsi_type:

    While when I do this, it just works (case 2)

    p = sample.program()
    p.programId=123
    p.toxml()
    -->'<?xml version="1.0" ?><program><programId>1234</programId></program>'
    p._element()
    --><pyxb.binding.basis.element at 0x2309c90>
    so = sample.shiporder()
    so.orderperson="1234"
    so.toxml()
    -->'<?xml version="1.0" ?><shiporder><orderperson>1234</orderperson></shiporder>'
    so._element()
    --><pyxb.binding.basis.element at 0x2309bd0>
    

    And even I change the schema like below:

    <?xml version="1.0" encoding="UTF-8" ?>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    
    <xs:element name="shiporder">
        <xs:complexType>
        <xs:sequence>
            <xs:element name="orderperson" type="xs:string"/>
    ++++        <xs:element name="program" type="programType"/>
        </xs:sequence>
        </xs:complexType>
    </xs:element>
    
    <xs:complexType name="programType">
        <xs:sequence>
            <xs:element name="programId" type="xs:integer"/>
        </xs:sequence>
    </xs:complexType>
    <xs:element name="program" type="programType" />
    
    </xs:schema>
    

    These commands will work (case 3)

    import sample
    pt = sample.programType()
    pt.programId=123
    pt._element()
    
    so = sample.shiporder()
    so.orderperson="1234"
    so.program = pt
    so.toxml()
    -->'<?xml version="1.0" ?><shiporder><orderperson>1234</orderperson><program><programId>1234</programId></program></shiporder>'
    pt._element()
    --><pyxb.binding.basis.element at 0x2309e10>
    pt.toxml()
    -->'<?xml version="1.0" ?><program><programId>1234</programId></program>'
    

    However all these code goes well with PyXB1.2.2. I looked into the error code in PyXB1.2.3 (pyxb/binding/basis.py) and compared with the PyXB1.2.2 (pyxb/binding/basis.py) found in 1.2.3

    486     def toDOM (self, bds=None, parent=None, element_name=None):
    ...
    501         if bds is None:
    502             bds = domutils.BindingDOMSupport()
    503         need_xsi_type = bds.requireXSIType()
    504         if isinstance(element_name, str):
    505             element_name = pyxb.namespace.ExpandedName(bds.defaultNamespace(), element_name)
    506         if (element_name is None) and (self._element() is not None):           
    507             element_binding = self._element()
    508             element_name = element_binding.name()
    509             need_xsi_type = need_xsi_type or element_binding.typeDefinition()._RequireXSIType(type(self))
    510         if element_name is None:
    511             raise pyxb.UnboundElementError(self)
    ...
    523     def toxml (self, encoding=None, bds=None, root_only=False):
    ...
    539         dom = self.toDOM(bds)
    

    while in 1.2.2

    491     def toDOM (self, bds=None, parent=None, element_name=None):
    ...
    506         if bds is None:
    507             bds = domutils.BindingDOMSupport()
    508         need_xsi_type = bds.requireXSIType()
    509         if isinstance(element_name, (str, unicode)):
    510             element_name = pyxb.namespace.ExpandedName(bds.defaultNamespace(), element_name)
    511         if (element_name is None) and (self._element() is not None):
    512             element_binding = self._element()
    513             element_name = element_binding.name()
    514             need_xsi_type = need_xsi_type or element_binding.typeDefinition()._RequireXSIType(type(self))
    515         if element_name is None:
    516             element_name = self._ExpandedName
    ...
    528     def toxml (self, encoding=None, bds=None, root_only=False):
    ...
    544         dom = self.toDOM(bds)
    

    The difference is at line 511 for 1.2.3 and 516 for 1.2.2. The error will be thrown out since it is certain that when we call toDOM through the toxml the element_name is None and I found usually an object created from complexType will return None when _element() is called, while that of the element (XSD object, e.g. so) will return a Python Object. In comparison, in case 3, when the complexType object (pt) is assigned to the element object (so), it can return something not None.

    So I just wondering if there is some requirements on the way of writing the schema files, like we need to define the programType as complexType and declare program as element. If so, it will inevitably introduce a host of updates on the schema files and is also error-prone to apply. Since in the old version, we can always, for example, create Python object from the complexType and also used it in isinstance() and in the new we can only do either, but never(?) the both.

    Not sure if there are some smarter ways to solve this issue.

    Thanks for anyone who could help and the rest who see this as well ;)

     
  • Peter A. Bigot

    Peter A. Bigot - 2014-02-19

    That's a fix for a long-standing unnoticed bug. It's always been wrong, since the XML generated by doing that would not almost certainly not validate when parsed (before the fix the XML element tag would be programType when it must instead be program). Case 1 is flat wrong; case 2 is right; case 3 corrects the error in case 1 because the element is bound when the unbound object is assigned to the element in its container.

    The issue is discussed in this topic and this one. Depending on how your code is arranged, either creating from the element or binding the element to an object created from the type could be the right approach. There should be no need to change the schema, but you will have to change your code.

     
    Last edit: Peter A. Bigot 2014-02-19
  • John Weng

    John Weng - 2014-02-19

    Yes, I found it is really not a fun to change the schema and it can be error-prone. But in terms of modifing the code, if we just need, for example, a program instance, we create an object from the program, which is a complexType, and still need to reate a shiporder so that I can bound the program to it and do toxml. Sounds a bit redundent.

    Hmm, basiclly, I think when convert object to xml, the name of the root depends on its bounding's name. This is indeed valid when we do need to bound some together; nevertheless, when we only need a single one by its own, I think use the name of the complexType is still resonable. I'm still bit confused about why it is invalidate without that patch. Would you please give an example?

    Thanks a lot!

     
  • Peter A. Bigot

    Peter A. Bigot - 2014-02-19

    An example is simple: Using your sample schema this program:

    import sample
    pt = sample.programType()
    pt.programId=123
    xmls = pt.toxml('utf-8')
    print(xmls)
    print(pt.validateBinding())
    i = sample.CreateFromDocument(xmls)
    

    produces this result under PyXB 1.2.2:

    <?xml version="1.0" encoding="utf-8"?><programType><programId>123</programId></programType>
    True
    Traceback (most recent call last):
      File "sampler.py", line 7, in <module>
        i = sample.CreateFromDocument(xmls)
      File "/tmp/pyxb/sample.py", line 54, in CreateFromDocument
        instance = handler.rootObject()
      File "/tmp/pyxb/pyxb/binding/saxer.py", line 274, in rootObject
        raise pyxb.UnrecognizedDOMRootNodeError(self.__rootObject)
    pyxb.exceptions_.UnrecognizedDOMRootNodeError: <pyxb.utils.saxdom.Element object at 0x21a6fd0>
    

    In short, you're creating an XML document (even if it's a fragment) that is not valid within its schema, because there are no element components with name "programType". The content of that element is valid in certain contexts, but that's not relevant because the enclosing element itself is not valid, and you'd have to remove its start and end tags to use the generated XML.

    It may have been useful in some cases that PyXB behaved as it did, but it was not valid, and validation is the primary driver for PyXB functionality. If you really need to do something like this, I'd create a pyxb.binding.element instance with a name like "NOT AN ELEMENT" and bind that to the complexType instance so that the XML is clearly not valid, but the conversion works. Under PyXB 1.2.3 the following program:

    import sample
    pt = sample.programType()
    pt.programId=123
    print(pt.validateBinding())
    
    import pyxb.binding.basis
    bogus = pyxb.binding.basis.element(sample.Namespace.createExpandedName('NOT AN ELEMENT'),
                                       sample.programType)
    pt._setElement(bogus)
    xmls = pt.toxml('utf-8')
    print(xmls)
    

    produces:

    True
    <?xml version="1.0" encoding="utf-8"?><NOT AN ELEMENT><programId>123</programId></NOT AN ELEMENT>
    

    which might satisfy your needs with a slightly smaller risk that the resulting document would be used in a context where somebody might think it's valid.

     
    Last edit: Peter A. Bigot 2014-02-19
  • John Weng

    John Weng - 2014-02-19

    Thanks for this detailed illustration. Sure, you are quite right. So I guess if we can have a general element that can wrap up all the rest complexTypes, we can be pretty safe. For example:

    <?xml version="1.0" encoding="UTF-8" ?>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    
    <xs:complexType name="shiporder">
        <xs:sequence>
            <xs:element name="orderperson" type="xs:string"/>
            <xs:element name="program" type="programType"/>
        </xs:sequence>
    </xs:complexType>
    
    <xs:complexType name="programType">
        <xs:sequence>
            <xs:element name="programId" type="xs:integer"/>
        </xs:sequence>
    </xs:complexType>
    
    <xs:element name="generalElement">
        <xs:complexType>
        <xs:choice>
            <xs:element name="program" type="programType"/>
            <xs:element name="shiporder" type="shiporder" />
        </xs:choice>
        </xs:complexType>
    </xs:element>
    
    </xs:schema>
    

    and in python we can do

    import sample
    pt = sample.programType()
    pt.programId = 123
    g = sample.generalElement()
    g.program = pt
    xml = g.toxml()
    print(xml)
    pE = sample.CreateFromDocument(xml)
    print(pE.program.programId)
    

    produces:

    '<?xml version="1.0" ?><generalElement><program><programId>123</programId></program></generalElement>'
    123
    

    And we can do the same thing to shiporder as well. So not matter which one we want, either a conbinational or just a single one, we just build a wraper for them. Does make sense right?

    Thanks!

     
  • Peter A. Bigot

    Peter A. Bigot - 2014-02-19

    I don't understand generally what you're trying to do, nor specifically why you are using complexTypes that don't already have top-level elements defined in the schema if your intent is to represent them in isolation as XML documents. It sounds like maybe your schema should undergo some review.

    But if that's out of scope, based on what you've said there are a variety of solutions that involve changes to the schemas or that can be done solely within PyXB (variations of the example I gave, including a function to synthesize a containing element from an unbound instance). Pick one that works for your needs.

     

Log in to post a comment.

Get latest updates about Open Source Projects, Conferences and News.

Sign up for the SourceForge newsletter:





No, thanks