beeseek-devs team mailing list archive
-
beeseek-devs team
-
Mailing list archive
-
Message #00130
[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.