[Zope3-checkins] SVN: Zope3/branches/srichter-blow-services/ Merged latest changes of the trunk to the branch. All tests pass and

Stephan Richter srichter at cosmos.phy.tufts.edu
Mon Feb 14 11:14:21 EST 2005


Log message for revision 29140:
  Merged latest changes of the trunk to the branch. All tests pass and 
  everything seems to be fine!
  
  

Changed:
  U   Zope3/branches/srichter-blow-services/doc/CHANGES.txt
  U   Zope3/branches/srichter-blow-services/src/ZEO/tests/ConnectionTests.py
  U   Zope3/branches/srichter-blow-services/src/ZEO/zrpc/client.py
  U   Zope3/branches/srichter-blow-services/src/ZEO/zrpc/connection.py
  U   Zope3/branches/srichter-blow-services/src/ZODB/DemoStorage.py
  U   Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsdump.py
  U   Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsoids.py
  U   Zope3/branches/srichter-blow-services/src/ZODB/component.xml
  U   Zope3/branches/srichter-blow-services/src/ZODB/fsIndex.py
  A   Zope3/branches/srichter-blow-services/src/ZODB/tests/test_fsdump.py
  U   Zope3/branches/srichter-blow-services/src/ZODB/tests/testfsIndex.py
  U   Zope3/branches/srichter-blow-services/src/ZODB/tests/testfsoids.py
  U   Zope3/branches/srichter-blow-services/src/zope/app/container/browser/contents.py
  U   Zope3/branches/srichter-blow-services/src/zope/app/container/browser/tests/test_contents.py
  U   Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/__init__.py
  U   Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/configure.zcml
  U   Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/interfaces.py
  U   Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/tests/test_rename.py
  U   Zope3/branches/srichter-blow-services/src/zope/app/form/browser/configure.zcml
  U   Zope3/branches/srichter-blow-services/src/zope/app/form/browser/source.py
  U   Zope3/branches/srichter-blow-services/src/zope/app/form/utility.py
  U   Zope3/branches/srichter-blow-services/src/zope/app/ftp/__init__.py
  U   Zope3/branches/srichter-blow-services/src/zope/app/ftp/tests/test_ftpview.py
  U   Zope3/branches/srichter-blow-services/src/zope/component/factory.py
  U   Zope3/branches/srichter-blow-services/src/zope/component/factory.txt
  U   Zope3/branches/srichter-blow-services/src/zope/interface/README.txt
  U   Zope3/branches/srichter-blow-services/src/zope/interface/__init__.py
  U   Zope3/branches/srichter-blow-services/src/zope/interface/declarations.py
  U   Zope3/branches/srichter-blow-services/src/zope/interface/interfaces.py
  U   Zope3/branches/srichter-blow-services/src/zope/schema/__init__.py
  U   Zope3/branches/srichter-blow-services/src/zope/schema/_field.py
  U   Zope3/branches/srichter-blow-services/src/zope/schema/interfaces.py
  A   Zope3/branches/srichter-blow-services/src/zope/schema/tests/test_timedelta.py

-=-
Modified: Zope3/branches/srichter-blow-services/doc/CHANGES.txt
===================================================================
--- Zope3/branches/srichter-blow-services/doc/CHANGES.txt	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/doc/CHANGES.txt	2005-02-14 16:14:21 UTC (rev 29140)
@@ -10,6 +10,8 @@
 
     New features
 
+      - New schema field: Timedelta.
+
       - Implemented some initial deprecation framework, see
         `zope.deprecation`. It allows one to deprecate methods and properties
         in classes as well as any name in a module.
@@ -361,6 +363,10 @@
           skin is specified. This allows developers to create skins that do
           not include the default layer.
 
+      - Replaced copypastemove.rename with adapters to IContainerItemRenamer.
+        Instead of `rename(container, oldName, newName)`, one should use
+        `IContainerItemRenamer(container).renameItem(oldName, newName)`.
+
     Bug Fixes
 
       - Partially fixed issue #306 (Problem 2: Browser page and view directive
@@ -422,7 +428,7 @@
 
       Jim Fulton, Fred Drake, Philipp von Weitershausen, Stephan Richter,
       Gustavo Niemeyer, Daniel Nouri, Volker Bachschneider, Roger Ineichen,
-      Shane Hathaway, Bjorn Tillenius, Garrett Smith
+      Shane Hathaway, Bjorn Tillenius, Garrett Smith, Marius Gedminas
 
       Note: If you are not listed and contributed, please add yourself. This
       note will be deleted before the release.

Modified: Zope3/branches/srichter-blow-services/src/ZEO/tests/ConnectionTests.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZEO/tests/ConnectionTests.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/ZEO/tests/ConnectionTests.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -216,7 +216,7 @@
     def pollUp(self, timeout=30.0, storage=None):
         if storage is None:
             storage = self._storage
-        # Poll until we're connected
+        # Poll until we're connected.
         now = time.time()
         giveup = now + timeout
         while not storage.is_connected():
@@ -224,9 +224,15 @@
             now = time.time()
             if now > giveup:
                 self.fail("timed out waiting for storage to connect")
+            # When the socket map is empty, poll() returns immediately,
+            # and this is a pure busy-loop then.  At least on some Linux
+            # flavors, that can starve the thread trying to connect,
+            # leading to grossly increased runtime (typical) or bogus
+            # "timed out" failures.  A little sleep here cures both.
+            time.sleep(0.1)
 
     def pollDown(self, timeout=30.0):
-        # Poll until we're disconnected
+        # Poll until we're disconnected.
         now = time.time()
         giveup = now + timeout
         while self._storage.is_connected():
@@ -234,6 +240,8 @@
             now = time.time()
             if now > giveup:
                 self.fail("timed out waiting for storage to disconnect")
+            # See pollUp() for why we sleep a little here.
+            time.sleep(0.1)
 
 
 class ConnectionTests(CommonSetupTearDown):

Modified: Zope3/branches/srichter-blow-services/src/ZEO/zrpc/client.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZEO/zrpc/client.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/ZEO/zrpc/client.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -27,7 +27,7 @@
 
 from ZEO.zrpc.log import log
 from ZEO.zrpc.trigger import trigger
-from ZEO.zrpc.connection import ManagedConnection
+from ZEO.zrpc.connection import ManagedClientConnection
 
 class ConnectionManager(object):
     """Keeps a connection up over time"""
@@ -476,8 +476,8 @@
         Call the client's testConnection(), giving the client a chance
         to do app-level check of the connection.
         """
-        self.conn = ManagedConnection(self.sock, self.addr,
-                                      self.client, self.mgr)
+        self.conn = ManagedClientConnection(self.sock, self.addr,
+                                            self.client, self.mgr)
         self.sock = None # The socket is now owned by the connection
         try:
             self.preferred = self.client.testConnection(self.conn)

Modified: Zope3/branches/srichter-blow-services/src/ZEO/zrpc/connection.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZEO/zrpc/connection.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/ZEO/zrpc/connection.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -67,6 +67,64 @@
         self.ready.wait()
         Delay.error(self, exc_info)
 
+# PROTOCOL NEGOTIATION
+#
+# The code implementing protocol version 2.0.0 (which is deployed
+# in the field and cannot be changed) *only* talks to peers that
+# send a handshake indicating protocol version 2.0.0.  In that
+# version, both the client and the server immediately send out
+# their protocol handshake when a connection is established,
+# without waiting for their peer, and disconnect when a different
+# handshake is receive.
+#
+# The new protocol uses this to enable new clients to talk to
+# 2.0.0 servers.  In the new protocol:
+#
+#    The server sends its protocol handshake to the client at once.
+#
+#    The client waits until it receives the server's protocol handshake
+#    before sending its own handshake.  The client sends the lower of its
+#    own protocol version and the server protocol version, allowing it to
+#    talk to servers using later protocol versions (2.0.2 and higher) as
+#    well:  the effective protocol used will be the lower of the client
+#    and server protocol.
+#
+# [Ugly details:  In order to treat the first received message (protocol
+#  handshake) differently than all later messages, both client and server
+#  start by patching their message_input() method to refer to their
+#  recv_handshake() method instead.  In addition, the client has to arrange
+#  to queue (delay) outgoing messages until it receives the server's
+#  handshake, so that the first message the client sends to the server is
+#  the client's handshake.  This multiply-special treatment of the first
+#  message is delicate, and several asyncore and thread subtleties were
+#  handled unsafely before ZODB 3.2.6.
+# ]
+#
+# The ZEO modules ClientStorage and ServerStub have backwards
+# compatibility code for dealing with the previous version of the
+# protocol.  The client accepts the old version of some messages,
+# and will not send new messages when talking to an old server.
+#
+# As long as the client hasn't sent its handshake, it can't send
+# anything else; output messages are queued during this time.
+# (Output can happen because the connection testing machinery can
+# start sending requests before the handshake is received.)
+#
+# UPGRADING FROM ZEO 2.0.0 TO NEWER VERSIONS:
+#
+# Because a new client can talk to an old server, but not vice
+# versa, all clients should be upgraded before upgrading any
+# servers.  Protocol upgrades beyond 2.0.1 will not have this
+# restriction, because clients using protocol 2.0.1 or later can
+# talk to both older and newer servers.
+#
+# No compatibility with protocol version 1 is provided.
+
+# Connection is abstract (it must be derived from).  ManagedServerConnection
+# and ManagedClientConnection are the concrete subclasses.  They need to
+# supply a handshake() method appropriate for their role in protocol
+# negotiation.
+
 class Connection(smac.SizedMessageAsyncConnection, object):
     """Dispatcher for RPC on object on both sides of socket.
 
