gh-83383: Always mark the dbm.dumb database as unmodified after open() and sync() (GH-114560)

The directory file for a newly created database is now created
immediately after opening instead of deferring this until synchronizing
or closing.
This commit is contained in:
Serhiy Storchaka 2024-02-04 17:23:26 +02:00 committed by GitHub
parent ff7588b729
commit fc06096911
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 80 additions and 1 deletions

View File

@ -98,7 +98,8 @@ class _Database(collections.abc.MutableMapping):
except OSError: except OSError:
if flag not in ('c', 'n'): if flag not in ('c', 'n'):
raise raise
self._modified = True with self._io.open(self._dirfile, 'w', encoding="Latin-1") as f:
self._chmod(self._dirfile)
else: else:
with f: with f:
for line in f: for line in f:
@ -134,6 +135,7 @@ class _Database(collections.abc.MutableMapping):
# position; UTF-8, though, does care sometimes. # position; UTF-8, though, does care sometimes.
entry = "%r, %r\n" % (key.decode('Latin-1'), pos_and_siz_pair) entry = "%r, %r\n" % (key.decode('Latin-1'), pos_and_siz_pair)
f.write(entry) f.write(entry)
self._modified = False
sync = _commit sync = _commit

View File

@ -246,9 +246,27 @@ class DumbDBMTestCase(unittest.TestCase):
_delete_files() _delete_files()
with self.assertRaises(FileNotFoundError): with self.assertRaises(FileNotFoundError):
dumbdbm.open(_fname, value) dumbdbm.open(_fname, value)
self.assertFalse(os.path.exists(_fname + '.dat'))
self.assertFalse(os.path.exists(_fname + '.dir')) self.assertFalse(os.path.exists(_fname + '.dir'))
self.assertFalse(os.path.exists(_fname + '.bak')) self.assertFalse(os.path.exists(_fname + '.bak'))
for value in ('c', 'n'):
_delete_files()
with dumbdbm.open(_fname, value) as f:
self.assertTrue(os.path.exists(_fname + '.dat'))
self.assertTrue(os.path.exists(_fname + '.dir'))
self.assertFalse(os.path.exists(_fname + '.bak'))
self.assertFalse(os.path.exists(_fname + '.bak'))
for value in ('c', 'n'):
_delete_files()
with dumbdbm.open(_fname, value) as f:
f['key'] = 'value'
self.assertTrue(os.path.exists(_fname + '.dat'))
self.assertTrue(os.path.exists(_fname + '.dir'))
self.assertFalse(os.path.exists(_fname + '.bak'))
self.assertTrue(os.path.exists(_fname + '.bak'))
def test_missing_index(self): def test_missing_index(self):
with dumbdbm.open(_fname, 'n') as f: with dumbdbm.open(_fname, 'n') as f:
pass pass
@ -259,6 +277,60 @@ class DumbDBMTestCase(unittest.TestCase):
self.assertFalse(os.path.exists(_fname + '.dir')) self.assertFalse(os.path.exists(_fname + '.dir'))
self.assertFalse(os.path.exists(_fname + '.bak')) self.assertFalse(os.path.exists(_fname + '.bak'))
for value in ('c', 'n'):
with dumbdbm.open(_fname, value) as f:
self.assertTrue(os.path.exists(_fname + '.dir'))
self.assertFalse(os.path.exists(_fname + '.bak'))
self.assertFalse(os.path.exists(_fname + '.bak'))
os.unlink(_fname + '.dir')
for value in ('c', 'n'):
with dumbdbm.open(_fname, value) as f:
f['key'] = 'value'
self.assertTrue(os.path.exists(_fname + '.dir'))
self.assertFalse(os.path.exists(_fname + '.bak'))
self.assertTrue(os.path.exists(_fname + '.bak'))
os.unlink(_fname + '.dir')
os.unlink(_fname + '.bak')
def test_sync_empty_unmodified(self):
with dumbdbm.open(_fname, 'n') as f:
pass
os.unlink(_fname + '.dir')
for value in ('c', 'n'):
with dumbdbm.open(_fname, value) as f:
self.assertTrue(os.path.exists(_fname + '.dir'))
self.assertFalse(os.path.exists(_fname + '.bak'))
f.sync()
self.assertTrue(os.path.exists(_fname + '.dir'))
self.assertFalse(os.path.exists(_fname + '.bak'))
os.unlink(_fname + '.dir')
f.sync()
self.assertFalse(os.path.exists(_fname + '.dir'))
self.assertFalse(os.path.exists(_fname + '.bak'))
self.assertFalse(os.path.exists(_fname + '.dir'))
self.assertFalse(os.path.exists(_fname + '.bak'))
def test_sync_nonempty_unmodified(self):
with dumbdbm.open(_fname, 'n') as f:
pass
os.unlink(_fname + '.dir')
for value in ('c', 'n'):
with dumbdbm.open(_fname, value) as f:
f['key'] = 'value'
self.assertTrue(os.path.exists(_fname + '.dir'))
self.assertFalse(os.path.exists(_fname + '.bak'))
f.sync()
self.assertTrue(os.path.exists(_fname + '.dir'))
self.assertTrue(os.path.exists(_fname + '.bak'))
os.unlink(_fname + '.dir')
os.unlink(_fname + '.bak')
f.sync()
self.assertFalse(os.path.exists(_fname + '.dir'))
self.assertFalse(os.path.exists(_fname + '.bak'))
self.assertFalse(os.path.exists(_fname + '.dir'))
self.assertFalse(os.path.exists(_fname + '.bak'))
def test_invalid_flag(self): def test_invalid_flag(self):
for flag in ('x', 'rf', None): for flag in ('x', 'rf', None):
with self.assertRaisesRegex(ValueError, with self.assertRaisesRegex(ValueError,

View File

@ -0,0 +1,5 @@
Synchronization of the :mod:`dbm.dumb` database is now no-op if there was no
modification since opening or last synchronization.
The directory file for a newly created empty :mod:`dbm.dumb` database is now
created immediately after opening instead of deferring this until
synchronizing or closing.