Skip to content

Commit c610aba

Browse files
committed
Close python#19282: Native context management in dbm
1 parent eb8ea26 commit c610aba

8 files changed

Lines changed: 101 additions & 16 deletions

File tree

Doc/library/dbm.rst

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -73,33 +73,39 @@ Key and values are always stored as bytes. This means that when
7373
strings are used they are implicitly converted to the default encoding before
7474
being stored.
7575

76+
These objects also support being used in a :keyword:`with` statement, which
77+
will automatically close them when done.
78+
79+
.. versionchanged:: 3.4
80+
Added native support for the context management protocol to the objects
81+
returned by :func:`.open`.
82+
7683
The following example records some hostnames and a corresponding title, and
7784
then prints out the contents of the database::
7885

7986
import dbm
8087

8188
# Open database, creating it if necessary.
82-
db = dbm.open('cache', 'c')
89+
with dbm.open('cache', 'c') as db:
8390

84-
# Record some values
85-
db[b'hello'] = b'there'
86-
db['www.python.org'] = 'Python Website'
87-
db['www.cnn.com'] = 'Cable News Network'
91+
# Record some values
92+
db[b'hello'] = b'there'
93+
db['www.python.org'] = 'Python Website'
94+
db['www.cnn.com'] = 'Cable News Network'
8895

89-
# Note that the keys are considered bytes now.
90-
assert db[b'www.python.org'] == b'Python Website'
91-
# Notice how the value is now in bytes.
92-
assert db['www.cnn.com'] == b'Cable News Network'
96+
# Note that the keys are considered bytes now.
97+
assert db[b'www.python.org'] == b'Python Website'
98+
# Notice how the value is now in bytes.
99+
assert db['www.cnn.com'] == b'Cable News Network'
93100

94-
# Often-used methods of the dict interface work too.
95-
print(db.get('python.org', b'not present'))
101+
# Often-used methods of the dict interface work too.
102+
print(db.get('python.org', b'not present'))
96103

97-
# Storing a non-string key or value will raise an exception (most
98-
# likely a TypeError).
99-
db['www.yahoo.com'] = 4
104+
# Storing a non-string key or value will raise an exception (most
105+
# likely a TypeError).
106+
db['www.yahoo.com'] = 4
100107

101-
# Close when done.
102-
db.close()
108+
# db is automatically closed when leaving the with statement.
103109

104110

105111
.. seealso::

Lib/dbm/dumb.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,12 @@ def _chmod(self, file):
236236
if hasattr(self._os, 'chmod'):
237237
self._os.chmod(file, self._mode)
238238

239+
def __enter__(self):
240+
return self
241+
242+
def __exit__(self, *args):
243+
self.close()
244+
239245

