[Zope3-checkins] CVS: Zope3/src/zodb - component.xml:1.2.2.1 transact.py:1.2.2.1 config.py:1.3.16.1 conflict.py:1.14.10.1 connection.py:1.33.2.1 db.py:1.17.2.1 dbdump.py:1.3.40.1 serialize.py:1.21.2.1 ztransaction.py:1.2.40.1

Grégoire Weber zope@i-con.ch
Sun, 22 Jun 2003 10:23:58 -0400


Update of /cvs-repository/Zope3/src/zodb
In directory cvs.zope.org:/tmp/cvs-serv24874/src/zodb

Modified Files:
      Tag: cw-mail-branch
	config.py conflict.py connection.py db.py dbdump.py 
	serialize.py ztransaction.py 
Added Files:
      Tag: cw-mail-branch
	component.xml transact.py 
Log Message:
Synced up with HEAD

=== Added File Zope3/src/zodb/component.xml ===
<component prefix="zodb.config">

  <!-- XXX needs descriptions for everything -->

  <abstracttype name="zodb.storage"/>
  <abstracttype name="zodb.database"/>

  <sectiontype name="filestorage" datatype=".FileStorage"
               implements="zodb.storage">
    <key name="path" required="yes">
      <description>
        Path name to the main storage file.  The names for
        supplemental files, including index and lock files, will be
        computed from this.
      </description>
    </key>
    <key name="create" datatype="boolean" default="false">
      <description>
        Flag that indicates whether the storage should be truncated if
        it already exists.
      </description>
    </key>
    <key name="read-only" datatype="boolean" default="false">
      <description>
        If true, only reads may be executed against the storage.  Note
        that the "pack" operation is not considered a write operation
        and is still allowed on a read-only filestorage.
      </description>
    </key>
    <key name="quota" datatype="byte-size">
      <description>
        Maximum allowed size of the storage file.  Operations which
        would cause the size of the storage to exceed the quota will
        result in a zodb.FileStorage.FileStorageQuotaError being
        raised.
      </description>
    </key>
  </sectiontype>

  <sectiontype name="mappingstorage" datatype=".MappingStorage"
               implements="zodb.storage">
    <key name="name" default="Mapping Storage"/>
  </sectiontype>

  <!-- The BDB storages probably need to be revised somewhat still.
       The extension relationship seems a little odd.
    -->
  <sectiontype name="fullstorage" datatype=".BDBFullStorage"
               implements="zodb.storage">
    <key name="name" required="yes" />
    <key name="envdir" />
    <key name="interval" datatype="time-interval" default="2m" />
    <key name="kbyte" datatype="integer" default="0" />
    <key name="min" datatype="integer" default="0" />
    <key name="logdir" />
    <key name="cachesize" datatype="byte-size" default="128MB" />
    <key name="frequency" datatype="time-interval" default="0" />
    <key name="packtime" datatype="time-interval" default="4h" />
    <key name="gcpack" datatype="integer" default="0" />
    <key name="read-only" datatype="boolean" default="off"/>
  </sectiontype>

  <sectiontype name="minimalstorage" datatype=".BDBMinimalStorage"
               implements="zodb.storage" extends="fullstorage"/>

  <sectiontype name="zeoclient" datatype=".ZEOClient"
               implements="zodb.storage">
    <multikey name="server" datatype="socket-address" required="yes"/>
    <key name="storage" default="1">
      <description>
        The name of the storage that the client wants to use.  If the
        ZEO server serves more than one storage, the client selects
        the storage it wants to use by name.  The default name is '1',
        which is also the default name for the ZEO server.
      </description>
    </key>
    <key name="cache-size" datatype="integer" default="20000000">
      <description>
        The maximum size of the client cache, in bytes.
      </description>
    </key>
    <key name="name" default="">
      <description>
        The storage name.  If unspecified, the address of the server
        will be used as the name.
      </description>
    </key>
    <key name="client">
      <description>
        Enables persistent cache files.  The string passed here is
        used to construct the cache filenames.  If it is not
        specified, the client creates a temporary cache that will
        only be used by the current object.
      </description>
    </key>
    <key name="var">
      <description>
        The directory where persistent cache files are stored.  By
        default cache files, if they are persistent, are stored in 
        the current directory.
      </description>
    </key>
    <key name="min-disconnect-poll" datatype="integer" default="5">
      <description>
        The minimum delay in seconds between attempts to connect to
        the server, in seconds.  Defaults to 5 seconds.
      </description>
    </key>
    <key name="max-disconnect-poll" datatype="integer" default="300">
      <description>
        The maximum delay in seconds between attempts to connect to
        the server, in seconds.  Defaults to 300 seconds.
      </description>
    </key>
    <key name="wait" datatype="boolean" default="on">
      <description>
        A boolean indicating whether the constructor should wait
        for the client to connect to the server and verify the cache
        before returning.  The default is true.
      </description>
    </key>
    <key name="read-only" datatype="boolean" default="off">
      <description>
        A flag indicating whether this should be a read-only storage,
        defaulting to false (i.e. writing is allowed by default).
      </description>
    </key>
    <key name="read-only-fallback" datatype="boolean" default="off">
      <description>
        A flag indicating whether a read-only remote storage should be
        acceptable as a fallback when no writable storages are
        available.  Defaults to false.  At most one of read_only and
        read_only_fallback should be true.
      </description>
    </key>
    <key name="realm" required="no">
      <description>
        The authentication realm of the server.  Some authentication
        schemes use a realm to identify the logic set of usernames
        that are accepted by this server.
      </description>
    </key>
  </sectiontype>

  <sectiontype name="demostorage" datatype=".DemoStorage"
               implements="zodb.storage">
    <key name="name" default="Demo Storage"/>
    <section type="zodb.storage" name="*" attribute="base"/>
    <key name="quota" datatype="integer"/>
  </sectiontype>


  <sectiontype name="zodb" datatype=".ZODBDatabase"
               implements="zodb.database">
    <section type="zodb.storage" name="*" attribute="storage"/>
    <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"/>
  </sectiontype>

