From 8509b9d966a6057cef443e3f10d833e3faf29546 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Wed, 20 Aug 2025 01:19:12 +0300 Subject: [PATCH 1/5] Add comprehensive key-value type documentation to dbm module --- Doc/library/dbm.rst | 50 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 140ca5c1e3405a..c2bdd473babd04 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -100,7 +100,8 @@ The :meth:`!setdefault` method requires two arguments. Key and values are always stored as :class:`bytes`. This means that when strings are used they are implicitly converted to the default encoding before -being stored. +being stored. For detailed information about accepted types, see +:ref:`dbm-key-value-types`. These objects also support being used in a :keyword:`with` statement, which will automatically close them when done. @@ -121,6 +122,48 @@ will automatically close them when done. :meth:`!clear` methods are now available for all :mod:`dbm` backends. +.. _dbm-key-value-types: + +Key and Value Types +------------------- + +The accepted types for keys and values vary by backend: + +**Keys:** + +* **All backends**: Accept :class:`str` and :class:`bytes` objects +* **String keys** are automatically converted to bytes using the default + encoding +* **Bytes keys** are stored as-is + +**Values:** + +* **Traditional backends** (``dbm.gnu``, ``dbm.ndbm``, ``dbm.dumb``): Only + accept :class:`str` and :class:`bytes` objects +* **SQLite backend** (``dbm.sqlite3``): Accepts any object that can be + converted to bytes: + + * **Accepted**: :class:`str`, :class:`bytes`, :class:`int`, + :class:`float`, :class:`bool` + * **Rejected**: :class:`None`, :class:`list`, :class:`dict`, + :class:`tuple`, custom objects + +**Storage Format:** + +All keys and values are stored as :class:`bytes` objects in the database. +When retrieving, you'll always get bytes back, regardless of the original +type stored. + +**Type Conversion Examples:** + +* ``db['key'] = 'string'`` stored as ``b'string'`` +* ``db['key'] = 42`` stored as ``b'42'`` (sqlite3 only) +* ``db['key'] = 3.14`` stored as ``b'3.14'`` (sqlite3 only) +* ``db['key'] = True`` stored as ``b'True'`` (sqlite3 only) +* ``db['key'] = None`` fails on all backends +* ``db['key'] = [1, 2, 3]`` fails on all backends + + The following example records some hostnames and a corresponding title, and then prints out the contents of the database:: @@ -142,8 +185,9 @@ then prints out the contents of the database:: # Often-used methods of the dict interface work too. print(db.get('python.org', b'not present')) - # Storing a non-string key or value will raise an exception (most - # likely a TypeError). + # Storing a non-string key or value behavior depends on the backend: + # - Traditional backends (ndbm, gnu, dumb) will raise a TypeError + # - The sqlite3 backend accepts it and converts to bytes db['www.yahoo.com'] = 4 # db is automatically closed when leaving the with statement. From 6c9765f268c8a61ad2b06ce5f2fc1ef1945aebb4 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Sun, 14 Dec 2025 21:43:32 +0900 Subject: [PATCH 2/5] Clarify dbm type support and extend type coverage tests --- Doc/library/dbm.rst | 36 +++++++++++++++--------------------- Lib/test/test_dbm_dumb.py | 7 +++++++ Lib/test/test_dbm_sqlite3.py | 4 ++++ 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index c2bdd473babd04..3188df5b70d84e 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -127,26 +127,16 @@ will automatically close them when done. Key and Value Types ------------------- -The accepted types for keys and values vary by backend: +The accepted types for keys and values vary by backend. Keys and values are +handled identically: -**Keys:** +* **Traditional backends**: -* **All backends**: Accept :class:`str` and :class:`bytes` objects -* **String keys** are automatically converted to bytes using the default - encoding -* **Bytes keys** are stored as-is - -**Values:** - -* **Traditional backends** (``dbm.gnu``, ``dbm.ndbm``, ``dbm.dumb``): Only - accept :class:`str` and :class:`bytes` objects -* **SQLite backend** (``dbm.sqlite3``): Accepts any object that can be - converted to bytes: - - * **Accepted**: :class:`str`, :class:`bytes`, :class:`int`, - :class:`float`, :class:`bool` - * **Rejected**: :class:`None`, :class:`list`, :class:`dict`, - :class:`tuple`, custom objects + * :mod:`dbm.gnu` and :mod:`dbm.ndbm`: Accept :class:`str` and :class:`bytes` objects + * :mod:`dbm.dumb`: Accepts :class:`str`, :class:`bytes`, and :class:`bytearray` objects +* **SQLite backend** (:mod:`dbm.sqlite3`): Accepts :class:`str`, :class:`bytes`, + :class:`int`, :class:`float`, :class:`bool`, :class:`bytearray`, + :class:`memoryview`, and :class:`array.array` objects **Storage Format:** @@ -157,9 +147,13 @@ type stored. **Type Conversion Examples:** * ``db['key'] = 'string'`` stored as ``b'string'`` -* ``db['key'] = 42`` stored as ``b'42'`` (sqlite3 only) -* ``db['key'] = 3.14`` stored as ``b'3.14'`` (sqlite3 only) -* ``db['key'] = True`` stored as ``b'True'`` (sqlite3 only) +* ``db['key'] = bytearray(b'data')`` stored as ``b'data'`` (:mod:`dbm.dumb` and :mod:`dbm.sqlite3` only) +* ``db['key'] = 42`` stored as ``b'42'`` (:mod:`dbm.sqlite3` only) +* ``db['key'] = 3.14`` stored as ``b'3.14'`` (:mod:`dbm.sqlite3` only) +* ``db['key'] = True`` stored as ``b'1'`` (:mod:`dbm.sqlite3` only) +* ``db['key'] = False`` stored as ``b'0'`` (:mod:`dbm.sqlite3` only) +* ``db['key'] = memoryview(b'data')`` stored as ``b'data'`` (:mod:`dbm.sqlite3` only) +* ``db['key'] = array.array('i', [1, 2, 3])`` stored as binary data (:mod:`dbm.sqlite3` only) * ``db['key'] = None`` fails on all backends * ``db['key'] = [1, 2, 3]`` fails on all backends diff --git a/Lib/test/test_dbm_dumb.py b/Lib/test/test_dbm_dumb.py index 672f9092207cf6..8dfdcbcdee0177 100644 --- a/Lib/test/test_dbm_dumb.py +++ b/Lib/test/test_dbm_dumb.py @@ -130,6 +130,13 @@ def test_str_write_contains(self): self._dict['\u00fc'.encode('utf-8')]) self.assertEqual(f[b'1'], b'a') + def test_bytearray(self): + with contextlib.closing(dumbdbm.open(_fname, 'n')) as f: + f['key'] = bytearray(b'bytearray_value') + self.assertEqual(f[b'key'], b'bytearray_value') + f[bytearray(b'bytearray_key')] = b'value' + self.assertEqual(f[b'bytearray_key'], b'value') + def test_line_endings(self): # test for bug #1172763: dumbdbm would die if the line endings # weren't what was expected. diff --git a/Lib/test/test_dbm_sqlite3.py b/Lib/test/test_dbm_sqlite3.py index 9216da8a63f957..68bac7d157938e 100644 --- a/Lib/test/test_dbm_sqlite3.py +++ b/Lib/test/test_dbm_sqlite3.py @@ -216,6 +216,10 @@ class DataTypes(_SQLiteDbmTests): (3.14, b"3.14"), ("string", b"string"), (b"bytes", b"bytes"), + (True, b"1"), + (False, b"0"), + (bytearray(b"bytearray"), b"bytearray"), + (memoryview(b"memoryview"), b"memoryview"), ) def setUp(self): From 1db39354e464af8e8e7887e8bf66cf16bd1ab67e Mon Sep 17 00:00:00 2001 From: furkanonder Date: Sun, 14 Dec 2025 22:29:55 +0900 Subject: [PATCH 3/5] Re-clarify traditional dbm backend types and drop dumb bytearray test --- Doc/library/dbm.rst | 7 ++----- Lib/test/test_dbm_dumb.py | 7 ------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 3188df5b70d84e..dca2ea9cf62a5f 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -130,13 +130,10 @@ Key and Value Types The accepted types for keys and values vary by backend. Keys and values are handled identically: -* **Traditional backends**: - - * :mod:`dbm.gnu` and :mod:`dbm.ndbm`: Accept :class:`str` and :class:`bytes` objects - * :mod:`dbm.dumb`: Accepts :class:`str`, :class:`bytes`, and :class:`bytearray` objects +* **Traditional backends**: :mod:`dbm.gnu`, :mod:`dbm.ndbm`, and :mod:`dbm.dumb` accept :class:`str` and :class:`bytes` objects. * **SQLite backend** (:mod:`dbm.sqlite3`): Accepts :class:`str`, :class:`bytes`, :class:`int`, :class:`float`, :class:`bool`, :class:`bytearray`, - :class:`memoryview`, and :class:`array.array` objects + :class:`memoryview`, and :class:`array.array` objects. **Storage Format:** diff --git a/Lib/test/test_dbm_dumb.py b/Lib/test/test_dbm_dumb.py index 8dfdcbcdee0177..672f9092207cf6 100644 --- a/Lib/test/test_dbm_dumb.py +++ b/Lib/test/test_dbm_dumb.py @@ -130,13 +130,6 @@ def test_str_write_contains(self): self._dict['\u00fc'.encode('utf-8')]) self.assertEqual(f[b'1'], b'a') - def test_bytearray(self): - with contextlib.closing(dumbdbm.open(_fname, 'n')) as f: - f['key'] = bytearray(b'bytearray_value') - self.assertEqual(f[b'key'], b'bytearray_value') - f[bytearray(b'bytearray_key')] = b'value' - self.assertEqual(f[b'bytearray_key'], b'value') - def test_line_endings(self): # test for bug #1172763: dumbdbm would die if the line endings # weren't what was expected. From 2ba913ca0612a95fd7144756f825ee6fc1af2373 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Sun, 14 Dec 2025 22:58:58 +0900 Subject: [PATCH 4/5] Clarify dbm docs for sqlite3 array storage bytes example --- Doc/library/dbm.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index b017906f2dca06..72121d18282b33 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -150,7 +150,7 @@ type stored. * ``db['key'] = True`` stored as ``b'1'`` (:mod:`dbm.sqlite3` only) * ``db['key'] = False`` stored as ``b'0'`` (:mod:`dbm.sqlite3` only) * ``db['key'] = memoryview(b'data')`` stored as ``b'data'`` (:mod:`dbm.sqlite3` only) -* ``db['key'] = array.array('i', [1, 2, 3])`` stored as binary data (:mod:`dbm.sqlite3` only) +* ``db['key'] = array.array('i', [1, 2, 3])`` stored as bytes (e.g. on little-endian: ``b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'``) (:mod:`dbm.sqlite3` only) * ``db['key'] = None`` fails on all backends * ``db['key'] = [1, 2, 3]`` fails on all backends From 72af303c4efd6cfbde3f0dc98e59461957ac7eb3 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Sun, 14 Dec 2025 23:27:48 +0900 Subject: [PATCH 5/5] Clarify dbm.dumb bytearray handling and cover with test --- Doc/library/dbm.rst | 7 ++++--- Lib/test/test_dbm_dumb.py | 9 +++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 72121d18282b33..b7f7c8cf2f6e33 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -130,7 +130,10 @@ Key and Value Types The accepted types for keys and values vary by backend. Keys and values are handled identically: -* **Traditional backends**: :mod:`dbm.gnu`, :mod:`dbm.ndbm`, and :mod:`dbm.dumb` accept :class:`str` and :class:`bytes` objects. +* **Traditional backends**: + + * :mod:`dbm.gnu` and :mod:`dbm.ndbm`: Accept :class:`str` and :class:`bytes` objects. + * :mod:`dbm.dumb`: Accepts :class:`str` and :class:`bytes` objects; :class:`bytearray` is acceptable as a value, but not as a key. * **SQLite backend** (:mod:`dbm.sqlite3`): Accepts :class:`str`, :class:`bytes`, :class:`int`, :class:`float`, :class:`bool`, :class:`bytearray`, :class:`memoryview`, and :class:`array.array` objects. @@ -151,8 +154,6 @@ type stored. * ``db['key'] = False`` stored as ``b'0'`` (:mod:`dbm.sqlite3` only) * ``db['key'] = memoryview(b'data')`` stored as ``b'data'`` (:mod:`dbm.sqlite3` only) * ``db['key'] = array.array('i', [1, 2, 3])`` stored as bytes (e.g. on little-endian: ``b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'``) (:mod:`dbm.sqlite3` only) -* ``db['key'] = None`` fails on all backends -* ``db['key'] = [1, 2, 3]`` fails on all backends The following example records some hostnames and a corresponding title, and diff --git a/Lib/test/test_dbm_dumb.py b/Lib/test/test_dbm_dumb.py index 672f9092207cf6..c24c9c3817a1e7 100644 --- a/Lib/test/test_dbm_dumb.py +++ b/Lib/test/test_dbm_dumb.py @@ -31,6 +31,7 @@ class DumbDBMTestCase(unittest.TestCase): b'd': b'way', b'f': b'Guido', b'g': b'intended', + b'h': bytearray(b'bytearray_value'), '\u00fc'.encode('utf-8') : b'!', } @@ -368,6 +369,14 @@ def test_nonascii_filename(self): self.assertTrue(b'key' in db) self.assertEqual(db[b'key'], b'value') + def test_bytearray(self): + self.init_db() + with contextlib.closing(dumbdbm.open(_fname, 'r')) as f: + self.assertEqual(f[b'h'], b'bytearray_value') + with contextlib.closing(dumbdbm.open(_fname)) as f: + with self.assertRaises(TypeError): + f[bytearray(b'bytearray_key')] = b'value' + def test_open_with_pathlib_path(self): dumbdbm.open(os_helper.FakePath(_fname), "c").close()