Commit 8a1f8584 authored by Jim Fulton's avatar Jim Fulton

Added blob support to DemoStorage.

parent 7709fd15
...@@ -20,12 +20,21 @@ The base storage must not change. ...@@ -20,12 +20,21 @@ The base storage must not change.
""" """
import random import random
import tempfile
import threading import threading
import ZODB.blob
import ZODB.interfaces
import ZODB.MappingStorage import ZODB.MappingStorage
import ZODB.POSException import ZODB.POSException
import ZODB.utils import ZODB.utils
import zope.interface
class DemoStorage: class DemoStorage(object):
zope.interface.implements(
ZODB.interfaces.IStorage,
ZODB.interfaces.IStorageIteration,
)
def __init__(self, name=None, base=None, changes=None): def __init__(self, name=None, base=None, changes=None):
if base is None: if base is None:
...@@ -34,17 +43,40 @@ class DemoStorage: ...@@ -34,17 +43,40 @@ class DemoStorage:
if changes is None: if changes is None:
changes = ZODB.MappingStorage.MappingStorage() changes = ZODB.MappingStorage.MappingStorage()
zope.interface.alsoProvides(self, ZODB.interfaces.IBlobStorage)
self._temporary_changes = True
self._blob_dir = None
else:
if ZODB.interfaces.IBlobStorage.providedBy(changes):
zope.interface.alsoProvides(self, ZODB.interfaces.IBlobStorage)
self._temporary_changes = False
self.changes = changes self.changes = changes
if name is None: if name is None:
name = 'DemoStorage(%r, %r)' % (base.getName(), changes.getName()) name = 'DemoStorage(%r, %r)' % (base.getName(), changes.getName())
self.__name__ = name self.__name__ = name
supportsUndo = getattr(changes, 'supportsUndo', None) self._copy_methods_from_changes(changes)
if supportsUndo is not None and supportsUndo():
for meth in ('supportsUndo', 'undo', 'undoLog', 'undoInfo'): def _blobify(self):
setattr(self, meth, getattr(changes, meth)) if self._temporary_changes and self._blob_dir is None:
self._blob_dir = tempfile.mkdtemp('blobs')
self.changes = ZODB.blob.BlobStorage(self._blob_dir, self.changes)
self._copy_methods_from_changes(self.changes)
return True
def cleanup(self):
self.base.cleanup()
self.changes.cleanup()
def close(self):
self.base.close()
self.changes.close()
if getattr(self, '_blob_dir', ''):
ZODB.blob.remove_committed_dir(self._blob_dir)
def _copy_methods_from_changes(self, changes):
for meth in ( for meth in (
'_lock_acquire', '_lock_release', '_lock_acquire', '_lock_release',
'getSize', 'history', 'isReadOnly', 'registerDB', 'getSize', 'history', 'isReadOnly', 'registerDB',
...@@ -53,17 +85,15 @@ class DemoStorage: ...@@ -53,17 +85,15 @@ class DemoStorage:
): ):
setattr(self, meth, getattr(changes, meth)) setattr(self, meth, getattr(changes, meth))
supportsUndo = getattr(changes, 'supportsUndo', None)
if supportsUndo is not None and supportsUndo():
for meth in ('supportsUndo', 'undo', 'undoLog', 'undoInfo'):
setattr(self, meth, getattr(changes, meth))
zope.interface.alsoProvides(self, ZODB.interfaces.IStorageUndoable)
lastInvalidations = getattr(changes, 'lastInvalidations', None) lastInvalidations = getattr(changes, 'lastInvalidations', None)
if lastInvalidations is not None: if lastInvalidations is not None:
self.lastInvalidations = lastInvalidations self.lastInvalidations = lastInvalidations
def cleanup(self):
self.base.cleanup()
self.changes.cleanup()
def close(self):
self.base.close()
self.changes.close()
def getName(self): def getName(self):
return self.__name__ return self.__name__
...@@ -113,7 +143,23 @@ class DemoStorage: ...@@ -113,7 +143,23 @@ class DemoStorage:
pass pass
return result return result
def loadBlob(self, oid, serial):
try:
return self.changes.loadBlob(oid, serial)
except ZODB.POSException.POSKeyError:
try:
return self.base.loadBlob(oid, serial)
except AttributeError:
if not zope.interface.IBlobStorage.providBy(self.base):
raise ZODB.POSException.POSKeyError(oid, serial)
raise
except AttributeError:
if self._blobify():
return self.loadBlob(oid, serial)
raise
def loadSerial(self, oid, serial): def loadSerial(self, oid, serial):
try: try:
return self.changes.loadSerial(oid, serial) return self.changes.loadSerial(oid, serial)
...@@ -163,3 +209,22 @@ class DemoStorage: ...@@ -163,3 +209,22 @@ class DemoStorage:
oid=oid, serials=(old, serial)) # XXX untested branch oid=oid, serials=(old, serial)) # XXX untested branch
return self.changes.store(oid, serial, data, '', transaction) return self.changes.store(oid, serial, data, '', transaction)
def storeBlob(self, oid, oldserial, data, blobfilename, version,
transaction):
try:
return self.changes.storeBlob(
oid, oldserial, data, blobfilename, version, transaction)
except AttributeError:
if self._blobify():
return self.changes.storeBlob(
oid, oldserial, data, blobfilename, version, transaction)
raise
def temporaryDirectory(self):
try:
return self.changes.temporaryDirectory()
except AttributeError:
if self._blobify():
return self.changes.temporaryDirectory()
raise
==========================
DemoStorage demo (doctest) DemoStorage demo (doctest)
-------------------------- ==========================
Note that most people will configure the storage through ZConfig. If Note that most people will configure the storage through ZConfig. If
you are one of those people, you may want to stop here. :) The you are one of those people, you may want to stop here. :) The
...@@ -120,3 +121,93 @@ Undo methods are simply copied from the changes storage: ...@@ -120,3 +121,93 @@ Undo methods are simply copied from the changes storage:
... for name in ('supportsUndo', 'undo', 'undoLog', 'undoInfo') ... for name in ('supportsUndo', 'undo', 'undoLog', 'undoInfo')
... ] ... ]
[True, True, True, True] [True, True, True, True]
>>> db.close()
Blob Support
============
DemoStorage supports Blobs if the changes database supports blobs.
>>> import ZODB.blob
>>> base = ZODB.blob.BlobStorage('base', FileStorage('base.fs'))
>>> db = DB(base)
>>> conn = db.open()
>>> conn.root()['blob'] = ZODB.blob.Blob()
>>> conn.root()['blob'].open('w').write('state 1')
>>> transaction.commit()
>>> db.close()
>>> base = ZODB.blob.BlobStorage('base',
... FileStorage('base.fs', read_only=True))
>>> changes = ZODB.blob.BlobStorage('changes',
... FileStorage('changes.fs', create=True))
>>> storage = DemoStorage(base=base, changes=changes)
>>> db = DB(storage)
>>> conn = db.open()
>>> conn.root()['blob'].open().read()
'state 1'
>>> _ = transaction.begin()
>>> conn.root()['blob'].open('w').write('state 2')
>>> transaction.commit()
>>> conn.root()['blob'].open().read()
'state 2'
>>> storage.temporaryDirectory() == changes.temporaryDirectory()
True
>>> db.close()
It isn't necessary for the base database to support blobs.
>>> base = FileStorage('base.fs', read_only=True)
>>> changes = ZODB.blob.BlobStorage('changes', FileStorage('changes.fs'))
>>> storage = DemoStorage(base=base, changes=changes)
>>> db = DB(storage)
>>> conn = db.open()
>>> conn.root()['blob'].open().read()
'state 2'
>>> _ = transaction.begin()
>>> conn.root()['blob2'] = ZODB.blob.Blob()
>>> conn.root()['blob2'].open('w').write('state 1')
>>> conn.root()['blob2'].open().read()
'state 1'
>>> db.close()
If the changes database is created implicitly, it will get a blob
storage wrapped around it when necessary:
>>> base = ZODB.blob.BlobStorage('base',
... FileStorage('base.fs', read_only=True))
>>> storage = DemoStorage(base=base)
>>> type(storage.changes).__name__
'MappingStorage'
>>> db = DB(storage)
>>> conn = db.open()
>>> conn.root()['blob'].open().read()
'state 1'
>>> type(storage.changes).__name__
'BlobStorage'
>>> _ = transaction.begin()
>>> conn.root()['blob'].open('w').write('state 2')
>>> transaction.commit()
>>> conn.root()['blob'].open().read()
'state 2'
>>> storage.temporaryDirectory() == storage.changes.temporaryDirectory()
True
>>> db.close()
.. Check that the temporary directory is gone
>>> import os
>>> os.path.exists(storage.temporaryDirectory())
False
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment