Commit a1f9cba8 authored by Jim Fulton's avatar Jim Fulton

Refactored stacking support:

- explicit push and pop methods

- backward compatible with old behavior.
parent be4f7918
...@@ -19,7 +19,9 @@ to be layered over a base database. ...@@ -19,7 +19,9 @@ to be layered over a base database.
The base storage must not change. The base storage must not change.
""" """
import os
import random import random
import weakref
import tempfile import tempfile
import threading import threading
import ZODB.blob import ZODB.blob
...@@ -36,18 +38,18 @@ class DemoStorage(object): ...@@ -36,18 +38,18 @@ class DemoStorage(object):
ZODB.interfaces.IStorageIteration, ZODB.interfaces.IStorageIteration,
) )
def __init__(self, name=None, base=None, changes=None, def __init__(self, name=None, base=None, changes=None):
keep_base_open=False):
self._keep_base_open = keep_base_open
if base is None: if base is None:
base = ZODB.MappingStorage.MappingStorage() base = ZODB.MappingStorage.MappingStorage()
self._temporary_base = True
else:
self._temporary_base = False
self.base = base self.base = base
if changes is None: if changes is None:
changes = ZODB.MappingStorage.MappingStorage() changes = ZODB.MappingStorage.MappingStorage()
zope.interface.alsoProvides(self, ZODB.interfaces.IBlobStorage) zope.interface.alsoProvides(self, ZODB.interfaces.IBlobStorage)
self._temporary_changes = True self._temporary_changes = True
self._blob_dir = None
else: else:
if ZODB.interfaces.IBlobStorage.providedBy(changes): if ZODB.interfaces.IBlobStorage.providedBy(changes):
zope.interface.alsoProvides(self, ZODB.interfaces.IBlobStorage) zope.interface.alsoProvides(self, ZODB.interfaces.IBlobStorage)
...@@ -61,10 +63,16 @@ class DemoStorage(object): ...@@ -61,10 +63,16 @@ class DemoStorage(object):
self._copy_methods_from_changes(changes) self._copy_methods_from_changes(changes)
def _blobify(self): def _blobify(self):
if self._temporary_changes and self._blob_dir is None: if (self._temporary_changes and
self._blob_dir = tempfile.mkdtemp('blobs') isinstance(self.changes, ZODB.MappingStorage.MappingStorage)
self.changes = ZODB.blob.BlobStorage(self._blob_dir, self.changes) ):
blob_dir = tempfile.mkdtemp('.demoblobs')
_temporary_blobdirs[
weakref.ref(self, cleanup_temporary_blobdir)
] = blob_dir
self.changes = ZODB.blob.BlobStorage(blob_dir, self.changes)
self._copy_methods_from_changes(self.changes) self._copy_methods_from_changes(self.changes)
return True return True
...@@ -73,11 +81,10 @@ class DemoStorage(object): ...@@ -73,11 +81,10 @@ class DemoStorage(object):
self.changes.cleanup() self.changes.cleanup()
def close(self): def close(self):
if not self._keep_base_open: if not self._temporary_base:
self.base.close() self.base.close()
if not self._temporary_changes:
self.changes.close() self.changes.close()
if getattr(self, '_blob_dir', ''):
ZODB.blob.remove_committed_dir(self._blob_dir)
def _copy_methods_from_changes(self, changes): def _copy_methods_from_changes(self, changes):
for meth in ( for meth in (
...@@ -195,6 +202,13 @@ class DemoStorage(object): ...@@ -195,6 +202,13 @@ class DemoStorage(object):
pass # The gc arg isn't supported. Don't pack pass # The gc arg isn't supported. Don't pack
raise raise
def pop(self):
self.changes.close()
return self.base
def push(self, changes=None):
return self.__class__(base=self, changes=changes)
def store(self, oid, serial, data, version, transaction): def store(self, oid, serial, data, version, transaction):
assert version=='', "versions aren't supported" assert version=='', "versions aren't supported"
...@@ -231,3 +245,12 @@ class DemoStorage(object): ...@@ -231,3 +245,12 @@ class DemoStorage(object):
if self._blobify(): if self._blobify():
return self.changes.temporaryDirectory() return self.changes.temporaryDirectory()
raise raise
_temporary_blobdirs = {}
def cleanup_temporary_blobdir(
ref,
_temporary_blobdirs=_temporary_blobdirs, # Make sure it stays around
):
blob_dir = _temporary_blobdirs.pop(ref, None)
if blob_dir and os.path.exists(blob_dir):
ZODB.blob.remove_committed_dir(blob_dir)
...@@ -120,37 +120,66 @@ Undo methods are simply copied from the changes storage: ...@@ -120,37 +120,66 @@ Undo methods are simply copied from the changes storage:
... ] ... ]
[True, True, True, True] [True, True, True, True]
>>> db.close()
Normally, when we close a demo storage, the changes and base storages Storage Stacking
are closed: ================
>>> db.close() A common use case is to stack demo storages. DemoStorage provides
>>> base._file.closed some helper functions to help with this. The push method, just
creates a new demo storage who's base is the original demo storage:
>>> demo = DemoStorage()
>>> demo2 = demo.push()
>>> demo2.base is demo
True True
>>> changes._file.closed
We can also supply an explicit changes storage, if we wish:
>>> from ZODB.MappingStorage import MappingStorage
>>> changes = MappingStorage()
>>> demo3 = demo2.push(changes)
>>> demo3.changes is changes, demo3.base is demo2
(True, True)
The pop method closes the changes storage and returns the base
*without* closing it:
>>> demo3.pop() is demo2
True True
A common use case is to stack multiple DemoStorages, returning to a >>> changes.opened()
previous state by popping a DemoStorage off the stack. In this case, False
we want to leave the base storage open:
>>> base = FileStorage('base.fs', read_only=True) Special backward compatibility support
>>> storage = DemoStorage(base=base, keep_base_open=True) --------------------------------------
Here, we didn't specify a changes storage. A MappingStorage was Normally, when a demo storage is closed, it's base and changes
automatically created: storage are closed:
>>> type(storage.changes).__name__ >>> demo = DemoStorage(base=MappingStorage(), changes=MappingStorage())
'MappingStorage' >>> demo.close()
>>> demo.base.opened(), demo.changes.opened()
(False, False)
Because we specified the keep_base_open option, the base storage is Older versions of DemoStorage didn't have a separate changes storage
left open when we close the DemoStorage: and didn't close or discard their changes when they were closed. When
a stack was built solely of demo storages, the close method
effectively did nothing. To maintain backward compatibility, when no
base or changes storage is supplied in the constructor, the underlying
storage created by the demo storage isn't closed by the demo storage.
This backward-compatibility is deprecated.
>>> storage.close() >>> demo = DemoStorage()
>>> base._file.closed >>> demo.close()
False >>> demo.changes.opened(), demo.base.opened()
>>> storage.changes.opened() (True, True)
False
>>> demo = DemoStorage(base=MappingStorage())
>>> demo2 = demo.push()
>>> demo2.close()
>>> demo2.changes.opened(), demo2.base.base.opened()
(True, False)
Blob Support Blob Support
============ ============
...@@ -236,6 +265,16 @@ storage wrapped around it when necessary: ...@@ -236,6 +265,16 @@ storage wrapped around it when necessary:
.. Check that the temporary directory is gone .. Check that the temporary directory is gone
For now, it won't go until the storage does.
>>> transaction.abort()
>>> conn.close()
>>> blobdir = storage.temporaryDirectory()
>>> del db, conn, storage, _
>>> import gc
>>> _ = gc.collect()
>>> import os >>> import os
>>> os.path.exists(storage.temporaryDirectory()) >>> os.path.exists(blobdir)
False 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