Commit cce6e719 authored by Benjamin Peterson's avatar Benjamin Peterson

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

Patch by Claudiu Popa.
parent 1689ee8f
...@@ -118,9 +118,14 @@ class _Database(collections.MutableMapping): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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):
......
...@@ -3,10 +3,12 @@ ...@@ -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): ...@@ -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()
......
...@@ -39,6 +39,9 @@ Core and Builtins ...@@ -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.
......
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