Index: Doc/library/dbm.rst =================================================================== --- Doc/library/dbm.rst (revision 88383) +++ Doc/library/dbm.rst (working copy) @@ -63,11 +63,13 @@ The object returned by :func:`.open` supports the same basic functionality as dictionaries; keys and their corresponding values can be stored, retrieved, and -deleted, and the :keyword:`in` operator and the :meth:`keys` method are -available, as well as :meth:`get` and :meth:`setdefault`. +deleted, and the :keyword:`in` operator and the :meth:`keys`, :meth:`values`, +:meth:`items` methods are available, as well as :meth:`get`, :meth:`setdefault`, +:meth:`pop`, :meth:`popitem` :meth:`clear` and :meth:`update`. .. versionchanged:: 3.2 - :meth:`get` and :meth:`setdefault` are now available in all database modules. + The MutableMapping interfaces and :meth:`setdefault` are now available in all + database modules. Key and values are always stored as bytes. This means that when strings are used they are implicitly converted to the default encoding before @@ -126,9 +128,7 @@ The :mod:`dbm.gnu` module provides an interface to the GNU DBM library. ``dbm.gnu.gdbm`` objects behave like mappings (dictionaries), except that keys and values are always converted to bytes before storing. Printing a ``gdbm`` -object doesn't print the -keys and values, and the :meth:`items` and :meth:`values` methods are not -supported. +object doesn't print the keys and values. .. exception:: error @@ -228,7 +228,7 @@ The :mod:`dbm.ndbm` module provides an interface to the Unix "(n)dbm" library. Dbm objects behave like mappings (dictionaries), except that keys and values are always stored as bytes. Printing a ``dbm`` object doesn't print the keys and -values, and the :meth:`items` and :meth:`values` methods are not supported. +values. This module can be used with the "classic" ndbm interface or the GNU GDBM compatibility interface. On Unix, the :program:`configure` script will attempt Index: Lib/dbm/dumb.py =================================================================== --- Lib/dbm/dumb.py (revision 88383) +++ Lib/dbm/dumb.py (working copy) @@ -208,12 +208,6 @@ # XXX why shouldn't __setitem__? self._commit() - def keys(self): - return list(self._index.keys()) - - def items(self): - return [(key, self[key]) for key in self._index.keys()] - def __contains__(self, key): if isinstance(key, str): key = key.encode('utf-8') Index: Lib/test/test_dbm_dumb.py =================================================================== --- Lib/test/test_dbm_dumb.py (revision 88383) +++ Lib/test/test_dbm_dumb.py (working copy) @@ -34,7 +34,7 @@ def test_dumbdbm_creation(self): f = dumbdbm.open(_fname, 'c') - self.assertEqual(list(f.keys()), []) + self.assertTupleEqual((), tuple(f.keys())) for key in self._dict: f[key] = self._dict[key] self.read_helper(f) @@ -90,6 +90,18 @@ keys = self.keys_helper(f) f.close() + def test_dumbdbm_values(self): + self.init_db() + f = dumbdbm.open(_fname) + values = self.values_helper(f) + f.close() + + def test_dumbdbm_items(self): + self.init_db() + f = dumbdbm.open(_fname) + items = self.items_helper(f) + f.close() + def test_write_contains(self): f = dumbdbm.open(_fname) f[b'1'] = b'hello' @@ -161,8 +173,25 @@ keys = sorted(f.keys()) dkeys = sorted(self._dict.keys()) self.assertEqual(keys, dkeys) + for k in self._dict: + self.assertIn(k, f) + self.assertEqual(len(keys), len(dkeys)) return keys + def values_helper(self, f): + values = sorted(f.values()) + dvalues = sorted(self._dict.values()) + self.assertEqual(values, dvalues) + self.assertEqual(len(values), len(dvalues)) + return values + + def items_helper(self, f): + items = sorted(f.items()) + ditems = sorted(self._dict.items()) + self.assertEqual(items, ditems) + self.assertEqual(len(items), len(ditems)) + return items + # Perform randomized operations. This doesn't make assumptions about # what *might* fail. def test_random(self): Index: Lib/test/test_dbm.py =================================================================== --- Lib/test/test_dbm.py (revision 88383) +++ Lib/test/test_dbm.py (working copy) @@ -64,7 +64,7 @@ def test_anydbm_creation(self): f = dbm.open(_fname, 'c') - self.assertEqual(list(f.keys()), []) + self.assertTupleEqual((), tuple(f.keys())) for key in self._dict: f[key.encode("ascii")] = self._dict[key] self.read_helper(f) @@ -148,7 +148,7 @@ def test_keys(self): self.d = dbm.open(self.filename, 'c') - self.assertEqual(self.d.keys(), []) + self.assertTupleEqual((), tuple(self.d.keys())) a = [(b'a', b'b'), (b'12345678910', b'019237410982340912840198242')] for k, v in a: self.d[k] = v Index: Lib/test/test_dbm_gnu.py =================================================================== --- Lib/test/test_dbm_gnu.py (revision 88383) +++ Lib/test/test_dbm_gnu.py (working copy) @@ -16,14 +16,122 @@ self.g.close() unlink(filename) + def test_get(self): + self.g = gdbm.open(filename, 'c') + self.g['a'] = 'b' + self.assertEqual(b'b', self.g.get('a')) + self.assertEqual(b'b', self.g.get(b'a')) + self.assertEqual(b'b', self.g.get('a', None)) + self.assertIsNone(self.g.get('b')) + self.assertIsNone(self.g.get(b'b')) + self.assertEqual('c', self.g.get('b', 'c')) + + def test_contains(self): + self.g = gdbm.open(filename, 'c') + self.g['a'] = 'b' + self.assertIn('a', self.g) + self.assertIn(b'a', self.g) + self.assertNotIn('b', self.g) + + def test_values(self): + self.g = gdbm.open(filename, 'c') + self.assertTupleEqual((), tuple(self.g.values())) + self.g['a'] = 'b' + self.g['c'] = 'd' + self.assertSetEqual(set((b'b', b'd')), set(self.g.values())) + + def test_items(self): + self.g = gdbm.open(filename, 'c') + self.assertTupleEqual((), tuple(self.g.items())) + self.g['a'] = 'b' + self.g['c'] = 'd' + self.assertSetEqual(set(((b'a', b'b'), (b'c', b'd'))), + set(self.g.items())) + + def test_pop(self): + self.g = gdbm.open(filename, 'c') + self.assertIsNone(self.g.pop('a')) + self.g['a'] = 'b' + self.g['c'] = 'd' + self.assertIsNone(self.g.pop('e')) + self.assertSetEqual(set((b'a', b'c')), set(self.g.keys())) + self.assertIsNone(self.g.pop('e', None)) + self.assertSetEqual(set((b'a', b'c')), set(self.g.keys())) + self.assertEqual(b'b', self.g.pop('a')) + self.assertSetEqual(set((b'c', )), set(self.g.keys())) + self.assertEqual(b'd', self.g.pop('c', None)) + self.assertTupleEqual((), tuple(self.g.keys())) + + def test_popitem(self): + self.g = gdbm.open(filename, 'c') + self.assertRaises(KeyError, self.g.popitem) + self.g['a'] = 'b' + self.assertEqual((b'a', b'b'), self.g.popitem()) + self.assertRaises(KeyError, self.g.popitem) + + def test_clear(self): + self.g = gdbm.open(filename, 'c') + self.assertTupleEqual((), tuple(self.g.keys())) + self.g['a'] = 'b' + self.g['c'] = 'c' + self.g.clear() + self.assertTupleEqual((), tuple(self.g.keys())) + + def test_iteration(self): + self.g = gdbm.open(filename, 'c') + self.assertTupleEqual((), tuple(self.g.keys())) + self.assertRaises(StopIteration, next, iter(self.g)) + self.g['a'] = 'b' + self.g['c'] = 'd' + gi = iter(self.g) + self.assertEqual(set((b'a', b'c')), set((next(gi), next(gi)))) + self.assertRaises(StopIteration, next, gi) + + def test_keys(self): + self.g = gdbm.open(filename, 'c') + self.assertTupleEqual((), tuple(self.g.keys())) + self.assertEqual(len(self.g.keys()), 0) + self.g['a'] = 'b' + self.g[b'bytes'] = b'data' + self.g['12345678910'] = '019237410982340912840198242' + self.assertEqual(len(self.g.keys()), 3) + self.assertIn(b'a', self.g) + self.assertNotIn(b'b', self.g) + self.assertEqual(self.g[b'bytes'], b'data') + + def test_values(self): + self.g = gdbm.open(filename, 'c') + self.assertSetEqual(set(), set(self.g.values())) + self.assertEqual(0, len(self.g.values())) + self.g['a'] = 'b' + self.g['c'] = 'd' + self.assertSetEqual(set((b'b', b'd')), set(self.g.values())) + self.assertEqual(2, len(self.g.values())) + + def test_items(self): + self.g = gdbm.open(filename, 'c') + self.assertTupleEqual((), tuple(self.g.items())) + self.assertEqual(0, len(self.g.items())) + self.g['a'] = 'b' + self.g['c'] = 'd' + self.assertSetEqual(set(((b'a', b'b'), (b'c', b'd'))), set(self.g.items())) + self.assertEqual(2, len(self.g.items())) + + def test_abc(self): + self.g = gdbm.open(filename, 'c') + from collections import Iterable, Mapping, MutableMapping + self.assertIsInstance(self.g, Iterable) + self.assertIsInstance(self.g, Mapping) + self.assertIsInstance(self.g, MutableMapping) + def test_key_methods(self): self.g = gdbm.open(filename, 'c') - self.assertEqual(self.g.keys(), []) + self.assertTupleEqual((), tuple(self.g.keys())) self.g['a'] = 'b' self.g['12345678910'] = '019237410982340912840198242' self.g[b'bytes'] = b'data' key_set = set(self.g.keys()) - self.assertEqual(key_set, set([b'a', b'bytes', b'12345678910'])) + self.assertSetEqual(key_set, set([b'a', b'bytes', b'12345678910'])) self.assertIn(b'a', self.g) self.assertEqual(self.g[b'bytes'], b'data') key = self.g.firstkey() @@ -37,6 +145,38 @@ self.assertEqual(self.g.setdefault(b'xxx', b'foo'), b'foo') self.assertEqual(self.g[b'xxx'], b'foo') + def test_update1(self): + self.g = gdbm.open(filename, 'c') + self.assertTupleEqual((), tuple(self.g.keys())) + self.g['a'] = 'a' + self.g.update({'b':'b', 'c':'c'}) + self.assertSetEqual(set(((b'a', b'a'), (b'b', b'b'), (b'c', b'c'))), + set(self.g.items())) + + def test_update2(self): + self.g = gdbm.open(filename, 'c') + self.assertTupleEqual((), tuple(self.g.keys())) + self.g['a'] = 'a' + class Other(object): + def __init__(self, dic): + self._dict = dic + def keys(self): + return self._dict.keys() + def __getitem__(self, key): + return self._dict[key] + + self.g.update(Other({'b':'b', 'c':'c'})) + self.assertSetEqual(set(((b'a', b'a'), (b'b', b'b'), (b'c', b'c'))), + set(self.g.items())) + + def test_update3(self): + self.g = gdbm.open(filename, 'c') + self.assertTupleEqual((), tuple(self.g.keys())) + self.g['a'] = 'a' + self.g.update({b'b':b'b', b'c':b'c'}.items()) + self.assertSetEqual(set(((b'a', b'a'), (b'b', b'b'), (b'c', b'c'))), + set(self.g.items())) + def test_error_conditions(self): # Try to open a non-existent database. unlink(filename) Index: Lib/test/test_dbm_ndbm.py =================================================================== --- Lib/test/test_dbm_ndbm.py (revision 88383) +++ Lib/test/test_dbm_ndbm.py (working copy) @@ -17,17 +17,133 @@ for suffix in ['', '.pag', '.dir', '.db']: support.unlink(self.filename + suffix) + def test_get(self): + self.g = dbm.ndbm.open(self.filename, 'c') + self.g['a'] = 'b' + self.assertEqual(b'b', self.g.get('a')) + self.assertEqual(b'b', self.g.get(b'a')) + self.assertEqual(b'b', self.g.get('a', None)) + self.assertIsNone(self.g.get('b')) + self.assertIsNone(self.g.get(b'b')) + self.assertEqual('c', self.g.get('b', 'c')) + + def test_contains(self): + self.g = dbm.ndbm.open(self.filename, 'c') + self.g['a'] = 'b' + self.assertIn('a', self.g) + self.assertIn(b'a', self.g) + self.assertNotIn('b', self.g) + + def test_values(self): + self.g = dbm.ndbm.open(self.filename, 'c') + self.assertSetEqual(set(), set(self.g.values())) + self.assertEqual(0, len(self.g.values())) + self.g['a'] = 'b' + self.g['c'] = 'd' + self.assertSetEqual(set((b'b', b'd')), set(self.g.values())) + self.assertEqual(2, len(self.g.values())) + + def test_items(self): + self.g = dbm.ndbm.open(self.filename, 'c') + self.assertTupleEqual((), tuple(self.g.items())) + self.assertEqual(0, len(self.g.items())) + self.g['a'] = 'b' + self.g['c'] = 'd' + self.assertSetEqual(set(((b'a', b'b'), (b'c', b'd'))), set(self.g.items())) + self.assertEqual(2, len(self.g.items())) + + def test_pop(self): + self.g = dbm.ndbm.open(self.filename, 'c') + self.assertIsNone(self.g.pop('a')) + self.g['a'] = 'b' + self.g['c'] = 'd' + self.assertIsNone(self.g.pop('e')) + self.assertTupleEqual((b'a', b'c'), tuple(self.g.keys())) + self.assertIsNone(self.g.pop('e', None)) + self.assertTupleEqual((b'a', b'c'), tuple(self.g.keys())) + self.assertEqual(b'b', self.g.pop('a')) + self.assertTupleEqual((b'c', ), tuple(self.g.keys())) + self.assertEqual(b'd', self.g.pop('c', None)) + self.assertTupleEqual((), tuple(self.g.keys())) + + def test_popitem(self): + self.g = dbm.ndbm.open(self.filename, 'c') + self.assertRaises(KeyError, self.g.popitem) + self.g['a'] = 'b' + self.assertEqual((b'a', b'b'), self.g.popitem()) + self.assertRaises(KeyError, self.g.popitem) + + def test_clear(self): + self.g = dbm.ndbm.open(self.filename, 'c') + self.assertTupleEqual((), tuple(self.g.keys())) + self.g['a'] = 'b' + self.g['c'] = 'c' + self.g.clear() + self.assertTupleEqual((), tuple(self.g.keys())) + + def test_iteration(self): + self.g = dbm.ndbm.open(self.filename, 'c') + self.assertTupleEqual((), tuple(self.g.keys())) + self.assertRaises(StopIteration, next, iter(self.g)) + self.g['a'] = 'b' + self.g['c'] = 'd' + gi = iter(self.g) + self.assertTupleEqual((b'a', b'c'), (next(gi), next(gi))) + self.assertRaises(StopIteration, next, gi) + + def test_abc(self): + self.g = dbm.ndbm.open(self.filename, 'c') + from collections import Iterable, Mapping, MutableMapping + self.assertIsInstance(self.g, Iterable) + self.assertIsInstance(self.g, Mapping) + self.assertIsInstance(self.g, MutableMapping) + def test_keys(self): self.d = dbm.ndbm.open(self.filename, 'c') - self.assertTrue(self.d.keys() == []) + self.assertTupleEqual((), tuple(self.d.keys())) + self.assertEqual(len(self.d.keys()), 0) self.d['a'] = 'b' self.d[b'bytes'] = b'data' self.d['12345678910'] = '019237410982340912840198242' self.d.keys() + self.assertEqual(len(self.d.keys()), 3) self.assertIn(b'a', self.d) + self.assertNotIn(b'b', self.d) self.assertEqual(self.d[b'bytes'], b'data') self.d.close() + def test_update1(self): + self.g = dbm.ndbm.open(self.filename, 'c') + self.assertTupleEqual((), tuple(self.g.keys())) + self.g['a'] = 'a' + self.g.update({'b':'b', 'c':'c'}) + self.assertTupleEqual(((b'a', b'a'), (b'b', b'b'), (b'c', b'c')), + tuple(self.g.items())) + + def test_update2(self): + self.g = dbm.ndbm.open(self.filename, 'c') + self.assertTupleEqual((), tuple(self.g.keys())) + self.g['a'] = 'a' + class Other(object): + def __init__(self, dic): + self._dict = dic + def keys(self): + return self._dict.keys() + def __getitem__(self, key): + return self._dict[key] + + self.g.update(Other({'b':'b', 'c':'c'})) + self.assertTupleEqual(((b'a', b'a'), (b'b', b'b'), (b'c', b'c')), + tuple(self.g.items())) + + def test_update3(self): + self.g = dbm.ndbm.open(self.filename, 'c') + self.assertTupleEqual((), tuple(self.g.keys())) + self.g['a'] = 'a' + self.g.update({b'b':b'b', b'c':b'c'}.items()) + self.assertTupleEqual(((b'a', b'a'), (b'b', b'b'), (b'c', b'c')), + tuple(self.g.items())) + def test_modes(self): for mode in ['r', 'rw', 'w', 'n']: try: Index: Modules/_gdbmmodule.c =================================================================== --- Modules/_gdbmmodule.c (revision 88383) +++ Modules/_gdbmmodule.c (working copy) @@ -41,6 +41,7 @@ { PyErr_SetString(DbmError, "GDBM object has already been closed"); \ return NULL; } +#define PyDbm_Check(op) PyObject_TypeCheck(op, &Dbmtype) static PyObject *DbmError; @@ -241,69 +242,20 @@ return Py_None; } -/* XXX Should return a set or a set view */ -PyDoc_STRVAR(dbm_keys__doc__, -"keys() -> list_of_keys\n\ -Get a list of all keys in the database."); - -static PyObject * -dbm_keys(register dbmobject *dp, PyObject *unused) -{ - register PyObject *v, *item; - datum key, nextkey; - int err; - - if (dp == NULL || !is_dbmobject(dp)) { - PyErr_BadInternalCall(); - return NULL; - } - check_dbmobject_open(dp); - - v = PyList_New(0); - if (v == NULL) - return NULL; - - key = gdbm_firstkey(dp->di_dbm); - while (key.dptr) { - item = PyBytes_FromStringAndSize(key.dptr, key.dsize); - if (item == NULL) { - free(key.dptr); - Py_DECREF(v); - return NULL; - } - err = PyList_Append(v, item); - Py_DECREF(item); - if (err != 0) { - free(key.dptr); - Py_DECREF(v); - return NULL; - } - nextkey = gdbm_nextkey(dp->di_dbm, key); - free(key.dptr); - key = nextkey; - } - return v; -} - static int dbm_contains(PyObject *self, PyObject *arg) { dbmobject *dp = (dbmobject *)self; datum key; + if (!PyArg_Parse(arg, "s#", &key.dptr, &key.dsize)) + return -1; + if ((dp)->di_dbm == NULL) { PyErr_SetString(DbmError, "GDBM object has already been closed"); return -1; } - if (!PyBytes_Check(arg)) { - PyErr_Format(PyExc_TypeError, - "gdbm key must be bytes, not %.100s", - arg->ob_type->tp_name); - return -1; - } - key.dptr = PyBytes_AS_STRING(arg); - key.dsize = PyBytes_GET_SIZE(arg); return gdbm_exists(dp->di_dbm, key); } @@ -416,15 +368,850 @@ return Py_None; } +PyDoc_STRVAR(dbm_pop__doc__, +"pop([key[, default]) -> value\n\ +Return the value for key and delete the key-value pair if present, \n\ +otherwise return default if specified, raise KeyError if not specified."); + +static PyObject * +dbm_pop(dbmobject *dp, PyObject *args) +{ + datum key, val; + PyObject *defvalue = Py_None, *result; + char *tmp_ptr; + Py_ssize_t tmp_size; + + if (!PyArg_ParseTuple(args, "s#|O:get", &tmp_ptr, &tmp_size, &defvalue)) + return NULL; + + key.dptr = tmp_ptr; + key.dsize = tmp_size; + check_dbmobject_open(dp); + val = gdbm_fetch(dp->di_dbm, key); + if (val.dptr != NULL) { + result = PyBytes_FromStringAndSize(val.dptr, val.dsize); + free(val.dptr); + if (result == NULL) + return NULL; + if (gdbm_delete(dp->di_dbm, key) < 0) { + PyErr_SetString(PyExc_ValueError, "Delete key error"); + Py_DECREF(result); + return NULL; + } + return result; + } + else { + Py_INCREF(defvalue); + return defvalue; + } +} + +PyDoc_STRVAR(dbm_popitem__doc__, +"popitem() -> (key, value)\n\ +Return the next (key, value) tuple if there is items in db, \n\ +otherwise raise KeyError."); + +static PyObject * +dbm_popitem(dbmobject *dp, PyObject *unused) +{ + datum k, v; + PyObject *result, *iter, *key, *val; + iter = PyObject_GetIter((PyObject *)dp); + if (iter == NULL) + return NULL; + key = PyIter_Next(iter); + Py_DECREF(iter); + if (key == NULL) { + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_KeyError, "GDBM object has no more items"); + return NULL; + } + assert(PyBytes_Check(key)); + if (PyBytes_AsStringAndSize(key, &k.dptr, &k.dsize)) + goto fail1; + v = gdbm_fetch(dp->di_dbm, k); + assert(v.dptr != NULL); + val = PyBytes_FromStringAndSize(v.dptr, v.dsize); + free(v.dptr); + if (val == NULL) + goto fail1; + result = PyTuple_New(2); + if (result == NULL) + goto fail2; + if (PyTuple_SetItem(result, 0, key)) { + Py_DECREF(val); + Py_DECREF(result); + return NULL; + } + if (PyTuple_SetItem(result, 1, val)) { + Py_DECREF(result); + return NULL; + } + if (gdbm_delete(dp->di_dbm, k) < 0) { + PyErr_SetString(PyExc_ValueError, "Delete key error"); + Py_DECREF(result); + return NULL; + } + return result; +fail1: + Py_DECREF(val); +fail2: + Py_DECREF(key); + return NULL; +} + +PyDoc_STRVAR(dbm_clear__doc__, +"clear() -> None\n\ +Delete all the items in db."); + +static PyObject * +dbm_clear(dbmobject *dp, PyObject *unused) +{ + datum key; + + if (dp == NULL || !is_dbmobject(dp)) { + PyErr_BadInternalCall(); + return NULL; + } + check_dbmobject_open(dp); + + key = gdbm_firstkey(dp->di_dbm); + while (key.dptr) { + if (gdbm_delete(dp->di_dbm, key) < 0) { + PyErr_SetString(PyExc_ValueError, "Delete key error"); + free(key.dptr); + return NULL; + } + free(key.dptr); + key = gdbm_firstkey(dp->di_dbm); + } + Py_RETURN_NONE; +} + +static PyObject * +dbm_update(dbmobject *dp, PyObject *args, PyObject *kwds) +{ + datum krec, drec; + PyObject *arg = NULL; + PyObject *coll_mod, *mapping_abc; + PyObject *arg_iter, *arg_keys, *iter_item, *item_value; + PyObject *key, *value; + Py_ssize_t pos = 0; + + if (dp == NULL || !is_dbmobject(dp)) { + PyErr_BadInternalCall(); + return NULL; + } + check_dbmobject_open(dp); + + if ((coll_mod = PyImport_ImportModule("collections")) == NULL) + return NULL; + if ((mapping_abc = PyObject_GetAttrString(coll_mod, "Mapping")) == + NULL) { + Py_DECREF(coll_mod); + return NULL; + } + Py_DECREF(coll_mod); + + if (!PyArg_UnpackTuple(args, "update", 0, 1, &arg)) + Py_DECREF(mapping_abc); + else if (arg != NULL) { + if (PyObject_IsInstance(arg, mapping_abc)) { + Py_DECREF(mapping_abc); + if ((arg_iter = PyObject_GetIter(arg)) == NULL) + return NULL; + while ((iter_item = PyIter_Next(arg_iter)) != NULL) { + if (!PyArg_Parse(iter_item, "s#", &krec.dptr, &krec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm key can only be buffers."); + Py_DECREF(arg_iter); + Py_DECREF(iter_item); + return NULL; + } + Py_DECREF(iter_item); + if ((item_value = PyObject_GetItem(arg, iter_item)) == NULL) { + Py_DECREF(arg_iter); + return NULL; + } + if (!PyArg_Parse(item_value, "s#", &drec.dptr, &drec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm value can only be buffers."); + Py_DECREF(arg_iter); + Py_DECREF(item_value); + return NULL; + } + Py_DECREF(item_value); + if (gdbm_store(dp->di_dbm, krec, drec, GDBM_REPLACE) < 0 ) { + if (errno != 0) + PyErr_SetFromErrno(DbmError); + else + PyErr_SetString(DbmError, gdbm_strerror(gdbm_errno)); + Py_DECREF(arg_iter); + return NULL; + } + } + Py_DECREF(arg_iter); + if (PyErr_Occurred()) + return NULL; + } + else if (PyObject_HasAttrString(arg, "keys")) { + Py_DECREF(mapping_abc); + if ((arg_keys = PyObject_CallMethod(arg, "keys", NULL)) == NULL) + return NULL; + if ((arg_iter = PyObject_GetIter(arg_keys)) == NULL) { + Py_DECREF(arg_keys); + return NULL; + } + Py_DECREF(arg_keys); + while ((iter_item = PyIter_Next(arg_iter)) != NULL) { + if (!PyArg_Parse(iter_item, "s#", &krec.dptr, &krec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm key can only be buffers."); + Py_DECREF(arg_iter); + Py_DECREF(iter_item); + return NULL; + } + Py_DECREF(iter_item); + if ((item_value = PyObject_GetItem(arg, iter_item)) == NULL) { + Py_DECREF(arg_iter); + return NULL; + } + if (!PyArg_Parse(item_value, "s#", &drec.dptr, &drec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm value can only be buffers."); + Py_DECREF(arg_iter); + Py_DECREF(item_value); + return NULL; + } + Py_DECREF(item_value); + if (gdbm_store(dp->di_dbm, krec, drec, GDBM_REPLACE) < 0 ) { + if (errno != 0) + PyErr_SetFromErrno(DbmError); + else + PyErr_SetString(DbmError, gdbm_strerror(gdbm_errno)); + Py_DECREF(arg_iter); + return NULL; + } + } + Py_DECREF(arg_iter); + if (PyErr_Occurred()) + return NULL; + } + else { + Py_DECREF(mapping_abc); + if ((arg_iter = PyObject_GetIter(arg)) == NULL) + return NULL; + while ((iter_item = PyIter_Next(arg_iter)) != NULL) { + if (!PySequence_Check(iter_item) || + PySequence_Size(iter_item) != 2) { + Py_DECREF(iter_item); + Py_DECREF(arg_iter); + return NULL; + } + if ((key = PySequence_GetItem(iter_item, 0)) == NULL) { + Py_DECREF(iter_item); + Py_DECREF(arg_iter); + return NULL; + } + if (!PyArg_Parse(key, "s#", &krec.dptr, &krec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm key can only be buffers."); + Py_DECREF(iter_item); + Py_DECREF(arg_iter); + Py_DECREF(key); + return NULL; + } + Py_DECREF(key); + + if ((value = PySequence_GetItem(iter_item, 1)) == NULL) { + Py_DECREF(iter_item); + Py_DECREF(arg_iter); + return NULL; + } + if (!PyArg_Parse(value, "s#", &drec.dptr, &drec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm value can only be buffers."); + Py_DECREF(arg_iter); + return NULL; + } + Py_DECREF(value); + Py_DECREF(iter_item); + + if (gdbm_store(dp->di_dbm, krec, drec, GDBM_REPLACE) < 0 ) { + if (errno != 0) + PyErr_SetFromErrno(DbmError); + else + PyErr_SetString(DbmError, gdbm_strerror(gdbm_errno)); + Py_DECREF(arg_iter); + return NULL; + } + } + Py_DECREF(arg_iter); + if (PyErr_Occurred()) + return NULL; + } + } + if (kwds == NULL) + Py_RETURN_NONE; + while (PyDict_Next(kwds, &pos, &key, &value)) { + if (!PyArg_Parse(key, "s#", &krec.dptr, &krec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm key can only be buffers."); + return NULL; + } + if (!PyArg_Parse(value, "s#", &drec.dptr, &drec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm value can only be buffers."); + return NULL; + } + if (gdbm_store(dp->di_dbm, krec, drec, GDBM_REPLACE) < 0 ) { + if (errno != 0) + PyErr_SetFromErrno(DbmError); + else + PyErr_SetString(DbmError, gdbm_strerror(gdbm_errno)); + PyErr_SetString(DbmError, "Cannot add item to database"); + return NULL; + } + } + Py_RETURN_NONE; +} + +PyDoc_STRVAR(dbm_update__doc__, +"update([other][, **keywords]) -> None\n\ +Add all the items in other if other is a mapping object, raise TypeError " +"else, as well as those in keywords."); + +typedef struct { + PyObject_HEAD + dbmobject *dbm; /* Set to NULL when iterator is exhausted */ + datum curr_key; +} dbmiterobject; + +static void +dbmiter_dealloc(dbmiterobject *di) +{ + Py_XDECREF(di->dbm); + if (di->curr_key.dptr) + free(di->curr_key.dptr); + PyObject_GC_Del(di); +} + +static int +dbmiter_traverse(dbmiterobject *di, visitproc visit, void *arg) +{ + Py_VISIT(di->dbm); + return 0; +} + +static PyObject * +dbmiter_iternextkey(dbmiterobject *di) +{ + PyObject *result; + datum next; + if (di->dbm != NULL) { + if (di->curr_key.dptr == NULL) + di->curr_key = gdbm_firstkey(di->dbm->di_dbm); + else { + next = gdbm_nextkey(di->dbm->di_dbm, di->curr_key); + free(di->curr_key.dptr); + di->curr_key = next; + } + if (di->curr_key.dptr == NULL) { + PyErr_SetNone(PyExc_StopIteration); + di->dbm = NULL; + return NULL; + } + result = PyBytes_FromStringAndSize(di->curr_key.dptr, + di->curr_key.dsize); + return result; + } + else { + //the iteration has already exhaust. + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } +} + +PyTypeObject DbmIterKey_Type= { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "DbmIterKey_Type", /* tp_name */ + sizeof(dbmiterobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)dbmiter_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + 0, /* tp_doc */ + (traverseproc)dbmiter_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + (iternextfunc)dbmiter_iternextkey, /* tp_iternext */ + 0, /* tp_methods */ + 0, +}; + +static PyObject * +dbmiter_iternextvalue(dbmiterobject *di) +{ + PyObject *result_key, *result_value; + datum next, result_data; + if (di->dbm != NULL) { + if (di->curr_key.dptr == NULL) + di->curr_key = gdbm_firstkey(di->dbm->di_dbm); + else { + next = gdbm_nextkey(di->dbm->di_dbm, di->curr_key); + free(di->curr_key.dptr); + di->curr_key = next; + } + if (di->curr_key.dptr == NULL) { + PyErr_SetNone(PyExc_StopIteration); + di->dbm = NULL; + return NULL; + } + result_key = PyBytes_FromStringAndSize(di->curr_key.dptr, + di->curr_key.dsize); + if (result_key == NULL) + return NULL; + result_data = gdbm_fetch(di->dbm->di_dbm, di->curr_key); + if (result_data.dptr == 0) { + PyErr_SetObject(PyExc_KeyError, result_key); + Py_DECREF(result_key); + return NULL; + } + result_value = PyBytes_FromStringAndSize(result_data.dptr, + result_data.dsize); + return result_value; + } + else { + //the iteration has already exhaust. + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } +} + +PyTypeObject DbmIterValue_Type= { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "DbmIterValue_Type", /* tp_name */ + sizeof(dbmiterobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)dbmiter_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + 0, /* tp_doc */ + (traverseproc)dbmiter_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + (iternextfunc)dbmiter_iternextvalue, /* tp_iternext */ + 0, /* tp_methods */ + 0, +}; + +static PyObject * +dbmiter_iternextitem(dbmiterobject *di) +{ + PyObject *result_key, *result_value; + datum next, result_data; + PyObject *result; + if (di->dbm != NULL) { + if (di->curr_key.dptr == NULL) + di->curr_key = gdbm_firstkey(di->dbm->di_dbm); + else { + next = gdbm_nextkey(di->dbm->di_dbm, di->curr_key); + free(di->curr_key.dptr); + di->curr_key = next; + } + if (di->curr_key.dptr == NULL) { + PyErr_SetNone(PyExc_StopIteration); + di->dbm = NULL; + return NULL; + } + result_key = PyBytes_FromStringAndSize(di->curr_key.dptr, + di->curr_key.dsize); + if (result_key == NULL) + return NULL; + result_data = gdbm_fetch(di->dbm->di_dbm, di->curr_key); + if (result_data.dptr == 0) { + PyErr_SetObject(PyExc_KeyError, result_key); + Py_DECREF(result_key); + return NULL; + } + result_value = PyBytes_FromStringAndSize(result_data.dptr, + result_data.dsize); + if (result_value == NULL) { + Py_DECREF(result_key); + return NULL; + } + result = PyTuple_New(2); + if (result == NULL) { + Py_DECREF(result_key); + Py_DECREF(result_value); + return NULL; + } + if (PyTuple_SetItem(result, 0, result_key)) { + Py_DECREF(result); + Py_DECREF(result_value); + return NULL; + } + if (PyTuple_SetItem(result, 1, result_value)) { + Py_DECREF(result); + return NULL; + } + return result; + } + else { + //the iteration has already exhaust. + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } +} + +PyTypeObject DbmIterItem_Type= { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "DbmIterItem_Type", /* tp_name */ + sizeof(dbmiterobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)dbmiter_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + 0, /* tp_doc */ + (traverseproc)dbmiter_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + (iternextfunc)dbmiter_iternextitem, /* tp_iternext */ + 0, /* tp_methods */ + 0, +}; + +static PyObject * +dbmiter_new(dbmobject *dbm, PyTypeObject *iter_type) +{ + dbmiterobject *di = PyObject_GC_New(dbmiterobject, iter_type); + if (di == NULL) + return NULL; + Py_INCREF(dbm); + di->dbm = dbm; + di->curr_key.dptr = NULL; + _PyObject_GC_TRACK(di); + return (PyObject *)di; +} + +static PyObject * +dbm_iter(dbmobject *dbm) +{ + return dbmiter_new(dbm, &DbmIterKey_Type); +} + +typedef struct { + PyObject_HEAD + dbmobject *dv_dbm; +} dbmviewobject; + +static void +dbmview_dealloc(dbmviewobject *dv) +{ + Py_XDECREF(dv->dv_dbm); + PyObject_GC_Del(dv); +} + +static Py_ssize_t +dbmview_len(dbmviewobject *dv) +{ + return dbm_length(dv->dv_dbm); +} + +static PyObject * +dbmview_new(PyObject *dbm, PyTypeObject *type) +{ + dbmviewobject *dv; + if (dbm == NULL) { + PyErr_BadInternalCall(); + return NULL; + } + if (!PyDbm_Check(dbm)) { + /* XXX Get rid of this restriction later */ + PyErr_Format(PyExc_TypeError, + "%s() requires a dbm argument, not '%s'", + type->tp_name, dbm->ob_type->tp_name); + return NULL; + } + dv = PyObject_GC_New(dbmviewobject, type); + if (dv == NULL) + return NULL; + Py_INCREF(dbm); + dv->dv_dbm = (dbmobject *)dbm; + _PyObject_GC_TRACK(dv); + return (PyObject *)dv; +} + +/*** dbm_keys ***/ + +static PyObject * +dbmkeys_iter(dbmviewobject *dv) +{ + if (dv->dv_dbm == NULL) { + Py_RETURN_NONE; + } + return dbm_iter(dv->dv_dbm); +} + +static int +dbmkeys_contains(dbmviewobject *dv, PyObject *obj) +{ + if (dv->dv_dbm == NULL) + return 0; + return dbm_contains((PyObject *)dv->dv_dbm, obj); +} + +static PySequenceMethods dbmkeys_as_sequence = { + (lenfunc)dbmview_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + (objobjproc)dbmkeys_contains, /* sq_contains */ +}; + +PyTypeObject PyDbmKeys_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "dbm_keys", /* tp_name */ + sizeof(dbmviewobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)dbmview_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + &dbmkeys_as_sequence, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)dbmkeys_iter, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, +}; + +/* XXX Should return a set or a set view */ +PyDoc_STRVAR(dbm_keys__doc__, +"keys() -> a new view of dbm keys set\n\ +Get a new view of keys set in the database."); + +static PyObject * +dbmkeys_new(PyObject *dbm) +{ + return dbmview_new(dbm, &PyDbmKeys_Type); +} + + +/*** dbm_values ***/ + +static PyObject * +dbmvalues_iter(dbmviewobject *dv) +{ + if (dv->dv_dbm == NULL) { + Py_RETURN_NONE; + } + return dbmiter_new(dv->dv_dbm, &DbmIterValue_Type); +} + +static PySequenceMethods dbmvalues_as_sequence = { + (lenfunc)dbmview_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + 0, /* sq_contains */ +}; + +PyTypeObject PyDbmValues_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "dbm_values", /* tp_name */ + sizeof(dbmviewobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)dbmview_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + &dbmvalues_as_sequence, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)dbmvalues_iter, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, +}; + + +/*** dbm_items ***/ + +static PyObject * +dbmitems_iter(dbmviewobject *dv) +{ + if (dv->dv_dbm == NULL) { + Py_RETURN_NONE; + } + return dbmiter_new(dv->dv_dbm, &DbmIterItem_Type); +} + +static PySequenceMethods dbmitems_as_sequence = { + (lenfunc)dbmview_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + 0, /* sq_contains */ +}; + +PyTypeObject PyDbmItems_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "dbm_items", /* tp_name */ + sizeof(dbmviewobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)dbmview_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + &dbmitems_as_sequence, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)dbmitems_iter, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, +}; + +PyDoc_STRVAR(dbm_items__doc__, +"items() -> a new view of (key, value) tuples set\n\ +Get a view of (key, value) tuples set in the database."); + +static PyObject * +dbmitems_new(PyObject *dbm) +{ + return dbmview_new(dbm, &PyDbmItems_Type); +} + +PyDoc_STRVAR(dbm_values__doc__, +"values() -> a new view of dbm values set\n\ +Get a view of values set in the database."); + +static PyObject * +dbmvalues_new(PyObject *dbm) +{ + return dbmview_new(dbm, &PyDbmValues_Type); +} + static PyMethodDef dbm_methods[] = { {"close", (PyCFunction)dbm_close, METH_NOARGS, dbm_close__doc__}, - {"keys", (PyCFunction)dbm_keys, METH_NOARGS, dbm_keys__doc__}, + {"keys", (PyCFunction)dbmkeys_new, METH_NOARGS, dbm_keys__doc__}, {"firstkey", (PyCFunction)dbm_firstkey,METH_NOARGS, dbm_firstkey__doc__}, {"nextkey", (PyCFunction)dbm_nextkey, METH_VARARGS, dbm_nextkey__doc__}, {"reorganize",(PyCFunction)dbm_reorganize,METH_NOARGS, dbm_reorganize__doc__}, {"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__}, + {"values", (PyCFunction)dbmvalues_new, METH_NOARGS, dbm_values__doc__}, + {"items", (PyCFunction)dbmitems_new, METH_NOARGS, dbm_items__doc__}, + {"pop", (PyCFunction)dbm_pop, METH_VARARGS, dbm_pop__doc__}, + {"popitem", (PyCFunction)dbm_popitem, METH_VARARGS, dbm_popitem__doc__}, + {"clear", (PyCFunction)dbm_clear, METH_VARARGS, dbm_clear__doc__}, + {"update", (PyCFunction)dbm_update, METH_VARARGS|METH_KEYWORDS, + dbm_update__doc__}, {NULL, NULL} /* sentinel */ }; @@ -454,7 +1241,7 @@ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ + (getiterfunc)dbm_iter, /*tp_iter*/ 0, /*tp_iternext*/ dbm_methods, /*tp_methods*/ }; @@ -575,9 +1362,23 @@ PyMODINIT_FUNC PyInit__gdbm(void) { PyObject *m, *d, *s; + PyObject *coll_mod, *mapping_abc; if (PyType_Ready(&Dbmtype) < 0) return NULL; + + if ((coll_mod = PyImport_ImportModule("collections")) == NULL) + return NULL; + mapping_abc = PyObject_GetAttrString(coll_mod, "MutableMapping"); + Py_DECREF(coll_mod); + if (mapping_abc == NULL) + return NULL; + if (PyObject_CallMethod(mapping_abc, "register", "O", &Dbmtype) == NULL) { + Py_DECREF(mapping_abc); + return NULL; + } + Py_DECREF(mapping_abc); + m = PyModule_Create(&_gdbmmodule); if (m == NULL) return NULL; Index: Modules/_dbmmodule.c =================================================================== --- Modules/_dbmmodule.c (revision 88383) +++ Modules/_dbmmodule.c (working copy) @@ -40,6 +40,8 @@ static PyTypeObject Dbmtype; +#define PyDbm_Check(op) PyObject_TypeCheck(op, &Dbmtype) + #define is_dbmobject(v) (Py_TYPE(v) == &Dbmtype) #define check_dbmobject_open(v) if ((v)->di_dbm == NULL) \ { PyErr_SetString(DbmError, "DBM object has already been closed"); \ @@ -47,6 +49,14 @@ static PyObject *DbmError; +static Py_ssize_t dbm_length(dbmobject *dp); +static PyObject * dbm_iter(dbmobject *dbm); +static int dbm_contains(PyObject *self, PyObject *arg); +static PyObject * dbm_values(register dbmobject *dp, PyObject *unused); +static PyObject * dbmkeys_new(PyObject *dbm); +static PyObject * dbmvalues_new(PyObject *dbm); +static PyObject * dbmitems_new(PyObject *dbm); + static PyObject * newdbmobject(char *file, int flags, int mode) { @@ -187,7 +197,7 @@ int err; check_dbmobject_open(dp); - v = PyList_New(0); + v = PySet_New(NULL); if (v == NULL) return NULL; for (key = dbm_firstkey(dp->di_dbm); key.dptr; @@ -197,7 +207,7 @@ Py_DECREF(v); return NULL; } - err = PyList_Append(v, item); + err = PySet_Add(v, item); Py_DECREF(item); if (err != 0) { Py_DECREF(v); @@ -252,7 +262,7 @@ dbm_get(register dbmobject *dp, PyObject *args) { datum key, val; - PyObject *defvalue = Py_None; + PyObject *defvalue = Py_None, *result; char *tmp_ptr; Py_ssize_t tmp_size; @@ -263,8 +273,10 @@ key.dsize = tmp_size; check_dbmobject_open(dp); val = dbm_fetch(dp->di_dbm, key); - if (val.dptr != NULL) - return PyBytes_FromStringAndSize(val.dptr, val.dsize); + if (val.dptr != NULL) { + result = PyBytes_FromStringAndSize(val.dptr, val.dsize); + return result; + } else { Py_INCREF(defvalue); return defvalue; @@ -313,11 +325,368 @@ return defvalue; } +static PyObject * +dbm_values(register dbmobject *dp, PyObject *unused) +{ + register PyObject *v, *item; + datum key, val; + int err; + + check_dbmobject_open(dp); + v = PyList_New(0); + if (v == NULL) + return NULL; + for (key = dbm_firstkey(dp->di_dbm); key.dptr; + key = dbm_nextkey(dp->di_dbm)) { + val = dbm_fetch(dp->di_dbm, key); + assert(val.dptr != NULL); + item = PyBytes_FromStringAndSize(val.dptr, val.dsize); + if (item == NULL) { + Py_DECREF(v); + return NULL; + } + err = PyList_Append(v, item); + Py_DECREF(item); + if (err != 0) { + Py_DECREF(v); + return NULL; + } + } + return v; +} + +static PyObject * +dbm_items(register dbmobject *dp, PyObject *unused) +{ + register PyObject *result, *k, *v, *item; + datum key, val; + int err; + + check_dbmobject_open(dp); + result = PySet_New(NULL); + if (result == NULL) + return NULL; + for (key = dbm_firstkey(dp->di_dbm); key.dptr; + key = dbm_nextkey(dp->di_dbm)) { + assert(val.dptr != NULL); + k = PyBytes_FromStringAndSize(key.dptr, key.dsize); + if (k == NULL) + goto fail; + val = dbm_fetch(dp->di_dbm, key); + v = PyBytes_FromStringAndSize(val.dptr, val.dsize); + if (v == NULL) { + Py_DECREF(k); + goto fail; + } + item = PyTuple_New(2); + if (item == NULL) { + Py_DECREF(k); + Py_DECREF(v); + goto fail; + } + if (PyTuple_SetItem(item, 0, k)) { + Py_DECREF(v); + Py_DECREF(item); + goto fail; + } + if (PyTuple_SetItem(item, 1, v)) { + Py_DECREF(item); + goto fail; + } + err = PySet_Add(result, item); + Py_DECREF(item); + if (err != 0) + goto fail; + } + return result; +fail: + Py_DECREF(result); + return NULL; +} + +static PyObject * +dbm_pop(register dbmobject *dp, PyObject *args) +{ + datum key, val; + PyObject *defvalue = Py_None, *result; + char *tmp_ptr; + Py_ssize_t tmp_size; + + if (!PyArg_ParseTuple(args, "s#|O:get", + &tmp_ptr, &tmp_size, &defvalue)) + return NULL; + key.dptr = tmp_ptr; + key.dsize = tmp_size; + check_dbmobject_open(dp); + val = dbm_fetch(dp->di_dbm, key); + if (val.dptr != NULL) { + result = PyBytes_FromStringAndSize(val.dptr, val.dsize); + if (result == NULL) + return NULL; + dbm_delete(dp->di_dbm, key); + return result; + } + else { + Py_INCREF(defvalue); + return defvalue; + } +} + +static PyObject * +dbm_popitem(register dbmobject *dp, PyObject *unused) +{ + datum k, v; + PyObject *result, *iter, *key, *val; + iter = PyObject_GetIter((PyObject *)dp); + if (iter == NULL) + return NULL; + key = PyIter_Next(iter); + Py_DECREF(iter); + if (key == NULL) { + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_KeyError, "DBM object has no more items"); + return NULL; + } + assert(PyBytes_Check(key)); + if (PyBytes_AsStringAndSize(key, &k.dptr, &k.dsize)) + goto fail1; + v = dbm_fetch(dp->di_dbm, k); + assert(v.dptr != NULL); + val = PyBytes_FromStringAndSize(v.dptr, v.dsize); + if (val == NULL) + goto fail1; + result = PyTuple_New(2); + if (result == NULL) + goto fail2; + if (PyTuple_SetItem(result, 0, key)) { + Py_DECREF(val); + Py_DECREF(result); + return NULL; + } + if (PyTuple_SetItem(result, 1, val)) { + Py_DECREF(result); + return NULL; + } + if (dbm_delete(dp->di_dbm, k) < 0) { + PyErr_SetString(PyExc_ValueError, "Delete key error"); + Py_DECREF(result); + return NULL; + } + return result; +fail1: + Py_DECREF(val); +fail2: + Py_DECREF(key); + return NULL; +} + +static PyObject * +dbm_clear(dbmobject *dp, PyObject *unused) +{ + datum key; + + if (dp == NULL || !is_dbmobject(dp)) { + PyErr_BadInternalCall(); + return NULL; + } + check_dbmobject_open(dp); + + key = dbm_firstkey(dp->di_dbm); + while (key.dptr) { + if (dbm_delete(dp->di_dbm, key) < 0) { + PyErr_SetString(PyExc_ValueError, "Delete key error"); + return NULL; + } + key = dbm_firstkey(dp->di_dbm); + } + Py_RETURN_NONE; +} + +static PyObject * +dbm_update(dbmobject *dp, PyObject *args, PyObject *kwds) +{ + datum krec, drec; + PyObject *arg = NULL; + PyObject *coll_mod, *mapping_abc; + PyObject *arg_iter, *arg_keys, *iter_item, *item_value; + PyObject *key, *value; + Py_ssize_t pos = 0; + + if (dp == NULL || !is_dbmobject(dp)) { + PyErr_BadInternalCall(); + return NULL; + } + check_dbmobject_open(dp); + + if ((coll_mod = PyImport_ImportModule("collections")) == NULL) + return NULL; + if ((mapping_abc = PyObject_GetAttrString(coll_mod, "Mapping")) == + NULL) { + Py_DECREF(coll_mod); + return NULL; + } + Py_DECREF(coll_mod); + + if (!PyArg_UnpackTuple(args, "update", 0, 1, &arg)) + Py_DECREF(mapping_abc); + else if (arg != NULL) { + if (PyObject_IsInstance(arg, mapping_abc)) { + Py_DECREF(mapping_abc); + if ((arg_iter = PyObject_GetIter(arg)) == NULL) + return NULL; + while ((iter_item = PyIter_Next(arg_iter)) != NULL) { + if (!PyArg_Parse(iter_item, "s#", &krec.dptr, &krec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm key can only be buffers."); + Py_DECREF(arg_iter); + Py_DECREF(iter_item); + return NULL; + } + Py_DECREF(iter_item); + if ((item_value = PyObject_GetItem(arg, iter_item)) == NULL) { + Py_DECREF(arg_iter); + return NULL; + } + if (!PyArg_Parse(item_value, "s#", &drec.dptr, &drec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm value can only be buffers."); + Py_DECREF(arg_iter); + Py_DECREF(item_value); + return NULL; + } + Py_DECREF(item_value); + if (dbm_store(dp->di_dbm, krec, drec, DBM_REPLACE) < 0 ) { + dbm_clearerr(dp->di_dbm); + PyErr_SetString(DbmError, "Cannot add item to database"); + Py_DECREF(arg_iter); + return NULL; + } + } + Py_DECREF(arg_iter); + if (PyErr_Occurred()) + return NULL; + } + else if (PyObject_HasAttrString(arg, "keys")) { + Py_DECREF(mapping_abc); + if ((arg_keys = PyObject_CallMethod(arg, "keys", NULL)) == NULL) + return NULL; + if ((arg_iter = PyObject_GetIter(arg_keys)) == NULL) { + Py_DECREF(arg_keys); + return NULL; + } + Py_DECREF(arg_keys); + while ((iter_item = PyIter_Next(arg_iter)) != NULL) { + if (!PyArg_Parse(iter_item, "s#", &krec.dptr, &krec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm key can only be buffers."); + Py_DECREF(arg_iter); + Py_DECREF(iter_item); + return NULL; + } + Py_DECREF(iter_item); + if ((item_value = PyObject_GetItem(arg, iter_item)) == NULL) { + Py_DECREF(arg_iter); + return NULL; + } + if (!PyArg_Parse(item_value, "s#", &drec.dptr, &drec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm value can only be buffers."); + Py_DECREF(arg_iter); + Py_DECREF(item_value); + return NULL; + } + Py_DECREF(item_value); + if (dbm_store(dp->di_dbm, krec, drec, DBM_REPLACE) < 0 ) { + dbm_clearerr(dp->di_dbm); + PyErr_SetString(DbmError, "Cannot add item to database"); + Py_DECREF(arg_iter); + return NULL; + } + } + Py_DECREF(arg_iter); + if (PyErr_Occurred()) + return NULL; + } + else { + Py_DECREF(mapping_abc); + if ((arg_iter = PyObject_GetIter(arg)) == NULL) + return NULL; + while ((iter_item = PyIter_Next(arg_iter)) != NULL) { + if (!PySequence_Check(iter_item) || + PySequence_Size(iter_item) != 2) { + Py_DECREF(iter_item); + Py_DECREF(arg_iter); + return NULL; + } + if ((key = PySequence_GetItem(iter_item, 0)) == NULL) { + Py_DECREF(iter_item); + Py_DECREF(arg_iter); + return NULL; + } + if (!PyArg_Parse(key, "s#", &krec.dptr, &krec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm key can only be buffers."); + Py_DECREF(iter_item); + Py_DECREF(arg_iter); + Py_DECREF(key); + return NULL; + } + Py_DECREF(key); + + if ((value = PySequence_GetItem(iter_item, 1)) == NULL) { + Py_DECREF(iter_item); + Py_DECREF(arg_iter); + return NULL; + } + if (!PyArg_Parse(value, "s#", &drec.dptr, &drec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm value can only be buffers."); + Py_DECREF(arg_iter); + return NULL; + } + Py_DECREF(value); + Py_DECREF(iter_item); + + if (dbm_store(dp->di_dbm, krec, drec, DBM_REPLACE) < 0 ) { + dbm_clearerr(dp->di_dbm); + PyErr_SetString(DbmError, "Cannot add item to database"); + Py_DECREF(arg_iter); + return NULL; + } + } + Py_DECREF(arg_iter); + if (PyErr_Occurred()) + return NULL; + } + } + if (kwds == NULL) + Py_RETURN_NONE; + while (PyDict_Next(kwds, &pos, &key, &value)) { + if (!PyArg_Parse(key, "s#", &krec.dptr, &krec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm key can only be buffers."); + return NULL; + } + if (!PyArg_Parse(value, "s#", &drec.dptr, &drec.dsize)) { + PyErr_SetString(PyExc_TypeError, + "dbm value can only be buffers."); + return NULL; + } + if (dbm_store(dp->di_dbm, krec, drec, DBM_REPLACE) < 0 ) { + dbm_clearerr(dp->di_dbm); + PyErr_SetString(DbmError, "Cannot add item to database"); + return NULL; + } + } + Py_RETURN_NONE; +} + static PyMethodDef dbm_methods[] = { {"close", (PyCFunction)dbm__close, METH_NOARGS, "close()\nClose the database."}, - {"keys", (PyCFunction)dbm_keys, METH_NOARGS, - "keys() -> list\nReturn a list of all keys in the database."}, + {"keys", (PyCFunction)dbmkeys_new, METH_NOARGS, + "keys() -> a new view of dbm keys set.\n" + "Return a new view of keys set in the database."}, {"get", (PyCFunction)dbm_get, METH_VARARGS, "get(key[, default]) -> value\n" "Return the value for key if present, otherwise default."}, @@ -325,9 +694,271 @@ "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."}, + {"values", (PyCFunction)dbmvalues_new, METH_NOARGS, + "values() -> a new view of dbm values set.\n" + "Return a new view of dbm values set in the database."}, + {"items", (PyCFunction)dbmitems_new, METH_NOARGS, + "items() -> a new view of (key1, value1) tuples set\n" + "Return a new view of (key, values) tuples in the database."}, + {"pop", (PyCFunction)dbm_pop, METH_VARARGS, + "pop(key[, default]) -> value\n" + "Return the value for key and delete the key-value pair if present,\n" + "otherwise return default if specified, raise KeyError if not " + "specified."}, + {"popitem", (PyCFunction)dbm_popitem, METH_NOARGS, + "popitem() -> (key, value)\n" + "Return the next (key, value) tuple if there is items in db, otherwise\n" + " raise KeyError."}, + {"clear", (PyCFunction)dbm_clear, METH_NOARGS, + "clear() -> None\n" + "Delete all the items in db."}, + {"update", (PyCFunction)dbm_update, METH_VARARGS | METH_KEYWORDS, + "update([other][, **keywords]) -> None\n" + "Add all the items in other if other is a mapping object, raise TypeError" + " else, as well as those in keywords."}, {NULL, NULL} /* sentinel */ }; +static PyObject * +dbm_iter(dbmobject *dbm) +{ + return PyObject_GetIter(dbm_keys(dbm, NULL)); +} + +typedef struct { + PyObject_HEAD + dbmobject *dv_dbm; +} dbmviewobject; + + +static void +dbmview_dealloc(dbmviewobject *dv) +{ + Py_XDECREF(dv->dv_dbm); + PyObject_GC_Del(dv); +} + +static Py_ssize_t +dbmview_len(dbmviewobject *dv) +{ + return dbm_length(dv->dv_dbm); +} + +static PyObject * +dbmview_new(PyObject *dbm, PyTypeObject *type) +{ + dbmviewobject *dv; + if (dbm == NULL) { + PyErr_BadInternalCall(); + return NULL; + } + if (!PyDbm_Check(dbm)) { + /* XXX Get rid of this restriction later */ + PyErr_Format(PyExc_TypeError, + "%s() requires a dbm argument, not '%s'", + type->tp_name, dbm->ob_type->tp_name); + return NULL; + } + dv = PyObject_GC_New(dbmviewobject, type); + if (dv == NULL) + return NULL; + Py_INCREF(dbm); + dv->dv_dbm = (dbmobject *)dbm; + _PyObject_GC_TRACK(dv); + return (PyObject *)dv; +} + +/*** dbm_keys ***/ + +static PyObject * +dbmkeys_iter(dbmviewobject *dv) +{ + if (dv->dv_dbm == NULL) { + Py_RETURN_NONE; + } + return dbm_iter(dv->dv_dbm); +} + +static int +dbmkeys_contains(dbmviewobject *dv, PyObject *obj) +{ + if (dv->dv_dbm == NULL) + return 0; + return dbm_contains((PyObject *)dv->dv_dbm, obj); +} + +static PySequenceMethods dbmkeys_as_sequence = { + (lenfunc)dbmview_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + (objobjproc)dbmkeys_contains, /* sq_contains */ +}; + +PyTypeObject PyDbmKeys_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "dbm_keys", /* tp_name */ + sizeof(dbmviewobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)dbmview_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + &dbmkeys_as_sequence, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)dbmkeys_iter, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, +}; + +static PyObject * +dbmkeys_new(PyObject *dbm) +{ + return dbmview_new(dbm, &PyDbmKeys_Type); +} + +/*** dbm_values ***/ + +static PyObject * +dbmvalues_iter(dbmviewobject *dv) +{ + if (dv->dv_dbm == NULL) { + Py_RETURN_NONE; + } + return PyObject_GetIter(dbm_values(dv->dv_dbm, NULL)); +} + +static PySequenceMethods dbmvalues_as_sequence = { + (lenfunc)dbmview_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + 0, /* sq_contains */ +}; + +PyTypeObject PyDbmValues_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "dbm_values", /* tp_name */ + sizeof(dbmviewobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)dbmview_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + &dbmvalues_as_sequence, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)dbmvalues_iter, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, +}; + +static PyObject * +dbmvalues_new(PyObject *dbm) +{ + return dbmview_new(dbm, &PyDbmValues_Type); +} + +/*** dbm_items ***/ + +static PyObject * +dbmitems_iter(dbmviewobject *dv) +{ + if (dv->dv_dbm == NULL) { + Py_RETURN_NONE; + } + return PyObject_GetIter(dbm_items(dv->dv_dbm, NULL)); +} + +static PySequenceMethods dbmitems_as_sequence = { + (lenfunc)dbmview_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + 0, /* sq_contains */ +}; + +PyTypeObject PyDbmItems_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "dbm_items", /* tp_name */ + sizeof(dbmviewobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)dbmview_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + &dbmitems_as_sequence, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)dbmitems_iter, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, +}; + +static PyObject * +dbmitems_new(PyObject *dbm) +{ + return dbmview_new(dbm, &PyDbmItems_Type); +} + static PyTypeObject Dbmtype = { PyVarObject_HEAD_INIT(NULL, 0) "_dbm.dbm", @@ -354,7 +985,7 @@ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ + (getiterfunc)dbm_iter, /*tp_iter*/ 0, /*tp_iternext*/ dbm_methods, /*tp_methods*/ }; @@ -412,9 +1043,23 @@ PyMODINIT_FUNC PyInit__dbm(void) { PyObject *m, *d, *s; + PyObject *coll_mod, *mapping_abc; if (PyType_Ready(&Dbmtype) < 0) return NULL; + + if ((coll_mod = PyImport_ImportModule("collections")) == NULL) + return NULL; + mapping_abc = PyObject_GetAttrString(coll_mod, "MutableMapping"); + Py_DECREF(coll_mod); + if (mapping_abc == NULL) + return NULL; + if (PyObject_CallMethod(mapping_abc, "register", "O", &Dbmtype) == NULL) { + Py_DECREF(mapping_abc); + return NULL; + } + Py_DECREF(mapping_abc); + m = PyModule_Create(&_dbmmodule); if (m == NULL) return NULL;