From c610aba1ed57a30104a254ccd1f9fe07d02b1334 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Sun, 17 Nov 2013 15:59:51 +1000 Subject: [PATCH] Close #19282: Native context management in dbm --- Doc/library/dbm.rst | 38 ++++++++++++++++++++++---------------- Lib/dbm/dumb.py | 6 ++++++ Lib/test/test_dbm_dumb.py | 13 +++++++++++++ Lib/test/test_dbm_gnu.py | 11 +++++++++++ Lib/test/test_dbm_ndbm.py | 13 +++++++++++++ Misc/NEWS | 3 +++ Modules/_dbmmodule.c | 17 +++++++++++++++++ Modules/_gdbmmodule.c | 16 ++++++++++++++++ 8 files changed, 101 insertions(+), 16 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 81a05c70734..f5496d5b99c 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -73,33 +73,39 @@ Key and values are always stored as bytes. This means that when strings are used they are implicitly converted to the default encoding before being stored. +These objects also support being used in a :keyword:`with` statement, which +will automatically close them when done. + +.. versionchanged:: 3.4 + Added native support for the context management protocol to the objects + returned by :func:`.open`. + The following example records some hostnames and a corresponding title, and then prints out the contents of the database:: import dbm # Open database, creating it if necessary. - db = dbm.open('cache', 'c') + with dbm.open('cache', 'c') as db: - # Record some values - db[b'hello'] = b'there' - db['www.python.org'] = 'Python Website' - db['www.cnn.com'] = 'Cable News Network' + # Record some values + db[b'hello'] = b'there' + db['www.python.org'] = 'Python Website' + db['www.cnn.com'] = 'Cable News Network' - # Note that the keys are considered bytes now. - assert db[b'www.python.org'] == b'Python Website' - # Notice how the value is now in bytes. - assert db['www.cnn.com'] == b'Cable News Network' + # Note that the keys are considered bytes now. + assert db[b'www.python.org'] == b'Python Website' + # Notice how the value is now in bytes. + assert db['www.cnn.com'] == b'Cable News Network' - # Often-used methods of the dict interface work too. - print(db.get('python.org', b'not present')) + # 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). - db['www.yahoo.com'] = 4 + # Storing a non-string key or value will raise an exception (most + # likely a TypeError). + db['www.yahoo.com'] = 4 - # Close when done. - db.close() + # db is automatically closed when leaving the with statement. .. seealso:: diff --git a/Lib/dbm/dumb.py b/Lib/dbm/dumb.py index 9ac7852978f..ba6a20d0c9a 100644 --- a/Lib/dbm/dumb.py +++ b/Lib/dbm/dumb.py @@ -236,6 +236,12 @@ class _Database(collections.MutableMapping): if hasattr(self._os, 'chmod'): self._os.chmod(file, self._mode) + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + def open(file, flag=None, mode=0o666): """Open the database file, filename, and return corresponding object. diff --git a/Lib/test/test_dbm_dumb.py b/Lib/test/test_dbm_dumb.py index 208bc4c3823..9fd8cde59a8 100644 --- a/Lib/test/test_dbm_dumb.py +++ b/Lib/test/test_dbm_dumb.py @@ -184,6 +184,19 @@ class DumbDBMTestCase(unittest.TestCase): self.assertEqual(expected, got) f.close() + def test_context_manager(self): + with dumbdbm.open(_fname, 'c') as db: + db["dumbdbm context manager"] = "context manager" + + with dumbdbm.open(_fname, 'r') as db: + self.assertEqual(list(db.keys()), [b"dumbdbm context manager"]) + + # This currently just raises AttributeError rather than a specific + # exception like the GNU or NDBM based implementations. See + # http://bugs.python.org/issue19385 for details. + with self.assertRaises(Exception): + db.keys() + def tearDown(self): _delete_files() diff --git a/Lib/test/test_dbm_gnu.py b/Lib/test/test_dbm_gnu.py index 4fb66c54b8c..a7808f51c75 100755 --- a/Lib/test/test_dbm_gnu.py +++ b/Lib/test/test_dbm_gnu.py @@ -81,6 +81,17 @@ class TestGdbm(unittest.TestCase): size2 = os.path.getsize(filename) self.assertTrue(size1 > size2 >= size0) + def test_context_manager(self): + with gdbm.open(filename, 'c') as db: + db["gdbm context manager"] = "context manager" + + with gdbm.open(filename, 'r') as db: + self.assertEqual(list(db.keys()), [b"gdbm context manager"]) + + with self.assertRaises(gdbm.error) as cm: + db.keys() + self.assertEqual(str(cm.exception), + "GDBM object has already been closed") if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_dbm_ndbm.py b/Lib/test/test_dbm_ndbm.py index b57e1f06535..2291561defc 100755 --- a/Lib/test/test_dbm_ndbm.py +++ b/Lib/test/test_dbm_ndbm.py @@ -37,5 +37,18 @@ class DbmTestCase(unittest.TestCase): except error: self.fail() + def test_context_manager(self): + with dbm.ndbm.open(self.filename, 'c') as db: + db["ndbm context manager"] = "context manager" + + with dbm.ndbm.open(self.filename, 'r') as db: + self.assertEqual(list(db.keys()), [b"ndbm context manager"]) + + with self.assertRaises(dbm.ndbm.error) as cm: + db.keys() + self.assertEqual(str(cm.exception), + "DBM object has already been closed") + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS b/Misc/NEWS index 89c37d9ba18..9dedb6aa51f 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -50,6 +50,9 @@ Core and Builtins Library ------- +- Issue #19282: dbm.open now supports the context manager protocol. (Inital + patch by Claudiu Popa) + - Issue #8311: Added support for writing any bytes-like objects in the aifc, sunau, and wave modules. diff --git a/Modules/_dbmmodule.c b/Modules/_dbmmodule.c index 60802b694a4..370f67039da 100644 --- a/Modules/_dbmmodule.c +++ b/Modules/_dbmmodule.c @@ -313,6 +313,21 @@ dbm_setdefault(dbmobject *dp, PyObject *args) return defvalue; } +static PyObject * +dbm__enter__(PyObject *self, PyObject *args) +{ + Py_INCREF(self); + return self; +} + +static PyObject * +dbm__exit__(PyObject *self, PyObject *args) +{ + _Py_IDENTIFIER(close); + return _PyObject_CallMethodId(self, &PyId_close, NULL); +} + + static PyMethodDef dbm_methods[] = { {"close", (PyCFunction)dbm__close, METH_NOARGS, "close()\nClose the database."}, @@ -325,6 +340,8 @@ static PyMethodDef dbm_methods[] = { "setdefault(key[, default]) -> value\n" "Return the value for key if present, otherwise default. If key\n" "is not in the database, it is inserted with default as the value."}, + {"__enter__", dbm__enter__, METH_NOARGS, NULL}, + {"__exit__", dbm__exit__, METH_VARARGS, NULL}, {NULL, NULL} /* sentinel */ }; diff --git a/Modules/_gdbmmodule.c b/Modules/_gdbmmodule.c index 36c06d13b79..229e16e627d 100644 --- a/Modules/_gdbmmodule.c +++ b/Modules/_gdbmmodule.c @@ -425,6 +425,20 @@ dbm_sync(dbmobject *dp, PyObject *unused) return Py_None; } +static PyObject * +dbm__enter__(PyObject *self, PyObject *args) +{ + Py_INCREF(self); + return self; +} + +static PyObject * +dbm__exit__(PyObject *self, PyObject *args) +{ + _Py_IDENTIFIER(close); + return _PyObject_CallMethodId(self, &PyId_close, NULL); +} + static PyMethodDef dbm_methods[] = { {"close", (PyCFunction)dbm_close, METH_NOARGS, dbm_close__doc__}, {"keys", (PyCFunction)dbm_keys, METH_NOARGS, dbm_keys__doc__}, @@ -434,6 +448,8 @@ static PyMethodDef dbm_methods[] = { {"sync", (PyCFunction)dbm_sync, METH_NOARGS, dbm_sync__doc__}, {"get", (PyCFunction)dbm_get, METH_VARARGS, dbm_get__doc__}, {"setdefault",(PyCFunction)dbm_setdefault,METH_VARARGS, dbm_setdefault__doc__}, + {"__enter__", dbm__enter__, METH_NOARGS, NULL}, + {"__exit__", dbm__exit__, METH_VARARGS, NULL}, {NULL, NULL} /* sentinel */ };