[Checkins] SVN: zc.freeze/trunk/src/zc/freeze/ the 'version' side of copyversion

Gary Poster gary at zope.com
Wed Aug 23 17:51:35 EDT 2006


Log message for revision 69739:
  the 'version' side of copyversion
  

Changed:
  A   zc.freeze/trunk/src/zc/freeze/README.txt
  A   zc.freeze/trunk/src/zc/freeze/__init__.py
  A   zc.freeze/trunk/src/zc/freeze/configure.zcml
  A   zc.freeze/trunk/src/zc/freeze/copier.py
  A   zc.freeze/trunk/src/zc/freeze/copier.txt
  A   zc.freeze/trunk/src/zc/freeze/interfaces.py
  A   zc.freeze/trunk/src/zc/freeze/rwproperty.py
  A   zc.freeze/trunk/src/zc/freeze/subscribers.py
  A   zc.freeze/trunk/src/zc/freeze/subscribers.txt
  A   zc.freeze/trunk/src/zc/freeze/tests.py

-=-
Copied: zc.freeze/trunk/src/zc/freeze/README.txt (from rev 69734, zc.copyversion/trunk/src/zc/copyversion/README.txt)
===================================================================
--- zc.copyversion/trunk/src/zc/copyversion/README.txt	2006-08-23 19:03:56 UTC (rev 69734)
+++ zc.freeze/trunk/src/zc/freeze/README.txt	2006-08-23 21:51:35 UTC (rev 69739)
@@ -0,0 +1,197 @@
+========
+Freezing
+========
+
+This package implements basic functionality for freezing objects:
+spellings to query whether an object can be frozen, to query whether it
+has been frozen, and to actually freeze an object. Further policies may
+be implemented above the basic code in this package; and much of the
+code in this package is offered as pluggable choices which can be
+omitted while still keeping the basic API.
+
+To discover whether an object is freezable, client code should ask if it
+provides zc.freeze.interfaces.IFreezable.
+
+Site configurations or code that declares that an object is IFreezable
+is assuring that the object provides or can be adaptable to
+zc.freeze.interfaces.IFreezing.  This interface has only three elements:
+_z_frozen is a readonly boolean that returns whether the object has been
+versioned; _z_freeze_datetime is a readonly datetime in pytz.utc
+specifying when the object was frozen (or None, if it is not yet
+frozen); and _z_freeze is a method that actually freezes the object.  If
+the object is already frozen, it raises
+zc.freeze.interfaces.FrozenError.  If the object is not in a state to be
+frozen, it may raise zc.freeze.interfaces.FreezeError. If the freezing
+may succeed, the method should send a
+zc.freeze.interfaces.IObjectFrozenEvent (such as
+zc.freeze.interfaces.ObjectFrozenEvent).
+
+That's the heart of the package: an API and an agreement, with nothing to test
+directly.  One policy that this package does not directly support is that
+freezing an object might first create a copy and then version the copy
+rather than the original; or version the original but replace the copy in the
+location of the original; or make any other choices.  These approaches are
+intended to be implemented on top of--above--the zc.freeze API.  This
+package provides much simpler capabilities.
+
+Conveniences
+============
+
+The package does provide two default implementations of IFreezing, and a few
+conveniences.
+
+One IFreezing implementation is for objects that are directly aware of this
+API (as opposed to having the functionality assembled from adapters and other
+components).
+
+    >>> import zc.freeze
+    >>> v = zc.freeze.Freezing()
+    >>> from zc.freeze import interfaces
+    >>> from zope.interface.verify import verifyObject
+    >>> verifyObject(interfaces.IFreezing, v)
+    True
+    >>> verifyObject(interfaces.IFreezable, v)
+    True
+    >>> v._z_frozen
+    False
+    >>> v._z_frozen = True
+    Traceback (most recent call last):
+    ...
+    AttributeError: can't set attribute
+    >>> import pytz
+    >>> import datetime
+    >>> before = datetime.datetime.now(pytz.utc)
+    >>> v._z_freeze()
+    >>> before <= v._z_freeze_timestamp <= datetime.datetime.now(pytz.utc)
+    True
+    >>> v._z_frozen
+    True
+    >>> interfaces.IObjectFrozenEvent.providedBy(events[-1])
+    True
+    >>> events[-1].object is v
+    True
+    >>> v._z_freeze()
+    Traceback (most recent call last):
+    ...
+    FrozenError
+
+Another available implementation is an adapter, and stores the information in
+an annotation.  Here's a quick demo.
+
+    >>> import zope.annotation.interfaces
+    >>> from zope import interface, component
+    >>> class Demo(object):
+    ...     interface.implements(zope.annotation.interfaces.IAnnotatable)
+    ...
+    >>> import UserDict
+    >>> class DemoAnnotations(UserDict.UserDict):
+    ...     interface.implements(zope.annotation.interfaces.IAnnotations)
+    ...     component.adapts(Demo)
+    ...     def __init__(self, context):
+    ...         self.context = context
+    ...         self.data = getattr(context, '_z_demo', None)
+    ...         if self.data is None:
+    ...             self.data = context._z_demo = {}
+    ...
+    >>> component.provideAdapter(DemoAnnotations)
+    >>> component.provideAdapter(zc.freeze.FreezingAdapter)
+    >>> d = Demo()
+    >>> verifyObject(interfaces.IFreezing, interfaces.IFreezing(d))
+    True
+    >>> verifyObject(interfaces.IFreezable, interfaces.IFreezing(d))
+    True
+    >>> interfaces.IFreezing(d)._z_frozen
+    False
+    >>> interfaces.IFreezing(d)._z_frozen = True
+    Traceback (most recent call last):
+    ...
+    AttributeError: can't set attribute
+    >>> before = datetime.datetime.now(pytz.utc)
+    >>> interfaces.IFreezing(d)._z_freeze()
+    >>> (before <= interfaces.IFreezing(d)._z_freeze_timestamp <=
+    ...  datetime.datetime.now(pytz.utc))
+    True
+    >>> interfaces.IFreezing(d)._z_frozen
+    True
+    >>> interfaces.IObjectFrozenEvent.providedBy(events[-1])
+    True
+    >>> events[-1].object is d
+    True
+    >>> interfaces.IFreezing(d)._z_freeze()
+    Traceback (most recent call last):
+    ...
+    FrozenError
+
+The zc.freeze module also contains three helpers for writing properties and
+methods that are freeze-aware.
+
+A 'method' function can generate a freeze-aware method that raises a
+FrozenError if the object has been frozen.
+
+'setproperty' and 'delproperty' functions can generate a freeze-aware
+descriptor that raises a FrozenError if the set or del methods are called
+on a frozen object.  These are rwproperties (see rwproperty.txt; imported
+from another project.)
+
+'makeProperty' generates a freeze-aware descriptor that does a simple
+get/set but raises FrozenError if the set is attempted on a frozen
+object.
+
+    >>> class BiggerDemo(Demo):
+    ...     counter = 0
+    ...     @zc.freeze.method
+    ...     def increase(self):
+    ...         self.counter += 1
+    ...     _complex = 1
+    ...     @property
+    ...     def complex_property(self):
+    ...         return str(self._complex)
+    ...     @zc.freeze.setproperty
+    ...     def complex_property(self, value):
+    ...         self._complex = value * 2
+    ...     zc.freeze.makeProperty('simple_property')
+    ...
+    >>> d = BiggerDemo()
+    >>> d.counter
+    0
+    >>> d.complex_property
+    '1'
+    >>> d.simple_property # None
+    >>> d.increase()
+    >>> d.counter
+    1
+    >>> d.complex_property = 4
+    >>> d.complex_property
+    '8'
+    >>> d.simple_property = 'hi'
+    >>> d.simple_property
+    'hi'
+    >>> interfaces.IFreezing(d)._z_frozen
+    False
+    >>> interfaces.IFreezing(d)._z_freeze()
+    >>> interfaces.IFreezing(d)._z_frozen
+    True
+    >>> d.counter
+    1
+    >>> d.increase()
+    Traceback (most recent call last):
+    ...
+    FrozenError
+    >>> d.counter
+    1
+    >>> d.complex_property
+    '8'
+    >>> d.complex_property = 10
+    Traceback (most recent call last):
+    ...
+    FrozenError
+    >>> d.complex_property
+    '8'
+    >>> d.simple_property
+    'hi'
+    >>> d.simple_property = 'bye'
+    Traceback (most recent call last):
+    ...
+    FrozenError
+    >>> d.simple_property
+    'hi'

