← Back to team overview

beeseek-devs team mailing list archive

[Branch ~beeseek-devs/beeseek/trunk] Rev 209: Update database structure allowing to save more space and making the

 

------------------------------------------------------------
revno: 209
committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
branch nick: trunk
timestamp: Sun 2009-02-08 13:27:09 +0100
message:
  Update database structure allowing to save more space and making the 
  access faster.
  
  The new code creates a new file (name.unused) that keeps a track of all 
  unused file blocks that are created when, for example, a key is deleted.
  Blocks are reused when a new item is added or updated. This makes the 
  database index easier to be read and key lookup is faster.
added:
  beeseek/database/base.py
  beeseek/tests/database.py
modified:
  beeseek/database/__init__.py
  beeseek/database/nested.py
  beeseek/honeybee/handler.py
  beeseek/honeybee/main.py
    ------------------------------------------------------------
    revno: 208.1.15
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-objects-structure
    timestamp: Fri 2009-02-06 19:55:30 +0100
    message:
      Update Honeybee.
    modified:
      beeseek/honeybee/main.py
    ------------------------------------------------------------
    revno: 208.1.14
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Fri 2009-02-06 18:47:23 +0100
    message:
      Release old blocks when updating an item.
    modified:
      beeseek/database/base.py
    ------------------------------------------------------------
    revno: 208.1.13
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Fri 2009-02-06 13:44:25 +0100
    message:
      Merge consecutive unused blocks.
    modified:
      beeseek/database/base.py
    ------------------------------------------------------------
    revno: 208.1.12
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Thu 2009-02-05 21:01:55 +0100
    message:
      Store new keys and values in the unused data blocks when possible.
    modified:
      beeseek/database/base.py
    ------------------------------------------------------------
    revno: 208.1.11
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Thu 2009-02-05 20:05:41 +0100
    message:
      Keep references to unused parts of index and data files.
    modified:
      beeseek/database/base.py
    ------------------------------------------------------------
    revno: 208.1.10
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Thu 2009-02-05 19:45:17 +0100
    message:
      Test item deletion.
    modified:
      beeseek/tests/database.py
    ------------------------------------------------------------
    revno: 208.1.9
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Thu 2009-02-05 18:19:24 +0100
    message:
      Reuse old keys when setting a new one or deleting it.
    modified:
      beeseek/database/base.py
      beeseek/database/nested.py
    ------------------------------------------------------------
    revno: 208.1.8
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Thu 2009-02-05 17:58:49 +0100
    message:
      Use md5 hashes for keys.
    modified:
      beeseek/database/base.py
      beeseek/tests/database.py
    ------------------------------------------------------------
    revno: 208.1.7
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Thu 2009-02-05 14:14:04 +0100
    message:
      Add test.
    added:
      beeseek/tests/database.py
    ------------------------------------------------------------
    revno: 208.1.6
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Thu 2009-02-05 14:13:36 +0100
    message:
      Fixed some syntax errors and cleanup.
    modified:
      beeseek/database/base.py
      beeseek/database/nested.py
    ------------------------------------------------------------
    revno: 208.1.5
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Wed 2009-02-04 20:41:47 +0100
    message:
      Improved and simplified NestedDatabase's code.
    modified:
      beeseek/database/nested.py
    ------------------------------------------------------------
    revno: 208.1.4
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Wed 2009-02-04 20:31:07 +0100
    message:
      Improved and simplified BaseDatabase's code.
    modified:
      beeseek/database/base.py
    ------------------------------------------------------------
    revno: 208.1.3
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Wed 2009-02-04 18:25:18 +0100
    message:
      Add IDatabase interface.
    modified:
      beeseek/database/base.py
      beeseek/database/nested.py
    ------------------------------------------------------------
    revno: 208.1.2
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Wed 2009-02-04 18:11:25 +0100
    message:
      Update old code.
    modified:
      beeseek/honeybee/handler.py
      beeseek/honeybee/main.py
    ------------------------------------------------------------
    revno: 208.1.1
    committer: Andrea Corbellini <andrea.corbellini@xxxxxxxxxxx>
    branch nick: database-structure
    timestamp: Wed 2009-02-04 18:08:07 +0100
    message:
      Moved classes from database.__init__ to database.base.
    added:
      beeseek/database/base.py
    modified:
      beeseek/database/__init__.py

