[Zope-CVS] CVS: Products/Ape/lib/apelib/core - io.py:1.1 events.py:1.4 interfaces.py:1.7 serializers.py:1.2

Shane Hathaway shane@zope.com
Mon, 26 May 2003 15:33:46 -0400


Update of /cvs-repository/Products/Ape/lib/apelib/core
In directory cvs.zope.org:/tmp/cvs-serv13513/core

Modified Files:
	events.py interfaces.py serializers.py 
Added Files:
	io.py 
Log Message:
Added some facades in a new module called apelib.core.io.  These are
designed to make it easier to:

  - reuse Ape in different frameworks

  - do simple operations like import / export objects

  - ignore classification and mapper_names details

In support of this:

  - Added tests for ape.core.io.

  - Renamed loadStub() to getObject().  The old name was silly. ;-)
    Left a deprecated alias.

  - Moved getExternalRefs() to ISDEvent so that IDeserializationEvent
    now implements it as well.  This mades it easier to implement object
    import.

  - Created IClassFactory.  IKeyedObjectSystem extends it.

  - Changed the signature of createEmptyInstance so that an
    IClassFactory is always expected.
			


=== Added File Products/Ape/lib/apelib/core/io.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.
#
##############################################################################
"""Ape I/O facades.

These facades implement commonly useful high-level mapper operations.

$Id: io.py,v 1.1 2003/05/26 19:33:15 shane Exp $
"""

from weakref import proxy

from events \
     import DatabaseInitEvent, GatewayEvent, LoadEvent, StoreEvent, \
     SerializationEvent, DeserializationEvent
from interfaces import ITPCConnection, IKeyedObjectSystem


class ClassifiedState:
    """Object state with classification information."""

    def __init__(self, state, classification, mapper_names):
        self.state = state
        self.classification = classification
        self.mapper_names = mapper_names


class GatewayIO:
    """Gateway operations facade."""

    def __init__(self, root_mapper, connections):
        self._root_mapper = root_mapper
        self._conn_map = connections
        # Sort the connections by sort key.  Use an extra index to avoid
        # using connections as sort keys.
        items = []  # [(sort_key, index, conn)]
        index = 0
        for c in connections.values():
            assert ITPCConnection.isImplementedBy(c)
            sort_key = c.sortKey()
            items.append((sort_key, index, c))
            index += 1
        items.sort()
        conn_list = []
        for sort_key, index, c in items:
            conn_list.append(c)
        self._conn_list = conn_list

    def openConnections(self):
        try:
            opened = []
            for c in self._conn_list:
                c.connect()
                opened.append(c)
        except:
            for c in opened:
                c.close()
            raise

    def closeConnections(self):
        for conn in self._conn_list:
            conn.close()

    def getConnectionList(self):
        return self._conn_list

    def getConnectionMap(self):
        return self._conn_map


    def initDatabases(self, clear_all=0):
        """Creates tables, etc.
        """
        # Find all initializers, eliminating duplicates.
        initializers = {}  # obj -> 1
        todo = [self._root_mapper]
        while todo:
            mapper = todo.pop()
            for obj in mapper.getInitializers():
                initializers[obj] = 1
            sub = mapper.listSubMapperNames()
            if sub:
                for name in sub:
                    m = mapper.getSubMapper(name)
                    todo.append(m)
        # Now call them.
        event = DatabaseInitEvent(self._conn_map, clear_all)
        for initializer in initializers.keys():
            initializer.init(event)


    def load(self, keychain, hash_only=0):
        mapper = self._root_mapper
        mapper_names = []
        # Follow the keychain to find the right mapper.
        classification = None
        for i in range(len(keychain)):
            k = keychain[:i + 1]
            cfr = mapper.getClassifier()
            assert cfr is not None, keychain
            event = LoadEvent(mapper, k, self._conn_map)
            classification, sub_mapper_name = cfr.classifyState(event)
            mapper_names.append(sub_mapper_name)
            mapper = mapper.getSubMapper(sub_mapper_name)
        event = LoadEvent(mapper, keychain, self._conn_map)
        if hash_only:
            event.hash_only = 1
        state, hash_value = mapper.getGateway().load(event)
        cs = ClassifiedState(state, classification, mapper_names)
        return cs, hash_value


    def store(self, keychain, classified_state):
        assert len(keychain) == len(classified_state.mapper_names)
        mapper = self._root_mapper
        prev_mapper = mapper
        for mapper_name in classified_state.mapper_names:
            prev_mapper = mapper
            mapper = mapper.getSubMapper(mapper_name)
        cfr = prev_mapper.getClassifier()
        event = StoreEvent(mapper, keychain, self._conn_map)
        new_hash = mapper.getGateway().store(event, classified_state.state)
        if cfr is not None:
            cfr.store(event, classified_state.classification)
        return new_hash


    def newKeychain(self):
        # Try to use the root keychain generator to make a keychain.
        kgen = root_mapper.getKeychainGenerator()
        event = GatewayEvent(self._root_mapper, (), self._conn_map)
        return kgen.makeKeychain(event, None, 1)


