diff -r 20bb8babc505 Lib/dbm/dumb.py --- a/Lib/dbm/dumb.py Wed Nov 30 12:10:54 2016 +0100 +++ b/Lib/dbm/dumb.py Thu Dec 01 14:41:10 2016 +0200 @@ -68,7 +68,7 @@ class _Database(collections.MutableMappi # Handle the creation self._create(flag) - self._update() + self._update(flag) def _create(self, flag): if flag == 'n': @@ -92,13 +92,19 @@ class _Database(collections.MutableMappi f.close() # Read directory file into the in-memory index dict. - def _update(self): + def _update(self, flag): self._index = {} try: f = _io.open(self._dirfile, 'r', encoding="Latin-1") except OSError: - pass + self._modified = not self._readonly + if flag not in ('c', 'n'): + import warnings + warnings.warn("The index file is missing, the " + "semantics of the 'c' flag will be used.", + DeprecationWarning, stacklevel=4) else: + self._modified = False with f: for line in f: line = line.rstrip() @@ -113,7 +119,7 @@ class _Database(collections.MutableMappi # CAUTION: It's vital that _commit() succeed, and _commit() can # be called from __del__(). Therefore we must never reference a # global in this routine. - if self._index is None: + if self._index is None or not self._modified: return # nothing to do try: @@ -197,6 +203,7 @@ class _Database(collections.MutableMappi elif not isinstance(val, (bytes, bytearray)): raise TypeError("values must be bytes or strings") self._verify_open() + self._modified = True if key not in self._index: self._addkey(key, self._addval(val)) else: @@ -229,6 +236,7 @@ class _Database(collections.MutableMappi if isinstance(key, str): key = key.encode('utf-8') self._verify_open() + self._modified = True # The blocks used by the associated value are lost. del self._index[key] # XXX It's unclear why we do a _commit() here (the code always diff -r 20bb8babc505 Lib/test/test_dbm_dumb.py --- a/Lib/test/test_dbm_dumb.py Wed Nov 30 12:10:54 2016 +0100 +++ b/Lib/test/test_dbm_dumb.py Thu Dec 01 14:41:10 2016 +0200 @@ -5,6 +5,7 @@ import io import operator import os +import stat import unittest import warnings import dbm.dumb as dumbdbm @@ -251,6 +252,20 @@ class DumbDBMTestCase(unittest.TestCase) f = dumbdbm.open(_fname, value) f.close() + def test_missing_index(self): + with dumbdbm.open(_fname, 'n') as f: + pass + os.unlink(_fname + '.dir') + for value in ('r', 'w'): + with self.assertWarnsRegex(DeprecationWarning, + "The index file is missing, the " + "semantics of the 'c' flag will " + "be used."): + f = dumbdbm.open(_fname, value) + f.close() + self.assertEqual(os.path.exists(_fname + '.dir'), value == 'w') + self.assertFalse(os.path.exists(_fname + '.bak')) + def test_invalid_flag(self): for flag in ('x', 'rf', None): with self.assertWarnsRegex(DeprecationWarning, @@ -259,6 +274,21 @@ class DumbDBMTestCase(unittest.TestCase) f = dumbdbm.open(_fname, flag) f.close() + @unittest.skipUnless(hasattr(os, 'chmod'), 'test needs os.chmod()') + def test_readonly_files(self): + with support.temp_dir() as dir: + fname = os.path.join(dir, 'db') + with dumbdbm.open(fname, 'n') as f: + self.assertEqual(list(f.keys()), []) + for key in self._dict: + f[key] = self._dict[key] + os.chmod(fname + ".dir", stat.S_IRUSR) + os.chmod(fname + ".dat", stat.S_IRUSR) + os.chmod(dir, stat.S_IRUSR|stat.S_IXUSR) + with dumbdbm.open(fname, 'r') as f: + self.assertEqual(sorted(f.keys()), sorted(self._dict)) + f.close() # don't write + def tearDown(self): _delete_files()