@@ -136,18 +194,33 @@
     #             getExtensionMethods().
     #             getInvalidations().
 
-    def __init__(self, sock, addr, obj=None):
+    # Client constructor passes 'C' for tag, server constructor 'S'.  This
+    # is used in log messages.
+    def __init__(self, sock, addr, obj, tag):
         self.obj = None
         self.marshal = Marshaller()
         self.closed = False
-        self.msgid = 0
-        self.peer_protocol_version = None # Set in recv_handshake()
-        self.logger = logging.getLogger('ZEO.zrpc.Connection')
+        self.peer_protocol_version = None # set in recv_handshake()
+
+        assert tag in "CS"
+        self.logger = logging.getLogger('ZEO.zrpc.Connection(%c)' % tag)
         if isinstance(addr, types.TupleType):
             self.log_label = "(%s:%d) " % addr
         else:
             self.log_label = "(%s) " % addr
-        self.__super_init(sock, addr)
+
+        # Supply our own socket map, so that we don't get registered with
+        # the asyncore socket map just yet.  The initial protocol messages
+        # are treated very specially, and we dare not get invoked by asyncore
+        # before that special-case setup is complete.  Some of that setup
+        # occurs near the end of this constructor, and the rest is done by
+        # a concrete subclass's handshake() method.  Unfortunately, because
+        # we ultimately derive from asyncore.dispatcher, it's not possible
+        # to invoke the superclass constructor without asyncore stuffing
+        # us into _some_ socket map.
+        ourmap = {}
+        self.__super_init(sock, addr, map=ourmap)
+
         # A Connection either uses asyncore directly or relies on an
         # asyncore mainloop running in a separate thread.  If
         # thr_async is true, then the mainloop is running in a
@@ -157,24 +230,54 @@
         self.thr_async = False
         self.trigger = None
         self._prepare_async()
+
         # The singleton dict is used in synchronous mode when a method
         # needs to call into asyncore to try to force some I/O to occur.
         # The singleton dict is a socket map containing only this object.
         self._singleton = {self._fileno: self}
+
         # msgid_lock guards access to msgid
+        self.msgid = 0
         self.msgid_lock = threading.Lock()
+
         # replies_cond is used to block when a synchronous call is
         # waiting for a response
         self.replies_cond = threading.Condition()
         self.replies = {}
+
         # waiting_for_reply is used internally to indicate whether
         # a call is in progress.  setting a session key is deferred
         # until after the call returns.
         self.waiting_for_reply = False
         self.delay_sesskey = None
         self.register_object(obj)
+
+        # The first message we see is a protocol handshake.  message_input()
+        # is temporarily replaced by recv_handshake() to treat that message
+        # specially.  revc_handshake() does "del self.message_input", which
+        # uncovers the normal message_input() method thereafter.
+        self.message_input = self.recv_handshake
+
+        # Server and client need to do different things for protocol
+        # negotiation, and handshake() is implemented differently in each.
         self.handshake()
 
+        # Now it's safe to register with asyncore's socket map; it was not
+        # safe before message_input was replaced, or before handshake() was
+        # invoked.
+        # Obscure:  in Python 2.4, the base asyncore.dispatcher class grew
+        # a ._map attribute, which is used instead of asyncore's global
+        # socket map when ._map isn't None.  Because we passed `ourmap` to
+        # the base class constructor above, in 2.4 asyncore believes we want
+        # to use `ourmap` instead of the global socket map -- but we don't.
+        # So we have to replace our ._map with the global socket map, and
+        # update the global socket map with `ourmap`.  Replacing our ._map
+        # isn't necessary before Python 2.4, but doesn't hurt then (it just
+        # gives us an unused attribute in 2.3); updating the global socket
+        # map is necessary regardless of Python version.
+        self._map = asyncore.socket_map
+        asyncore.socket_map.update(ourmap)
+
     def __repr__(self):
         return "<%s %s>" % (self.__class__.__name__, self.addr)
 
@@ -192,7 +295,7 @@
         self.__super_close()
 
     def close_trigger(self):
-        # Overridden by ManagedConnection
+        # Overridden by ManagedClientConnection.
         if self.trigger is not None:
             self.trigger.close()
 
@@ -200,24 +303,26 @@
         """Register obj as the true object to invoke methods on."""
         self.obj = obj
 
-    def handshake(self, proto=None):
-        # Overridden by ManagedConnection
+    # Subclass must implement.  handshake() is called by the constructor,
+    # near its end, but before self is added to asyncore's socket map.
+    # When a connection is created the first message sent is a 4-byte
+    # protocol version.  This allows the protocol to evolve over time, and
+    # lets servers handle clients using multiple versions of the protocol.
+    # In general, the server's handshake() just needs to send the server's
+    # preferred protocol; the client's also needs to queue (delay) outgoing
+    # messages until it sees the handshake from the server.
+    def handshake(self):
+        raise NotImplementedError
 
-        # When a connection is created the first message sent is a
-        # 4-byte protocol version.  This mechanism should allow the
-        # protocol to evolve over time, and let servers handle clients
-        # using multiple versions of the protocol.
-
-        # The mechanism replaces the message_input() method for the
-        # first message received.
-
-        # The client sends the protocol version it is using.
-        self.message_input = self.recv_handshake
-        self.message_output(proto or self.protocol_version)
-
+    # Replaces message_input() for the first message received.  Records the
+    # protocol sent by the peer in `peer_protocol_version`, restores the
+    # normal message_input() method, and raises an exception if the peer's
+    # protocol is unacceptable.  That's all the server needs to do.  The
+    # client needs to do additional work in response to the server's
+    # handshake, and extends this method.
     def recv_handshake(self, proto):
-        # Extended by ManagedConnection
-        del self.message_input
+        # Extended by ManagedClientConnection.
+        del self.message_input  # uncover normal-case message_input()
         self.peer_protocol_version = proto
         if self.oldest_protocol_version <= proto <= self.protocol_version:
             self.log("received handshake %r" % proto, level=logging.INFO)
@@ -227,7 +332,7 @@
             raise ZRPCError("bad handshake %r" % proto)
 
     def message_input(self, message):
-        """Decoding an incoming message and dispatch it"""
+        """Decode an incoming message and dispatch it"""
         # If something goes wrong during decoding, the marshaller
         # will raise an exception.  The exception will ultimately
         # result in asycnore calling handle_error(), which will
@@ -563,82 +668,86 @@
 
     def __init__(self, sock, addr, obj, mgr):
         self.mgr = mgr
-        self.__super_init(sock, addr, obj)
+        self.__super_init(sock, addr, obj, 'S')
         self.obj.notifyConnected(self)
 
+    def handshake(self):
+        # Send the server's preferred protocol to the client.
+        self.message_output(self.protocol_version)
+
     def close(self):
         self.obj.notifyDisconnected()
         self.mgr.close_conn(self)
         self.__super_close()
 
-class ManagedConnection(Connection):
+class ManagedClientConnection(Connection):
     """Client-side Connection subclass."""
     __super_init = Connection.__init__
     __super_close = Connection.close
+    base_message_output = Connection.message_output
 
     def __init__(self, sock, addr, obj, mgr):
         self.mgr = mgr
-        self.__super_init(sock, addr, obj)
+
+        # We can't use the base smac's message_output directly because the
+        # client needs to queue outgoing messages until it's seen the
+        # initial protocol handshake from the server.  So we have our own
+        # message_ouput() method, and support for initial queueing.  This is
+        # a delicate design, requiring an output mutex to be wholly
+        # thread-safe.
+        # Caution:  we must set this up before calling the base class
+        # constructor, because the latter registers us with asyncore;
+        # we need to guarantee that we'll queue outgoing messages before
+        # asyncore learns about us.
+        self.output_lock = threading.Lock()
+        self.queue_output = True
+        self.queued_messages = []
+
+        self.__super_init(sock, addr, obj, tag='C')
         self.check_mgr_async()
 