Copied: zc.freeze/trunk/src/zc/freeze/__init__.py (from rev 69734, zc.copyversion/trunk/src/zc/copyversion/versioning.py)
===================================================================
--- zc.copyversion/trunk/src/zc/copyversion/versioning.py	2006-08-23 19:03:56 UTC (rev 69734)
+++ zc.freeze/trunk/src/zc/freeze/__init__.py	2006-08-23 21:51:35 UTC (rev 69739)
@@ -0,0 +1,108 @@
+import sys
+import datetime
+import pytz
+import persistent
+from zope import interface, event, component
+import zope.annotation.interfaces
+from zope.cachedescriptors.property import Lazy
+
+from zc.freeze import interfaces
+# import rwproperty
+from zc.freeze import rwproperty
+
+def method(f):
+    def wrapper(self, *args, **kwargs):
+        try: # micro-optimize for the "yes, I'm already versioned" story
+            frozen = self._z_frozen
+        except AttributeError:
+            frozen = interfaces.IFreezing(self)._z_frozen
+        if frozen:
+            raise interfaces.FrozenError
+        return f(self, *args, **kwargs)
+    return wrapper
+
+class setproperty(rwproperty.rwproperty):
+
+    @staticmethod
+    def createProperty(func):
+        return property(None, method(func))
+
+    @staticmethod
+    def enhanceProperty(oldprop, func):
+        return property(oldprop.fget, method(func), oldprop.fdel)
+
+class delproperty(rwproperty.rwproperty):
+
+    @staticmethod
+    def createProperty(func):
+        return property(None, None, method(func))
+
+    @staticmethod
+    def enhanceProperty(oldprop, func):
+        return property(oldprop.fget, oldprop.fset, method(func))
+
+def makeProperty(name, default=None):
+    protected = '_z_%s__' % name
+    sys._getframe(1).f_locals[name] = property(
+        lambda self: getattr(self, protected, default),
+        method(lambda self, value: setattr(self, protected, value)))
+
+
+class Data(persistent.Persistent):
+    interface.implements(interfaces.IData)
+    def __init__(self):
+        self._z__freeze_timestamp = datetime.datetime.now(pytz.utc)
+
+    @property
+    def _z_freeze_timestamp(self):
+        return self._z__freeze_timestamp
+    
+
+class Freezing(object):
+    interface.implements(interfaces.IFreezing)
+
+    _z__freezing_data = None
+
+    @property
+    def _z_frozen(self):
+        return self._z__freezing_data is not None
+
+    @property
+    def _z_freeze_timestamp(self):
+        res = self._z__freezing_data
+        if res is not None:
+            return res._z_freeze_timestamp
+
+    @method
+    def _z_freeze(self):
+        self._z__freezing_data = Data()
+        event.notify(interfaces.ObjectFrozenEvent(self))
+
+KEY = "zc.freeze._z_freeze_timestamp"
+
+class FreezingAdapter(object):
+    interface.implements(interfaces.IFreezing)
+    component.adapts(zope.annotation.interfaces.IAnnotatable)
+
+    def __init__(self, context):
+        self.context = context
+
+    @Lazy
+    def annotations(self):
+        return zope.annotation.interfaces.IAnnotations(self.context)
+
+    @property
+    def _z_frozen(self):
+        return self.annotations.get(KEY) is not None
+
+    @property
+    def _z_freeze_timestamp(self):
+        res = self.annotations.get(KEY)
+        if res is not None:
+            return res._z_freeze_timestamp
+
+    @method
+    def _z_freeze(self):
+        self.annotations[KEY] = Data()
+        event.notify(interfaces.ObjectFrozenEvent(self.context))
+

