]> granicus.if.org Git - python/commitdiff
Close #19282: Native context management in dbm
authorNick Coghlan <ncoghlan@gmail.com>
Sun, 17 Nov 2013 05:59:51 +0000 (15:59 +1000)
committerNick Coghlan <ncoghlan@gmail.com>
Sun, 17 Nov 2013 05:59:51 +0000 (15:59 +1000)
Doc/library/dbm.rst
Lib/dbm/dumb.py
Lib/test/test_dbm_dumb.py
Lib/test/test_dbm_gnu.py
Lib/test/test_dbm_ndbm.py
Misc/NEWS
Modules/_dbmmodule.c
Modules/_gdbmmodule.c

index 81a05c7073496af6d81f72b0cb599fc7bce215ed..f5496d5b99c6f54f29d162abd220db2cda7263a0 100644 (file)
@@ -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::
index 9ac7852978f18fe4ec48ac2e2a085370df001e74..ba6a20d0c9acdd7a52497826d67b70882ccb1902 100644 (file)
@@ -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.
index 208bc4c3823e003c8d0f9cc0f62531f8a21b7e4b..9fd8cde59a8454e295afa7e7a5e283d04ab9f5a5 100644 (file)
@@ -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()
 
index 4fb66c54b8c6afbdee59d48e910636b8be8fe3b5..a7808f51c756dce6ecdd37bbfddcd6875a7b8033 100755 (executable)
@@ -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()
index b57e1f0653573b8bbb3e7d63431c39ad516b562c..2291561defc4d77c91edcc36a73f63ec844ab322 100755 (executable)
@@ -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()
index 89c37d9ba187483b331ff22b2a68e893cc2579ca..9dedb6aa51f6f89abe5d40dd2e776e6affb98f21 100644 (file)
--- 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.
 
index 60802b694a4055a69f1889212bdefa9c20ffcab8..370f67039da960d3ddb00551926c4d3464cb1bd8 100644 (file)
@@ -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 */
 };
 
index 36c06d13b791dcc02419862ceb85830d74309c9c..229e16e627db86b10b7570433b6c25ef90d88918 100644 (file)
@@ -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 */
 };