-    # PROTOCOL NEGOTIATION:
-    #
-    # The code implementing protocol version 2.0.0 (which is deployed
-    # in the field and cannot be changed) *only* talks to peers that
-    # send a handshake indicating protocol version 2.0.0.  In that
-    # version, both the client and the server immediately send out
-    # their protocol handshake when a connection is established,
-    # without waiting for their peer, and disconnect when a different
-    # handshake is receive.
-    #
-    # The new protocol uses this to enable new clients to talk to
-    # 2.0.0 servers: in the new protocol, the client waits until it
-    # receives the server's protocol handshake before sending its own
-    # handshake.  The client sends the lower of its own protocol
-    # version and the server protocol version, allowing it to talk to
-    # servers using later protocol versions (2.0.2 and higher) as
-    # well: the effective protocol used will be the lower of the
-    # client and server protocol.
-    #
-    # The ZEO modules ClientStorage and ServerStub have backwards
-    # compatibility code for dealing with the previous version of the
-    # protocol.  The client accept the old version of some messages,
-    # and will not send new messages when talking to an old server.
-    #
-    # As long as the client hasn't sent its handshake, it can't send
-    # anything else; output messages are queued during this time.
-    # (Output can happen because the connection testing machinery can
-    # start sending requests before the handshake is received.)
-    #
-    # UPGRADING FROM ZEO 2.0.0 TO NEWER VERSIONS:
-    #
-    # Because a new client can talk to an old server, but not vice
-    # versa, all clients should be upgraded before upgrading any
-    # servers.  Protocol upgrades beyond 2.0.1 will not have this
-    # restriction, because clients using protocol 2.0.1 or later can
-    # talk to both older and newer servers.
-    #
-    # No compatibility with protocol version 1 is provided.
+    # Our message_ouput() queues messages until recv_handshake() gets the
+    # protocol handshake from the server.
+    def message_output(self, message):
+        self.output_lock.acquire()
+        try:
+            if self.queue_output:
+                self.queued_messages.append(message)
+            else:
+                assert not self.queued_messages
+                self.base_message_output(message)
+        finally:
+            self.output_lock.release()
 
     def handshake(self):
-        self.message_input = self.recv_handshake
-        self.message_output = self.queue_output
-        self.output_queue = []
-        # The handshake is sent by recv_handshake() below
+        # The client waits to see the server's handshake.  Outgoing messages
+        # are queued for the duration.  The client will send its own
+        # handshake after the server's handshake is seen, in recv_handshake()
+        # below.  It will then send any messages queued while waiting.
+        assert self.queue_output # the constructor already set this
 
-    def queue_output(self, message):
-        self.output_queue.append(message)
-
     def recv_handshake(self, proto):
-        del self.message_output
+        # The protocol to use is the older of our and the server's preferred
+        # protocols.
         proto = min(proto, self.protocol_version)
-        Connection.recv_handshake(self, proto) # Raise error if wrong proto
-        self.message_output(proto)
-        queue = self.output_queue
-        del self.output_queue
-        for message in queue:
-            self.message_output(message)
 
+        # Restore the normal message_input method, and raise an exception
+        # if the protocol version is too old.
+        Connection.recv_handshake(self, proto)
+
+        # Tell the server the protocol in use, then send any messages that
+        # were queued while waiting to hear the server's protocol, and stop
+        # queueing messages.
+        self.output_lock.acquire()
+        try:
+            self.base_message_output(proto)
+            for message in self.queued_messages:
+                self.base_message_output(message)
+            self.queued_messages = []
+            self.queue_output = False
+        finally:
+            self.output_lock.release()
+
     # Defer the ThreadedAsync work to the manager.
 
     def close_trigger(self):

Modified: Zope3/branches/srichter-blow-services/src/ZODB/DemoStorage.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/DemoStorage.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/ZODB/DemoStorage.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -69,12 +69,12 @@
 as changes are made.  For example, in Zope, you can create an external
 method::
 
-  import Zope
+  import Zope2
 
   def info(RESPONSE):
       RESPONSE['Content-type']= 'text/plain'
 
-      return Zope.DB._storage._splat()
+      return Zope2.DB._storage._splat()
 
 and call it to monitor the storage.
 

Modified: Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsdump.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsdump.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsdump.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -11,7 +11,6 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-import md5
 import struct
 
 from ZODB.FileStorage import FileIterator
@@ -22,44 +21,40 @@
 from ZODB.tests.StorageTestBase import zodb_unpickle
 
 def fsdump(path, file=None, with_offset=1):
-    i = 0
     iter = FileIterator(path)
-    for trans in iter:
+    for i, trans in enumerate(iter):
         if with_offset:
             print >> file, "Trans #%05d tid=%016x time=%s offset=%d" % \
-                  (i, u64(trans.tid), str(TimeStamp(trans.tid)), trans._pos)
+                  (i, u64(trans.tid), TimeStamp(trans.tid), trans._pos)
         else:
             print >> file, "Trans #%05d tid=%016x time=%s" % \
-                  (i, u64(trans.tid), str(TimeStamp(trans.tid)))
-        print >> file, "\tstatus=%s user=%s description=%s" % \
-              (`trans.status`, trans.user, trans.description)
-        j = 0
-        for rec in trans:
+                  (i, u64(trans.tid), TimeStamp(trans.tid))
+        print >> file, "    status=%r user=%r description=%r" % \
+              (trans.status, trans.user, trans.description)
+
+        for j, rec in enumerate(trans):
             if rec.data is None:
                 fullclass = "undo or abort of object creation"
+                size = ""
             else:
                 modname, classname = get_pickle_metadata(rec.data)
-                dig = md5.new(rec.data).hexdigest()
+                size = " size=%d" % len(rec.data)
                 fullclass = "%s.%s" % (modname, classname)
-            # special case for testing purposes
-            if fullclass == "ZODB.tests.MinPO.MinPO":
-                obj = zodb_unpickle(rec.data)
-                fullclass = "%s %s" % (fullclass, obj.value)
+
             if rec.version:
-                version = "version=%s " % rec.version
+                version = " version=%r" % rec.version
             else:
-                version = ''
+                version = ""
+
             if rec.data_txn:
                 # XXX It would be nice to print the transaction number
                 # (i) but it would be too expensive to keep track of.
-                bp = "bp=%016x" % u64(rec.data_txn)
+                bp = " bp=%016x" % u64(rec.data_txn)
             else:
                 bp = ""
-            print >> file, "  data #%05d oid=%016x %sclass=%s %s" % \
-                  (j, u64(rec.oid), version, fullclass, bp)
-            j += 1
-        print >> file
-        i += 1
+
+            print >> file, "  data #%05d oid=%016x%s%s class=%s%s" % \
+                  (j, u64(rec.oid), version, size, fullclass, bp)
     iter.close()
 
 def fmt(p64):

Modified: Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsoids.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsoids.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsoids.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -14,7 +14,7 @@
 
 import ZODB.FileStorage
 from ZODB.utils import get_pickle_metadata
-from ZODB.utils import U64, p64, oid_repr, tid_repr, get_refs
+from ZODB.utils import p64, oid_repr, tid_repr, get_refs
 from ZODB.TimeStamp import TimeStamp
 
 # Extract module.class string from pickle.
@@ -125,6 +125,8 @@
     def run(self):
         """Find all occurrences of the registered oids in the database."""
 
+        # Maps oid of a reference to its module.class name.
+        self._ref2name = {}
         for txn in ZODB.FileStorage.FileIterator(self.path):
             self._check_trec(txn)
 
@@ -133,43 +135,66 @@
         # txn has members tid, status, user, description,
         # _extension, _pos, _tend, _file, _tpos
         self._produced_msg = False
+        # Map and list for save data records for current transaction.
+        self._records_map = {}
+        self._records = []
         for drec in txn:
+            self._save_references(drec)
+        for drec in self._records:
             self._check_drec(drec)
         if self._produced_msg:
             # Copy txn info for later output.
             self.tid2info[txn.tid] = (txn.status, txn.user, txn.description,
                                       txn._tpos)
 
+    def _save_references(self, drec):
+        # drec has members oid, tid, version, data, data_txn
+        tid, oid, pick, pos = drec.tid, drec.oid, drec.data, drec.pos
+        if pick:
+            if oid in self.oids:
+                klass = get_class(pick)
+                self._msg(oid, tid, "new revision", klass, "at", pos)
+                self.oids[oid] += 1
+                self.oid2name[oid] = self._ref2name[oid] = klass
+            self._records_map[oid] = drec
+            self._records.append(drec)
+        elif oid in self.oids:
+            # Or maybe it's a version abort.
+            self._msg(oid, tid, "creation undo at", pos)
+
     # Process next data record.  If a message is produced, self._produced_msg
     # will be set True.
     def _check_drec(self, drec):
         # drec has members oid, tid, version, data, data_txn
         tid, oid, pick, pos = drec.tid, drec.oid, drec.data, drec.pos
+        ref2name = self._ref2name
+        ref2name_get = ref2name.get
+        records_map_get = self._records_map.get
         if pick:
-            oidclass = None
-            if oid in self.oids:
-                oidclass = get_class(pick)
-                self._msg(oid, tid, "new revision", oidclass,
-                          "at", drec.pos)
-                self.oids[oid] += 1
-                self.oid2name[oid] = oidclass
-
+            oid_in_oids = oid in self.oids
             for ref, klass in get_refs(pick):
-                if klass is None:
-                    klass = '<unknown>'
-                elif isinstance(klass, tuple):
-                    klass = "%s.%s" % klass
-
                 if ref in self.oids:
+                    oidclass = ref2name_get(oid, None)
                     if oidclass is None:
-                        oidclass = get_class(pick)
+                        ref2name[oid] = oidclass = get_class(pick)
                     self._msg(ref, tid, "referenced by", oid_repr(oid),
                               oidclass, "at", pos)
 
