diff --git a/psycopg/error.h b/psycopg/error.h new file mode 100644 index 00000000..f6551d50 --- /dev/null +++ b/psycopg/error.h @@ -0,0 +1,40 @@ +/* error.h - definition for the psycopg base Error type + * + * Copyright (C) 2013 Daniele Varrazzo + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 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 Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_ERROR_H +#define PSYCOPG_ERROR_H 1 + +extern HIDDEN PyTypeObject ErrorType; + +typedef struct { + PyBaseExceptionObject exc; + + PyObject *pgerror; + PyObject *pgcode; + cursorObject *cursor; + PGresult *result; +} PsycoErrorObject; + +#endif /* PSYCOPG_ERROR_H */ diff --git a/psycopg/error_type.c b/psycopg/error_type.c new file mode 100644 index 00000000..cc1a5c0c --- /dev/null +++ b/psycopg/error_type.c @@ -0,0 +1,277 @@ +/* error_type.c - python interface to the Error objects + * + * Copyright (C) 2013 Daniele Varrazzo + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 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 Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/error.h" +#include "psycopg/diagnostics.h" +#include "psycopg/pqpath.h" + + +static const char pgerror_doc[] = + "The error message returned by the backend, if available, else None"; + +static const char pgcode_doc[] = + "The error code returned by the backend, if available, else None"; + +static const char cursor_doc[] = + "The cursor that raised the exception, if available, else None"; + +static PyMemberDef error_members[] = { + { "pgerror", T_OBJECT, offsetof(PsycoErrorObject, pgerror), + READONLY, (char *)pgerror_doc }, + { "pgcode", T_OBJECT, offsetof(PsycoErrorObject, pgcode), + READONLY, (char *)pgcode_doc }, + { "cursor", T_OBJECT, offsetof(PsycoErrorObject, cursor), + READONLY, (char *)cursor_doc }, + { NULL } +}; + +static PyObject * +error_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + return ((PyTypeObject *)PyExc_StandardError)->tp_new( + type, args, kwargs); +} + +static int +error_init(PsycoErrorObject *self, PyObject *args, PyObject *kwargs) +{ + if (((PyTypeObject *)PyExc_StandardError)->tp_init( + (PyObject *)self, args, kwargs) < 0) { + return -1; + } + Py_CLEAR(self->pgerror); + Py_CLEAR(self->pgcode); + Py_CLEAR(self->cursor); + IFCLEARPGRES(self->result); + return 0; +} + +static int +error_traverse(PsycoErrorObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->pgerror); + Py_VISIT(self->pgcode); + Py_VISIT(self->cursor); + return ((PyTypeObject *)PyExc_StandardError)->tp_traverse( + (PyObject *)self, visit, arg); +} + +static int +error_clear(PsycoErrorObject *self) +{ + Py_CLEAR(self->pgerror); + Py_CLEAR(self->pgcode); + Py_CLEAR(self->cursor); + IFCLEARPGRES(self->result); + return ((PyTypeObject *)PyExc_StandardError)->tp_clear((PyObject *)self); +} + +static void +error_dealloc(PsycoErrorObject *self) +{ + error_clear(self); + return Py_TYPE(self)->tp_free((PyObject *)self); +} + + +static PyObject * +error_get_diag(PsycoErrorObject *self, void *closure) +{ + return PyObject_CallFunctionObjArgs( + (PyObject *)&diagnosticsType, (PyObject *)self, NULL); +} + +static struct PyGetSetDef error_getsets[] = { + { "diag", (getter)error_get_diag, NULL, + "Database error diagnostics" }, + {NULL} +}; + + +#if PY_VERSION_HEX >= 0x02050000 + +/* Error.__reduce__ + * + * The method is required to make exceptions picklable: set the cursor + * attribute to None. Only working from Py 2.5: previous versions + * would require implementing __getstate__, and as of 2012 it's a little + * bit too late to care. */ +static PyObject * +psyco_error_reduce(PsycoErrorObject *self) +{ + PyObject *meth = NULL; + PyObject *tuple = NULL; + PyObject *dict = NULL; + PyObject *rv = NULL; + + if (!(meth = PyObject_GetAttrString(PyExc_StandardError, "__reduce__"))) { + goto error; + } + if (!(tuple = PyObject_CallFunctionObjArgs(meth, self, NULL))) { + goto error; + } + + /* tuple is (type, args): convert it to (type, args, dict) + * with our extra items in the dict. + * + * If these checks fail, we can still return a valid object. Pickle + * will likely fail downstream, but there's nothing else we can do here */ + if (!PyTuple_Check(tuple)) { goto exit; } + if (2 != PyTuple_GET_SIZE(tuple)) { goto exit; } + + if (!(dict = PyDict_New())) { goto error; } + if (0 != PyDict_SetItemString(dict, "pgerror", self->pgerror)) { goto error; } + if (0 != PyDict_SetItemString(dict, "pgcode", self->pgcode)) { goto error; } + + { + PyObject *newtuple; + if (!(newtuple = PyTuple_Pack(3, + PyTuple_GET_ITEM(tuple, 0), + PyTuple_GET_ITEM(tuple, 1), + dict))) { + goto error; + } + Py_DECREF(tuple); + tuple = newtuple; + } + +exit: + rv = tuple; + tuple = NULL; + +error: + Py_XDECREF(dict); + Py_XDECREF(tuple); + Py_XDECREF(meth); + + return rv; +} + +PyObject * +psyco_error_setstate(PsycoErrorObject *self, PyObject *state) +{ + PyObject *rv = NULL; + + /* we don't call the StandartError's setstate as it would try to load the + * dict content as attributes */ + + if (state == Py_None) { + goto exit; + } + if (!PyDict_Check(state)) { + PyErr_SetString(PyExc_TypeError, "state is not a dictionary"); + goto error; + } + + /* load the dict content in the structure */ + Py_CLEAR(self->pgerror); + self->pgerror = PyDict_GetItemString(state, "pgerror"); + Py_XINCREF(self->pgerror); + + Py_CLEAR(self->pgcode); + self->pgcode = PyDict_GetItemString(state, "pgcode"); + Py_XINCREF(self->pgcode); + + Py_CLEAR(self->cursor); + /* We never expect a cursor in the state as it's not picklable. + * at most there could be a None here, coming from Psycopg < 2.5 */ + +exit: + rv = Py_None; + Py_INCREF(rv); + +error: + return rv; +} + +#endif /* PY_VERSION_HEX >= 0x02050000 */ + +static PyMethodDef error_methods[] = { +#if PY_VERSION_HEX >= 0x02050000 + /* Make Error and all its subclasses picklable. + * + * TODO: this comment applied to the __reduce_ex__ implementation: now + * pickling may work on Py 2.4 too... but it's 2013 now. + * + * Don't do it it on Py 2.4: [__reduce_ex__] it is not used by the pickle + * protocol, and if called manually fails in an unsettling way, + * probably because the exceptions were old-style classes. */ + {"__reduce__", (PyCFunction)psyco_error_reduce, METH_NOARGS }, + {"__setstate__", (PyCFunction)psyco_error_setstate, METH_O }, +#endif + {NULL} +}; + + +PyTypeObject ErrorType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.Error", + sizeof(PsycoErrorObject), + 0, + (destructor)error_dealloc, /* tp_dealloc */ + 0, /*tp_print*/ + 0, /*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*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, /*tp_flags*/ + Error_doc, /*tp_doc*/ + (traverseproc)error_traverse, /*tp_traverse*/ + (inquiry)error_clear, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + error_methods, /*tp_methods*/ + error_members, /*tp_members*/ + error_getsets, /*tp_getset*/ + 0, /*tp_base Will be set to StandardError in module init */ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)error_init, /*tp_init*/ + 0, /*tp_alloc will be set to PyType_GenericAlloc in module init*/ + error_new, /*tp_new*/ + 0, /*tp_free Low-level free-memory routine */ + 0, /*tp_is_gc For PyObject_IS_GC */ + 0, /*tp_bases*/ + 0, /*tp_mro method resolution order */ + 0, /*tp_cache*/ + 0, /*tp_subclasses*/ + 0 /*tp_weaklist*/ +}; diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index 34985254..1b4b0d6f 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -35,6 +35,7 @@ #include "psycopg/typecast.h" #include "psycopg/microprotocols.h" #include "psycopg/microprotocols_proto.h" +#include "psycopg/error.h" #include "psycopg/diagnostics.h" #include "psycopg/adapter_qstring.h" @@ -409,8 +410,8 @@ static struct { PyObject **base; const char *docstr; } exctable[] = { - { "psycopg2.Error", &Error, 0, Error_doc }, - { "psycopg2.Warning", &Warning, 0, Warning_doc }, + { "psycopg2.Error", &Error, &PyExc_StandardError, Error_doc }, + { "psycopg2.Warning", &Warning, &PyExc_StandardError, Warning_doc }, { "psycopg2.InterfaceError", &InterfaceError, &Error, InterfaceError_doc }, { "psycopg2.DatabaseError", &DatabaseError, &Error, DatabaseError_doc }, { "psycopg2.InternalError", &InternalError, &DatabaseError, InternalError_doc }, @@ -434,61 +435,6 @@ static struct { }; -#if PY_VERSION_HEX >= 0x02050000 - -/* Error.__reduce_ex__ - * - * The method is required to make exceptions picklable: set the cursor - * attribute to None. Only working from Py 2.5: previous versions - * would require implementing __getstate__, and as of 2012 it's a little - * bit too late to care. */ -static PyObject * -psyco_error_reduce_ex(PyObject *self, PyObject *args) -{ - PyObject *proto = NULL; - PyObject *super = NULL; - PyObject *tuple = NULL; - PyObject *dict = NULL; - PyObject *rv = NULL; - - /* tuple = Exception.__reduce_ex__(self, proto) */ - if (!PyArg_ParseTuple(args, "O", &proto)) { - goto error; - } - if (!(super = PyObject_GetAttrString(PyExc_Exception, "__reduce_ex__"))) { - goto error; - } - if (!(tuple = PyObject_CallFunctionObjArgs(super, self, proto, NULL))) { - goto error; - } - - /* tuple[2]['cursor'] = None - * - * If these checks fail, we can still return a valid object. Pickle - * will likely fail downstream, but there's nothing else we can do here */ - if (!PyTuple_Check(tuple)) { goto exit; } - if (3 > PyTuple_GET_SIZE(tuple)) { goto exit; } - dict = PyTuple_GET_ITEM(tuple, 2); /* borrowed */ - if (!PyDict_Check(dict)) { goto exit; } - - /* Modify the tuple inplace and return it */ - if (0 != PyDict_SetItemString(dict, "cursor", Py_None)) { - goto error; - } - -exit: - rv = tuple; - tuple = NULL; - -error: - Py_XDECREF(tuple); - Py_XDECREF(super); - - return rv; -} - -#endif /* PY_VERSION_HEX >= 0x02050000 */ - static int psyco_errors_init(void) { @@ -498,18 +444,13 @@ psyco_errors_init(void) int i; PyObject *dict = NULL; - PyObject *base; PyObject *str = NULL; - PyObject *descr = NULL; - PyObject *diag_property = NULL; int rv = -1; -#if PY_VERSION_HEX >= 0x02050000 - static PyMethodDef psyco_error_reduce_ex_def = - {"__reduce_ex__", psyco_error_reduce_ex, METH_VARARGS, "pickle helper"}; -#endif + /* 'Error' has been defined elsewhere: only init the other classes */ + Error = (PyObject *)&ErrorType; - for (i=0; exctable[i].name; i++) { + for (i = 1; exctable[i].name; i++) { if (!(dict = PyDict_New())) { goto exit; } if (exctable[i].docstr) { @@ -518,58 +459,16 @@ psyco_errors_init(void) Py_CLEAR(str); } - if (exctable[i].base == 0) { - #if PY_MAJOR_VERSION < 3 - base = PyExc_StandardError; - #else - /* StandardError is gone in 3.0 */ - base = NULL; - #endif - } - else - base = *exctable[i].base; - if (!(*exctable[i].exc = PyErr_NewException( - exctable[i].name, base, dict))) { + exctable[i].name, *exctable[i].base, dict))) { goto exit; } Py_CLEAR(dict); } - /* Make pgerror, pgcode and cursor default to None on psycopg - error objects. This simplifies error handling code that checks - these attributes. */ - PyObject_SetAttrString(Error, "pgerror", Py_None); - PyObject_SetAttrString(Error, "pgcode", Py_None); - PyObject_SetAttrString(Error, "cursor", Py_None); - - if (!(diag_property = PyObject_CallFunctionObjArgs( - (PyObject *) &PyProperty_Type, &diagnosticsType, NULL))) { - goto exit; - } - PyObject_SetAttrString(Error, "diag", diag_property); - - /* install __reduce_ex__ on Error to make all the subclasses picklable. - * - * Don't install it on Py 2.4: it is not used by the pickle - * protocol, and if called manually fails in an unsettling way, - * probably because the exceptions were old-style classes. */ -#if PY_VERSION_HEX >= 0x02050000 - if (!(descr = PyDescr_NewMethod((PyTypeObject *)Error, - &psyco_error_reduce_ex_def))) { - goto exit; - } - if (0 != PyObject_SetAttrString(Error, - psyco_error_reduce_ex_def.ml_name, descr)) { - goto exit; - } -#endif - rv = 0; exit: - Py_XDECREF(diag_property); - Py_XDECREF(descr); Py_XDECREF(str); Py_XDECREF(dict); return rv; @@ -636,22 +535,22 @@ psyco_set_error(PyObject *exc, cursorObject *curs, const char *msg, return; } - if (err) { + if (err && PyObject_TypeCheck(err, &ErrorType)) { + PsycoErrorObject *perr = (PsycoErrorObject *)err; if (curs) { - PyObject_SetAttrString(err, "cursor", (PyObject *)curs); + Py_INCREF(curs); + perr->cursor = curs; } if (pgerror) { if ((t = conn_text_from_chars(conn, pgerror))) { - PyObject_SetAttrString(err, "pgerror", t); - Py_DECREF(t); + perr->pgerror = t; } } if (pgcode) { if ((t = conn_text_from_chars(conn, pgcode))) { - PyObject_SetAttrString(err, "pgcode", t); - Py_DECREF(t); + perr->pgcode = t; } } @@ -891,6 +790,7 @@ INIT_MODULE(_psycopg)(void) Py_TYPE(&chunkType) = &PyType_Type; Py_TYPE(&NotifyType) = &PyType_Type; Py_TYPE(&XidType) = &PyType_Type; + Py_TYPE(&ErrorType) = &PyType_Type; Py_TYPE(&diagnosticsType) = &PyType_Type; if (PyType_Ready(&connectionType) == -1) goto exit; @@ -908,6 +808,8 @@ INIT_MODULE(_psycopg)(void) if (PyType_Ready(&chunkType) == -1) goto exit; if (PyType_Ready(&NotifyType) == -1) goto exit; if (PyType_Ready(&XidType) == -1) goto exit; + ErrorType.tp_base = (PyTypeObject *)PyExc_StandardError; + if (PyType_Ready(&ErrorType) == -1) goto exit; if (PyType_Ready(&diagnosticsType) == -1) goto exit; #ifdef PSYCOPG_EXTENSIONS @@ -1043,6 +945,7 @@ INIT_MODULE(_psycopg)(void) pydatetimeType.tp_alloc = PyType_GenericAlloc; NotifyType.tp_alloc = PyType_GenericAlloc; XidType.tp_alloc = PyType_GenericAlloc; + ErrorType.tp_alloc = PyType_GenericAlloc; diagnosticsType.tp_alloc = PyType_GenericAlloc; #ifdef PSYCOPG_EXTENSIONS diff --git a/psycopg/python.h b/psycopg/python.h index f6d6be0a..9fce6e13 100644 --- a/psycopg/python.h +++ b/psycopg/python.h @@ -114,6 +114,7 @@ typedef unsigned long Py_uhash_t; #define PyInt_AsLong PyLong_AsLong #define PyInt_FromLong PyLong_FromLong #define PyInt_FromSsize_t PyLong_FromSsize_t +#define PyExc_StandardError PyExc_Exception #define PyString_FromFormat PyUnicode_FromFormat #define Py_TPFLAGS_HAVE_ITER 0L #define Py_TPFLAGS_HAVE_RICHCOMPARE 0L diff --git a/setup.py b/setup.py index e270689f..3d6cf624 100644 --- a/setup.py +++ b/setup.py @@ -424,7 +424,7 @@ sources = [ 'connection_int.c', 'connection_type.c', 'cursor_int.c', 'cursor_type.c', - 'diagnostics_type.c', + 'diagnostics_type.c', 'error_type.c', 'lobject_int.c', 'lobject_type.c', 'notify_type.c', 'xid_type.c', @@ -437,8 +437,8 @@ sources = [ depends = [ # headers - 'config.h', 'pgtypes.h', 'psycopg.h', 'python.h', - 'connection.h', 'cursor.h', 'diagnostics.h', 'green.h', 'lobject.h', + 'config.h', 'pgtypes.h', 'psycopg.h', 'python.h', 'connection.h', + 'cursor.h', 'diagnostics.h', 'error.h', 'green.h', 'lobject.h', 'notify.h', 'pqpath.h', 'xid.h', 'adapter_asis.h', 'adapter_binary.h', 'adapter_datetime.h',