/** python-cdb 0.32 Copyright 2001, 2002 Michael J. Pomraning This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA **/ #include #include #include #include #include "cdb.h" #include "cdb_make.h" #define open_read(x) (open((x),O_RDONLY|O_NDELAY)) /* ala djb's open_foo */ #define VERSION "0.32" #define CDBVERSION "0.75" /* ------------------- cdb object -------------------- */ static char cdbo_object_doc[] = "\ This object represents a CDB database: a reliable, constant\n\ database mapping strings of bytes (\"keys\") to strings of bytes\n\ (\"data\"), and designed for fast lookups.\n\ \n\ Unlike a conventional mapping, CDBs can meaningfully store multiple\n\ records under one key (though this feature is not often used).\n\ \n\ A CDB object 'cdb_o' offers the following interesting attributes:\n\ \n\ Dict-like Lookup Methods:\n\ cdb_o[key], get(key), getnext(), getall(key)\n\ \n\ Key-based Iteration Methods:\n\ keys(), firstkey(), nextkey()\n\ (Key-based iteration returns only distinct keys.)\n\ \n\ Raw Iteration Method:\n\ each()\n\ (\"Dumping\" may return the same key more than once.)\n\ \n\ __members__:\n\ fd - File descriptor of the underlying cdb.\n\ name - Name of the cdb, or None if not known.\n\ size - Size of the cdb, or None if not mmap()d.\n\ \n\ __length__:\n\ len(cdb_o) returns the total number of items in a cdb,\n\ which may or may not exceed the number of distinct keys.\n"; typedef struct { PyObject_HEAD struct cdb c; PyObject * name_py; /* 'filename' or Py_None */ PyObject * getkey; /* squirreled away for getnext() */ uint32 eod; /* as in cdbdump */ uint32 iter_pos; uint32 each_pos; uint32 numrecords; } CdbObject; staticforward PyTypeObject CdbType; PyObject * CDBError; #define CDBerr PyErr_SetFromErrno(CDBError) static PyObject * cdb_pyread(CdbObject *cdb_o, unsigned int len, uint32 pos) { struct cdb *c; PyObject *s = NULL; c = &cdb_o->c; if (c->map) { if ((pos > c->size) || (c->size - pos < len)) goto FORMAT; s = PyString_FromStringAndSize(c->map + pos, len); } else { s = PyString_FromStringAndSize(NULL, len); if (s == NULL) return NULL; if (lseek(c->fd,pos,SEEK_SET) == -1) goto ERRNO; while (len > 0) { int r; char * buf = PyString_AsString(s); do { Py_BEGIN_ALLOW_THREADS r = read(c->fd,buf,len); Py_END_ALLOW_THREADS } while ((r == -1) && (errno == EINTR)); if (r == -1) goto ERRNO; if (r == 0) goto FORMAT; buf += r; len -= r; } } return s; FORMAT: Py_XDECREF(s); PyErr_SetFromErrno(PyExc_RuntimeError); return NULL; ERRNO: Py_XDECREF(s); return CDBerr; } #define CDBO_CURDATA(x) (cdb_pyread(x, x->c.dlen, x->c.dpos)) /* ------------------- CdbObject methods -------------------- */ static char cdbo_has_key_doc[] = "cdb_o.has_key(k) -> 1 (or 0)\n\ \n\ Returns true if the CDB contains key k."; static PyObject * cdbo_has_key(CdbObject *self, PyObject *args) { char * key; unsigned int klen; int r; if (!PyArg_ParseTuple(args, "s#", &key, &klen)) return NULL; r = cdb_find(&self->c, key, klen); if (r == -1) return CDBerr; return Py_BuildValue("i", r); } static char cdbo_get_doc[] = "cdb_o.get(k [, i]) -> data (or None)\n\ \n\ Fetches the record stored under key k, skipping past the first i\n\ records under that key (default: 0). Prepares the next call to\n\ getnext().\n\ \n\ Assuming cdb_o.has_key(k) == 1, then all of the following return:\n\ the first record stored under key k:\n\ \n\ cdb_o.get(k) == cdb_o[k] == cdb_o.getall(k)[0]\n"; static PyObject * cdbo_get(CdbObject *self, PyObject *args) { char * key; unsigned int klen; int r; int i = 0; if (!PyArg_ParseTuple(args, "s#|i:get", &key, &klen, &i)) return NULL; cdb_findstart(&self->c); for (;;) { r = cdb_findnext(&self->c, key, klen); if (r == -1) return CDBerr; if (!r) return Py_BuildValue(""); if (!i) break; --i; } /* prep. possibly ensuing call to getnext() */ Py_XDECREF(self->getkey); self->getkey = PyString_FromStringAndSize(key, klen); if (self->getkey == NULL) return NULL; return CDBO_CURDATA(self); } static char cdbo_getall_doc[] = "cdb_o.getall(k) -> ['data', ... ]\n\ \n\ Return a list of all records stored under key k."; static PyObject * cdbo_getall(CdbObject *self, PyObject *args) { PyObject * list, * data; char * key; unsigned int klen; int r, err; if (!PyArg_ParseTuple(args, "s#:getall", &key, &klen)) return NULL; list = PyList_New(0); if (list == NULL) return NULL; cdb_findstart(&self->c); while ((r = cdb_findnext(&self->c, key, klen))) { if (r == -1) { Py_DECREF(list); return CDBerr; } data = CDBO_CURDATA(self); if (data == NULL) { Py_DECREF(list); return NULL; } err = PyList_Append(list, data); Py_DECREF(data); if (err != 0) { Py_DECREF(list); return NULL; } } return list; } static char cdbo_getnext_doc[] = "cdb_o.getnext() -> 'data' (or None)\n\ \n\ For iteration over the records stored under one key, avoiding loading\n\ all items into memory). The \"current key\" is determined by the most\n\ recent call to get().\n\ \n\ The following loops through all items stored under key k:\n\ \n\ ## cdb_o.getall(k) possibly too big for memory\n\ rec = cdb_o.get(k)\n\ while rec is not None:\n\ do_something(rec)\n\ rec = cdb_o.getnext()\n"; static PyObject * cdbo_getnext(CdbObject *self, PyObject *args) { if (!PyArg_ParseTuple(args, ":getnext")) return NULL; if (self->getkey == NULL) { PyErr_SetString(PyExc_TypeError, "getnext() called without first calling get()"); return NULL; } switch(cdb_findnext(&self->c, PyString_AsString(self->getkey), PyString_Size(self->getkey))) { case -1: return CDBerr; case 0: Py_DECREF(self->getkey); self->getkey = NULL; return Py_BuildValue(""); default: return CDBO_CURDATA(self); } /* not reached */ } uint32 _cdbo_init_eod(CdbObject *self) { char nonce[4]; if (cdb_read(&self->c, nonce, 4, 0) == -1) return 0; uint32_unpack(nonce, &self->eod); return self->eod; } /* * _cdbo_keyiter(cdb_o) * * Whiz-bang all-in-one: * extract current record * compare current pos to pos implied by cdb_find(current_key) * (Different? adv. iter cursor, loop and try again) * advance iteration cursor * return key */ static PyObject * _cdbo_keyiter(CdbObject *self) { PyObject *key; char buf[8]; uint32 klen, dlen; if (! self->eod) _cdbo_init_eod(self); while (self->iter_pos < self->eod) { if (cdb_read(&self->c, buf, 8, self->iter_pos) == -1) return CDBerr; uint32_unpack(buf, &klen); uint32_unpack(buf+4, &dlen); key = cdb_pyread(self, klen, self->iter_pos + 8); if (key == NULL) return NULL; switch(cdb_find(&self->c,PyString_AsString(key),PyString_Size(key))) { case -1: Py_DECREF(key); key = NULL; return CDBerr; case 0: /* bizarre, impossible? PyExc_RuntimeError? */ PyErr_SetString(PyExc_KeyError, PyString_AS_STRING((PyStringObject *) key)); Py_DECREF(key); key = NULL; default: if (key == NULL) /* already raised error */ return NULL; if (cdb_datapos(&self->c) == self->iter_pos + klen + 8) { /** first occurrence of key in the cdb **/ self->iter_pos += 8 + klen + dlen; return key; } Py_DECREF(key); /* better luck next time around */ self->iter_pos += 8 + klen + dlen; } } return Py_BuildValue(""); /* iter_pos >= eod; we're done */ } static char cdbo_keys_doc[] = "cdb_o.keys() -> ['k1', 'k2', ... ]\n\ \n\ Returns a list of all (distinct) keys in the database."; static PyObject * cdbo_keys(CdbObject *self, PyObject *args) { PyObject *r, *key; uint32 pos; int err; if (! PyArg_ParseTuple(args, "")) return NULL; r = PyList_New(0); if (r == NULL) return NULL; pos = self->iter_pos; /* don't interrupt a manual iteration */ self->iter_pos = 2048; key = _cdbo_keyiter(self); while (key != Py_None) { err = PyList_Append(r, key); Py_DECREF(key); if (err != 0) { Py_DECREF(r); self->iter_pos = pos; return NULL; } key = _cdbo_keyiter(self); } Py_DECREF(key); self->iter_pos = pos; return r; } static char cdbo_firstkey_doc[] = "cdb_o.firstkey() -> key (or None)\n\ \n\ Return the first key in the database, resetting the internal\n\ iteration cursor. firstkey() and nextkey() may be used to\n\ traverse all distinct keys in the cdb. See each() for raw\n\ iteration."; static PyObject * cdbo_firstkey(CdbObject *self, PyObject *args) { if (! PyArg_ParseTuple(args, ":firstkey")) return NULL; self->iter_pos = 2048; return _cdbo_keyiter(self); } static char cdbo_nextkey_doc[] = "cdb_o.nextkey() -> key (or None)\n\ \n\ Return the next distinct key in the cdb.\n\ \n\ The following code walks the CDB one key at a time:\n\ \n\ key = cdb_o.firstkey()\n\ while key is not None:\n\ print key\n\ key = cdb_o.nextkey()\n"; static PyObject * cdbo_nextkey(CdbObject *self, PyObject *args) { if (! PyArg_ParseTuple(args, ":nextkey")) return NULL; return _cdbo_keyiter(self); } static char cdbo_each_doc[] = "cdb_o.each() -> (key, data) (or None)\n\ \n\ Fetch the next ('key', 'data') record from the underlying cdb file,\n\ returning None and resetting the iteration cursor when all records\n\ have been fetched.\n\ \n\ Keys appear with each item under them -- e.g., (key,foo), (key2,bar),\n\ (key,baz) -- order of records is determined by actual position on\n\ disk. Both keys() and (for GDBM fanciers) firstkey()/nextkey()-style\n\ iteration go to pains to present the user with only distinct keys."; static PyObject * cdbo_each(CdbObject *self, PyObject *args) { PyObject *tup, *key, *dat; char buf[8]; uint32 klen, dlen; if (! PyArg_ParseTuple(args, ":each")) return NULL; tup = PyTuple_New(2); if (tup == NULL) return NULL; if (! self->eod) (void) _cdbo_init_eod(self); if (self->each_pos >= self->eod) { /* all done, reset cursor */ self->each_pos = 2048; Py_INCREF(Py_None); return Py_None; } if (cdb_read(&self->c, buf, 8, self->each_pos) == -1) return CDBerr; uint32_unpack(buf, &klen); uint32_unpack(buf+4, &dlen); key = cdb_pyread(self, klen, self->each_pos + 8); dat = cdb_pyread(self, dlen, self->each_pos + 8 + klen); self->each_pos += klen + dlen + 8; if (key == NULL || dat == NULL) { Py_XDECREF(key); Py_XDECREF(dat); Py_DECREF(tup); return NULL; } if (PyTuple_SetItem(tup, 0, key) || PyTuple_SetItem(tup, 1, dat)) { Py_DECREF(key); Py_DECREF(dat); Py_DECREF(tup); return NULL; } return tup; } /*** cdb object as mapping ***/ static int cdbo_length(CdbObject *self) { if (! self->numrecords) { char buf[8]; uint32 pos, klen, dlen; pos = 2048; if (! self->eod) (void) _cdbo_init_eod(self); while (pos < self->eod) { if (cdb_read(&self->c, buf, 8, pos) == -1) return -1; uint32_unpack(buf, &klen); uint32_unpack(buf + 4, &dlen); pos += 8 + klen + dlen; self->numrecords++; } } return (int) self->numrecords; } static PyObject * cdbo_subscript(CdbObject *self, PyObject *k) { char * key; int klen; if (! PyArg_Parse(k, "s#", &key, &klen)) return NULL; switch(cdb_find(&self->c, key, (unsigned int)klen)) { case -1: return CDBerr; case 0: PyErr_SetString(PyExc_KeyError, PyString_AS_STRING((PyStringObject *) k)); return NULL; default: return CDBO_CURDATA(self); } /* not reached */ } static PyMappingMethods cdbo_as_mapping = { (inquiry)cdbo_length, (binaryfunc)cdbo_subscript, (objobjargproc)0 }; static PyMethodDef cdb_methods[] = { {"get", (PyCFunction)cdbo_get, METH_VARARGS, cdbo_get_doc }, {"getnext", (PyCFunction)cdbo_getnext, METH_VARARGS, cdbo_getnext_doc }, {"getall", (PyCFunction)cdbo_getall, METH_VARARGS, cdbo_getall_doc }, {"has_key", (PyCFunction)cdbo_has_key, METH_VARARGS, cdbo_has_key_doc }, {"keys", (PyCFunction)cdbo_keys, METH_VARARGS, cdbo_keys_doc }, {"firstkey", (PyCFunction)cdbo_firstkey, METH_VARARGS, cdbo_firstkey_doc }, {"nextkey", (PyCFunction)cdbo_nextkey, METH_VARARGS, cdbo_nextkey_doc }, {"each", (PyCFunction)cdbo_each, METH_VARARGS, cdbo_each_doc }, { NULL, NULL } }; /* ------------------- cdb operations -------------------- */ static PyObject * _wrap_cdb_init(int fd) { /* constructor implementation */ CdbObject *self; self = PyObject_NEW(CdbObject, &CdbType); if (self == NULL) return NULL; self->c.map = 0; /* break encapsulation -- cdb struct init'd to zero */ cdb_init(&self->c, fd); self->iter_pos = 2048; self->each_pos = 2048; self->numrecords = 0; self->eod = 0; self->getkey = NULL; return (PyObject *) self; } static PyObject * cdbo_constructor(PyObject *ignore, PyObject *args) { PyObject *self; PyObject *f; PyObject *name_attr = Py_None; int fd; if (! PyArg_ParseTuple(args, "O:new", &f)) return NULL; if (PyString_Check(f)) { if ((fd = open_read(PyString_AsString(f))) == -1) return CDBerr; name_attr = f; } else if (PyInt_Check(f)) { fd = (int) PyInt_AsLong(f); } else { PyErr_SetString(PyExc_TypeError, "expected filename or file descriptor"); return NULL; } self = _wrap_cdb_init(fd); if (self == NULL) return NULL; ((CdbObject *)self)->name_py = name_attr; Py_INCREF(name_attr); return self; } static void cdbo_dealloc(CdbObject *self) { /* del(cdb_o) */ if (self->name_py != NULL) { /* if cdb_o.name is not None: we open()d it ourselves, so close it */ if (PyString_Check(self->name_py)) close(self->c.fd); Py_DECREF(self->name_py); } Py_XDECREF(self->getkey); cdb_free(&self->c); PyMem_DEL(self); } static PyObject * cdbo_getattr(CdbObject *self, char *name) { PyObject * r; r = Py_FindMethod(cdb_methods, (PyObject *) self, name); if (r != NULL) return r; PyErr_Clear(); if (!strcmp(name,"__members__")) return Py_BuildValue("[sss]", "fd", "name", "size"); if (!strcmp(name,"fd")) { return Py_BuildValue("i", self->c.fd); /* cdb_o.fd */ } if (!strcmp(name,"name")) { Py_INCREF(self->name_py); return self->name_py; /* cdb_o.name */ } if (!strcmp(name,"size")) { /* cdb_o.size */ return self->c.map ? /** mmap()d ? stat.st_size : None **/ Py_BuildValue("l", (long) self->c.size) : Py_BuildValue(""); } PyErr_SetString(PyExc_AttributeError, name); return NULL; } /* ----------------- cdbmake object ------------------ */ static char cdbmake_object_doc[] = "cdbmake objects resemble the struct cdb_make interface:\n\ \n\ CDB Construction Methods:\n\ add(k, v), finish()\n\ \n\ __members__:\n\ fd - fd of underlying CDB, or -1 if finish()ed\n\ fn, fntmp - as from the cdb package's cdbmake utility\n\ numentries - current number of records add()ed\n"; typedef struct { PyObject_HEAD struct cdb_make cm; PyObject * fn; PyObject * fntmp; } cdbmakeobject; staticforward PyTypeObject CdbMakeType; #define CDBMAKEerr PyErr_SetFromErrno(PyExc_IOError) /* ----------------- CdbMake methods ------------------ */ static PyObject * CdbMake_add(cdbmakeobject *self, PyObject *args) { char * key, * dat; unsigned int klen, dlen; if (!PyArg_ParseTuple(args,"s#s#:add",&key,&klen,&dat,&dlen)) return NULL; if (cdb_make_add(&self->cm, key, klen, dat, dlen) == -1) return CDBMAKEerr; return Py_BuildValue(""); } static PyObject * CdbMake_finish(cdbmakeobject *self, PyObject *args) { if (!PyArg_ParseTuple(args, ":finish")) return NULL; if (cdb_make_finish(&self->cm) == -1) return CDBMAKEerr; /* cleanup as in cdb dist's cdbmake */ if (fsync(fileno(self->cm.fp)) == -1) return CDBMAKEerr; if (fclose(self->cm.fp) != 0) return CDBMAKEerr; self->cm.fp = NULL; if (rename(PyString_AsString(self->fntmp), PyString_AsString(self->fn)) == -1) return CDBMAKEerr; return Py_BuildValue(""); } static PyMethodDef cdbmake_methods[] = { {"add", (PyCFunction)CdbMake_add, METH_VARARGS, "cm.add(key, data) -> None\n\ \n\ Add 'key' -> 'data' pair to the underlying CDB." }, {"finish", (PyCFunction)CdbMake_finish, METH_VARARGS, "cm.finish() -> None\n\ \n\ Finish safely composing a new CDB, renaming cm.fntmp to\n\ cm.fn." }, { NULL, NULL } }; /* ----------------- cdbmake operations ------------------ */ static PyObject * new_cdbmake(PyObject *ignore, PyObject *args) { cdbmakeobject *self; PyObject *fn, *fntmp; FILE * f; if (! PyArg_ParseTuple(args, "SS|i", &fn, &fntmp)) return NULL; f = fopen(PyString_AsString(fntmp), "w+b"); if (f == NULL) { return CDBMAKEerr; } self = PyObject_NEW(cdbmakeobject, &CdbMakeType); if (self == NULL) return NULL; self->fn = fn; Py_INCREF(self->fn); self->fntmp = fntmp; Py_INCREF(fntmp); if (cdb_make_start(&self->cm, f) == -1) { Py_DECREF(self); CDBMAKEerr; return NULL; } return (PyObject *) self; } static void cdbmake_dealloc(cdbmakeobject *self) { Py_XDECREF(self->fn); if (self->fntmp != NULL) { if (self->cm.fp != NULL) { fclose(self->cm.fp); unlink(PyString_AsString(self->fntmp)); } Py_DECREF(self->fntmp); } PyMem_DEL(self); } static PyObject * cdbmake_getattr(cdbmakeobject *self, char *name) { if (!strcmp(name,"__members__")) return Py_BuildValue("[ssss]", "fd", "fn", "fntmp", "numentries"); if (!strcmp(name,"fd")) return Py_BuildValue("i", fileno(self->cm.fp)); /* self.fd */ if (!strcmp(name,"fn")) { Py_INCREF(self->fn); return self->fn; /* self.fn */ } if (!strcmp(name,"fntmp")) { Py_INCREF(self->fntmp); return self->fntmp; /* self.fntmp */ } if (!strcmp(name,"numentries")) return Py_BuildValue("l", self->cm.numentries); /* self.numentries */ return Py_FindMethod(cdbmake_methods, (PyObject *) self, name); } /* ---------------- Type delineation -------------------- */ statichere PyTypeObject CdbType = { /* The ob_type field must be initialized in the module init function * to be portable to Windows without using C++. */ PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "cdb", /*tp_name*/ sizeof(CdbObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)cdbo_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ (getattrfunc)cdbo_getattr, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ &cdbo_as_mapping, /*tp_as_mapping*/ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ 0, /*tp_xxx4*/ cdbo_object_doc, /*tp_doc*/ }; statichere PyTypeObject CdbMakeType = { /* The ob_type field must be initialized in the module init function * to be portable to Windows without using C++. */ PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "cdbmake", /*tp_name*/ sizeof(cdbmakeobject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)cdbmake_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ (getattrfunc)cdbmake_getattr, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ 0, /*tp_xxx4*/ cdbmake_object_doc, /*tp_doc*/ }; /* ---------------- exported functions ------------------ */ static PyObject * _wrap_cdb_hash(PyObject *ignore, PyObject *args) { char *s; int sz; if (! PyArg_ParseTuple(args, "s#:hash", &s, &sz)) return NULL; return Py_BuildValue("l", cdb_hash(s, (unsigned int) sz)); } /* ---------------- cdb Module -------------------- */ static PyMethodDef module_functions[] = { {"init", cdbo_constructor, METH_VARARGS, "cdb.init(f) -> cdb_object\n\ \n\ Open a CDB specified by f and return a cdb object.\n\ f may be a filename or an integral file descriptor\n\ (e.g., init( sys.stdin.fileno() )...)."}, {"cdbmake", new_cdbmake, METH_VARARGS, "cdb.cdbmake(cdb, tmp) -> cdbmake_object\n\ \n\ Interface to the creation of a new CDB file \"cdb\".\n\ \n\ The cdbmake object first writes records to the temporary file\n\ \"tmp\" (records are inserted via the object's add() method).\n\ The finish() method then atomically renames \"tmp\" to \"cdb\",\n\ ensuring that readers of \"cdb\" need never wait for updates to\n\ complete." }, {"hash", _wrap_cdb_hash, METH_VARARGS, "hash(s) -> hashval\n\ \n\ Compute the 32-bit hash value of some sequence of bytes s."}, {NULL, NULL} }; static char module_doc[] = "Python adaptation of D. J. Bernstein's constant database (CDB)\n\ package. See \n\ \n\ CDB objects, created by init(), provide read-only, dict-like\n\ access to cdb files, as well as iterative methods.\n\ \n\ CDBMake objects, created by cdbmake(), allow for creation and\n\ atomic replacement of CDBs.\n\ \n\ This module defines a new Exception \"error\"."; DL_EXPORT(void) initcdb() { PyObject *m, *d, *v; CdbType.ob_type = &PyType_Type; CdbMakeType.ob_type = &PyType_Type; m = Py_InitModule3("cdb", module_functions, module_doc); d = PyModule_GetDict(m); CDBError = PyErr_NewException("cdb.error", NULL, NULL); PyDict_SetItemString(d, "error", CDBError); PyDict_SetItemString(d, "__version__", v = PyString_FromString(VERSION)); PyDict_SetItemString(d, "__cdb_version__", v = PyString_FromString(CDBVERSION)); Py_XDECREF(v); }