-                if oid in self.oids:
+                if oid_in_oids:
+                    if klass is None:
+                        klass = ref2name_get(ref, None)
+                        if klass is None:
+                            r = records_map_get(ref, None)
+                            # For save memory we only save references
+                            # seen in one transaction with interesting
+                            # objects changes. So in some circumstances
+                            # we may still got "<unknown>" class name.
+                            if r is None:
+                                klass = "<unknown>"
+                            else:
+                                ref2name[ref] = klass = get_class(r.data)
+                    elif isinstance(klass, tuple):
+                        ref2name[ref] = klass = "%s.%s" % klass
+
                     self._msg(oid, tid, "references", oid_repr(ref), klass,
                               "at", pos)
-
-        elif oid in self.oids:
-            # Or maybe it's a version abort.
-            self._msg(oid, tid, "creation undo at", pos)

Modified: Zope3/branches/srichter-blow-services/src/ZODB/component.xml
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/component.xml	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/ZODB/component.xml	2005-02-14 16:14:21 UTC (rev 29140)
@@ -152,7 +152,7 @@
   <sectiontype name="zodb" datatype=".ZODBDatabase"
                implements="ZODB.database">
     <section type="ZODB.storage" name="*" attribute="storage"/>
-    <key name="cache-size" datatype="byte-size" default="5000"/>
+    <key name="cache-size" datatype="integer" default="5000"/>
     <key name="pool-size" datatype="integer" default="7"/>
     <key name="version-pool-size" datatype="integer" default="3"/>
     <key name="version-cache-size" datatype="integer" default="100"/>

Modified: Zope3/branches/srichter-blow-services/src/ZODB/fsIndex.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/fsIndex.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/ZODB/fsIndex.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -48,7 +48,7 @@
 def str2num(s):
     return struct.unpack(">Q", "\000\000" + s)[0]
 
-class fsIndex:
+class fsIndex(object):
 
     def __init__(self):
         self._data = {}
@@ -76,7 +76,7 @@
 
     def __len__(self):
         r = 0
-        for tree in self._data.values():
+        for tree in self._data.itervalues():
             r += len(tree)
         return r
 
@@ -85,7 +85,7 @@
             self[k] = v
 
     def has_key(self, key):
-        v=self.get(key, self)
+        v = self.get(key, self)
         return v is not self
 
     def __contains__(self, key):
@@ -101,27 +101,27 @@
         self._data.clear()
 
     def __iter__(self):
-        for prefix, tree in self._data.items():
+        for prefix, tree in self._data.iteritems():
             for suffix in tree:
                 yield prefix + suffix
 
+    iterkeys = __iter__
+
     def keys(self):
-        r = []
-        for prefix, tree in self._data.items():
-            for suffix in tree.keys():
-                r.append(prefix + suffix)
-        return r
+        return list(self.iterkeys())
 
+    def iteritems(self):
+        for prefix, tree in self._data.iteritems():
+            for suffix, value in tree.iteritems():
+                yield (prefix + suffix, str2num(value))
+
     def items(self):
-        r = []
-        for prefix, tree in self._data.items():
-            for suffix, v in tree.items():
-                r.append(((prefix + suffix), str2num(v)))
-        return r
+        return list(self.iteritems())
 
+    def itervalues(self):
+        for tree in self._data.itervalues():
+            for value in tree.itervalues():
+                yield str2num(value)
+
     def values(self):
-        r = []
-        for prefix, tree in self._data.items():
-            for v in tree.values():
-                r.append(str2num(v))
-        return r
+        return list(self.itervalues())

Copied: Zope3/branches/srichter-blow-services/src/ZODB/tests/test_fsdump.py (from rev 29115, Zope3/trunk/src/ZODB/tests/test_fsdump.py)

Modified: Zope3/branches/srichter-blow-services/src/ZODB/tests/testfsIndex.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/tests/testfsIndex.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/ZODB/tests/testfsIndex.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -18,12 +18,15 @@
 
 class Test(unittest.TestCase):
 
-    def testInserts(self):
-        index=fsIndex()
+    def setUp(self):
+        self.index = fsIndex()
 
         for i in range(200):
-            index[p64(i*1000)]=(i*1000L+1)
+            self.index[p64(i * 1000)] = (i * 1000L + 1)
 
+    def testInserts(self):
+        index = self.index
+
         for i in range(0,200):
             self.assertEqual((i,index[p64(i*1000)]), (i,(i*1000L+1)))
 
@@ -40,7 +43,7 @@
         # self.failUnless(len(index._data) > 1)
 
     def testUpdate(self):
-        index=fsIndex()
+        index = self.index
         d={}
 
         for i in range(200):
@@ -63,7 +66,52 @@
         self.assertEqual(index.get(p64(399000)), 399002)
         self.assertEqual(len(index), 600)
 
+    def testKeys(self):
+        keys = list(iter(self.index))
+        keys.sort()
 
+        for i, k in enumerate(keys):
+            self.assertEqual(k, p64(i * 1000))
+
+        keys = list(self.index.iterkeys())
+        keys.sort()
+
+        for i, k in enumerate(keys):
+            self.assertEqual(k, p64(i * 1000))
+
+        keys = self.index.keys()
+        keys.sort()
+
+        for i, k in enumerate(keys):
+            self.assertEqual(k, p64(i * 1000))
+
+    def testValues(self):
+        values = list(self.index.itervalues())
+        values.sort()
+
+        for i, v in enumerate(values):
+            self.assertEqual(v, (i * 1000L + 1))
+
+        values = self.index.values()
+        values.sort()
+
+        for i, v in enumerate(values):
+            self.assertEqual(v, (i * 1000L + 1))
+
+    def testItems(self):
+        items = list(self.index.iteritems())
+        items.sort()
+
+        for i, item in enumerate(items):
+            self.assertEqual(item, (p64(i * 1000), (i * 1000L + 1)))
+
+        items = self.index.items()
+        items.sort()
+
+        for i, item in enumerate(items):
+            self.assertEqual(item, (p64(i * 1000), (i * 1000L + 1)))
+
+
 def test_suite():
     loader=unittest.TestLoader()
     return loader.loadTestsFromTestCase(Test)

Modified: Zope3/branches/srichter-blow-services/src/ZODB/tests/testfsoids.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/tests/testfsoids.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/ZODB/tests/testfsoids.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -94,7 +94,7 @@
         tid user=''
         tid description='added an OOBTree'
         new revision persistent.mapping.PersistentMapping at 207
-        references 0x01 <unknown> at 207
+        references 0x01 BTrees._OOBTree.OOBTree at 207
 oid 0x01 BTrees._OOBTree.OOBTree 1 revision
     tid 0x... offset=168 ...
         tid user=''
@@ -103,19 +103,7 @@
         referenced by 0x00 persistent.mapping.PersistentMapping at 207
 
 So there are two revisions of oid 0 now, and the second references oid 1.
-It's peculiar that the class shows as <unknown> in:
 
-        references 0x01 <unknown> at 207
-
-The code that does this takes long tours through undocumented code in
-cPickle.c (using cPickle features that aren't in pickle.py, and aren't even
-documented as existing).  Whatever the reason, ZODB/util.py's get_refs()
-function returns (oid_0x01, None) for the reference to oid 1, instead of the
-usual (oid, (module_name, class_name)) form.  Before I wrote this test,
-I never saw a case of that before!  "references" lines usually identify
-the class of the object.  Anyway, the correct class is given in the new
-output for oid 1.
-
 One more, storing a reference in the BTree back to the root object:
 
 >>> tree = root['tree']
@@ -123,7 +111,7 @@
 >>> txn.get().note('circling back to the root')
 >>> txn.get().commit()
 >>> t = Tracer(path)
->>> t.register_oids(*range(3))
+>>> t.register_oids(0, 1, 2)
 >>> t.run(); t.report() #doctest: +ELLIPSIS
 oid 0x00 persistent.mapping.PersistentMapping 2 revisions
     tid 0x... offset=4 ...
@@ -134,7 +122,7 @@
         tid user=''
         tid description='added an OOBTree'
         new revision persistent.mapping.PersistentMapping at 207
-        references 0x01 <unknown> at 207
+        references 0x01 BTrees._OOBTree.OOBTree at 207
     tid 0x... offset=443 ...
         tid user=''
         tid description='circling back to the root'
@@ -149,7 +137,7 @@
         tid user=''
         tid description='circling back to the root'
         new revision BTrees._OOBTree.OOBTree at 491
-        references 0x00 <unknown> at 491
+        references 0x00 persistent.mapping.PersistentMapping at 491
 oid 0x02 <unknown> 0 revisions
     this oid was not defined (no data record for it found)
 