class ObjectSystemIO:
    """Object system (de)serialization facade."""

    def __init__(self, root_mapper, kos):
        self._root_mapper = root_mapper
        self._kos = kos


    def serialize(self, keychain, obj):
        mapper = self._root_mapper
        mapper_names = []
        classification = None
        if keychain:
            # Classify the parents first to discover what mapper to
            # use for storage.
            for i in range(1, len(keychain)):
                k = keychain[:i]
                o = self._kos.getObject(k)
                cfr = mapper.getClassifier()
                classification, sub_mapper_name = cfr.classifyObject(o, k)
                mapper_names.append(sub_mapper_name)
                mapper = mapper.getSubMapper(sub_mapper_name)
            # Now classify the object being stored.
            cfr = mapper.getClassifier()
            classification, sub_mapper_name = cfr.classifyObject(obj, keychain)
            mapper_names.append(sub_mapper_name)
            mapper = mapper.getSubMapper(sub_mapper_name)
        # Now serialize.
        ser = mapper.getSerializer()
        event = SerializationEvent(self._kos, mapper, keychain, obj)
        state = ser.serialize(obj, event)
        cs = ClassifiedState(state, classification, mapper_names)
        return event, cs


    def deserialize(self, keychain, obj, classified_state):
        mapper = self._root_mapper
        assert len(keychain) == len(classified_state.mapper_names)
        for mapper_name in classified_state.mapper_names:
            mapper = mapper.getSubMapper(mapper_name)
        ser = mapper.getSerializer()
        event = DeserializationEvent(self._kos, mapper, keychain, obj)
        ser.deserialize(obj, event, classified_state.state)
        return event


    def newObject(self, classified_state):
        mapper = self._root_mapper
        for mapper_name in classified_state.mapper_names:
            mapper = mapper.getSubMapper(mapper_name)
        ser = mapper.getSerializer()
        return ser.createEmptyInstance(
            self._kos, classification=classified_state.classification)


class ExportImport:
    """Simple import/export facade.
    """

    __implements__ = IKeyedObjectSystem

    def __init__(self, root_mapper, connections, class_factory=None):
        self._objects = {}     # { keychain -> obj }
        self._keychains = {}   # { id(obj) -> keychain }
        self._class_factory = class_factory
        # Avoid a circular reference by making a weakref proxy
        self.obj_io = ObjectSystemIO(root_mapper, proxy(self))
        self.gw_io = GatewayIO(root_mapper, connections)


    def _register(self, keychain, obj):
        is_new = 0
        if self._objects.has_key(keychain):
            if self._objects[keychain] is not obj:
                raise ValueError, (
                    "Multiple objects for keychain %s" % repr(keychain))
        else:
            self._objects[keychain] = obj
            is_new = 1
        obj_id = id(obj)
        if self._keychains.has_key(obj_id):
            if self._keychains[obj_id] != keychain:
                raise ValueError, (
                    "Multiple keychains for object %s" % repr(obj))
        else:
            self._keychains[obj_id] = keychain
            is_new = 1
        return is_new


    def exportObject(self, obj, keychain=None, deactivate_func=None):
        count = 0
        if keychain is None:
            keychain = (self.newKey(),)
        self._register(keychain, obj)
        todo = [(keychain, obj)]
        while todo:
            keychain, obj = todo.pop()
            event, classified_state = self.obj_io.serialize(keychain, obj)
            count += 1
            if deactivate_func is not None:
                deactivate_func(obj, count)
            self.gw_io.store(keychain, classified_state)
            ext_refs = event.getExternalRefs()
            if ext_refs:
                for ext_keychain, ext_obj in ext_refs:
                    if self._register(ext_keychain, ext_obj):
                        todo.append((ext_keychain, ext_obj))


    def importObject(self, keychain, obj=None, commit_func=None):
        count = 0
        if obj is None:
            classified_state, hash_value = self.gw_io.load(keychain)
            obj = self.obj_io.newObject(classified_state)
        root_obj = obj
        self._register(keychain, obj)
        todo = [(keychain, obj)]
        while todo:
            keychain, obj = todo.pop()
            classified_state, hash_value = self.gw_io.load(keychain)
            event = self.obj_io.deserialize(keychain, obj, classified_state)
            count += 1
            if commit_func is not None:
                commit_func(obj, count)
            ext_refs = event.getExternalRefs()
            if ext_refs:
                for ext_keychain, ext_obj in ext_refs:
                    if self._register(ext_keychain, ext_obj):
                        todo.append((ext_keychain, ext_obj))
        return root_obj


    # IKeyedObjectSystem implementation

    def getObject(self, keychain, hints=None):
        return self._objects[keychain]

    loadStub = getObject

    def identifyObject(self, obj):
        return self._keychains.get(id(obj))

    def newKey(self):
        return self.gw_io.newKeychain()[-1]

    def getClass(self, module, name):
        if self._class_factory is not None:
            return self._class_factory.getClass(module, name)
        else:
            m = __import__(module, {}, {}, ('__doc__',))
            return getattr(m, name)