=== modified file 'beeseek/database/__init__.py'
--- beeseek/database/__init__.py	2009-01-13 13:08:37 +0000
+++ beeseek/database/__init__.py	2009-02-04 17:08:07 +0000
@@ -15,212 +15,5 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-import thread
-
-
-def _hex(i):
-    if i >= 0:
-        return hex(i)[2:]
-    else:
-        return '-' + hex(i)[3:]
-
-class BaseDatabase(object):
-
-    def __init__(self, readonly=False):
-        self._readonly = readonly
-        self._lock = thread.allocate_lock()
-
-    def _open(self, filename, readonly):
-        if not isinstance(filename, basestring):
-            return filename
-
-        if readonly:
-            return file(filename, 'rb')
-        else:
-            fp = None
-            for mode in ('r+b', 'w+b'):
-                try:
-                    fp = file(filename, mode)
-                except IOError:
-                    pass
-                else:
-                    break
-            if not fp:
-                raise
-            return fp
-
-    def _compile_signature(self, signature):
-        text = ''
-        readfunc = []
-        writefunc = []
-        for item in signature:
-            pack = self._get_pack(item)
-            text += pack[0]
-            readfunc.append(pack[1])
-            writefunc.append(pack[2])
-        return ''.join(text), readfunc, writefunc
-
-    def _get_pack(self, datatype):
-        if datatype == int:
-            text = '\xa0'
-            readfunc = lambda fp: int(fp.readline(), 16)
-            writefunc = lambda data: (_hex(data) + '\n')
-        elif datatype == long:
-            text = '\xa1'
-            readfunc = lambda fp: long(fp.readline(), 16)
-            writefunc = lambda data: (_hex(data) + '\n')
-        elif datatype in (basestring, str):
-            text = '\xb0'
-            readfunc = lambda fp: fp.read(int(fp.readline(), 16))
-            writefunc = lambda data: (_hex(len(data)) + '\n' + data)
-        elif datatype == unicode:
-            text = '\xb1'
-            readfunc = lambda fp: unicode(fp.read(int(fp.readline(), 16)))
-            writefunc = lambda data: (_hex(len(data)) + '\n' + data)
-        elif datatype == tuple:
-            # TODO Add support for int, long and unicode
-            text = '\xc0'
-            readfunc = lambda fp: tuple(fp.read(int(fp.readline(), 16))
-                        for i in xrange(int(fp.readline(), 16)))
-            writefunc = lambda data: (_hex(len(data)) + '\n' +
-                        ''.join((_hex(len(item)) + '\n' + item)
-                        for item in data) + '\n')
-        else:
-            raise ValueError()
-        return text, readfunc, writefunc
-
-    def _check_header(self, fileobj, signature):
-        from beeseek import version_info
-        hexversion = hex(version_info) + '\n'
-        sign, self._read, self._write = self._compile_signature(signature)
-        sign += '\n'
-
-        fileobj.seek(0)
-        # The first line is generally a warning message for the user
-        if not fileobj.readline():
-            # The file is new
-            # XXX Make more user-friendly
-            fileobj.write('This is a database file, please do not edit.\n')
-            fileobj.write(hexversion)
-            fileobj.write(sign)
-        else:
-            if fileobj.readline() != hexversion:
-                raise NotImplementedError('Version is not supported.')
-            if fileobj.readline() != sign:
-                raise ValueError('Signature does not match.')
-        return fileobj.tell()
-
-    @property
-    def readonly(self):
-        return self._readonly
-
-    def __getitem__(self, name):
-        raise NotImplementedError()
-
-    def __setitem__(self, name, value):
-        raise NotImplementedError()
-
-    def __delitem__(self, name):
-        raise NotImplementedError()
-
-    def flush(self):
-        raise NotImplementedError()
-
-    def close(self):
-        raise NotImplementedError()
-
-    @property
-    def closed(self):
-        return True
-
-    def __del__(self):
-        try:
-            self.close()
-        except Exception:
-            pass
-
-
-class FileDatabase(BaseDatabase):
-
-    def __init__(self, index_file, data_file, signature=None, readonly=False):
-        if not readonly and not signature:
-            raise ValueError()
-        self._index = index_file = self._open(index_file, readonly)
-        self._data = data_file = self._open(data_file, readonly)
-        self._start = self._check_header(index_file, signature)
-        self._check_header(data_file, signature)
-        BaseDatabase.__init__(self, readonly)
-
-
-    def _get_values(self, index):
-        data_file = self._data
-        data_file.seek(index)
-
-        return [funct(data_file) for funct in self._read]
-
-    def _get_reference(self, key):
-        index_file = self._index
-        index_file.seek(self._start)
-        key = _hex(hash(key)) + '\x00'
-        match = [line for line in index_file if line.startswith(key)]
-        if not match:
-            return 0
-        return long(match[-1][len(key):], 16)
-
-    def __getitem__(self, name):
-        self._lock.acquire()
-        try:
-            index = self._get_reference(name)
-            if not index:
-                raise KeyError()
-            return self._get_values(index)
-        finally:
-            self._lock.release()
-
-
-    def _append_values(self, values):
-        data_file = self._data
-        data_file.seek(0, 2)
-        index = data_file.tell()
-
-        values = iter(values)
-        for funct in self._write:
-            data_file.write(funct(values.next()))
-        return index
-
-    def _append_reference(self, key, index):
-        index_file = self._index
-        index_file.seek(0, 2)
-        index_file.write('%s\x00%s\n' % (_hex(hash(key)), _hex(index)))
-
-    def __setitem__(self, name, values):
-        self._lock.acquire()
-        try:
-            index = self._append_values(values)
-            self._append_reference(name, index)
-        finally:
-            self._lock.release()
-
-    def __delitem__(self, name):
-        self._lock.acquire()
-        try:
-            self._append_reference(name, 0)
-        finally:
-            self._lock.release()
-
-
-    def flush(self):
-        self._index.flush()
-        self._data.flush()
-
-    def close(self):
-        self._lock.acquire()
-        try:
-            self._index.close()
-            self._data.close()
-        finally:
-            self._lock.release()
-
-    @property
-    def closed(self):
-        return self._index.closed
+from beeseek.database.base import *
+from beeseek.database.nested import *

