[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