Commit 2e35c9ea authored by Jim Fulton's avatar Jim Fulton

Added blob support to DemoStorage.

parent 03a4d20f
......@@ -20,12 +20,21 @@ The base storage must not change.
"""
import random
import tempfile
import threading
import ZODB.blob
import ZODB.interfaces
import ZODB.MappingStorage
import ZODB.POSException
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):
if base is None:
......@@ -34,17 +43,40 @@ class DemoStorage:
if changes is None:
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
if name is None:
name = 'DemoStorage(%r, %r)' % (base.getName(), changes.getName())
self.__name__ = name
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))
self._copy_methods_from_changes(changes)
def _blobify(self):
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 (
'_lock_acquire', '_lock_release',
'getSize', 'history', 'isReadOnly', 'registerDB',
......@@ -53,17 +85,15 @@ class DemoStorage:
):
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)
if lastInvalidations is not None:
self.lastInvalidations = lastInvalidations
def cleanup(self):
self.base.cleanup()
self.changes.cleanup()
def close(self):
self.base.close()
self.changes.close()
def getName(self):
return self.__name__
......@@ -113,7 +143,23 @@ class DemoStorage:
pass
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):
try:
return self.changes.loadSerial(oid, serial)
......@@ -163,3 +209,22 @@ class DemoStorage:
oid=oid, serials=(old, serial)) # XXX untested branch
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)
--------------------------
==========================
Note that most people will configure the storage through ZConfig. If
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:
... for name in ('supportsUndo', 'undo', 'undoLog', 'undoInfo')
... ]
[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