=== added file 'beeseek/database/base.py'
--- beeseek/database/base.py	1970-01-01 00:00:00 +0000
+++ beeseek/database/base.py	2009-02-06 17:47:23 +0000
@@ -0,0 +1,268 @@
+#coding=UTF-8
+
+# Copyright (C) 2007-2009  BeeSeek Developers
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import thread
+from md5 import md5
+from beeseek.interfaces import Interface, implements
+
+__all__ = ['IDatabase', 'BaseDatabase', 'FileDatabase']
+
+
+class IDatabase(Interface):
+
+    typestable = dict
+
+    def __init__(self, dbpath, datatable):
+        """Initialize the database located at ``dbpath``."""
+
+    def __getitem__(self, name):
+        """Return the specified item."""
+
+    def __setitem__(self, name, value):
+        """Store the item."""
+
+    def __delitem__(self, name):
+        """Delete the item from the index archive."""
+
+    def flush(self):
+        """Flush the internal buffer."""
+
+    def close(self):
+        """Close the file pointers for the database."""
+
+    def __del__(self):
+        """Call close()."""
+
+
+class BaseDatabase(object):
+
+    implements(IDatabase, isbase=True)
+
+    typestable = {
+        int: (lambda fp: int(fp.readline(), 16),
+              lambda data: ('%x\n' % data)),
+        str: (lambda fp: fp.read(int(fp.readline(), 16)),
+              lambda data: ('%x\n%s' % (len(data), data))),
+        list: (lambda fp: tuple(fp.read(int(fp.readline(), 16))
+                   for i in xrange(int(fp.readline(), 16))),
+               lambda data: ('%x\n%s' % (len(data),
+                   ''.join('%x\n%s' % (len(item), item) for item in data)))),
+        }
+
+    def __init__(self, dbpath, datatable):
+        self._lock = thread.allocate_lock()
+
+    def _open(self, filename):
+        for mode in ('r+b', 'w+b'):
+            try:
+                fp = file(filename, mode)
+            except IOError:
+                pass
+            else:
+                return fp
+        raise
+
+    def _make_header(self, fileobj, datatable):
+        from beeseek import version_info
+        hexversion = hex(version_info) + '\n'
+
+        typestable = self.typestable
+        self._read = tuple(typestable[item][0] for item in datatable)
+        self._write = tuple(typestable[item][1] for item in datatable)
+
+        if not fileobj.readline():
+            # The file is new
+            fileobj.write('This is a database file for BeeSeek, '
+                          'do not edit please.\n')
+            fileobj.write(hexversion)
+        else:
+            if fileobj.readline() != hexversion:
+                raise NotImplementedError('Version is not supported.')
+        return fileobj.tell()
+
+    def __del__(self):
+        try:
+            self.close()
+        except Exception:
+            pass
+
+
+class FileDatabase(BaseDatabase):
+
+    implements(IDatabase)
+
+    def __init__(self, dbpath, datatable):
+        BaseDatabase.__init__(self, dbpath, datatable)
+        self._index = index_file = self._open(dbpath + '.index')
+        self._unused = unused_file = self._open(dbpath + '.unused')
+        self._data = data_file = self._open(dbpath + '.data')
+        self._start = self._make_header(index_file, datatable)
+        self._make_header(unused_file, datatable)
+        self._make_header(data_file, datatable)
+
+    def __getitem__(self, name):
+        self._lock.acquire()
+        try:
+            index = self._get_reference(name)
+            if not index:
+                raise KeyError
+            return self._get_values(index)
+        finally:
+            self._lock.release()
+
+    def _find_reference(self, key):
+        index_file = self._index
+        index_file.seek(self._start)
+        key = md5(key).digest()
+        while True:
+            if index_file.read(16) == key:
+                # Found
+                return 1
+            if not index_file.read(6):
+                break
+        return 0
+
+    def _get_reference(self, key):
+        if not self._find_reference(key):
+            return 0
+        return int(self._index.read(6), 16)
+
+    def _get_values(self, index):
+        data_file = self._data
+        data_file.seek(index)
+        return tuple(funct(data_file) for funct in self._read)
+
+
+    def __setitem__(self, name, values):
+        self._lock.acquire()
+        try:
+            index = self._get_reference(name)
+            if index:
+                self._delete_reference(name)
+                self._delete_value(index)
+            index = self._append_values(values)
+            self._append_reference(name, index)
+        finally:
+            self._lock.release()
+
+    def _append_reference(self, key, index):
+        index_file = self._index
+        pos = self._get_unused('i', 22)
+        if pos:
+            index_file.seek(pos)
+        else:
+            index_file.seek(0, 2)
+        index_file.write(md5(key).digest())
+        index_file.write('%06x' % index)
+
+    def _append_values(self, values):
+        data_file = self._data
+        values = iter(values)
+        values = ''.join(funct(values.next()) for funct in self._write)
+        index = self._get_unused('v', len(values))
+        if index:
+            data_file.seek(index)
+        else:
+            data_file.seek(0, 2)
+            index = data_file.tell()
+        data_file.write(values)
+        return index
+
+    def _get_unused(self, ident, size):
+        unused_file = self._unused
+        unused_file.seek(self._start)
+        while True:
+            byte = unused_file.read(1)
+            if not byte:
+                return 0
+            elif byte == ident:
+                available = int(unused_file.read(6), 16)
+                if available >= size:
+                    break
+                unused_file.seek(6, 1)
+            else:
+                unused_file.seek(12, 1)
+
+        available -= size
+        index = int(unused_file.read(6), 16)
+        unused_file.seek(-13, 1)
+        unused_file.write('0')
+        if available:
+            self._append_unused(ident, available, index + size)
+        return index
+
+
+    def __delitem__(self, name):
+        self._lock.acquire()
+        try:
+            index = self._get_reference(name)
+            if not index:
+                return
+            self._delete_reference(name)
+            self._delete_value(index)
+        finally:
+            self._lock.release()
+
+    def _delete_reference(self, key):
+        index_file = self._index
+        index_file.seek(-6, 1)
+        index_file.write('000000')
+        self._append_unused('i', 22, index_file.tell() - 22)
+
+    def _delete_value(self, index):
+        self._get_values(index)
+        self._append_unused('v', self._data.tell() - index, index)
+
+    def _append_unused(self, ident, size, index):
+        unused_file = self._unused
+        unused_file.seek(self._start)
+        while True:
+            byte = unused_file.read(1)
+            if not byte:
+                break
+            if byte == ident:
+                itemsize = int(unused_file.read(6), 16)
+                itemindex = int(unused_file.read(6), 16)
+                if itemindex + itemsize == index:
+                    index = itemindex
+                    size += itemsize
+                    unused_file.seek(-13, 1)
+                    break
+                elif index + size == itemindex:
+                    size += itemsize
+                    unused_file.seek(-13, 1)
+                    break
+            elif byte == '0':
+                unused_file.seek(-1, 1)
+                break
+            else:
+                unused_file.seek(12, 1)
+        unused_file.write('%s%06x%06x' % (ident, size, index))
+
+
+    def flush(self):
+        self._index.flush()
+        self._unused.flush()
+        self._data.flush()
+
+    def close(self):
+        self._lock.acquire()
+        try:
+            self._index.close()
+            self._data.close()
+        finally:
+            self._lock.release()