</component>


=== Added File Zope3/src/zodb/transact.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Tools to simplify transactions within applications."""

from transaction import get_transaction
from zodb.interfaces import ReadConflictError, ConflictError

def _commit(note):
    t = get_transaction()
    if note:
        t.note(note)
    t.commit()

def transact(f, note=None, retries=5):
    """Returns transactional version of function argument f.

    Higher-order function that converts a regular function into
    a transactional function.  The transactional function will
    retry up to retries time before giving up.  If note, it will
    be added to the transaction metadata when it commits.

    The retries occur on ConflictErrors.  If some other
    TransactionError occurs, the transaction will not be retried.
    """

    # XXX deal with ZEO disconnected errors?
    
    def g(*args, **kwargs):
        n = retries
        while n:
            n -= 1
            try:
                r = f(*args, **kwargs)
            except ReadConflictError, msg:
                get_transaction().abort()
                if not n:
                    raise
                continue
            try:
                _commit(note)
            except ConflictError, msg:
                get_transaction().abort()
                if not n:
                    raise
                continue
            return r
        raise RuntimeError, "couldn't commit transaction"
    return g


=== Zope3/src/zodb/config.py 1.3 => 1.3.16.1 ===
--- Zope3/src/zodb/config.py:1.3	Wed Apr  9 17:15:16 2003
+++ Zope3/src/zodb/config.py	Sun Jun 22 10:22:26 2003
@@ -11,182 +11,162 @@
 # FOR A PARTICULAR PURPOSE
 #
 ##############################################################################
-"""Default storage types.
+"""Open database and storage from a configuration.
 
-Adapted from DBTab/StorageTypes.py.
-"""
+$Id$"""
 
-import re
+import os
+import StringIO
 
-from ZConfig.Config import asBoolean
+import ZConfig
 
+import zodb.db
 
-def convertFileStorageArgs(quota=None, stop=None, **kw):
-    if kw.has_key('name'):
-        # FileStorage doesn't accept a 'name' arg
-        del kw['name']
-    if quota is not None:
-        kw['quota'] = long(quota) or None
-    if stop is not None:
-        stop = long(stop)
-        if not stop:
-            stop = None
-        else:
-            from zodb.utils import p64
-            stop = p64(stop)
-        kw['stop'] = stop
-
-    # Boolean args
-    for name in (
-        'create', 'read_only'
-        ):
-        if kw.has_key(name):
-            kw[name] = asBoolean(kw[name])
-
-    return kw
-
-
-# Match URLs of the form 'zeo://zope.example.com:1234'
-zeo_url_re = re.compile('zeo:/*(?P<host>[A-Za-z0-9\.-]+):(?P<port>[0-9]+)')
-
-def convertAddresses(s):
-    # Allow multiple addresses using semicolons as a split character.
-    res = []
-    for a in s.split(';'):
-        a = a.strip()
-        if a:
-            mo = zeo_url_re.match(a)
-            if mo is not None:
-                # ZEO URL
-                host, port = mo.groups()
-                res.append((host, int(port)))
-            else:
-                # Socket file
-                res.append(a)
-    return res
-
-
-def convertClientStorageArgs(addr=None, **kw):
-    if addr is None:
-        raise RuntimeError, 'An addr parameter is required for ClientStorage.'
-    kw['addr'] = convertAddresses(addr)
-
-    # Integer args
-    for name in (
-        'cache_size', 'min_disconnect_poll', 'max_disconnect_poll',
-        ):
-        if kw.has_key(name):
-            kw[name] = int(kw[name])
-
-    # Boolean args
-    for name in (
-        'wait', 'read_only', 'read_only_fallback',
-        ):
-        if kw.has_key(name):
-            kw[name] = asBoolean(kw[name])
-
-    # The 'client' parameter must be None to be false.  Yuck.
-    if kw.has_key('client') and not kw['client']:
-        kw['client'] = None
-
-    return kw
-
-
-# Currently unused
-def convertBDBStorageArgs(**kw):
-    from zodb.storage.base import BerkeleyConfig
-    config = BerkeleyConfig()
-    for name in dir(BerkeleyConfig):
-        if name.startswith('_'):
-            continue
-        val = kw.get(name)
-        if val is not None:
-            if name == 'read_only':
-                val = asBoolean(val)
-            elif name != 'logdir':
-                val = int(val)
-            setattr(config, name, val)
-            del kw[name]
-    # XXX: Nobody ever passes in env
-    assert not kw.has_key('env')
-    kw['config'] = config
-    return kw
-
-
-storage_types = {
-    # A mapping from "type" (i.e. class) to 2-tuple of (module, converter).
-    # converter may be None for no conversion necessary.  type and module are
-    # both strings, and module should be the dotted path for use in an
-    # __import__().
-    'FileStorage'         : ('zodb.storage.file', convertFileStorageArgs),
-    'MappingStorage'      : ('zodb.storage.mapping', None),
-    'ClientStorage'       : ('zodb.zeo.client', convertClientStorageArgs),
-    'BDBFullStorage'      : ('zodb.storage.bdbfull', convertBDBStorageArgs),
-    'BDBMinimalStorage'   : ('zodb.storage.bdbminimal', convertBDBStorageArgs),
-    'MemoryFullStorage'   : ('zodb.storage.memory', convertBDBStorageArgs),
-    'MemoryMinimalStorage': ('zodb.storage.memory', convertBDBStorageArgs),
-    }
-
-
-"""Higher-level support for configuring storages.
-
-Storages are configured a la DBTab.
-
-A storage section has the form
-
-  <Storage Name (dependent)>
-    # For example
-    type        FileStorage
-    file_name   var/Data.fs
-    read_only   1
-  </Storage>
-
-where Name and (dependent) are optional.  Once you have retrieved the
-section object (probably with getSection("Storage", name), the
-function creatStorage() in this module will create the storage object
-for you.
-"""
-
-
-
-def createStorage(section):
-    """Create a storage specified by a configuration section."""
-    klass, args = getStorageInfo(section)
-    return klass(**args)
-
-def getStorageInfo(section):
-    """Extract a storage description from a configuration section.
-
-    Return a tuple (klass, args) where klass is the storage class and
-    args is a dictionary of keyword arguments.  To create the storage,
-    call klass(**args).
+db_schema_path = os.path.join(zodb.__path__[0], "config.xml")
+_db_schema = None
+
+s_schema_path = os.path.join(zodb.__path__[0], "storage.xml")
+_s_schema = None
+
+def getDbSchema():
+    global _db_schema
+    if _db_schema is None:
+        _db_schema = ZConfig.loadSchema(db_schema_path)
+    return _db_schema
+
+def getStorageSchema():
+    global _s_schema
+    if _s_schema is None:
+        _s_schema = ZConfig.loadSchema(s_schema_path)
+    return _s_schema
+
+def databaseFromString(s):
+    return databaseFromFile(StringIO.StringIO(s))
+
+def databaseFromFile(f):
+    config, handle = ZConfig.loadConfigFile(getDbSchema(), f)
+    return databaseFromConfig(config.database)
+
+def databaseFromURL(url):
+    config, handler = ZConfig.loadConfig(getDbSchema(), url)
+    return databaseFromConfig(config.database)
+
+def databaseFromConfig(section):
+    return section.open()
+
+def storageFromString(s):
+    return storageFromFile(StringIO.StringIO(s))
+
+def storageFromFile(f):
+    config, handle = ZConfig.loadConfigFile(getStorageSchema(), f)
+    return storageFromConfig(config.storage)
+
+def storageFromURL(url):
+    config, handler = ZConfig.loadConfig(getStorageSchema(), url)
+    return storageFromConfig(config.storage)
+
+def storageFromConfig(section):
+    return section.open()
+
+
+class BaseConfig:
+    """Object representing a configured storage or database.
+
+    Methods:
+
+    open() -- open and return the configured object
+
+    Attributes:
+
+    name   -- name of the storage
 
-    Adapted from DatabaseFactory.setStorageParams() in DBTab.py.
     """
-    type = section.get("type")
-    if not type:
-        raise RuntimeError, "A storage type is required"
-    module = None
-    pos = type.rfind(".")
-    if pos >= 0:
-        # Specified the module
-        module, type = type[:pos], type[pos+1:]
-    converter = None
-    if not module:
-        # Use a default module and argument converter.
-        info = storage_types.get(type)
-        if not info:
-            raise RuntimeError, "Unknown storage type: %s" % type
-        module, converter = info
-    m = __import__(module, {}, {}, [type])
-    klass = getattr(m, type)
-
-    args = {}
-    if section.name:
-        args["name"] = section.name
-    for key in section.keys():
-        if key.lower() != "type":
-            args[key] = section.get(key)
-    if converter is not None:
-        args = converter(**args)
-    return (klass, args)
+
+    def __init__(self, config):
+        self.config = config
+        self.name = config.getSectionName()
+
+    def open(self):
+        """Open and return the storage object."""
+        raise NotImplementedError
+
+class ZODBDatabase(BaseConfig):
+
+    def open(self):
+        section = self.config
+        return zodb.db.DB(section.storage.open(),
+                          pool_size=section.pool_size,
+                          cache_size=section.cache_size,
+                          version_pool_size=section.version_pool_size,
+                          version_cache_size=section.version_cache_size)
+
+class MappingStorage(BaseConfig):
+
+    def open(self):
+        from zodb.storage.mapping import MappingStorage
+        return MappingStorage(self.config.name)
+
+class DemoStorage(BaseConfig):
+
+    def open(self):
+        from zodb.storage.demo import DemoStorage
+        if self.config.base:
+            base = self.config.base.open()
+        else:
+            base = None
+        return DemoStorage(self.config.name,
+                           base=base,
+                           quota=self.config.quota)
+
+class FileStorage(BaseConfig):
+
+    def open(self):
+        from zodb.storage.file import FileStorage
+        return FileStorage(self.config.path,
+                           create=self.config.create,
+                           read_only=self.config.read_only,
+                           quota=self.config.quota)
+
+class ZEOClient(BaseConfig):
+
+    def open(self):
+        from zodb.zeo.client import ClientStorage
+        # config.server is a multikey of socket-address values
+        # where the value is a socket family, address tuple.
+        L = [server.address for server in self.config.server]
+        return ClientStorage(
+            L,
+            storage=self.config.storage,
+            cache_size=self.config.cache_size,
+            name=self.config.name,
+            client=self.config.client,
+            var=self.config.var,
+            min_disconnect_poll=self.config.min_disconnect_poll,
+            max_disconnect_poll=self.config.max_disconnect_poll,
+            wait=self.config.wait,
+            read_only=self.config.read_only,
+            read_only_fallback=self.config.read_only_fallback)
+
+class BDBStorage(BaseConfig):
+
+    def open(self):
+        from zodb.storage.base import BerkeleyConfig
+        storageclass = self.get_storageclass()
+        bconf = BerkeleyConfig()
+        for name in dir(BerkeleyConfig):
+            if name.startswith('_'):
+                continue
+            setattr(bconf, name, getattr(self.config, name))
+        return storageclass(self.config.name, config=bconf)
+
+class BDBMinimalStorage(BDBStorage):
+
+    def get_storageclass(self):
+        from zodb.storage.bdbminimal import BDBMinimalStorage
+        return BDBMinimalStorage
+
+class BDBFullStorage(BDBStorage):
+
+    def get_storageclass(self):
+        from zodb.storage.bdbfull import BDBFullStorage
+        return BDBFullStorage


=== Zope3/src/zodb/conflict.py 1.14 => 1.14.10.1 ===
--- Zope3/src/zodb/conflict.py:1.14	Thu May  1 15:34:58 2003
+++ Zope3/src/zodb/conflict.py	Sun Jun 22 10:22:26 2003
@@ -23,6 +23,8 @@
 
 from zodb.interfaces import ConflictError
 from zodb.serialize import BaseObjectReader, ObjectWriter
+from zodb.interfaces import _fmt_oid
+from zodb.utils import u64
 
 ResolvedSerial = "rs"
 
@@ -167,13 +169,6 @@
             return None
         newstate = reader.getState(newpickle)
 
-        # XXX Using loadSerial() ties conflict resolution to the IUndoStorage
-        # interface.  This is a bad thing for non-versioning storages like
-        # BDBMinimalStorage and MemoryMinimalStorage because they don't
-        # support undo, and thus do not implement IUndoStorage.  IUndoStorage
-        # is the interface that defines loadSerial().  Hmm, maybe we should
-        # move loadSerial() out of that interface?
-
         p = self._storage.loadSerial(oid, oldSerial)
         try:
             old = reader.getState(p)
@@ -181,7 +176,13 @@
             logging.warn("CR: Error loading object: %s", err)
             return None
         if committedData is None:
-            committedData = self._storage.loadSerial(oid, committedSerial)
+            try:
+                committedData = self._storage.loadSerial(oid, committedSerial)
+            except KeyError:
+                logging.debug("CR: Could not load committed state "
+                              "oid=%s serial=%s" % (_fmt_oid(oid),
+                                                    u64(committedSerial)))
+                return None
         try:
             committed = reader.getState(committedData)
         except (EOFError, PicklingError), err:


=== Zope3/src/zodb/connection.py 1.33 => 1.33.2.1 ===
--- Zope3/src/zodb/connection.py:1.33	Tue May 20 15:07:24 2003
+++ Zope3/src/zodb/connection.py	Sun Jun 22 10:22:26 2003
@@ -41,6 +41,9 @@
 import struct
 import tempfile
 import threading
+from types import StringType
+
+from zope.interface import implements
 
 from zodb import interfaces
 from zodb.conflict import ResolvedSerial
@@ -67,10 +70,10 @@
     storage.
     """
 
-    __implements__ = (IAppConnection, IConnection, IPersistentDataManager,
-                      IDataManager)
- 
-    def __init__(self, db, storage, version='', cache_size=400): 
+    implements(IAppConnection, IConnection, IPersistentDataManager,
+               IDataManager)
+
+    def __init__(self, db, storage, version='', cache_size=400):
         self._db = db
         self._storage = storage
         self._version = version
@@ -153,7 +156,7 @@
         # We must explicitly deactivate it to turn it into a ghost.
         obj._p_deactivate()
         obj._p_serial = serial
-        
+
         self._cache.set(oid, obj)
         if oid == ZERO:
             # Keep a reference to the root so that the pickle cache
@@ -168,12 +171,12 @@
 
     def setstate(self, obj):
         oid = obj._p_oid
-        
+
         if not self._open:
             msg = "Attempt to load object on closed connection: %r" % oid
             self._log.warn(msg)
             raise POSError(msg)
-            
+
         try:
             # Avoid reading data from a transaction that committed
             # after the current transaction started, as that might
@@ -294,7 +297,7 @@
     ######################################################################
     # transaction.interfaces.IDataManager requires the next four methods
     # prepare(), abort(), commit(), savepoint()
- 
+
     def prepare(self, txn):
         if self._conflicts:
             # XXX should raise all of the conflicting oids, but
@@ -394,51 +397,47 @@
         # Now is a good time to collect some garbage
         self._cache.shrink()
 
-    def _handle_serial(self, store_return, oid=None, change=True):
+    def _handle_serial(self, store_return, oid=None, change=1):
         """Handle the returns from store() and tpc_vote() calls."""
 
-        # XXX We could simplify the storage interface if ZEO would
-        # raise the exception itself rather than passing it into the
-        # connection.
-
         # These calls can return different types depending on whether
         # ZEO is used.  ZEO uses asynchronous returns that may be
         # returned in batches by the ClientStorage.  ZEO1 can also
         # return an exception object and expect that the Connection
         # will raise the exception.
 
-        # When _commit_sub() exceutes a store, there is no need to
+        # When commit_sub() exceutes a store, there is no need to
         # update the _p_changed flag, because the subtransaction
-        # tpcVote() calls already did this.  The change=1 argument
-        # exists to allow _commit_sub() to avoid setting the flag
+        # tpc_vote() calls already did this.  The change=1 argument
+        # exists to allow commit_sub() to avoid setting the flag
         # again.
+
+        # When conflict resolution occurs, the object state held by
+        # the connection does not match what is written to the
+        # database.  Invalidate the object here to guarantee that
+        # the new state is read the next time the object is used.
+        
         if not store_return:
             return
-        if isinstance(store_return, str):
+        if isinstance(store_return, StringType):
             assert oid is not None
-            serial = store_return
-            obj = self._cache.get(oid)
-            if obj is None:
-                return
-            if serial == ResolvedSerial:
-                obj._p_deactivate()
-            else:
-                if change:
-                    obj._p_changed = 0
-                obj._p_serial = serial
+            self._handle_one_serial(oid, store_return, change)
         else:
             for oid, serial in store_return:
-                if not isinstance(serial, str):
-                    raise serial
-                obj = self._cache.get(oid)
-                if obj is None:
-                    continue
-                if serial == ResolvedSerial:
-                    obj._p_deactivate()
-                else:
-                    if change:
-                        obj._p_changed = 0
-                    obj._p_serial = serial
+                self._handle_one_serial(oid, serial, change)
+
+    def _handle_one_serial(self, oid, serial, change=1):
+        if not isinstance(serial, StringType):
+            raise serial
+        obj = self._cache.get(oid, None)
+        if obj is None:
+            return
+        if serial == ResolvedSerial:
+            obj._p_deactivate(force=True)
+        else:
+            if change:
+                obj._p_changed = False
+            obj._p_serial = serial
 
     def _objcommit(self, obj, transaction):
         oid = obj._p_oid
@@ -532,7 +531,7 @@
     # XXX Should it be possible to rollback() to the same savepoint
     # more than once?  (Yes.)
 
-    __implements__ = IRollback
+    implements(IRollback)
 
     def __init__(self, conn, tmp_undo):
         self._conn = conn


=== Zope3/src/zodb/db.py 1.17 => 1.17.2.1 ===
--- Zope3/src/zodb/db.py:1.17	Tue May 20 15:07:24 2003
+++ Zope3/src/zodb/db.py	Sun Jun 22 10:22:26 2003
@@ -23,6 +23,8 @@
 from time import time
 import logging
 
+from zope.interface import implements
+
 from zodb.storage.interfaces import *
 from zodb.connection import Connection
 from zodb.serialize import getDBRoot
@@ -139,6 +141,10 @@
         AbortVersion(self, version)
 
     def close(self):
+        # XXX Jim observes that database close typically occurs when
+        # the app server is shutting down.  If an errant thread is
+        # still running, it may not be possible to stop it.  Thus,
+        # the error on connection.close() may be counter-productive.
         for c in self._allocated:
             c.close()
         del self._allocated[:]
@@ -332,7 +338,7 @@
 
 class SimpleDataManager:
 
-    __implements__ = IDataManager
+    implements(IDataManager)
 
     def __init__(self, db):
         self._db = db


=== Zope3/src/zodb/dbdump.py 1.3 => 1.3.40.1 ===
--- Zope3/src/zodb/dbdump.py:1.3	Mon Dec 30 16:40:30 2002
+++ Zope3/src/zodb/dbdump.py	Sun Jun 22 10:22:26 2003
@@ -39,11 +39,13 @@
         for rec in trans:
             if rec.data is None:
                 fullclass = "undo or abort of object creation"
+                size = 0
             else:
                 # Any object reader will do
                 reader = SimpleObjectReader()
                 fullclass = reader.getClassName(rec.data)
                 dig = md5.new(rec.data).hexdigest()
+                size = len(rec.data)
             # special case for testing purposes
             if fullclass == "zodb.tests.minpo.MinPO":
                 obj = zodb_unpickle(rec.data)
@@ -52,8 +54,8 @@
                 version = "version=%s " % rec.version
             else:
                 version = ''
-            print >> outp, "  data #%05d oid=%016x %sclass=%s" % \
-                  (j, u64(rec.oid), version, fullclass)
+            print >> outp, "  data #%05d oid=%016x %sclass=%s size=%d" % \
+                  (j, u64(rec.oid), version, fullclass, size)
             j += 1
         print >> outp
         i += 1


=== Zope3/src/zodb/serialize.py 1.21 => 1.21.2.1 ===
--- Zope3/src/zodb/serialize.py:1.21	Tue May 20 15:07:24 2003
+++ Zope3/src/zodb/serialize.py	Sun Jun 22 10:22:26 2003
@@ -218,8 +218,8 @@
 
     def getClassName(self, pickle):
         unpickler = self._get_unpickler(pickle)
-        module, classname, newargs = unpickler.load()
-        return "%s.%s" % (module, classname)
+        cls, newargs = unpickler.load()
+        return cls.__name__
 
     def getGhost(self, pickle):
         unpickler = self._get_unpickler(pickle)


=== Zope3/src/zodb/ztransaction.py 1.2 => 1.2.40.1 ===
--- Zope3/src/zodb/ztransaction.py:1.2	Wed Dec 25 09:12:16 2002
+++ Zope3/src/zodb/ztransaction.py	Sun Jun 22 10:22:26 2003
@@ -11,6 +11,9 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
+
+from zope.interface import implements
+
 from transaction import set_factory
 from transaction.txn import Transaction as BaseTransaction
 
@@ -18,7 +21,7 @@
 
 class Transaction(BaseTransaction):
 
-    __implements__ = ITransaction, ITransactionAttrs
+    implements(ITransaction, ITransactionAttrs)
 
     user = ""
     description = ""