# We use the URI format when opening the database.
uri = _normalize_uri(path)
uri = f"{uri}?mode={flag}"
+ if flag == "ro":
+ # Add immutable=1 to allow read-only SQLite access even if wal/shm missing
+ uri += "&immutable=1"
try:
self._cx = sqlite3.connect(uri, autocommit=True, uri=True)
except sqlite3.Error as exc:
raise error(str(exc))
- # This is an optimization only; it's ok if it fails.
- with suppress(sqlite3.OperationalError):
- self._cx.execute("PRAGMA journal_mode = wal")
+ if flag != "ro":
+ # This is an optimization only; it's ok if it fails.
+ with suppress(sqlite3.OperationalError):
+ self._cx.execute("PRAGMA journal_mode = wal")
- if flag == "rwc":
- self._execute(BUILD_TABLE)
+ if flag == "rwc":
+ self._execute(BUILD_TABLE)
def _execute(self, *args, **kwargs):
if not self._cx:
+import os
+import stat
import sys
import unittest
from contextlib import closing
from dbm.sqlite3 import _normalize_uri
+root_in_posix = False
+if hasattr(os, 'geteuid'):
+ root_in_posix = (os.geteuid() == 0)
+
+
class _SQLiteDbmTests(unittest.TestCase):
def setUp(self):
self.assertEqual([k for k in self.db], [b"key1", b"key2"])
+@unittest.skipIf(root_in_posix, "test is meanless with root privilege")
+class ReadOnlyFilesystem(unittest.TestCase):
+
+ def setUp(self):
+ self.test_dir = os_helper.TESTFN
+ self.addCleanup(os_helper.rmtree, self.test_dir)
+ os.mkdir(self.test_dir)
+ self.db_path = os.path.join(self.test_dir, "test.db")
+
+ db = dbm_sqlite3.open(self.db_path, "c")
+ db[b"key"] = b"value"
+ db.close()
+
+ def test_readonly_file_read(self):
+ os.chmod(self.db_path, stat.S_IREAD)
+ with dbm_sqlite3.open(self.db_path, "r") as db:
+ self.assertEqual(db[b"key"], b"value")
+
+ def test_readonly_file_write(self):
+ os.chmod(self.db_path, stat.S_IREAD)
+ with dbm_sqlite3.open(self.db_path, "w") as db:
+ with self.assertRaises(dbm_sqlite3.error):
+ db[b"newkey"] = b"newvalue"
+
+ def test_readonly_dir_read(self):
+ os.chmod(self.test_dir, stat.S_IREAD | stat.S_IEXEC)
+ with dbm_sqlite3.open(self.db_path, "r") as db:
+ self.assertEqual(db[b"key"], b"value")
+
+ def test_readonly_dir_write(self):
+ os.chmod(self.test_dir, stat.S_IREAD | stat.S_IEXEC)
+ with dbm_sqlite3.open(self.db_path, "w") as db:
+ try:
+ db[b"newkey"] = b"newvalue"
+ modified = True # on Windows and macOS
+ except dbm_sqlite3.error:
+ modified = False
+ with dbm_sqlite3.open(self.db_path, "r") as db:
+ if modified:
+ self.assertEqual(db[b"newkey"], b"newvalue")
+ else:
+ self.assertNotIn(b"newkey", db)
+
+
class ReadWrite(_SQLiteDbmTests):
def setUp(self):