=== modified file 'beeseek/database/nested.py'
--- beeseek/database/nested.py	2009-01-13 13:08:37 +0000
+++ beeseek/database/nested.py	2009-02-05 17:19:24 +0000
@@ -15,30 +15,31 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from os import path
-from beeseek import database
-from beeseek.database import _hex
-
-
-class NestedDatabase(database.BaseDatabase):
-
-    def __init__(self, directory, signature=None, readonly=False):
-        self._directory = directory
-        self._signature = signature
-
-        index = self._open(self._getpath('database'), readonly)
-        self._check_header(index, signature)
-
+import os
+from beeseek.interfaces import implements
+from beeseek.database import IDatabase, BaseDatabase, FileDatabase
+
+__all__ = ['NestedDatabase']
+
+
+class NestedDatabase(BaseDatabase):
+
+    implements(IDatabase)
+
+    def __init__(self, dbpath, datatable):
+        BaseDatabase.__init__(self, dbpath, datatable)
+        self._dbpath = dbpath
+        self._datatable = datatable
+        index_file = self._open(self._getpath('database'))
+        self._make_header(index_file, datatable)
         self._fpbuffer = {}
         self._keysbuffer = [None for i in xrange(10)]
 
-        database.BaseDatabase.__init__(self, readonly)
-
     def _getpath(self, name):
