]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-83383: Always mark the dbm.dumb database as unmodified after open() and sync(...
authorSerhiy Storchaka <storchaka@gmail.com>
Sun, 4 Feb 2024 15:23:26 +0000 (17:23 +0200)
committerGitHub <noreply@github.com>
Sun, 4 Feb 2024 15:23:26 +0000 (17:23 +0200)
The directory file for a newly created database is now created
immediately after opening instead of deferring this until synchronizing
or closing.

Lib/dbm/dumb.py
Lib/test/test_dbm_dumb.py
Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst [new file with mode: 0644]

index 754624ccc8f50085ef77d2076c18cd50808f0b9c..def120ffc3778b6ac5e92abd330a1725ba2c2d2c 100644 (file)
@@ -98,7 +98,8 @@ class _Database(collections.abc.MutableMapping):
         except OSError:
             if flag not in ('c', 'n'):
                 raise
-            self._modified = True
+            with self._io.open(self._dirfile, 'w', encoding="Latin-1") as f:
+                self._chmod(self._dirfile)
         else:
             with f:
                 for line in f:
@@ -134,6 +135,7 @@ class _Database(collections.abc.MutableMapping):
                 # position; UTF-8, though, does care sometimes.
                 entry = "%r, %r\n" % (key.decode('Latin-1'), pos_and_siz_pair)
                 f.write(entry)
+        self._modified = False
 
     sync = _commit
 
index a481175b3bfdbdebab2c8c0a77db54e8c2fe3479..672f9092207cf627f4b599ca90fa7ff01a511826 100644 (file)
@@ -246,9 +246,27 @@ class DumbDBMTestCase(unittest.TestCase):
             _delete_files()
             with self.assertRaises(FileNotFoundError):
                 dumbdbm.open(_fname, value)
+            self.assertFalse(os.path.exists(_fname + '.dat'))
             self.assertFalse(os.path.exists(_fname + '.dir'))
             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):
         with dumbdbm.open(_fname, 'n') as f:
             pass
@@ -259,6 +277,60 @@ class DumbDBMTestCase(unittest.TestCase):
             self.assertFalse(os.path.exists(_fname + '.dir'))
             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):
         for flag in ('x', 'rf', None):
             with self.assertRaisesRegex(ValueError,
diff --git a/Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst b/Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst
new file mode 100644 (file)
index 0000000..e633620
--- /dev/null
@@ -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.