240246
def open(file, flag=None, mode=0o666):
241247
"""Open the database file, filename, and return corresponding object.

Lib/test/test_dbm_dumb.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,19 @@ def test_random(self):
184184
self.assertEqual(expected, got)
185185
f.close()
186186

187+
def test_context_manager(self):
188+
with dumbdbm.open(_fname, 'c') as db:
189+
db["dumbdbm context manager"] = "context manager"
190+
191+
with dumbdbm.open(_fname, 'r') as db:
192+
self.assertEqual(list(db.keys()), [b"dumbdbm context manager"])
193+
194+
# This currently just raises AttributeError rather than a specific
195+
# exception like the GNU or NDBM based implementations. See
196+
# http://bugs.python.org/issue19385 for details.
197+
with self.assertRaises(Exception):
198+
db.keys()
199+
187200
def tearDown(self):
188201
_delete_files()
189202

Lib/test/test_dbm_gnu.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,17 @@ def test_reorganize(self):
8181
size2 = os.path.getsize(filename)
8282
self.assertTrue(size1 > size2 >= size0)
8383

84+
def test_context_manager(self):
85+
with gdbm.open(filename, 'c') as db:
86+
db["gdbm context manager"] = "context manager"
87+
88+
with gdbm.open(filename, 'r') as db:
89+
self.assertEqual(list(db.keys()), [b"gdbm context manager"])
90+
91+
with self.assertRaises(gdbm.error) as cm:
92+
db.keys()
93+
self.assertEqual(str(cm.exception),
94+
"GDBM object has already been closed")
8495

8596
if __name__ == '__main__':
8697
unittest.main()

Lib/test/test_dbm_ndbm.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,18 @@ def test_modes(self):
3737
except error:
3838
self.fail()
3939

40+
def test_context_manager(self):
41+
with dbm.ndbm.open(self.filename, 'c') as db:
42+
db["ndbm context manager"] = "context manager"
43+
44+
with dbm.ndbm.open(self.filename, 'r') as db:
45+
self.assertEqual(list(db.keys()), [b"ndbm context manager"])
46+
47+
with self.assertRaises(dbm.ndbm.error) as cm:
48+
db.keys()
49+
self.assertEqual(str(cm.exception),
50+
"DBM object has already been closed")
51+
52+
4053
if __name__ == '__main__':
4154
unittest.main()

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ Core and Builtins
5050
Library
5151
-------
5252

53+
- Issue #19282: dbm.open now supports the context manager protocol. (Inital
54+
patch by Claudiu Popa)
55+
5356
- Issue #8311: Added support for writing any bytes-like objects in the aifc,
5457
sunau, and wave modules.
5558

Modules/_dbmmodule.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,21 @@ dbm_setdefault(dbmobject *dp, PyObject *args)
313313
return defvalue;
314314
}
315315

316+
static PyObject *
317+
dbm__enter__(PyObject *self, PyObject *args)
318+
{
319+
Py_INCREF(self);
320+
return self;
321+
}
322+
323+
static PyObject *
324+
dbm__exit__(PyObject *self, PyObject *args)
325+
{
326+
_Py_IDENTIFIER(close);
327+
return _PyObject_CallMethodId(self, &PyId_close, NULL);
328+
}
329+
330+
316331
static PyMethodDef dbm_methods[] = {
317332
{"close", (PyCFunction)dbm__close, METH_NOARGS,
318333
"close()\nClose the database."},
@@ -325,6 +340,8 @@ static PyMethodDef dbm_methods[] = {
325340
"setdefault(key[, default]) -> value\n"
326341
"Return the value for key if present, otherwise default. If key\n"
327342
"is not in the database, it is inserted with default as the value."},
343+
{"__enter__", dbm__enter__, METH_NOARGS, NULL},
344+
{"__exit__", dbm__exit__, METH_VARARGS, NULL},
328345
{NULL, NULL} /* sentinel */
329346
};
330347

Modules/_gdbmmodule.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,20 @@ dbm_sync(dbmobject *dp, PyObject *unused)
425425
return Py_None;
426426
}
427427

428+
static PyObject *
429+
dbm__enter__(PyObject *self, PyObject *args)
430+
{
431+
Py_INCREF(self);
432+
return self;
433+
}
434+
435+
static PyObject *
436+
dbm__exit__(PyObject *self, PyObject *args)
437+
{
438+
_Py_IDENTIFIER(close);
439+
return _PyObject_CallMethodId(self, &PyId_close, NULL);
440+
}
441+
428442
static PyMethodDef dbm_methods[] = {
429443
{"close", (PyCFunction)dbm_close, METH_NOARGS, dbm_close__doc__},
430444
{"keys", (PyCFunction)dbm_keys, METH_NOARGS, dbm_keys__doc__},
@@ -434,6 +448,8 @@ static PyMethodDef dbm_methods[] = {
434448
{"sync", (PyCFunction)dbm_sync, METH_NOARGS, dbm_sync__doc__},
435449
{"get", (PyCFunction)dbm_get, METH_VARARGS, dbm_get__doc__},
436450
{"setdefault",(PyCFunction)dbm_setdefault,METH_VARARGS, dbm_setdefault__doc__},
451+
{"__enter__", dbm__enter__, METH_NOARGS, NULL},
452+
{"__exit__", dbm__exit__, METH_VARARGS, NULL},
437453
{NULL, NULL} /* sentinel */
438454
};
439455

0 commit comments

Comments
 (0)