-        return path.join(self._directory, name)
+        return os.path.join(self._dbpath, name)
 
     def _get_file(self, name):
-        prefix = _hex(hash(name))[:2]
+        prefix = ('%x' % abs(hash(name)))[:2]
         fpbuffer = self._fpbuffer
         keysbuffer = self._keysbuffer
 
@@ -52,11 +53,9 @@
                 del fpbuffer[keysbuffer[0]]
             del keysbuffer[0]
             keysbuffer.append(prefix)
-
-            fp = fpbuffer[prefix] = database.FileDatabase(
-                self._getpath(prefix + '.index'),
-                self._getpath(prefix + '.data'),
-                self._signature, self._readonly)
+            fp = fpbuffer[prefix] = FileDatabase(
+                self._getpath(prefix),
+                self._datatable)
             return fp
 
 
@@ -92,7 +91,3 @@
             self._fpbuffer = None
         finally:
             self._lock.release()
-
-    @property
-    def closed(self):
-        return self._fpbuffer is None

=== modified file 'beeseek/honeybee/handler.py'
--- beeseek/honeybee/handler.py	2009-02-02 17:38:17 +0000
+++ beeseek/honeybee/handler.py	2009-02-04 17:11:25 +0000
@@ -18,7 +18,6 @@
 from urllib import unquote_plus
 from beeseek.ui import html
 from beeseek import instance