=== Products/Ape/lib/apelib/core/events.py 1.3 => 1.4 ===
--- Products/Ape/lib/apelib/core/events.py:1.3	Mon May 19 15:32:33 2003
+++ Products/Ape/lib/apelib/core/events.py	Mon May 26 15:33:15 2003
@@ -110,6 +110,9 @@
         self._keyed_ob_sys = keyed_ob_sys
         self._object = object
         self._unmanaged = []
+        # _refs is the list of externally referenced objects.
+        # It has the form [(keychain, value)]
+        self._refs = []
 
     def getKeyedObjectSystem(self):
         """Returns the IKeyedObjectSystem that generated this event.
@@ -140,6 +143,10 @@
         """Returns the list of unmanaged persistent objects."""
         return self._unmanaged
 
+    def getExternalRefs(self):
+        """Returns the list of external references"""
+        return self._refs
+
 
 class DeserializationEvent (SDEvent):
 
@@ -161,11 +168,11 @@
         """Retrieves a referenced subobject (usually ghosted initially).
         """
         kos = self.getKeyedObjectSystem()
-        ob = kos.loadStub(keychain, hints)
+        ob = kos.getObject(keychain, hints)
+        self._refs.append((keychain, ob))
         self.notifyDeserialized(name, ob)
         return ob
 
-
     # IFullDeserializationEvent interface methods:
 
     def loadInternalRef(self, ref):
@@ -184,10 +191,6 @@
         SDEvent.__init__(
             self, keyed_ob_sys, object_mapper, keychain, object)
         self._attrs = {}
-
-        # _refs is the list of externally referenced objects.
-        # It has the form [(keychain, value)]
-        self._refs = []
         # _internal_refs:
         # id(ob) -> (serializer_name, name)
         self._internal_refs = {}
@@ -195,7 +198,6 @@
         # internally.  This only ensures that id(ob) stays consistent.
         self._internal_ref_list = []
 
-
     # ISerializationEvent interface methods:
 
     def notifySerialized(self, name, value, is_attribute):
@@ -237,10 +239,6 @@
         """See the ISerializationEvent interface."""
         for name in names:
             self._attrs[name] = 1
-
-    def getExternalRefs(self):
-        """Returns the list of external references"""
-        return self._refs
 
 
     # IFullSerializationEvent interface methods:


=== Products/Ape/lib/apelib/core/interfaces.py 1.6 => 1.7 ===
--- Products/Ape/lib/apelib/core/interfaces.py:1.6	Mon May 19 15:32:33 2003
+++ Products/Ape/lib/apelib/core/interfaces.py	Mon May 26 15:33:15 2003
@@ -41,16 +41,28 @@
 
 
 
-class IKeyedObjectSystem (Interface):
+class IClassFactory(Interface):
+    """Class finder."""
+
+    def getClass(module_name, class_name):
+        """Returns the named class.
+
+        A default implementation may use the Python's standard import
+        mechanism.
+        """
+
+
+class IKeyedObjectSystem (IClassFactory):
     """A collection of objects identifiable by keychain.
 
     In apelib.zodb3, the ZODB Connection object (the _p_jar) is
     an IKeyedObjectSystem.
     """
 
-    def loadStub(keychain, hints=None):
+    def getObject(keychain, hints=None):
         """Returns a class instance, possibly ghosted.
 
