Skip to main content

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

Release files for z3c.schema2xml 1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for z3c.schema2xml 1.0
File Size Uploaded
z3c.schema2xml-1.0.tar.gz 12.5 kB Details

Release files / z3c.schema2xml-1.0.tar.gz

Download URL z3c.schema2xml-1.0.tar.gz
Size 12.5 kB
Tags Source
SHA-256 checksum
How to use checksums
2fb6f8703db1a78a317eb8822cd6769750107219212e7c1e0fa2effd74ec6bb5
BLAKE2b-256 checksum
How to use checksums
3a8793f10ec0ca060127412d03dd0ec1d351dccd3267fcbf59d23940adea5800
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No

Release history Release notifications | RSS feed

This release

1.0 This release

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page