-from beeseek.database import nested
 from beeseek.interfaces import implements
 from beeseek.decoders import NullDecoder, GzipDecoder
 from beeseek.network import (IServer, BaseServer, IPSocket,

=== modified file 'beeseek/honeybee/main.py'
--- beeseek/honeybee/main.py	2009-02-02 17:07:23 +0000
+++ beeseek/honeybee/main.py	2009-02-06 18:55:30 +0000
@@ -17,11 +17,11 @@
 
 
 from beeseek import log
+from beeseek.network import SocketError
+from beeseek.interfaces import implements
+from beeseek.database import NestedDatabase
+from beeseek.session import ISession, BaseSession
 from beeseek.honeybee import version_info, handler
-from beeseek.database import nested
-from beeseek.session import ISession, BaseSession
-from beeseek.interfaces import implements
-from beeseek.network import SocketError
 
 
 class HoneybeeSession(BaseSession):
@@ -51,6 +51,5 @@
         self.server.listen(5)
 
     def make_databases(self):
-        self.keysdb = nested.NestedDatabase('search-data/keywords', (tuple,))
-        self.urlsdb = nested.NestedDatabase('search-data/pages',
-            (int, str, str))
+        self.keysdb = NestedDatabase('search-data/keywords', (list,))
+        self.urlsdb = NestedDatabase('search-data/pages', (int, str, str))

=== added file 'beeseek/tests/database.py'
--- beeseek/tests/database.py	1970-01-01 00:00:00 +0000
+++ beeseek/tests/database.py	2009-02-05 18:45:17 +0000
@@ -0,0 +1,69 @@
+#coding=UTF-8
+
+# Copyright (C) 2007-2009  BeeSeek Developers
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Test the database module."""
+
+import os
+import tempfile
+from unittest import TestCase
+from beeseek.database import FileDatabase, NestedDatabase
+
+
+class TestDatabase(TestCase):
+
+    def test_filedatabase(self):
+        name = tempfile.NamedTemporaryFile().name
+        firstitem = (123, 'abc', ('some', 'text'))
+        seconditem = (9876543210L, 'Line 1\nLine 2\n', ())
+
+        db = FileDatabase(name, (int, str, list))
+        db['firstitem'] = firstitem
+        db['seconditem'] = seconditem
+        assert db['firstitem'] == firstitem
+        assert db['seconditem'] == seconditem
+        del db['firstitem']
+        try:
+            db['firstitem']
+        except KeyError:
+            pass
+        else:
+            raise AssertionError
+        db.close()
+
+    def test_nesteddatabase(self):
+        dirname = tempfile.mkdtemp()
+        firstitem = (123, 'abc', ('some', 'text'))
+        seconditem = (9876543210L, 'Line 1\nLine 2\n', ())
+
+        try:
+            db = NestedDatabase(dirname, (int, str, list))
+            db['firstitem'] = firstitem
+            db['seconditem'] = seconditem
+            assert db['firstitem'] == firstitem
+            assert db['seconditem'] == seconditem
+            del db['firstitem']
+            try:
+                db['firstitem']
+            except KeyError:
+                pass
+            else:
+                raise AssertionError
+            db.close()
+        finally:
+            for filename in os.listdir(dirname):
+                os.unlink(os.path.join(dirname, filename))
+            os.rmdir(dirname)



--
BeeSeek mainline
https://code.launchpad.net/~beeseek-devs/beeseek/trunk

Your team BeeSeek Developers is subscribed to branch lp:beeseek.
To unsubscribe from this branch go to https://code.launchpad.net/~beeseek-devs/beeseek/trunk/+edit-subscription.