Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 6 commits into from
Dec 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Doc/library/dbm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ available, as well as :meth:`get` and :meth:`setdefault`.
.. versionchanged:: 3.2
: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
strings are used they are implicitly converted to the default encoding before
being stored.
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,12 @@ Changes in the Python API
external entities by default.
(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
------------------------

Expand Down
4 changes: 2 additions & 2 deletions Lib/dbm/dumb.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def _addkey(self, key, pos_and_siz_pair):

def __setitem__(self, key, val):
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):
key = key.encode('utf-8')
elif not isinstance(key, (bytes, bytearray)):
Expand Down Expand Up @@ -222,7 +222,7 @@ def __setitem__(self, key, val):

def __delitem__(self, key):
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):
key = key.encode('utf-8')
self._verify_open()
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_dbm_dumb.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ def test_dumbdbm_read(self):
self.init_db()
f = dumbdbm.open(_fname, 'r')
self.read_helper(f)
with self.assertRaisesRegex(ValueError,
with self.assertRaisesRegex(dumbdbm.error,
'The database is opened for reading only'):
f[b'g'] = b'x'
with self.assertRaisesRegex(ValueError,
with self.assertRaisesRegex(dumbdbm.error,
'The database is opened for reading only'):
del f[b'a']
# get() works as in the dict interface
Expand Down
11 changes: 11 additions & 0 deletions Lib/test/test_dbm_gnu.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@ def test_unicode(self):
self.assertEqual(db['Unicode key \U0001f40d'],
'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,
'requires OS support of non-ASCII encodings')
def test_nonascii_filename(self):
Expand Down
11 changes: 11 additions & 0 deletions Lib/test/test_dbm_ndbm.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@ def test_unicode(self):
self.assertEqual(db['Unicode key \U0001f40d'],
'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,
'requires OS support of non-ASCII encodings')
def test_nonascii_filename(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Deleting a key from a read-only dbm database raises module specfic error
instead of KeyError.
15 changes: 12 additions & 3 deletions Modules/_dbmmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class _dbm.dbm "dbmobject *" "&Dbmtype"

typedef struct {
PyObject_HEAD
int flags;
int di_size; /* -1 means recompute */
DBM *di_dbm;
} dbmobject;
Expand All @@ -60,6 +61,7 @@ newdbmobject(const char *file, int flags, int mode)
if (dp == NULL)
return NULL;
dp->di_size = -1;
dp->flags = flags;
/* See issue #19296 */
if ( (dp->di_dbm = dbm_open((char *)file, flags, mode)) == 0 ) {
PyErr_SetFromErrnoWithFilename(DbmError, file);
Expand Down Expand Up @@ -143,13 +145,20 @@ dbm_ass_sub(dbmobject *dp, PyObject *v, PyObject *w)
if (w == NULL) {
if ( dbm_delete(dp->di_dbm, krec) < 0 ) {
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;
}
} else {
if ( !PyArg_Parse(w, "s#", &drec.dptr, &tmp_size) ) {
PyErr_SetString(PyExc_TypeError,
"dbm mappings have byte or string elements only");
"dbm mappings have bytes or string elements only");
return -1;
}
drec.dsize = tmp_size;
Expand Down Expand Up @@ -335,7 +344,7 @@ _dbm_dbm_setdefault_impl(dbmobject *self, const char *key,
else {
if ( !PyArg_Parse(default_value, "s#", &val.dptr, &tmp_size) ) {
PyErr_SetString(PyExc_TypeError,
"dbm mappings have byte string elements only");
"dbm mappings have bytes or string elements only");
return NULL;
}
val.dsize = tmp_size;
Expand Down
9 changes: 7 additions & 2 deletions Modules/_gdbmmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,14 +186,19 @@ dbm_ass_sub(dbmobject *dp, PyObject *v, PyObject *w)
dp->di_size = -1;
if (w == NULL) {
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;
}
}
else {
if (!PyArg_Parse(w, "s#", &drec.dptr, &drec.dsize)) {
PyErr_SetString(PyExc_TypeError,
"gdbm mappings have byte or string elements only");
"gdbm mappings have bytes or string elements only");
return -1;
}
errno = 0;
Expand Down