Modified: Zope3/branches/srichter-blow-services/src/zope/app/container/browser/contents.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/container/browser/contents.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/app/container/browser/contents.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -16,7 +16,7 @@
 $Id$
 """
 __docformat__ = 'restructuredtext'
-        
+
 import urllib
 
 from zope.app.traversing.interfaces import TraversalError
@@ -33,7 +33,7 @@
 from zope.app.copypastemove.interfaces import IPrincipalClipboard
 from zope.app.copypastemove.interfaces import IObjectCopier
 from zope.app.copypastemove.interfaces import IObjectMover
-from zope.app.copypastemove import rename
+from zope.app.copypastemove.interfaces import IContainerItemRenamer
 from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
 
 from zope.app.container.browser.adding import Adding
@@ -205,9 +205,10 @@
         ids = request.get("rename_ids")
         newids = request.get("new_value")
 
+        renamer = IContainerItemRenamer(self.context)
         for oldid, newid in map(None, ids, newids):
             if newid != oldid:
-                rename(self.context, oldid, newid)
+                renamer.renameItem(oldid, newid)
 
     def changeTitle(self):
         """Given a sequence of tuples of old, new ids we rename"""
@@ -241,7 +242,7 @@
             # above, because there is no "+" view.
             adding.__parent__ = self.context
             adding.__name__ = '+'
-             
+
         adding.action(request['type_name'], new)
 
     def removeObjects(self):

Modified: Zope3/branches/srichter-blow-services/src/zope/app/container/browser/tests/test_contents.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/container/browser/tests/test_contents.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/app/container/browser/tests/test_contents.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -22,14 +22,17 @@
 from zope.app.annotation.interfaces import IAnnotations
 from zope.app.component.testing import PlacefulSetup
 from zope.app.container.contained import contained
+from zope.app.copypastemove import ContainerItemRenamer
 from zope.app.copypastemove import ObjectMover, ObjectCopier
 from zope.app.copypastemove import PrincipalClipboard
+from zope.app.copypastemove.interfaces import IContainerItemRenamer
 from zope.app.copypastemove.interfaces import IObjectMover, IObjectCopier
 from zope.app.copypastemove.interfaces import IPrincipalClipboard
 from zope.app.principalannotation import PrincipalAnnotationUtility
 from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
 from zope.app.testing import ztapi
 from zope.app.traversing.api import traverse
+from zope.app.container.interfaces import IContainer, IContained
 
 
 class BaseTestContentsBrowserView(PlacefulSetup):
@@ -45,10 +48,12 @@
     def setUp(self):
         PlacefulSetup.setUp(self)
         PlacefulSetup.buildFolders(self)
-        
-        ztapi.provideAdapter(None, IObjectCopier, ObjectCopier)
-        ztapi.provideAdapter(None, IObjectMover, ObjectMover)
 
+        ztapi.provideAdapter(IContained, IObjectCopier, ObjectCopier)
+        ztapi.provideAdapter(IContained, IObjectMover, ObjectMover)
+        ztapi.provideAdapter(IContainer, IContainerItemRenamer,
+            ContainerItemRenamer)
+
         ztapi.provideAdapter(IAnnotations, IPrincipalClipboard,
                              PrincipalClipboard)
         ztapi.provideUtility(IPrincipalAnnotationUtility,
@@ -83,7 +88,7 @@
         container = self._TestView__newContext()
         subcontainer = self._TestView__newContext()
         container[u'f\xf6\xf6'] = subcontainer
-        
+
         fc = self._TestView__newView(container)
         info_list = fc.listContentInfo()
 
@@ -155,7 +160,7 @@
 
 
 class Principal(object):
-    
+
     id = 'bob'
 
 
@@ -164,8 +169,10 @@
     def setUp(self):
         PlacefulSetup.setUp(self)
         PlacefulSetup.buildFolders(self)
-        ztapi.provideAdapter(None, IObjectCopier, ObjectCopier)
-        ztapi.provideAdapter(None, IObjectMover, ObjectMover)
+        ztapi.provideAdapter(IContained, IObjectCopier, ObjectCopier)
+        ztapi.provideAdapter(IContained, IObjectMover, ObjectMover)
+        ztapi.provideAdapter(IContainer, IContainerItemRenamer,
+            ContainerItemRenamer)
 
         ztapi.provideAdapter(IAnnotations, IPrincipalClipboard,
                              PrincipalClipboard)

Modified: Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/__init__.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/__init__.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -19,16 +19,25 @@
 
 from zope.interface import implements, Invalid
 from zope.exceptions import NotFoundError, DuplicationError
+from zope.component import adapts
+from zope.event import notify
 
+from zope.app.annotation.interfaces import IAnnotations
 from zope.app.container.sample import SampleContainer
-from zope.event import notify
 from zope.app.event.objectevent import ObjectCopiedEvent
-from zope.app.copypastemove.interfaces import IObjectMover
-from zope.app.copypastemove.interfaces import IObjectCopier
 from zope.app.location.pickling import locationCopy
+from zope.app.container.interfaces import IContainer, IOrderedContainer
+from zope.app.container.interfaces import IContained
 from zope.app.container.interfaces import INameChooser
 from zope.app.container.constraints import checkObject
 
+from zope.app.copypastemove.interfaces import IObjectMover
+from zope.app.copypastemove.interfaces import IObjectCopier
+from zope.app.copypastemove.interfaces import IContainerItemRenamer
+from zope.app.copypastemove.interfaces import IPrincipalClipboard
+
+import warnings # BBB (remove in 3.3)
+
 class ObjectMover(object):
     """Adapter for moving objects between containers
 
@@ -159,6 +168,8 @@
 
     """
 
+    adapts(IContained)
+
     implements(IObjectMover)
 
     def __init__(self, object):
@@ -166,10 +177,10 @@
         self.__parent__ = object # TODO: see if we can automate this
 
     def moveTo(self, target, new_name=None):
-        '''Move this object to the `target` given.
+        """Move this object to the `target` given.
 
         Returns the new name within the `target`
-        Typically, the `target` is adapted to `IPasteTarget`.'''
+        Typically, the `target` is adapted to `IPasteTarget`."""
 
         obj = self.context
         container = obj.__parent__
@@ -192,15 +203,15 @@
         return new_name
 
     def moveable(self):
-        '''Returns ``True`` if the object is moveable, otherwise ``False``.'''
+        """Returns ``True`` if the object is moveable, otherwise ``False``."""
         return True
 
     def moveableTo(self, target, name=None):
-        '''Say whether the object can be moved to the given target.
+        """Say whether the object can be moved to the given target.
 
         Returns ``True`` if it can be moved there. Otherwise, returns
         ``False``.
-        '''
+        """
         if name is None:
             name = self.context.__name__
         try:
@@ -349,6 +360,8 @@
 
     """
 
+    adapts(IContained)
+
     implements(IObjectCopier)
 
     def __init__(self, object):
@@ -359,7 +372,7 @@
         """Copy this object to the `target` given.
 
         Returns the new name within the `target`.
-        
+
         Typically, the `target` is adapted to `IPasteTarget`.
         After the copy is added to the `target` container, publish
         an `IObjectCopied` event in the context of the target container.
@@ -387,26 +400,26 @@
 
     def _configureCopy(self, copy, target, new_name):
         """Configures the copied object before it is added to `target`.
-        
+
         `target` and `new_name` are provided as additional information.
-        
+
         By default, `copy.__parent__` and `copy.__name__` are set to ``None``.
-        
+
         Subclasses may override this method to perform additional
         configuration of the copied object.
         """
         copy.__parent__ = copy.__name__ = None
 
     def copyable(self):
-        '''Returns True if the object is copyable, otherwise False.'''
+        """Returns True if the object is copyable, otherwise False."""
         return True
 
     def copyableTo(self, target, name=None):
