Issue #28847: dbm.dumb now supports reading read-only files and no longer

writes the index file when it is not changed.
This commit is contained in:
Serhiy Storchaka 2016-12-07 10:56:39 +02:00
parent e503126074
commit 028ace1ccb
4 changed files with 28 additions and 5 deletions

View file

@ -47,6 +47,7 @@ class _Database(collections.MutableMapping):
def __init__(self, filebasename, mode, flag='c'): def __init__(self, filebasename, mode, flag='c'):
self._mode = mode self._mode = mode
self._readonly = (flag == 'r')
# The directory file is a text file. Each line looks like # The directory file is a text file. Each line looks like
# "%r, (%d, %d)\n" % (key, pos, siz) # "%r, (%d, %d)\n" % (key, pos, siz)
@ -91,8 +92,9 @@ class _Database(collections.MutableMapping):
try: try:
f = _io.open(self._dirfile, 'r', encoding="Latin-1") f = _io.open(self._dirfile, 'r', encoding="Latin-1")
except OSError: except OSError:
pass self._modified = not self._readonly
else: else:
self._modified = False
with f: with f:
for line in f: for line in f:
line = line.rstrip() line = line.rstrip()
@ -107,7 +109,7 @@ class _Database(collections.MutableMapping):
# CAUTION: It's vital that _commit() succeed, and _commit() can # CAUTION: It's vital that _commit() succeed, and _commit() can
# be called from __del__(). Therefore we must never reference a # be called from __del__(). Therefore we must never reference a
# global in this routine. # global in this routine.
if self._index is None: if self._index is None or not self._modified:
return # nothing to do return # nothing to do
try: try:
@ -187,6 +189,7 @@ class _Database(collections.MutableMapping):
elif not isinstance(val, (bytes, bytearray)): elif not isinstance(val, (bytes, bytearray)):
raise TypeError("values must be bytes or strings") raise TypeError("values must be bytes or strings")
self._verify_open() self._verify_open()
self._modified = True
if key not in self._index: if key not in self._index:
self._addkey(key, self._addval(val)) self._addkey(key, self._addval(val))
else: else:
@ -215,6 +218,7 @@ class _Database(collections.MutableMapping):
if isinstance(key, str): if isinstance(key, str):
key = key.encode('utf-8') key = key.encode('utf-8')
self._verify_open() self._verify_open()
self._modified = True
# The blocks used by the associated value are lost. # The blocks used by the associated value are lost.
del self._index[key] del self._index[key]
# XXX It's unclear why we do a _commit() here (the code always # XXX It's unclear why we do a _commit() here (the code always

View file

@ -359,9 +359,9 @@ if sys.platform.startswith("win"):
mode = 0 mode = 0
if stat.S_ISDIR(mode): if stat.S_ISDIR(mode):
_waitfor(_rmtree_inner, fullname, waitall=True) _waitfor(_rmtree_inner, fullname, waitall=True)
_force_run(path, os.rmdir, fullname) _force_run(fullname, os.rmdir, fullname)
else: else:
_force_run(path, os.unlink, fullname) _force_run(fullname, os.unlink, fullname)
_waitfor(_rmtree_inner, path, waitall=True) _waitfor(_rmtree_inner, path, waitall=True)
_waitfor(lambda p: _force_run(p, os.rmdir, p), path) _waitfor(lambda p: _force_run(p, os.rmdir, p), path)
else: else:
@ -933,7 +933,7 @@ def temp_dir(path=None, quiet=False):
yield path yield path
finally: finally:
if dir_created: if dir_created:
shutil.rmtree(path) rmtree(path)
@contextlib.contextmanager @contextlib.contextmanager
def change_cwd(path, quiet=False): def change_cwd(path, quiet=False):

View file

@ -5,6 +5,7 @@
import io import io
import operator import operator
import os import os
import stat
import unittest import unittest
import dbm.dumb as dumbdbm import dbm.dumb as dumbdbm
from test import support from test import support
@ -234,6 +235,21 @@ class DumbDBMTestCase(unittest.TestCase):
pass pass
self.assertEqual(stdout.getvalue(), '') self.assertEqual(stdout.getvalue(), '')
@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): def tearDown(self):
_delete_files() _delete_files()

View file

@ -121,6 +121,9 @@ Core and Builtins
Library Library
------- -------
- Issue #28847: dbm.dumb now supports reading read-only files and no longer
writes the index file when it is not changed.
- Issue #25659: In ctypes, prevent a crash calling the from_buffer() and - Issue #25659: In ctypes, prevent a crash calling the from_buffer() and
from_buffer_copy() methods on abstract classes like Array. from_buffer_copy() methods on abstract classes like Array.