Commit 61aeabc6 authored by Jim Fulton's avatar Jim Fulton

consumeFile no-longer works with open files.

Now it also truly consumes files.

Binary mode is implicit.
parent c6679cc2
......@@ -6,20 +6,28 @@ an O(1) operation we call `consume`::
Let's create a file::
>>> import tempfile
>>> to_import = tempfile.NamedTemporaryFile()
>>> to_import = open('to_import', 'wb')
>>> to_import.write("I'm a Blob and I feel fine.")
>>> to_import.flush()
The file *must* be closed before giving it to consumeFile:
>>> to_import.close()
Now, let's consume this file in a blob by specifying it's name::
>>> from ZODB.Blobs.Blob import Blob
>>> blob = Blob()
>>> blob.consumeFile(to_import.name)
>>> blob.consumeFile('to_import')
After the consumeFile operation, the original file has been removed:
>>> import os
>>> os.path.exists('to_import')
False
We now can call open on the blob and read and write the data::
>>> blob_read = blob.open('rb')
>>> blob_read = blob.open('r')
>>> blob_read.read()
"I'm a Blob and I feel fine."
>>> blob_read.close()
......@@ -27,41 +35,24 @@ We now can call open on the blob and read and write the data::
>>> blob_write.write('I was changed.')
>>> blob_write.close()
Please note that the interface for the `consume` method specifies a hard-link
as a part of the contract so your existing file and the blob file will be the
same. If one gets changed the other will reflect those changes as well. This
is especially a known side-effect when consuming a file and then opening the
blob for writing before committing in between::
>>> to_import.seek(0)
>>> to_import.read()
'I was changed.'
(Applications are expected that files for consumption are typically copies of
existing data and that the imported link to the file will be removed after a
successfull import. This can be achieved (as in this test) by using a
NamedTempFile.)
We can not consume a file when there is a reader or writer around for a blob
already::
>>> to_import2 = tempfile.NamedTemporaryFile()
>>> to_import2.write('I am another blob.')
>>> to_import2.flush()
>>> open('to_import', 'wb').write('I am another blob.')
>>> blob_read = blob.open('r')
>>> blob.consumeFile(to_import2.name)
>>> blob.consumeFile('to_import')
Traceback (most recent call last):
BlobError: Already opened for reading.
>>> blob_read.close()
>>> blob_write = blob.open('w')
>>> blob.consumeFile(to_import2.name)
>>> blob.consumeFile('to_import')
Traceback (most recent call last):
BlobError: Already opened for writing.
>>> blob_write.close()
Now, after closing all readers and writers we can consume files again::
>>> blob.consumeFile(to_import2.name)
>>> blob.consumeFile('to_import')
>>> blob_read = blob.open('r')
>>> blob_read.read()
'I am another blob.'
......@@ -70,14 +61,13 @@ Now, after closing all readers and writers we can consume files again::
Edge cases
==========
There are some edge cases what happens when the link() operation fails. We simulate this in different states:
There are some edge cases what happens when the link() operation
fails. We simulate this in different states:
Case 1: We don't have uncommitted data, but the link operation fails. The
exception will be re-raised and the target file will not exist::
>>> input = tempfile.NamedTemporaryFile()
>>> input.write('Some data')
>>> input.flush()
>>> open('to_import', 'wb').write('Some data.')
>>> def failing_link(self, filename):
... raise Exception("I can't link.")
......@@ -88,7 +78,7 @@ exception will be re-raised and the target file will not exist::
BlobError: Blob does not exist.
>>> blob._os_link = failing_link
>>> blob.consumeFile(input.name)
>>> blob.consumeFile('to_import')
Traceback (most recent call last):
Exception: I can't link.
......@@ -102,18 +92,13 @@ Case 2: We thave uncommitted data, but the link operation fails. The
exception will be re-raised and the target file will exist with the previous
uncomitted data::
>>> input = tempfile.NamedTemporaryFile()
>>> input.write('Unimported data')
>>> input.flush()
>>> blob = Blob()
>>> blob_writing = blob.open('w')
>>> blob_writing.write('Uncommitted data')
>>> blob_writing.close()
>>> blob._os_link = failing_link
>>> blob.consumeFile(input.name)
>>> blob.consumeFile('to_import')
Traceback (most recent call last):
Exception: I can't link.
......
......@@ -28,15 +28,16 @@ from ZODB import utils
class BlobUndoTests(unittest.TestCase):
def setUp(self):
self.storagefile = tempfile.mktemp()
self.blob_dir = tempfile.mkdtemp()
self.test_dir = tempfile.mkdtemp()
self.here = os.getcwd()
os.chdir(self.test_dir)
self.storagefile = 'Data.fs'
os.mkdir('blobs')
self.blob_dir = 'blobs'
def tearDown(self):
try:
os.unlink(self.storagefile)
except (OSError, IOError):
pass
shutil.rmtree(self.blob_dir)
os.chdir(self.here)
shutil.rmtree(self.test_dir)
def testUndoWithoutPreviousVersion(self):
base_storage = FileStorage(self.storagefile)
......@@ -60,7 +61,8 @@ class BlobUndoTests(unittest.TestCase):
root = connection.root()
# the blob footprint object should exist no longer
self.assertRaises(KeyError, root.__getitem__, 'blob')
database.close()
def testUndo(self):
base_storage = FileStorage(self.storagefile)
blob_storage = BlobStorage(self.blob_dir, base_storage)
......@@ -93,6 +95,7 @@ class BlobUndoTests(unittest.TestCase):
blob = root['blob']
self.assertEqual(blob.open('r').read(), 'this is state 1')
transaction.abort()
database.close()
def testUndoAfterConsumption(self):
base_storage = FileStorage(self.storagefile)
......@@ -101,22 +104,16 @@ class BlobUndoTests(unittest.TestCase):
connection = database.open()
root = connection.root()
transaction.begin()
to_consume = tempfile.NamedTemporaryFile()
to_consume.write('this is state 1')
to_consume.flush()
open('consume1', 'w').write('this is state 1')
blob = Blob()
blob.consumeFile(to_consume.name)
blob.consumeFile('consume1')
root['blob'] = blob
transaction.commit()
transaction.begin()
blob = root['blob']
to_consume = tempfile.NamedTemporaryFile()
to_consume.write('this is state 2')
to_consume.flush()
blob.consumeFile(to_consume.name)
open('consume2', 'w').write('this is state 2')
blob.consumeFile('consume2')
transaction.commit()
transaction.begin()
......@@ -135,6 +132,8 @@ class BlobUndoTests(unittest.TestCase):
self.assertEqual(blob.open('r').read(), 'this is state 1')
transaction.abort()
database.close()
def testRedo(self):
base_storage = FileStorage(self.storagefile)
blob_storage = BlobStorage(self.blob_dir, base_storage)
......@@ -174,7 +173,9 @@ class BlobUndoTests(unittest.TestCase):
blob = root['blob']
self.assertEqual(blob.open('r').read(), 'this is state 2')
transaction.abort()
database.close()
def testRedoOfCreation(self):
base_storage = FileStorage(self.storagefile)
blob_storage = BlobStorage(self.blob_dir, base_storage)
......@@ -207,6 +208,7 @@ class BlobUndoTests(unittest.TestCase):
self.assertEqual(blob.open('r').read(), 'this is state 1')
transaction.abort()
database.close()
def test_suite():
suite = unittest.TestSuite()
......
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