Commit 4fb0b8bc authored by Xiang Zhang's avatar Xiang Zhang Committed by GitHub

bpo-33106: change dbm key deletion error for readonly file from KeyError to dbm.error (#6295)

parent 5a718e91
...@@ -73,6 +73,10 @@ available, as well as :meth:`get` and :meth:`setdefault`. ...@@ -73,6 +73,10 @@ available, as well as :meth:`get` and :meth:`setdefault`.
.. versionchanged:: 3.2 .. versionchanged:: 3.2
:meth:`get` and :meth:`setdefault` are now available in all database modules. :meth:`get` and :meth:`setdefault` are now available in all database modules.
.. versionchanged:: 3.8
Deleting a key from a read-only database raises database module specific error
instead of :exc:`KeyError`.
Key and values are always stored as bytes. This means that when Key and values are always stored as bytes. This means that when
strings are used they are implicitly converted to the default encoding before strings are used they are implicitly converted to the default encoding before
being stored. being stored.
......
...@@ -483,6 +483,12 @@ Changes in the Python API ...@@ -483,6 +483,12 @@ Changes in the Python API
external entities by default. external entities by default.
(Contributed by Christian Heimes in :issue:`17239`.) (Contributed by Christian Heimes in :issue:`17239`.)
* Deleting a key from a read-only :mod:`dbm` database (:mod:`dbm.dumb`,
:mod:`dbm.gnu` or :mod:`dbm.ndbm`) raises :attr:`error` (:exc:`dbm.dumb.error`,
:exc:`dbm.gnu.error` or :exc:`dbm.ndbm.error`) instead of :exc:`KeyError`.
(Contributed by Xiang Zhang in :issue:`33106`.)
CPython bytecode changes CPython bytecode changes
------------------------ ------------------------
......
...@@ -185,7 +185,7 @@ class _Database(collections.abc.MutableMapping): ...@@ -185,7 +185,7 @@ class _Database(collections.abc.MutableMapping):
def __setitem__(self, key, val): def __setitem__(self, key, val):
if self._readonly: if self._readonly:
raise ValueError('The database is opened for reading only') raise error('The database is opened for reading only')
if isinstance(key, str): if isinstance(key, str):
key = key.encode('utf-8') key = key.encode('utf-8')
elif not isinstance(key, (bytes, bytearray)): elif not isinstance(key, (bytes, bytearray)):
...@@ -222,7 +222,7 @@ class _Database(collections.abc.MutableMapping): ...@@ -222,7 +222,7 @@ class _Database(collections.abc.MutableMapping):
def __delitem__(self, key): def __delitem__(self, key):
if self._readonly: if self._readonly:
raise ValueError('The database is opened for reading only') raise error('The database is opened for reading only')
if isinstance(key, str): if isinstance(key, str):
key = key.encode('utf-8') key = key.encode('utf-8')
self._verify_open() self._verify_open()
......
...@@ -82,10 +82,10 @@ class DumbDBMTestCase(unittest.TestCase): ...@@ -82,10 +82,10 @@ class DumbDBMTestCase(unittest.TestCase):
self.init_db() self.init_db()
f = dumbdbm.open(_fname, 'r') f = dumbdbm.open(_fname, 'r')
self.read_helper(f) self.read_helper(f)
with self.assertRaisesRegex(ValueError, with self.assertRaisesRegex(dumbdbm.error,
'The database is opened for reading only'): 'The database is opened for reading only'):
f[b'g'] = b'x' f[b'g'] = b'x'
with self.assertRaisesRegex(ValueError, with self.assertRaisesRegex(dumbdbm.error,
'The database is opened for reading only'): 'The database is opened for reading only'):
del f[b'a'] del f[b'a']
# get() works as in the dict interface # get() works as in the dict interface
......
...@@ -131,6 +131,17 @@ class TestGdbm(unittest.TestCase): ...@@ -131,6 +131,17 @@ class TestGdbm(unittest.TestCase):
self.assertEqual(db['Unicode key \U0001f40d'], self.assertEqual(db['Unicode key \U0001f40d'],
'Unicode value \U0001f40d'.encode()) 'Unicode value \U0001f40d'.encode())
def test_write_readonly_file(self):
with gdbm.open(filename, 'c') as db:
db[b'bytes key'] = b'bytes value'
with gdbm.open(filename, 'r') as db:
with self.assertRaises(gdbm.error):
del db[b'not exist key']
with self.assertRaises(gdbm.error):
del db[b'bytes key']
with self.assertRaises(gdbm.error):
db[b'not exist key'] = b'not exist value'
@unittest.skipUnless(TESTFN_NONASCII, @unittest.skipUnless(TESTFN_NONASCII,
'requires OS support of non-ASCII encodings') 'requires OS support of non-ASCII encodings')
def test_nonascii_filename(self): def test_nonascii_filename(self):
......
...@@ -90,6 +90,17 @@ class DbmTestCase(unittest.TestCase): ...@@ -90,6 +90,17 @@ class DbmTestCase(unittest.TestCase):
self.assertEqual(db['Unicode key \U0001f40d'], self.assertEqual(db['Unicode key \U0001f40d'],
'Unicode value \U0001f40d'.encode()) 'Unicode value \U0001f40d'.encode())
def test_write_readonly_file(self):
with dbm.ndbm.open(self.filename, 'c') as db:
db[b'bytes key'] = b'bytes value'
with dbm.ndbm.open(self.filename, 'r') as db:
with self.assertRaises(error):
del db[b'not exist key']
with self.assertRaises(error):
del db[b'bytes key']
with self.assertRaises(error):
db[b'not exist key'] = b'not exist value'
@unittest.skipUnless(support.TESTFN_NONASCII, @unittest.skipUnless(support.TESTFN_NONASCII,
'requires OS support of non-ASCII encodings') 'requires OS support of non-ASCII encodings')
def test_nonascii_filename(self): def test_nonascii_filename(self):
......
Deleting a key from a read-only dbm database raises module specfic error
instead of KeyError.
...@@ -36,6 +36,7 @@ class _dbm.dbm "dbmobject *" "&Dbmtype" ...@@ -36,6 +36,7 @@ class _dbm.dbm "dbmobject *" "&Dbmtype"
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
int flags;
int di_size; /* -1 means recompute */ int di_size; /* -1 means recompute */
DBM *di_dbm; DBM *di_dbm;
} dbmobject; } dbmobject;
...@@ -60,6 +61,7 @@ newdbmobject(const char *file, int flags, int mode) ...@@ -60,6 +61,7 @@ newdbmobject(const char *file, int flags, int mode)
if (dp == NULL) if (dp == NULL)
return NULL; return NULL;
dp->di_size = -1; dp->di_size = -1;
dp->flags = flags;
/* See issue #19296 */ /* See issue #19296 */
if ( (dp->di_dbm = dbm_open((char *)file, flags, mode)) == 0 ) { if ( (dp->di_dbm = dbm_open((char *)file, flags, mode)) == 0 ) {
PyErr_SetFromErrnoWithFilename(DbmError, file); PyErr_SetFromErrnoWithFilename(DbmError, file);
...@@ -143,13 +145,20 @@ dbm_ass_sub(dbmobject *dp, PyObject *v, PyObject *w) ...@@ -143,13 +145,20 @@ dbm_ass_sub(dbmobject *dp, PyObject *v, PyObject *w)
if (w == NULL) { if (w == NULL) {
if ( dbm_delete(dp->di_dbm, krec) < 0 ) { if ( dbm_delete(dp->di_dbm, krec) < 0 ) {
dbm_clearerr(dp->di_dbm); dbm_clearerr(dp->di_dbm);
PyErr_SetObject(PyExc_KeyError, v); /* we might get a failure for reasons like file corrupted,
but we are not able to distinguish it */
if (dp->flags & O_RDWR) {
PyErr_SetObject(PyExc_KeyError, v);
}
else {
PyErr_SetString(DbmError, "cannot delete item from database");
}
return -1; return -1;
} }
} else { } else {
if ( !PyArg_Parse(w, "s#", &drec.dptr, &tmp_size) ) { if ( !PyArg_Parse(w, "s#", &drec.dptr, &tmp_size) ) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"dbm mappings have byte or string elements only"); "dbm mappings have bytes or string elements only");
return -1; return -1;
} }
drec.dsize = tmp_size; drec.dsize = tmp_size;
...@@ -335,7 +344,7 @@ _dbm_dbm_setdefault_impl(dbmobject *self, const char *key, ...@@ -335,7 +344,7 @@ _dbm_dbm_setdefault_impl(dbmobject *self, const char *key,
else { else {
if ( !PyArg_Parse(default_value, "s#", &val.dptr, &tmp_size) ) { if ( !PyArg_Parse(default_value, "s#", &val.dptr, &tmp_size) ) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"dbm mappings have byte string elements only"); "dbm mappings have bytes or string elements only");
return NULL; return NULL;
} }
val.dsize = tmp_size; val.dsize = tmp_size;
......
...@@ -186,14 +186,19 @@ dbm_ass_sub(dbmobject *dp, PyObject *v, PyObject *w) ...@@ -186,14 +186,19 @@ dbm_ass_sub(dbmobject *dp, PyObject *v, PyObject *w)
dp->di_size = -1; dp->di_size = -1;
if (w == NULL) { if (w == NULL) {
if (gdbm_delete(dp->di_dbm, krec) < 0) { if (gdbm_delete(dp->di_dbm, krec) < 0) {
PyErr_SetObject(PyExc_KeyError, v); if (gdbm_errno == GDBM_ITEM_NOT_FOUND) {
PyErr_SetObject(PyExc_KeyError, v);
}
else {
PyErr_SetString(DbmError, gdbm_strerror(gdbm_errno));
}
return -1; return -1;
} }
} }
else { else {
if (!PyArg_Parse(w, "s#", &drec.dptr, &drec.dsize)) { if (!PyArg_Parse(w, "s#", &drec.dptr, &drec.dsize)) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"gdbm mappings have byte or string elements only"); "gdbm mappings have bytes or string elements only");
return -1; return -1;
} }
errno = 0; errno = 0;
......
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