-        '''Say whether the object can be copied to the given `target`.
+        """Say whether the object can be copied to the given `target`.
 
         Returns ``True`` if it can be copied there. Otherwise, returns
         ``False``.
-        '''
+        """
         if name is None:
             name = self.context.__name__
         try:
@@ -416,22 +429,161 @@
         return True
 
 
+class ContainerItemRenamer(object):
+    """An IContainerItemRenamer adapter for containers.
+
+    This adapter uses IObjectMover to move an item within the same container
+    to a different name. We need to first setup an adapter for IObjectMover:
+
+      >>> from zope.app.tests import ztapi
+      >>> from zope.app.container.interfaces import IContained
+      >>> ztapi.provideAdapter(IContained, IObjectMover, ObjectMover)
+
+    To rename an item in a container, instantiate a ContainerItemRenamer
+    with the container:
+
+      >>> container = SampleContainer()
+      >>> renamer = ContainerItemRenamer(container)
+
+    For this example, we'll rename an item 'foo':
+
+      >>> from zope.app.container.contained import Contained
+      >>> foo = Contained()
+      >>> container['foo'] = foo
+      >>> container['foo'] is foo
+      True
+
+    to 'bar':
+
+      >>> renamer.renameItem('foo', 'bar')
+      >>> container['foo'] is foo
+      Traceback (most recent call last):
+      KeyError: 'foo'
+      >>> container['bar'] is foo
+      True
+
+    If the item being renamed isn't in the container, a NotFoundError is raised:
+
+      >>> renamer.renameItem('foo', 'bar') # doctest:+ELLIPSIS
+      Traceback (most recent call last):
+      NotFoundError: (<...SampleContainer...>, 'foo')
+
+    If the new item name already exists, a DuplicationError is raised:
+
+      >>> renamer.renameItem('bar', 'bar')
+      Traceback (most recent call last):
+      DuplicationError: bar is already in use
+
+    """
+
+    adapts(IContainer)
+
+    implements(IContainerItemRenamer)
+
+    def __init__(self, container):
+        self.container = container
+
+    def renameItem(self, oldName, newName):
+        object = self.container.get(oldName)
+        if object is None:
+            raise NotFoundError(self.container, oldName)
+        mover = IObjectMover(object)
+
+        if newName in self.container:
+            raise DuplicationError("%s is already in use" % newName)
+
+        mover.moveTo(self.container, newName)
+
+
+class OrderedContainerItemRenamer(ContainerItemRenamer):
+    """Renames items within an ordered container.
+
+    This renamer preserves the original order of the contained items.
+
+    To illustrate, we need to setup an IObjectMover, which is used in the
+    renaming:
+
+      >>> from zope.app.tests import ztapi
+      >>> from zope.app.container.interfaces import IContained
+      >>> ztapi.provideAdapter(IContained, IObjectMover, ObjectMover)
+
+    To rename an item in an ordered container, we instantiate a
+    OrderedContainerItemRenamer with the container:
+
+      >>> from zope.app.container.ordered import OrderedContainer
+      >>> container = OrderedContainer()
+      >>> renamer = OrderedContainerItemRenamer(container)
+
+    We'll add three items to the container:
+
+      >>> container['1'] = 'Item 1'
+      >>> container['2'] = 'Item 2'
+      >>> container['3'] = 'Item 3'
+      >>> container.items()
+      [('1', 'Item 1'), ('2', 'Item 2'), ('3', 'Item 3')]
+
+    When we rename one of the items:
+
+      >>> renamer.renameItem('1', 'I')
+
+    the order is preserved:
+
+      >>> container.items()
+      [('I', 'Item 1'), ('2', 'Item 2'), ('3', 'Item 3')]
+
+    Renaming the other two items also preserves the origina order:
+
+      >>> renamer.renameItem('2', 'II')
+      >>> renamer.renameItem('3', 'III')
+      >>> container.items()
+      [('I', 'Item 1'), ('II', 'Item 2'), ('III', 'Item 3')]
+
+    As with the standard renamer, trying to rename a non-existent item raises
+    an error:
+
+      >>> renamer.renameItem('IV', '4') # doctest:+ELLIPSIS
+      Traceback (most recent call last):
+      NotFoundError: (<...OrderedContainer...>, 'IV')
+
+    And if the new item name already exists, a DuplicationError is raised:
+
+      >>> renamer.renameItem('III', 'I')
+      Traceback (most recent call last):
+      DuplicationError: I is already in use
+
+    """
+
+    adapts(IOrderedContainer)
+
+    implements(IContainerItemRenamer)
+
+    def renameItem(self, oldName, newName):
+        order = list(self.container.keys())
+        super(OrderedContainerItemRenamer, self).renameItem(oldName, newName)
+        order[order.index(oldName)] = newName
+        self.container.updateOrder(order)
+
+
 class PrincipalClipboard(object):
-    '''Principal clipboard
+    """Principal clipboard
 
     Clipboard information consists on tuples of
     ``{'action':action, 'target':target}``.
-    '''
+    """
 
+    adapts(IAnnotations)
+
+    implements(IPrincipalClipboard)
+
     def __init__(self, annotation):
         self.context = annotation
 
     def clearContents(self):
-        '''Clear the contents of the clipboard'''
+        """Clear the contents of the clipboard"""
         self.context['clipboard'] = ()
 
     def addItems(self, action, targets):
-        '''Add new items to the clipboard'''
+        """Add new items to the clipboard"""
         contents = self.getContents()
         actions = []
         for target in targets:
@@ -439,25 +591,26 @@
         self.context['clipboard'] = contents + tuple(actions)
 
     def setContents(self, clipboard):
-        '''Replace the contents of the clipboard by the given value'''
+        """Replace the contents of the clipboard by the given value"""
         self.context['clipboard'] = clipboard
 
     def getContents(self):
-        '''Return the contents of the clipboard'''
+        """Return the contents of the clipboard"""
         return self.context.get('clipboard', ())
 
 
 def rename(container, oldid, newid):
-    object = container.get(oldid)
-    if object is None:
-        raise NotFoundError(container, oldid)
-    mover = IObjectMover(object)
+    """Renames an item with oldid in the container to newid.
 
-    if newid in container:
-        raise DuplicationError("name, %s, is already in use" % newid)
+    This function is deprecated. Use IContainerItemRenamer instead.
+    """
+    # BBB (remove in 3.3)
+    warnings.warn(
+        "rename is deprecated and will not be supported starting in "
+        "ZopeX3 3.3. Use IContainerItemRenamer(container).renameItem "
+        "instead.", DeprecationWarning)
+    IContainerItemRenamer(container).renameItem(oldid, newid)
 
-    if mover.moveable() and mover.moveableTo(container, newid):
-        mover.moveTo(container, newid)
 
 class ExampleContainer(SampleContainer):
     # Sample container used for examples in doc stringss in this module

Modified: Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/configure.zcml	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/configure.zcml	2005-02-14 16:14:21 UTC (rev 29140)
@@ -5,25 +5,22 @@
     >
 
   <adapter
-      factory="zope.app.copypastemove.ObjectMover"
-      provides="zope.app.copypastemove.interfaces.IObjectMover"
+      factory=".ObjectMover"
       permission="zope.ManageContent"
-      for="*"
       trusted="y"
       />
 
   <adapter
-      factory="zope.app.copypastemove.ObjectCopier"
-      provides="zope.app.copypastemove.interfaces.IObjectCopier"
+      factory=".ObjectCopier"
       permission="zope.ManageContent"
-      for="*"
       trusted="y"
       />
 
-  <adapter
-      factory="zope.app.copypastemove.PrincipalClipboard"
-      provides="zope.app.copypastemove.interfaces.IPrincipalClipboard"
-      for="zope.app.annotation.interfaces.IAnnotations"
-      />
+  <adapter factory=".ContainerItemRenamer" />
 
+  <adapter factory=".OrderedContainerItemRenamer" />
+
+  <adapter factory=".PrincipalClipboard" />
+
+
 </configure>

Modified: Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/interfaces.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/interfaces.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/interfaces.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -20,23 +20,23 @@
 from zope.interface import Interface
 
 class IObjectMover(Interface):
-    '''Use `IObjectMover(obj)` to move an object somewhere.'''
+    """Use `IObjectMover(obj)` to move an object somewhere."""
 
     def moveTo(target, new_name=None):
-        '''Move this object to the target given.
+        """Move this object to the target given.
 
         Returns the new name within the target.
-        Typically, the target is adapted to `IPasteTarget`.'''
+        Typically, the target is adapted to `IPasteTarget`."""
 
     def moveable():
-        '''Returns ``True`` if the object is moveable, otherwise ``False``.'''
+        """Returns ``True`` if the object is moveable, otherwise ``False``."""
 
     def moveableTo(target, name=None):
-        '''Say whether the object can be moved to the given `target`.
+        """Say whether the object can be moved to the given `target`.
 
         Returns ``True`` if it can be moved there. Otherwise, returns
         ``False``.
-        '''
+        """
 
 class IObjectCopier(Interface):
 
@@ -52,31 +52,41 @@
         """
 
     def copyable():
-        '''Returns ``True`` if the object is copyable, otherwise ``False``.'''
+        """Returns ``True`` if the object is copyable, otherwise ``False``."""
 
     def copyableTo(target, name=None):
-        '''Say whether the object can be copied to the given `target`.
+        """Say whether the object can be copied to the given `target`.
 
         Returns ``True`` if it can be copied there. Otherwise, returns
         ``False``.
-        '''
+        """
 
+class IContainerItemRenamer(Interface):
+
+    def renameItem(oldName, newName):
+        """Renames an object in the container from oldName to newName.
+
+        Raises NotFoundError if oldName doesn't exist in the container.
+
+        Raises DuplicationError if newName is already used in the container.
+        """
+
 class IPrincipalClipboard(Interface):
-    '''Interface for adapters that store/retrieve clipboard information
+    """Interface for adapters that store/retrieve clipboard information
     for a principal.
 
     Clipboard information consists on tuples of
       ``{'action':action, 'target':target}``.
