##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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.
#
##############################################################################

ZODB Blob support
=================

You create a blob like this:

    >>> from ZODB.Blobs.Blob import Blob
    >>> myblob = Blob()

A blob implements the IBlob interface:

    >>> from ZODB.Blobs.interfaces import IBlob
    >>> IBlob.providedBy(myblob)
    True

Opening a new Blob for reading fails:

    >>> myblob.open("r")
    Traceback (most recent call last):
        ...
    BlobError: Blob does not exist.

But we can write data to a new Blob by opening it for writing:

    >>> f = myblob.open("w")
    >>> f.write("Hi, Blob!")

If we try to open a Blob again while it is open for writing, we get an error:

    >>> myblob.open("r")
    Traceback (most recent call last):
        ...
    BlobError: Already opened for writing.

We can close the file:

    >>> f.close()

Now we can open it for reading:

    >>> f2 = myblob.open("r")

And we get the data back:

    >>> f2.read()
    'Hi, Blob!'

If we want to, we can open it again:

    >>> f3 = myblob.open("r")
    >>> f3.read()
    'Hi, Blob!'

But we can't open it for writing, while it is opened for reading:

    >>> myblob.open("a")
    Traceback (most recent call last):
        ...
    BlobError: Already opened for reading.

Before we can write, we have to close the readers:

    >>> f2.close()
    >>> f3.close()

Now we can open it for writing again and e.g. append data:

    >>> f4 = myblob.open("a")
    >>> f4.write("\nBlob is fine.")
    >>> f4.close()

Now we can read it:

    >>> f4a = myblob.open("r")
    >>> f4a.read()
    'Hi, Blob!\nBlob is fine.'
    >>> f4a.close()

You shouldn't need to explicitly close a blob unless you hold a reference
to it via a name.  If the first line in the following test kept a reference
around via a name, the second call to open it in a writable mode would fail
with a BlobError, but it doesn't.

    >>> myblob.open("r+").read()
    'Hi, Blob!\nBlob is fine.'
    >>> f4b = myblob.open("a")
    >>> f4b.close()
    
We can read lines out of the blob too:

    >>> f5 = myblob.open("r")
    >>> f5.readline()
    'Hi, Blob!\n'
    >>> f5.readline()
    'Blob is fine.'
    >>> f5.close()

We can seek to certain positions in a blob and read portions of it:

    >>> f6 = myblob.open('r')
    >>> f6.seek(4)
    >>> int(f6.tell())
    4
    >>> f6.read(5)
    'Blob!'
    >>> f6.close()

We can use the object returned by a blob open call as an iterable:

    >>> f7 = myblob.open('r')
    >>> for line in f7:
    ...     print line
    Hi, Blob!
    <BLANKLINE>
    Blob is fine.
    >>> f7.close()

We can truncate a blob:

    >>> f8 = myblob.open('a')
    >>> f8.truncate(0)
    >>> f8.close()
    >>> f8 = myblob.open('r')
    >>> f8.read()
    ''
    >>> f8.close()

We can explicitly open Blobs in the different modified modes:

    >>> f9 = myblob.open("rb")
    >>> f9.mode
    'rb'
    >>> f9.close()

We can specify the tempdir that blobs use to keep uncommitted data by
modifying the ZODB_BLOB_TEMPDIR environment variable:

    >>> import os, tempfile, shutil
    >>> tempdir = tempfile.mkdtemp()
    >>> os.environ['ZODB_BLOB_TEMPDIR'] = tempdir
    >>> myblob = Blob()
    >>> len(os.listdir(tempdir))
    0
    >>> f = myblob.open('w')
    >>> len(os.listdir(tempdir))
    1
    >>> f.close()
    >>> shutil.rmtree(tempdir)
    >>> del os.environ['ZODB_BLOB_TEMPDIR']

Some cleanup in this test is needed:

    >>> import transaction
    >>> transaction.get().abort()
