Get a Snapshot of an Object
---------------------------

The lazr.lifecycle.event.ObjectModifiedEvent has an attribute giving the
initial state of the object before the modifications. The Snapshot class can
be used to easily represent such states:

    >>> from zope.interface import Interface, implements, Attribute
    >>> from zope.schema import List, TextLine, Text
    >>> from lazr.lifecycle.snapshot import Snapshot

    >>> class IFoo(Interface):
    ...     title = TextLine(title=u'My Title')
    ...     description = Text(title=u'Description')
    ...     remotes = List(title=u'remotes')
    ...     totals = Attribute('totals')

    >>> class Foo:
    ...     implements(IFoo)
    ...
    ...     @property
    ...     def remotes(self):
    ...         return ["OK"]
    ...
    ...     @property
    ...     def totals(self):
    ...         return "NOT"
    >>> foo = Foo()
    >>> foo.title = 'Some Title'
    >>> foo.description = 'bla bla bla'

A snapshot can be created by specifying the names of the attribute you want to
snapshot:

    >>> snapshot = Snapshot(foo, names=['title'])

Only the given attributes will be assigned to the snapshot:

    >>> snapshot.title == foo.title
    True
    >>> hasattr(snapshot, 'description')
    False

The snapshot won't provide the same interface as foo, though:

    >>> IFoo.providedBy(snapshot)
    False

If we want the snapshot to provide some interface, we have to specify
that explicitly:

    >>> snapshot = Snapshot(foo, names=['title'], providing=IFoo)
    >>> snapshot.title == foo.title
    True
    >>> hasattr(snapshot, 'description')
    False
    >>> IFoo.providedBy(snapshot)
    True

The API requires you to specify either 'names' or 'providing':

    >>> snapshot = Snapshot(foo)
    Traceback (most recent call last):
    ...
    SnapshotCreationError: ...

If no names argument is supplied, the snapshot will contain all 
IFields of the specified interface(s).

    >>> snapshot = Snapshot(foo, providing=IFoo)
    >>> snapshot.title == foo.title
    True
    >>> hasattr(snapshot, 'description')
    True

(Totals is not a Fields so isn't copied over).

    >>> hasattr(snapshot, 'totals')
    False
    >>> hasattr(snapshot, 'remotes')
    True
    >>> snapshot.remotes == ["OK"]
    True

We can also give more than one interface to provide as an iterable. If
we don't specify any names, all the names in the given interfaces will
be copied:

    >>> from zope.interface import providedBy
    >>> snapshot = Snapshot(foo, providing=providedBy(foo))
    >>> snapshot.title == foo.title
    True
    >>> snapshot.description == foo.description
    True
    >>> IFoo.providedBy(snapshot)
    True

    >>> class IBar(Interface):
    ...     name = Text(title=u'Name')

    >>> foo.name = "barbie"

    >>> snapshot = Snapshot(foo, providing=[IFoo, IBar])
    >>> IFoo.providedBy(snapshot)
    True
    >>> IBar.providedBy(snapshot)
    True

    >>> snapshot.title == foo.title
    True
    >>> snapshot.name == "barbie"
    True
    >>> snapshot.description == foo.description
    True
    >>> IFoo.providedBy(snapshot)
    True
    >>> IBar.providedBy(snapshot)
    True


ISnapshotValueFactory
---------------------

For some fields, assigning the existing value to the snapshot object
isn't appropriate. For these case, one can provide a factory registered
as an adapter for the value to ISnapshotValueFactory. The result of the
adaptation lookup will be stored in the snapshot attribute.

    >>> from zope.interface import implementer, Interface
    >>> from zope.component import adapter, getSiteManager

    >>> class IIterable(Interface):
    ...     """Marker for a value that needs a special snapshot."""

    >>> class EvenOrOddIterable:
    ...     """An object that will be snapshotted specially."""
    ...     implements(IIterable)
    ...
    ...     even = True
    ...     max = 10
    ...
    ...     def __iter__(self):
    ...         for i in range(self.max):
    ...             if i % 2  == 0 and self.even:
    ...                 yield i
    ...             elif i % 2  == 1 and not self.even:
    ...                 yield i
    ...             else:
    ...                 continue

    >>> from lazr.lifecycle.interfaces import ISnapshotValueFactory
    >>> @implementer(ISnapshotValueFactory)
    ... @adapter(IIterable)
    ... def snapshot_iterable(value):
    ...     return list(value)
    >>> getSiteManager().registerAdapter(snapshot_iterable)

    >>> foo = Foo()
    >>> foo.title = 'Even'
    >>> foo.description = 'Generates even number below 10.'
    >>> foo.remotes = EvenOrOddIterable()

    >>> snapshot = Snapshot(foo, providing=IFoo)
    >>> snapshot.remotes == list(foo.remotes)
    True

    >>> getSiteManager().unregisterAdapter(snapshot_iterable)
    True