-    '''
+    """
 
     def clearContents():
-        '''Clear the contents of the clipboard'''
+        """Clear the contents of the clipboard"""
 
     def addItems(action, targets):
-        '''Add new items to the clipboard'''
+        """Add new items to the clipboard"""
 
     def setContents(clipboard):
-        '''Replace the contents of the clipboard by the given value'''
+        """Replace the contents of the clipboard by the given value"""
 
     def getContents():
-        '''Return the contents of the clipboard'''
+        """Return the contents of the clipboard"""

Modified: Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/tests/test_rename.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/tests/test_rename.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/app/copypastemove/tests/test_rename.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -15,60 +15,16 @@
 
 $Id$
 """
-from unittest import TestCase, main, makeSuite
+import unittest
 
 from zope.testing.doctestunit import DocTestSuite
 from zope.app.testing.placelesssetup import setUp, tearDown
-from zope.app.testing import ztapi
 
-from zope.exceptions import NotFoundError, DuplicationError
-from zope.app.traversing.api import traverse
-from zope.app.component.testing import PlacefulSetup
-from zope.app.copypastemove.interfaces import IObjectMover
-from zope.app.copypastemove import ObjectMover
-from zope.app.copypastemove import rename
-
-class File(object):
-    pass
-
-class RenameTest(PlacefulSetup, TestCase):
-
-    def setUp(self):
-        PlacefulSetup.setUp(self)
-        PlacefulSetup.buildFolders(self)
-        ztapi.provideAdapter(None, IObjectMover, ObjectMover)
-
-    def test_simplerename(self):
-        root = self.rootFolder
-        folder1 = traverse(root, 'folder1')
-        self.failIf('file1' in folder1)
-        folder1['file1'] = File()
-        rename(folder1, 'file1', 'my_file1')
-        self.failIf('file1' in folder1)
-        self.failUnless('my_file1' in folder1)
-
-    def test_renamenonexisting(self):
-        root = self.rootFolder
-        folder1 = traverse(root, 'folder1')
-        self.failIf('a_test_file' in folder1)
-        self.assertRaises(NotFoundError, rename, folder1, 'file1', 'my_file1')
-
-    def test_renamesamename(self):
-        root = self.rootFolder
-        folder1 = traverse(root, 'folder1')
-        self.failIf('file1' in folder1)
-        self.failIf('file2' in folder1)
-        folder1['file1'] = File()
-        folder1['file2'] = File()
-        self.assertRaises(DuplicationError, rename, folder1, 'file1', 'file2')
-
 def test_suite():
-    suite = makeSuite(RenameTest)
-    suite.addTest(
+    return unittest.TestSuite((
         DocTestSuite('zope.app.copypastemove',
                      setUp=setUp, tearDown=tearDown),
-        )
-    return suite
+        ))
 
 if __name__=='__main__':
-    main(defaultTest='test_suite')
+    unittest.main(defaultTest='test_suite')

Modified: Zope3/branches/srichter-blow-services/src/zope/app/form/browser/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/form/browser/configure.zcml	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/app/form/browser/configure.zcml	2005-02-14 16:14:21 UTC (rev 29140)
@@ -397,10 +397,23 @@
       factory=".source.SourceInputWidget"
       permission="zope.Public"
       />
+
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
       for="zope.schema.interfaces.ISequence
            zope.schema.interfaces.ISource"
+      provides="zope.app.form.interfaces.IDisplayWidget"
+      factory=".source.SourceSequenceDisplayWidget"
+      permission="zope.Public"
+      />
+
+
+  <!-- XXX The configuration below should be for IList -->
+  <!--     We need a widget for tuples (and sets, for that matter). -->
+  <view
+      type="zope.publisher.interfaces.browser.IBrowserRequest"
+      for="zope.schema.interfaces.IList
+           zope.schema.interfaces.ISource"
       provides="zope.app.form.interfaces.IInputWidget"
       factory=".source.SourceListInputWidget"
       permission="zope.Public"

Modified: Zope3/branches/srichter-blow-services/src/zope/app/form/browser/source.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/form/browser/source.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/app/form/browser/source.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -23,8 +23,9 @@
 import zope.app.form.browser.widget
 import zope.app.form.browser.interfaces
 from zope.app.i18n import ZopeMessageIDFactory as _
+from zope.app.form.interfaces import WidgetInputError, MissingInputError
+from zope.app.form.browser.interfaces import IWidgetInputErrorView
 
-
 class SourceDisplayWidget(zope.app.form.Widget):
 
     def __init__(self, field, source, request):
@@ -65,6 +66,34 @@
 
         return value
 
+class SourceSequenceDisplayWidget(SourceDisplayWidget):
+
+    def __call__(self):
+
+        if self._renderedValueSet():
+            seq = self._data
+        else:
+            seq = self.context.default
+
+        terms = zapi.getMultiAdapter(
+            (self.source, self.request),
+            zope.app.form.browser.interfaces.ITerms,
+            )
+        result = []
+        for value in seq:
+            try:
+                term = terms.getTerm(value)
+            except LookupError:
+                value = self._translate(_("SourceDisplayWidget-invalid",
+                                          default="Invalid value"))
+            else:
+                value = cgi.escape(term.title)
+
+            result.append(value)
+
+        return '<br />\n'.join(result)
+    
+
 class SourceInputWidget(zope.app.form.InputWidget):
 
     _error = None
@@ -137,6 +166,7 @@
 
     def error(self):
         if self._error:
+            # XXX This code path is untested.
             return zapi.getMultiAdapter((self._error, self.request),
                                         IWidgetInputErrorView).snippet()
         return ""
@@ -240,6 +270,7 @@
 
         if token is None:
             if field.required:
+                # XXX This code path is untested.
                 raise zope.app.form.interfaces.MissingInputError(
                     field.__name__, self.label,
                     )
@@ -248,6 +279,7 @@
         try:
             value = self.terms.getValue(str(token))
         except LookupError:
+            # XXX This code path is untested.
             err = zope.schema.interfaces.ValidationError(
                 "Invalid value id", token)
             raise WidgetInputError(field.__name__, self.label, err)
@@ -258,6 +290,7 @@
         try:
             field.validate(value)
         except ValidationError, err:
+            # XXX This code path is untested.
             self._error = WidgetInputError(field.__name__, self.label, err)
             raise self._error
 
@@ -425,9 +458,11 @@
         # Remaining code copied from SimpleInputWidget
 
         # value must be valid per the field constraints
+        field = self.context
         try:
-            self.context.validate(value)
+            field.validate(value)
         except ValidationError, err:
+            # XXX This code path is untested.
             self._error = WidgetInputError(field.__name__, self.label, err)
             raise self._error
 

Modified: Zope3/branches/srichter-blow-services/src/zope/app/form/utility.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/form/utility.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/app/form/utility.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -117,6 +117,9 @@
 
     `view` is the view that will be configured with widgets. 
 
+    `viewType` is the type of widgets to create (e.g. IInputWidget or
+    IDisplayWidget).
+
     `schema` is an interface containing the fields that widgets will be
     created for.
 

Modified: Zope3/branches/srichter-blow-services/src/zope/app/ftp/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/ftp/__init__.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/app/ftp/__init__.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -31,7 +31,8 @@
 from zope.event import notify
 from zope.app.event.objectevent import ObjectCreatedEvent
 from zope.app.dublincore.interfaces import IZopeDublinCore
-from zope.app.copypastemove import rename
+from zope.app.copypastemove.interfaces import IContainerItemRenamer
+from zope.app.container.interfaces import IContainer
 
 class FTPView(object):
     implements(IFTPPublisher)
@@ -154,8 +155,8 @@
         self.remove(name)
 
     def rename(self, old, new):
-        dir = IWriteDirectory(self.context, None)
-        rename(dir, old, new)
+        dir = IContainer(self.context, None)
+        IContainerItemRenamer(dir).renameItem(old, new)
 
     def _overwrite(self, name, instream, start=None, end=None, append=False):
         file = self._dir[name]

Modified: Zope3/branches/srichter-blow-services/src/zope/app/ftp/tests/test_ftpview.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/ftp/tests/test_ftpview.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/app/ftp/tests/test_ftpview.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -18,7 +18,12 @@
 import datetime
 from StringIO import StringIO
 from unittest import TestCase, TestSuite, main, makeSuite
+
+from zope.interface import implements
+
 import zope.server.ftp.tests.demofs as demofs
+
+from zope.app.tests import ztapi
 from zope.app.filerepresentation.interfaces import IReadFile, IWriteFile
 from zope.app.filerepresentation.interfaces import IReadDirectory
 from zope.app.filerepresentation.interfaces import IWriteDirectory
@@ -27,14 +32,17 @@
 from zope.app.dublincore.interfaces import IZopeDublinCore
 from zope.app.ftp import FTPView
 from zope.app.testing.placelesssetup import PlacelessSetup
-from zope.interface import implements
 from zope.app.copypastemove.interfaces import IObjectMover
+from zope.app.copypastemove.interfaces import IContainerItemRenamer
+from zope.app.copypastemove import ContainerItemRenamer
 from zope.app.container.contained import setitem, Contained
+from zope.app.container.interfaces import IContainer
 
 class Directory(demofs.Directory, Contained):
 
     implements(IReadDirectory, IWriteDirectory, IFileFactory,
-               IDirectoryFactory, IZopeDublinCore, IObjectMover)
+               IDirectoryFactory, IZopeDublinCore, IObjectMover,
+               IContainer)
 
     modified = datetime.datetime(1990, 1,1)
 
@@ -53,7 +61,7 @@
 
     def moveableTo(self, target, name=None):
         return True
-    
+
     def __call__(self, name, content_type='', data=None):
         if data:
             r = File()
@@ -110,6 +118,8 @@
         root['f'] = File('contents of\nf')
         root['g'] = File('contents of\ng')
         self.__view = FTPView(root, None)
+        ztapi.provideAdapter(IContainer, IContainerItemRenamer,
+            ContainerItemRenamer)
 
     def test_type(self):
         self.assertEqual(self.__view.type('test'), 'd')

Modified: Zope3/branches/srichter-blow-services/src/zope/component/factory.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/component/factory.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/component/factory.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -41,9 +41,4 @@
             spec = Implements(*self._interfaces)
             spec.__name__ = getattr(self._callable, '__name__', '[callable]')
             return spec
-        try:
-            return implementedBy(self._callable)
-        except TypeError:
-            spec = Implements()
-            spec.__name__ = getattr(self._callable, '__name__', '[callable]')
-            return spec
+        return implementedBy(self._callable)

Modified: Zope3/branches/srichter-blow-services/src/zope/component/factory.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/component/factory.txt	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/component/factory.txt	2005-02-14 16:14:21 UTC (rev 29140)
@@ -83,20 +83,14 @@
   True
   >>> list(implemented)
   [<InterfaceClass __builtin__.IKlass>]
-  >>> implemented.__name__
-  '__builtin__.Klass'
   
   >>> implemented2 = factory2.getInterfaces()
   >>> list(implemented2)
   []
-  >>> implemented2.__name__
-  '<lambda>'
   
   >>> implemented3 = factory3.getInterfaces()
   >>> list(implemented3)
   [<InterfaceClass __builtin__.IFunction>]
-  >>> implemented3.__name__
-  '<lambda>'
 
 
 The Componant Architecture Factory API

Modified: Zope3/branches/srichter-blow-services/src/zope/interface/README.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/interface/README.txt	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/interface/README.txt	2005-02-14 16:14:21 UTC (rev 29140)
@@ -147,9 +147,16 @@
    It is important to note that classes don't usually provide the
    interfaces that the implement.
 
+   We can generalize this to factories.  For any callable object we
+   can declare that it produces objects that provides some interfaces
+   by saying that the factory implements the interfaces.
+
 Now that we've defined these terms, we can talk about the API for
 declaring interfaces.
 
+Declaring implemented interfaces
+--------------------------------
+
 The most common way to declare interfaces is using the implements
 function in a class statement::
 
@@ -190,17 +197,18 @@
   >>> list(zope.interface.implementedBy(Foo))
   [<InterfaceClass __main__.IFoo>]
 
-It's an error to ask for interfaces implemented by a non-class::
+It's an error to ask for interfaces implemented by a non-callable
+object::
 
   >>> IFoo.implementedBy(foo)
   Traceback (most recent call last):
   ...
-  TypeError: ('ImplementedBy called for non-type', Foo(None))
+  TypeError: ('ImplementedBy called for non-factory', Foo(None))
 
   >>> list(zope.interface.implementedBy(foo))
   Traceback (most recent call last):
   ...
-  TypeError: ('ImplementedBy called for non-type', Foo(None))
+  TypeError: ('ImplementedBy called for non-factory', Foo(None))
 
 Similarly, we can ask what interfaces are provided by an object::
 
@@ -209,6 +217,36 @@
   >>> list(zope.interface.providedBy(Foo))
   []
 
+We can declare interfaces implemented by other factories (besides
+classes).  We do this using a Python-2.4-style decorator named
+`implementer`.  In versions of Python before 2.4, this looks like:
+
+
+  >>> def yfoo(y):
+  ...     foo = Foo()
+  ...     foo.y = y
+  ...     return foo
+  >>> zope.interface.implementer(IFoo)(yfoo)
+
+  >>> list(zope.interface.implementedBy(yfoo))
+  [<InterfaceClass __main__.IFoo>]
+
+Note that the implementer decorator may modify it's argument. Callers
+should not assume that a new object is created.
+
+Also note that, at least for now, implementer cannt be used with
+classes:
+
+  >>> zope.interface.implementer(IFoo)(Foo)
+  ... # doctest: +NORMALIZE_WHITESPACE
+  Traceback (most recent call last):
+    ...
+  TypeError: Can't use implementer with classes.  
+  Use one of the class-declaration functions instead.
+
+Declaring provided interfaces
+-----------------------------
+
 We can declare interfaces directly provided by objects.  Suppose that
 we want to document what the `__init__` method of the `Foo` class
 does.  It's not *really* part of `IFoo`.  You wouldn't normally call

Modified: Zope3/branches/srichter-blow-services/src/zope/interface/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/interface/__init__.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/interface/__init__.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -63,7 +63,7 @@
 from zope.interface.declarations import providedBy, implementedBy
 from zope.interface.declarations import classImplements, classImplementsOnly
 from zope.interface.declarations import directlyProvidedBy, directlyProvides
-from zope.interface.declarations import alsoProvides
+from zope.interface.declarations import alsoProvides, implementer
 from zope.interface.declarations import implements, implementsOnly
 from zope.interface.declarations import classProvides, moduleProvides
 from zope.interface.declarations import Declaration

Modified: Zope3/branches/srichter-blow-services/src/zope/interface/declarations.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/interface/declarations.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/interface/declarations.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -346,12 +346,15 @@
         try:
             bases = cls.__bases__
         except AttributeError:
-            raise TypeError("ImplementedBy called for non-type", cls)
+            if not callable(cls):
+                raise TypeError("ImplementedBy called for non-factory", cls)
+            bases = ()
 
         spec = Implements(*[implementedBy(c) for c in bases])
         spec.inherit = cls
 
-    spec.__name__ = getattr(cls, '__module__', '?') + '.' + cls.__name__
+    spec.__name__ = (getattr(cls, '__module__', '?') or '?') + \
+                    '.' + cls.__name__
 
     try:
         cls.__implemented__ = spec
@@ -485,6 +488,23 @@
     classImplements(cls, *interfaces)
     return cls
 
+
+class implementer:
+
+    def __init__(self, *interfaces):
+        self.interfaces = interfaces
+
+    def __call__(self, ob):
+        if isinstance(ob, DescriptorAwareMetaClasses):
+            raise TypeError("Can't use implementer with classes.  Use one of "
+                            "the class-declaration functions instead."
+                            )
+        spec = Implements(*self.interfaces)
+        try:
+            ob.__implemented__ = spec
+        except AttributeError:
+            raise TypeError("Can't declare implements", ob)
+
 def _implements(name, interfaces, classImplements):
     frame = sys._getframe(2)
     locals = frame.f_locals

Modified: Zope3/branches/srichter-blow-services/src/zope/interface/interfaces.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/interface/interfaces.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/interface/interfaces.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -418,6 +418,14 @@
         instances of ``A`` and ``B`` provide.
         """
 
+    def implementer(*interfaces):
+        """Create a decorator for declaring interfaces implemented by a facory
+
+        A callable is returned that makes an implements declaration on
+        objects passed to it.
+        
+        """
+
     def classImplementsOnly(class_, *interfaces):
         """Declare the only interfaces implemented by instances of a class
 

Modified: Zope3/branches/srichter-blow-services/src/zope/schema/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/schema/__init__.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/schema/__init__.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -20,7 +20,8 @@
 from zope.schema._field import Bytes, ASCII, BytesLine
 from zope.schema._field import Text, TextLine, Bool, Int, Float
 from zope.schema._field import Tuple, List, Set
-from zope.schema._field import Password, Dict, Datetime, Date, SourceText
+from zope.schema._field import Password, Dict, Datetime, Date, Timedelta
+from zope.schema._field import SourceText
 from zope.schema._field import Object, URI, Id, DottedName
 from zope.schema._field import InterfaceField
 from zope.schema._schema import getFields, getFieldsInOrder

Modified: Zope3/branches/srichter-blow-services/src/zope/schema/_field.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/schema/_field.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/schema/_field.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -1,6 +1,5 @@
 # -*- coding: ISO-8859-1 -*-
 ##############################################################################
-#
 # Copyright (c) 2002 Zope Corporation and Contributors.
 # All Rights Reserved.
 #
@@ -19,7 +18,7 @@
 __docformat__ = 'restructuredtext'
 import warnings
 import re
-from datetime import datetime, date
+from datetime import datetime, date, timedelta
 from sets import Set as SetType
 
 from zope.interface import classImplements, implements, directlyProvides
@@ -32,7 +31,7 @@
 from zope.schema.interfaces import IBytes, IASCII, IBytesLine
 from zope.schema.interfaces import IBool, IInt, IFloat, IDatetime
 from zope.schema.interfaces import IChoice, ITuple, IList, ISet, IDict
-from zope.schema.interfaces import IPassword, IObject, IDate
+from zope.schema.interfaces import IPassword, IObject, IDate, ITimedelta
 from zope.schema.interfaces import IURI, IId, IFromUnicode
 from zope.schema.interfaces import ISource, IVocabulary
 
@@ -170,6 +169,11 @@
     implements(IDate)
     _type = date
 
+class Timedelta(Orderable, Field):
+    __doc__ = ITimedelta.__doc__
+    implements(ITimedelta)
+    _type = timedelta
+
 class Choice(Field):
     """Choice fields can have a value found in a constant or dynamic set of
     values given by the field definition.

Modified: Zope3/branches/srichter-blow-services/src/zope/schema/interfaces.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/schema/interfaces.py	2005-02-14 15:15:09 UTC (rev 29139)
+++ Zope3/branches/srichter-blow-services/src/zope/schema/interfaces.py	2005-02-14 16:14:21 UTC (rev 29140)
@@ -326,6 +326,9 @@
 class IDate(IMinMax, IField):
     u"""Field containing a date."""
 
+class ITimedelta(IMinMax, IField):
+    u"""Field containing a timedelta."""
+
 def _is_field(value):
     if not IField.providedBy(value):
         return False

Copied: Zope3/branches/srichter-blow-services/src/zope/schema/tests/test_timedelta.py (from rev 29104, Zope3/trunk/src/zope/schema/tests/test_timedelta.py)


Property changes on: Zope3/branches/srichter-blow-services/src/zope/schema/tests/test_timedelta.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native



More information about the Zope3-Checkins mailing list