Copied: zc.freeze/trunk/src/zc/freeze/configure.zcml (from rev 69734, zc.copyversion/trunk/src/zc/copyversion/configure.zcml)
===================================================================
--- zc.copyversion/trunk/src/zc/copyversion/configure.zcml	2006-08-23 19:03:56 UTC (rev 69734)
+++ zc.freeze/trunk/src/zc/freeze/configure.zcml	2006-08-23 21:51:35 UTC (rev 69739)
@@ -0,0 +1,17 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="zc.freeze">
+
+  <class class=".FreezingAdapter">
+    <require permission="zope.View"
+             attributes="_z_frozen _z_freeze_timestamp" />
+    <require permission="zope.ManageContent" attributes="_z_freeze" />
+  </class>
+
+  <adapter factory=".FreezingAdapter" trusted="1" />
+
+  <adapter factory=".copier.data_copyfactory" />
+
+  <subscriber handler=".subscribers.freezer" />
+
+</configure>

Copied: zc.freeze/trunk/src/zc/freeze/copier.py (from rev 69734, zc.copyversion/trunk/src/zc/copyversion/copier.py)
===================================================================
--- zc.copyversion/trunk/src/zc/copyversion/copier.py	2006-08-23 19:03:56 UTC (rev 69734)
+++ zc.freeze/trunk/src/zc/freeze/copier.py	2006-08-23 21:51:35 UTC (rev 69739)
@@ -0,0 +1,14 @@
+
+import zope.component
+import zope.interface
+
+import zc.copy.interfaces
+import zc.freeze.interfaces
+
+# this can be used to rip off old versioning data and put new values in place
+ at zope.component.adapter(zc.freeze.interfaces.IData)
+ at zope.interface.implementer(zc.copy.interfaces.ICopyHook)
+def data_copyfactory(obj):
+    def factory(location, register):
+        return None
+    return factory