+        Used during deserialization (loading/import).
         The hints argument, a mapping, may be provided as an
         optimization.  Without it, implementations of this method may
         have to load a full object rather than a ghosted object.
@@ -65,11 +77,20 @@
     def identifyObject(obj):
         """Returns the keychain of an object.
 
+        Used during serialization (storing/export).
         Returns None if the object is not in the keyed object system.
         """
 
     def newKey():
-        """Returns a new, unique key (which might be used in a keychain)."""
+        """Returns a new, unique key (which might be used in a keychain).
+
+        Used during serialization (storing/export).
+        """
+
+    # Deprecated
+    def loadStub(keychain, hints=None):
+        """Deprecated alias for getObject()."""
+
 
 
 
@@ -144,7 +165,7 @@
     """Base for serialization and deserialization events."""
 
     def getKeyedObjectSystem():
-        """Returns the IKeyedObjectSystem that generated the event."""
+        """Returns the IKeyedObjectSystem involved in the event."""
 
     def getObject():
         """Returns the object being (de)serialized."""
@@ -169,6 +190,13 @@
     def getUnmanagedPersistentObjects():
         """Returns the list of unmanaged persistent objects."""
 
+    def getExternalRefs():
+        """Returns the list of external references.
+
+        The list is built up during (de)serialization.
+        The returned list is of the form [(keychain, subobject)].
+        """
+
 
 class IDeserializationEvent(ISDEvent):
     """A helper in the object deserialization process.
@@ -232,12 +260,6 @@
     def ignoreAttributes(names):
         """Indicates that several attributes should be ignored."""
 
-    def getExternalRefs():
-        """Returns the list of external references.
-
-        The returned list is of the form [(keychain, subobject)].
-        """
-
 
 class IFullSerializationEvent(ISerializationEvent):
     """Serialization event with features for ensuring complete serialization.
@@ -290,7 +312,7 @@
     and schema of its constituent ISerializers.
     """
 
-    def createEmptyInstance(classification=None, class_factory=None):
+    def createEmptyInstance(class_factory, classification=None):
         """Returns a new instance.
 
         If this serializer works with instances of only one class,
@@ -299,10 +321,9 @@
         classification argument can return None when classification is
         None, but doing so may incur a performance penalty.
 
-        If a class factory is provided, the serializer may use it
+        class_factory is an IClassFactory.  The serializer may use it
         instead of the standard Python import mechanism to find a
-        class.  The class_factory will be called with a module and
-        class name.
+        class.
         """
 
 


=== Products/Ape/lib/apelib/core/serializers.py 1.1 => 1.2 ===
--- Products/Ape/lib/apelib/core/serializers.py:1.1	Wed Apr  9 23:09:55 2003
+++ Products/Ape/lib/apelib/core/serializers.py	Mon May 26 15:33:15 2003
@@ -99,12 +99,8 @@
             event.setSerializerName(name)
             serializer.deserialize(object, event, state)
 
-    def createEmptyInstance(self, classification=None, class_factory=None):
-        if class_factory is not None:
-            c = class_factory(self._module, self._name)
-        else:
-            m = __import__(self._module, {}, {}, ('__doc__',))
-            c = getattr(m, self._name)
+    def createEmptyInstance(self, class_factory, classification=None):
+        c = class_factory.getClass(self._module, self._name)
         return c.__basicnew__()
 
 
@@ -120,17 +116,13 @@
     def canSerialize(self, object):
         return 1
 
-    def createEmptyInstance(self, classification=None, class_factory=None):
+    def createEmptyInstance(self, class_factory, classification=None):
         if classification is None:
             # This serializer can't do anything without the classification.
             return None
         cn = classification['class_name']
         module, name = cn.split(':', 1)
-        if class_factory is not None:
-            c = class_factory(module, name)
-        else:
-            m = __import__(module, {}, {}, ('__doc__',))
-            c = getattr(m, name)
+        c = class_factory.getClass(module, name)
         return c.__basicnew__()