Added a C structure to psycopg.Error

This will allow to store a PGresult in it.
This commit is contained in:
Daniele Varrazzo 2013-03-19 03:06:00 +00:00
parent 49c3569919
commit 394312939e
5 changed files with 338 additions and 117 deletions

40
psycopg/error.h Normal file
View File

@ -0,0 +1,40 @@
/* error.h - definition for the psycopg base Error type
*
* Copyright (C) 2013 Daniele Varrazzo <daniele.varrazzo@gmail.com>
*
* 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 */

277
psycopg/error_type.c Normal file
View File

@ -0,0 +1,277 @@
/* error_type.c - python interface to the Error objects
*
* Copyright (C) 2013 Daniele Varrazzo <daniele.varrazzo@gmail.com>
*
* 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*/
};

View File

@ -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

View File

@ -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

View File

@ -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',