Copied: zc.freeze/trunk/src/zc/freeze/copier.txt (from rev 69734, zc.copyversion/trunk/src/zc/copyversion/copier.txt)
===================================================================
--- zc.copyversion/trunk/src/zc/copyversion/copier.txt	2006-08-23 19:03:56 UTC (rev 69734)
+++ zc.freeze/trunk/src/zc/freeze/copier.txt	2006-08-23 21:51:35 UTC (rev 69739)
@@ -0,0 +1,40 @@
+The copier module uses zc.copy.  See the README.txt for that module for
+explanations of how this hook fits in to the larger picture.
+
+Our use case is that copies of versioned objects should not be
+versioned.  We do this by storing versioning on a special object that is
+converted to None when it is copied.
+
+Open up copier.py and look at the data_copyfactory function.  It
+adapts zc.freeze.interfaces.IData and implements
+zc.copy.interfaces.ICopyHook.  It returns a function that always returns
+None, no matter what the main location being copied is.
+
+Let's register it and look at an example.  Here's what happens if we copy a
+versioned object without the adapter.
+
+    >>> import zc.freeze
+    >>> original = zc.freeze.Freezing()
+    >>> original._z_freeze()
+    >>> original._z_frozen
+    True
+    >>> import zc.copy
+    >>> copy = zc.copy.copy(original)
+    >>> copy is original
+    False
+    >>> copy._z_frozen
+    True
+
+Again, in the common case, we don't want copies to be versioned.  Let's
+register the adapter and try that again.
+
+    >>> import zope.component
+    >>> import zc.freeze.copier
+    >>> zope.component.provideAdapter(zc.freeze.copier.data_copyfactory)
+    >>> copy2 = zc.copy.copy(original)
+    >>> copy2 is original
+    False
+    >>> copy2._z_frozen
+    False
+
+Much better.

Copied: zc.freeze/trunk/src/zc/freeze/interfaces.py (from rev 69734, zc.copyversion/trunk/src/zc/copyversion/interfaces.py)
===================================================================
--- zc.copyversion/trunk/src/zc/copyversion/interfaces.py	2006-08-23 19:03:56 UTC (rev 69734)
+++ zc.freeze/trunk/src/zc/freeze/interfaces.py	2006-08-23 21:51:35 UTC (rev 69739)
@@ -0,0 +1,43 @@
+from zope import interface
+import zope.component.interfaces
+
+class FrozenError(Exception):
+    """The object is already frozen and cannot be changed."""
+
+class FreezeError(Exception):
+    """The object is unable to be frozen at this time."""
+
+class IObjectFrozenEvent(zope.component.interfaces.IObjectEvent):
+    """The object is being frozen"""
+
+class ObjectFrozenEvent(zope.component.interfaces.ObjectEvent):
+    """Object was frozen"""
+
+    interface.implements(IObjectFrozenEvent)
+
+class IFreezable(interface.Interface):
+    """Marker interface specifying that it is desirable to adapt the object to
+    IFreezing"""
+
+class IFreezing(IFreezable):
+    _z_frozen = interface.Attribute(
+        """Boolean, whether the object is frozen.  Readonly""")
+
+    _z_freeze_timestamp = interface.Attribute(
+        "datetime.datetime in pytz.utc of when frozen, or None.  Readonly.")
+
+    def _z_freeze():
+        """sets _z_frozen to True, sets _z_freeze_timestamp, and fires
+        ObjectFrozenEvent.  Raises FrozenError if _z_frozen is already True."""
+
+class ITokenEnforced(interface.Interface):
+    """A marker interface indicating that the instance wants to have its
+    freezing enforced by zope.locking tokens (see the subscribers module).
+    """
+
+class IData(interface.Interface):
+    """An object used to store freezing data for another object.  Useful for
+    the copy hook.  Only of internal interest."""
+
+    _z_freeze_timestamp = interface.Attribute(
+        "datetime.datetime in pytz.utc of when frozen, or None.")

