z3c.schema2xml 1.0
Convert schema-described Zope 3 objects to XML and back
Schema To XML
Introduction
This package can convert objects described by Zope 3 schema to simple XML structures. It's also able to convert this XML back into objects. The export and import processes are completely schema-driven; any attribute not described in the schema is not seen by this system at all.
This system can be used to create export and import systems for Zope 3 applications. It could also be used to provide XML representations of objects for other purposes, such as XSLT transformations, or even just to get a full-text representation for index purposes.
The package lies on lxml for the serialization to XML.
Serialization
Let's first define a simple Zope 3 schema:
>>> from zope import interface, schema >>> class IName(interface.Interface): ... first_name = schema.TextLine(title=u'First name') ... last_name = schema.TextLine(title=u'Last name')
Let's now make a class that implements this schema:
>>> from zope.interface import implements >>> class Name(object): ... implements(IName) ... def __init__(self, first_name, last_name): ... self.first_name = first_name ... self.last_name = last_name
Let's make an instance of the class:
>>> name = Name('Karel', 'Titulaer')
Now let's serialize it to XML:
>>> from z3c.schema2xml import serialize
>>> print serialize('container', IName, name)
<container>
<first_name>Karel</first_name>
<last_name>Titulaer</last_name>
</container>
This also works for other kinds of fields:
>>> from zope import interface, schema
>>> class IAddress(interface.Interface):
... street_name = schema.TextLine(title=u'Street name')
... number = schema.Int(title=u'House number')
>>> class Address(object):
... implements(IAddress)
... def __init__(self, street_name, number):
... self.street_name = street_name
... self.number = number
>>> address = Address('Hofplein', 42)
>>> print serialize('container', IAddress, address)
<container>
<street_name>Hofplein</street_name>
<number>42</number>
</container>
If a field is not filled in, the serialization will result in an empty element:
>>> address2 = Address(None, None)
>>> print serialize('container', IAddress, address2)
<container>
<street_name/>
<number/>
</container>
If a schema defines an Object field with its own schema, the serialization can also handle this:
>>> class IPerson(interface.Interface):
... name = schema.Object(title=u"Name", schema=IName)
... address = schema.Object(title=u"Address", schema=IAddress)
>>> class Person(object):
... implements(IPerson)
... def __init__(self, name, address):
... self.name = name
... self.address = address
>>> person = Person(name, address)
>>> print serialize('person', IPerson, person)
<person>
<name>
<first_name>Karel</first_name>
<last_name>Titulaer</last_name>
</name>
<address>
<street_name>Hofplein</street_name>
<number>42</number>
</address>
</person>
A schema can also define a List field with elements with their own schema. Let's make an object and serialize it:
>>> class ICommission(interface.Interface): ... members = schema.List( ... title=u"Commission", ... value_type=schema.Object(__name__='person', ... schema=IPerson))
Note that we have to explicitly specify __name__ for the field that's used for value_type here, otherwise we have no name to serialize to XML with.
>>> class Commission(object): ... implements(ICommission) ... def __init__(self, members): ... self.members = members>>> commission = Commission( ... [person, Person(Name('Chriet', 'Titulaer'), Address('Ruimteweg', 3))]) >>> print serialize('commission', ICommission, commission) <commission> <members> <person> <name> <first_name>Karel</first_name> <last_name>Titulaer</last_name> </name> <address> <street_name>Hofplein</street_name> <number>42</number> </address> </person> <person> <name> <first_name>Chriet</first_name> <last_name>Titulaer</last_name> </name> <address> <street_name>Ruimteweg</street_name> <number>3</number> </address> </person> </members> </commission>
We get an adapter lookop failure whenever we try to serialize a field type for which there's no an serializer:
>>> class IWithNonSerializableField(interface.Interface):
... field = schema.Field(title=u"Commission")
>>> class NotSerializable(object):
... implements(IWithNonSerializableField)
... def __init__(self, value):
... self.field = value
>>> not_serializable = NotSerializable(None)
>>> serialize('noway', IWithNonSerializableField, not_serializable)
Traceback (most recent call last):
...
TypeError: ('Could not adapt', <zope.schema._bootstrapfields.Field object at ...>, <InterfaceClass z3c.schema2xml._schema2xml.IXMLGenerator>)
Deserialization
Now we want to deserialize XML according to a schema to an object that provides this schema.
>>> from z3c.schema2xml import deserialize
>>> xml = '''
... <container>
... <first_name>Karel</first_name>
... <last_name>Titulaer</last_name>
... </container>
... '''
>>> name = Name('', '')
>>> deserialize(xml, IName, name)
>>> name.first_name
u'Karel'
>>> name.last_name
u'Titulaer'
The order of the fields in XML does not matter:
>>> xml = '''
... <container>
... <last_name>Titulaer</last_name>
... <first_name>Karel</first_name>
... </container>
... '''
>>> name = Name('', '')
>>> deserialize(xml, IName, name)
>>> name.first_name
u'Karel'
>>> name.last_name
u'Titulaer'
After deserialization, the object alsoProvides the schema interface:
>>> IName.providedBy(name) True
This also works for other kinds of fields:
>>> xml = '''
... <container>
... <street_name>Hofplein</street_name>
... <number>42</number>
... </container>
... '''
>>> address = Address('', 0)
>>> deserialize(xml, IAddress, address)
>>> address.street_name
u'Hofplein'
>>> address.number
42
If a schema defines an Object field with its own schema, the serialization can also handle this:
>>> xml = '''
... <person>
... <name>
... <first_name>Karel</first_name>
... <last_name>Titulaer</last_name>
... </name>
... <address>
... <street_name>Hofplein</street_name>
... <number>42</number>
... </address>
... </person>
... '''
>>> person = Person(Name('', ''), Address('', 0))
>>> deserialize(xml, IPerson, person)
>>> person.name.first_name
u'Karel'
>>> person.name.last_name
u'Titulaer'
>>> person.address.street_name
u'Hofplein'
>>> person.address.number
42
>>> IPerson.providedBy(person)
True
>>> IName.providedBy(person.name)
True
>>> IAddress.providedBy(person.address)
True
Again the order in which the fields come in XML shouldn't matter:
>>> xml = '''
... <person>
... <address>
... <number>42</number>
... <street_name>Hofplein</street_name>
... </address>
... <name>
... <last_name>Titulaer</last_name>
... <first_name>Karel</first_name>
... </name>
... </person>
... '''
>>> person = Person(Name('', ''), Address('', 0))
>>> deserialize(xml, IPerson, person)
>>> person.name.first_name
u'Karel'
>>> person.name.last_name
u'Titulaer'
>>> person.address.street_name
u'Hofplein'
>>> person.address.number
42
>>> IPerson.providedBy(person)
True
>>> IName.providedBy(person.name)
True
>>> IAddress.providedBy(person.address)
True
>>> xml = '''
... <commission>
... <members>
... <person>
... <name>
... <first_name>Karel</first_name>
... <last_name>Titulaer</last_name>
... </name>
... <address>
... <street_name>Hofplein</street_name>
... <number>42</number>
... </address>
... </person>
... <person>
... <name>
... <first_name>Chriet</first_name>
... <last_name>Titulaer</last_name>
... </name>
... <address>
... <street_name>Ruimteweg</street_name>
... <number>3</number>
... </address>
... </person>
... </members>
... </commission>
... '''
>>> commission = Commission([])
>>> deserialize(xml, ICommission, commission)
>>> len(commission.members)
2
>>> member = commission.members[0]
>>> member.name.first_name
u'Karel'
>>> member.address.street_name
u'Hofplein'
>>> member = commission.members[1]
>>> member.name.first_name
u'Chriet'
>>> member.address.street_name
u'Ruimteweg'
Whenever the XML element is empty, the resulting value should be None:
>>> from z3c.schema2xml import deserialize
>>> xml = '''
... <container>
... <first_name></first_name>
... <last_name/>
... </container>
... '''
>>> name = Name('', '')
>>> deserialize(xml, IName, name)
>>> name.first_name is None
True
>>> name.last_name is None
True
For all kinds of fields, like strings and ints...:
>>> xml = '''
... <container>
... <street_name/>
... <number/>
... </container>
... '''
>>> address = Address('', 0)
>>> deserialize(xml, IAddress, address)
>>> address.street_name is None
True
>>> address.number is None
True
...and the fields of subobjects (but not the subobject themselves!):
>>> xml = '''
... <person>
... <name>
... <first_name/>
... <last_name/>
... </name>
... <address>
... <street_name/>
... <number/>
... </address>
... </person>
... '''
>>> person = Person(Name('', ''), Address('', 0))
>>> deserialize(xml, IPerson, person)
>>> person.name.first_name is None
True
>>> person.name.last_name is None
True
>>> IPerson.providedBy(person)
True
>>> IName.providedBy(person.name)
True
>>> person.address is None
False
>>> person.address.street_name is None
True
>>> person.address.number is None
True
>>> IAddress.providedBy(person.address)
True
Similarly, where a sequence is expected the value should be an empty sequence:
>>> xml = ''' ... <commission> ... <members/> ... </commission> ... ''' >>> commission = Commission([]) >>> deserialize(xml, ICommission, commission) >>> len(commission.members) 0
TextLine, Int, Object and List have just been tested. Now follow tests for the other field types that have a serializer.
Datetime
Datetime objects:
>>> from datetime import datetime
>>> class IWithDatetime(interface.Interface):
... datetime = schema.Datetime(title=u'Date and time')
>>> class WithDatetime(object):
... implements(IWithDatetime)
... def __init__(self, datetime):
... self.datetime = datetime
>>> with_datetime = WithDatetime(datetime(2006, 12, 31))
>>> xml = serialize('container', IWithDatetime, with_datetime)
>>> print xml
<container>
<datetime>2006-12-31T00:00:00</datetime>
</container>
>>> new_datetime = WithDatetime(None)
>>> deserialize(xml, IWithDatetime, new_datetime)
>>> new_datetime.datetime.year
2006
>>> new_datetime.datetime.month
12
>>> new_datetime.datetime.day
31
Let's try it with the field not filled in:
>>> with_datetime = WithDatetime(None)
>>> xml = serialize('container', IWithDatetime, with_datetime)
>>> print xml
<container>
<datetime/>
</container>
>>> new_datetime= WithDatetime(None)
>>> deserialize(xml, IWithDatetime, new_datetime)
>>> new_datetime.datetime is None
True
Choice
Choice fields. For now, we only work with Choice fields that have text values:
>>> from zc.sourcefactory.basic import BasicSourceFactory
>>> class ChoiceSource(BasicSourceFactory):
... def getValues(self):
... return [u'alpha', u'beta']
>>> class IWithChoice(interface.Interface):
... choice = schema.Choice(title=u'Choice', required=False,
... source=ChoiceSource())
>>> class WithChoice(object):
... implements(IWithChoice)
... def __init__(self, choice):
... self.choice = choice
>>> with_choice = WithChoice('alpha')
>>> xml = serialize('container', IWithChoice, with_choice)
>>> print xml
<container>
<choice>alpha</choice>
</container>
>>> new_choice = WithChoice(None)
>>> deserialize(xml, IWithChoice, new_choice)
>>> new_choice.choice
'alpha'
>>> with_choice = WithChoice(None)
>>> xml = serialize('container', IWithChoice, with_choice)
>>> print xml
<container>
<choice/>
</container>
>>> deserialize(xml, IWithChoice, new_choice)
>>> new_choice.choice is None
True
Set
Set fields are very similar to List fields:
>>> class IWithSet(interface.Interface):
... set = schema.Set(title=u'Set', required=False,
... value_type=schema.Choice(__name__='choice',
... source=ChoiceSource()))
>>> class WithSet(object):
... implements(IWithSet)
... def __init__(self, set):
... self.set = set
>>> with_set = WithSet(set(['alpha']))
>>> xml = serialize('container', IWithSet, with_set)
>>> print xml
<container>
<set>
<choice>alpha</choice>
</set>
</container>
>>> with_set = WithSet(set(['alpha', 'beta']))
>>> xml = serialize('container', IWithSet, with_set)
>>> print xml
<container>
<set>
<choice>alpha</choice>
<choice>beta</choice>
</set>
</container>
>>> new_set = WithSet(None)
>>> deserialize(xml, IWithSet, new_set)
>>> new_set.set
set(['alpha', 'beta'])
CHANGES
1.0 (2008-12-05)
- Changed dependency on grokcore.component so that this becomes useful in straight Zope 3 applications as well.
- Run tests against lxml 2.0.9.
0.10 (2008-03-10)
- First checkin in svn.zope.org.
Download
| File | Type | Py Version | Uploaded on | Size | # downloads |
|---|---|---|---|---|---|
| z3c.schema2xml-1.0.tar.gz (md5) | Source | 2008-12-05 | 12KB | 1880 | |
- Author: Martijn Faassen, Jan-Wijbrand Kolman
- License: ZPL
- Package Index Owner: faassen
- DOAP record: z3c.schema2xml-1.0.xml
