Is it possible to use PyXB to generate a module from an XSD that has anonymous complex types that can have some basic type- and constraints-checking ?
Here's what I've managed so far:
Using a 3rd-Party (Anonymised) XSD:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://www.foo.com" xmlns="http://www.foo.com" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="ResultSet">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" minOccurs="0" name="Bar">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" name="code" type="xs:string"/>
<xs:element minOccurs="0" name="description" type="xs:string"/>
<xs:element minOccurs="0" name="name" type="xs:string"/>
</xs:sequence>
<xs:attribute name="href" type="xs:string"/>
<xs:attribute name="id" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
I generated a python module:
> pyxbgen -u Foo.xsd -m Foo
Python for http://www.foo.com requires 1 modules
> ls
Foo.py Foo.xsd
Then in the python interpreter, I was able to create a ResultSet
instance and create entries under Bar
:
> python
Python 2.7.6 (default, Mar 22 2014, 22:59:56)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyxb
>>> import Foo
>>> Foo
<module 'Foo' from 'Foo.py'>
>>> rs = Foo.ResultSet()
>>> rs
<Foo.CTD_ANON object at 0x7f7360834950>
>>> rs.Bar
<pyxb.binding.content._PluralBinding object at 0x7f7360834a90>
>>> rs.Bar.append( pyxb.BIND( name='a', code='b', description ='c' ) )
>>> rs.Bar
<pyxb.binding.content._PluralBinding object at 0x7f39f1018a90>
>>> [x for x in rs.Bar ]
[<Foo.CTD_ANON_ object at 0x7f39f1018b90>]
And I'm able to interact with the members of rs.Bar[0]
.
BUT what bothers me is that I can also:
>>> rs.Bar.append( pyxb.BIND( name='a' ) )
and it'll accept it, even though the code
element in the anonymous complexType
has an attribute of minOccurs="1"
. I gather from reading the docs and some other questions on StackOverflow that PyXB is trying to reverse-engineer the data types on the fly, so that might explain the permissiveness.
I'd like if possible to overlay something on the whole process that allows me to basically say:
>>> rs.Bar.append( Something( name='a', code='b' ) )
and have it complain about any missing parameters. So far I have mulled over just writing my own functions that return the result of pyxb.BIND()
with the appropriate parameters, but this implies quite a degree of manual intervention.
Is there a way with PyXB to create such smarts automatically ?
PyXB isn't being permissive because of the reverse-engineering, but because it doesn't validate elements at the time they are constructed, nor does it validate complex types when they are assigned to elements. This is because it may require multiple Python statements to assign the necessary content to the element: it can't always be reasonably provided through parameters to pyxb.BIND()
.
For example, your code can be completed by doing:
rs.Bar.append( pyxb.BIND( name='a' ) )
rs.Bar[-1].code = '42'
at which point rs
will be valid.
If you want the validation performed at the point the object is appended, you should use an intermediary function that validates the element that was appended. The following shows how to do this in a situation where the incoming parameter may be an unresolved pyxb.BIND
structure:
import pyxb
import Foo
def checkedAppendBar(rs, bar):
rs.Bar.append(bar)
bar = rs.Bar[-1]
try:
bar.validateBinding()
except pyxb.ValidationError as e:
print e.details()
return bar
rs = Foo.ResultSet()
# Fails:
#bar = checkedAppendBar(rs, pyxb.BIND(name='a'))
bar = checkedAppendBar(rs, pyxb.BIND(name='a', code='42'))