[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__()