make operations on closed dumb databases raise a consistent exception (closes #19385)

Patch by Claudiu Popa.
This commit is contained in:
Benjamin Peterson 2014-04-26 16:56:52 -04:00
parent d4992dccfb
commit e3083d3ad5
3 changed files with 40 additions and 4 deletions

View File

@ -118,9 +118,14 @@ class _Database(collections.MutableMapping):
sync = _commit sync = _commit
def _verify_open(self):
if self._index is None:
raise error('DBM object has already been closed')
def __getitem__(self, key): def __getitem__(self, key):
if isinstance(key, str): if isinstance(key, str):
key = key.encode('utf-8') key = key.encode('utf-8')
self._verify_open()
pos, siz = self._index[key] # may raise KeyError pos, siz = self._index[key] # may raise KeyError
f = _io.open(self._datfile, 'rb') f = _io.open(self._datfile, 'rb')
f.seek(pos) f.seek(pos)
@ -173,6 +178,7 @@ class _Database(collections.MutableMapping):
val = val.encode('utf-8') val = val.encode('utf-8')
elif not isinstance(val, (bytes, bytearray)): elif not isinstance(val, (bytes, bytearray)):
raise TypeError("values must be bytes or strings") raise TypeError("values must be bytes or strings")
self._verify_open()
if key not in self._index: if key not in self._index:
self._addkey(key, self._addval(val)) self._addkey(key, self._addval(val))
else: else:
@ -200,6 +206,7 @@ class _Database(collections.MutableMapping):
def __delitem__(self, key): def __delitem__(self, key):
if isinstance(key, str): if isinstance(key, str):
key = key.encode('utf-8') key = key.encode('utf-8')
self._verify_open()
# The blocks used by the associated value are lost. # The blocks used by the associated value are lost.
del self._index[key] del self._index[key]
# XXX It's unclear why we do a _commit() here (the code always # XXX It's unclear why we do a _commit() here (the code always
@ -209,21 +216,26 @@ class _Database(collections.MutableMapping):
self._commit() self._commit()
def keys(self): def keys(self):
self._verify_open()
return list(self._index.keys()) return list(self._index.keys())
def items(self): def items(self):
self._verify_open()
return [(key, self[key]) for key in self._index.keys()] return [(key, self[key]) for key in self._index.keys()]
def __contains__(self, key): def __contains__(self, key):
if isinstance(key, str): if isinstance(key, str):
key = key.encode('utf-8') key = key.encode('utf-8')
self._verify_open()
return key in self._index return key in self._index
def iterkeys(self): def iterkeys(self):
self._verify_open()
return iter(self._index.keys()) return iter(self._index.keys())
__iter__ = iterkeys __iter__ = iterkeys
def __len__(self): def __len__(self):
self._verify_open()
return len(self._index) return len(self._index)
def close(self): def close(self):

View File

@ -3,10 +3,12 @@
""" """
import io import io
import operator
import os import os
import unittest import unittest
import dbm.dumb as dumbdbm import dbm.dumb as dumbdbm
from test import support from test import support
from functools import partial
_fname = support.TESTFN _fname = support.TESTFN
@ -190,12 +192,31 @@ class DumbDBMTestCase(unittest.TestCase):
with dumbdbm.open(_fname, 'r') as db: with dumbdbm.open(_fname, 'r') as db:
self.assertEqual(list(db.keys()), [b"dumbdbm context manager"]) self.assertEqual(list(db.keys()), [b"dumbdbm context manager"])
# This currently just raises AttributeError rather than a specific with self.assertRaises(dumbdbm.error):
# exception like the GNU or NDBM based implementations. See
# http://bugs.python.org/issue19385 for details.
with self.assertRaises(Exception):
db.keys() db.keys()
def test_check_closed(self):
f = dumbdbm.open(_fname, 'c')
f.close()
for meth in (partial(operator.delitem, f),
partial(operator.setitem, f, 'b'),
partial(operator.getitem, f),
partial(operator.contains, f)):
with self.assertRaises(dumbdbm.error) as cm:
meth('test')
self.assertEqual(str(cm.exception),
"DBM object has already been closed")
for meth in (operator.methodcaller('keys'),
operator.methodcaller('iterkeys'),
operator.methodcaller('items'),
len):
with self.assertRaises(dumbdbm.error) as cm:
meth(f)
self.assertEqual(str(cm.exception),
"DBM object has already been closed")
def tearDown(self): def tearDown(self):
_delete_files() _delete_files()

View File

@ -39,6 +39,9 @@ Core and Builtins
Library Library
------- -------
- Issue #19385: Make operations on a closed dbm.dumb database always raise the
same exception.
- Issue #21207: Detect when the os.urandom cached fd has been closed or - Issue #21207: Detect when the os.urandom cached fd has been closed or
replaced, and open it anew. replaced, and open it anew.