Copied: zc.freeze/trunk/src/zc/freeze/rwproperty.py (from rev 69734, zc.copyversion/trunk/src/zc/copyversion/rwproperty.py)

Copied: zc.freeze/trunk/src/zc/freeze/subscribers.py (from rev 69734, zc.copyversion/trunk/src/zc/copyversion/subscribers.py)
===================================================================
--- zc.copyversion/trunk/src/zc/copyversion/subscribers.py	2006-08-23 19:03:56 UTC (rev 69734)
+++ zc.freeze/trunk/src/zc/freeze/subscribers.py	2006-08-23 21:51:35 UTC (rev 69739)
@@ -0,0 +1,16 @@
+from zope import component
+import zope.locking.interfaces
+import zope.locking.tokens
+
+from zc.freeze import interfaces
+
+ at component.adapter(interfaces.ITokenEnforced, interfaces.IObjectFrozenEvent)
+def freezer(obj, ev):
+    util = component.getUtility(zope.locking.interfaces.ITokenUtility)
+    token = util.get(obj)
+    if token is not None:
+        if zope.locking.interfaces.IEndable.providedBy(token):
+            token.end()
+        else:
+            return
+    util.register(zope.locking.tokens.Freeze(obj))

Copied: zc.freeze/trunk/src/zc/freeze/subscribers.txt (from rev 69734, zc.copyversion/trunk/src/zc/copyversion/subscribers.txt)
===================================================================
--- zc.copyversion/trunk/src/zc/copyversion/subscribers.txt	2006-08-23 19:03:56 UTC (rev 69734)
+++ zc.freeze/trunk/src/zc/freeze/subscribers.txt	2006-08-23 21:51:35 UTC (rev 69739)
@@ -0,0 +1,87 @@
+The subscribers module contains a subscriber for "enforcing" freezing
+using zope.locking tokens.  This, of course, assumes that zope.locking
+tokens are configured to themselves be enforced somehow, using
+approaches such as those in zc.tokenpolicy.  By default, this `freezer`
+subscriber is registered only for objects that provide
+zc.freeze.interfaces.ITokenEnforced.  Imagine a demo object that
+implements ITokenEnforced, with a token utility already in place [#setup]_.
+
+    >>> import zc.freeze.subscribers
+    >>> import zope.component
+    >>> zope.component.provideHandler(zc.freeze.subscribers.freezer)
+    >>> demo = Demo()
+    >>> import zc.freeze.interfaces
+    >>> import zope.interface
+    >>> zope.interface.directlyProvides(
+    ...     demo, zc.freeze.interfaces.ITokenEnforced)
+    >>> zc.freeze.interfaces.ITokenEnforced.providedBy(demo)
+    True
+    >>> util.get(demo) is None
+    True
+    >>> demo._z_frozen
+    False
+    >>> demo._z_freeze()
+    >>> demo._z_frozen
+    True
+    >>> import zope.locking.interfaces
+    >>> zope.locking.interfaces.IFreeze.providedBy(util.get(demo))
+    True
+
+If an object does not provide ITokenEnforced, the subscriber will not
+fire, given a default registration.
+
+    >>> demo2 = Demo()
+    >>> zc.freeze.interfaces.ITokenEnforced.providedBy(demo2)
+    False
+    >>> demo2._z_freeze()
+    >>> demo2._z_frozen
+    True
+    >>> util.get(demo2) is None
+    True
+
+=========
+Footnotes
+=========
+
+.. [#setup] This sets up a zope.locking token utility as a global,
+    non-persistent utility.  This is completely useless and even dangerous
+    in real use, but is fine for this test.  This code also creates a Demo
+    class that provides IVersioning and can be used in our non-persistent
+    setup.
+
+    >>> from zope import interface, component
+    >>> from zope.locking import utility, interfaces
+    >>> util = utility.TokenUtility()
+    >>> component.provideUtility(util, provides=interfaces.ITokenUtility)
+    >>> import zope.app.keyreference.interfaces
+    >>> class IDemo(interface.Interface):
+    ...     """a demonstration interface for a demonstration class"""
+    ...
+    >>> import zc.freeze
+    >>> class Demo(zc.freeze.Freezing):
+    ...     interface.implements(IDemo)
+    ...
+    >>> class DemoKeyReference(object):
+    ...     component.adapts(IDemo)
+    ...     _class_counter = 0
+    ...     interface.implements(
+    ...         zope.app.keyreference.interfaces.IKeyReference)
+    ...     def __init__(self, context):
+    ...         self.context = context
+    ...         class_ = type(self)
+    ...         self._id = getattr(context, '__demo_key_reference__', None)
+    ...         if self._id is None:
+    ...             self._id = class_._class_counter
+    ...             context.__demo_key_reference__ = self._id
+    ...             class_._class_counter += 1
+    ...     key_type_id = 'zc.freeze.DemoKeyReference'
+    ...     def __call__(self):
+    ...         return self.context
+    ...     def __hash__(self):
+    ...         return (self.key_type_id, self._id)
+    ...     def __cmp__(self, other):
+    ...         if self.key_type_id == other.key_type_id:
+    ...             return cmp(self._id, other._id)
+    ...         return cmp(self.key_type_id, other.key_type_id) 
+    ...
+    >>> component.provideAdapter(DemoKeyReference)

Copied: zc.freeze/trunk/src/zc/freeze/tests.py (from rev 69734, zc.copyversion/trunk/src/zc/copyversion/tests.py)
===================================================================
--- zc.copyversion/trunk/src/zc/copyversion/tests.py	2006-08-23 19:03:56 UTC (rev 69734)
+++ zc.freeze/trunk/src/zc/freeze/tests.py	2006-08-23 21:51:35 UTC (rev 69739)
@@ -0,0 +1,57 @@
+import unittest
+from zope.app.testing import placelesssetup
+import zope.testing.module
+from zope.testing import doctest
+from zope.component import testing, eventtesting
+from zope.app.container.tests.placelesssetup import PlacelessSetup
+
+container_setup = PlacelessSetup()
+
+def setUp(test):
+    placelesssetup.setUp(test)
+    events = test.globs['events'] = []
+    import zope.event
+    zope.event.subscribers.append(events.append)
+
+def tearDown(test):
+    placelesssetup.tearDown(test)
+    events = test.globs.pop('events')
+    import zope.event
+    assert zope.event.subscribers.pop().__self__ is events
+    del events[:] # being paranoid
+
+def subscribersSetUp(test):
+    placelesssetup.setUp(test)
+    zope.testing.module.setUp(test, 'zc.freeze.subscribers_txt')
+
+def subscribersTearDown(test):
+    zope.testing.module.tearDown(test)
+    placelesssetup.tearDown(test)
+
+def copierSetUp(test):
+    zope.testing.module.setUp(test, 'zc.freeze.copier_txt')
+    testing.setUp(test)
+    eventtesting.setUp(test)
+    container_setup.setUp()
+
+def copierTearDown(test):
+    zope.testing.module.tearDown(test)
+    testing.tearDown(test)
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocFileSuite(
+            'README.txt',
+            setUp=setUp, tearDown=tearDown),
+        doctest.DocFileSuite(
+            'subscribers.txt',
+            setUp=subscribersSetUp, tearDown=subscribersTearDown,
+            optionflags=doctest.INTERPRET_FOOTNOTES),
+        doctest.DocFileSuite(
+            'copier.txt',
+            setUp=copierSetUp,
+            tearDown=copierTearDown),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')



More information about the Checkins mailing list