Skip to content

Commit 37373f2

Browse files
author
James William Pye
committed
Use raise . from cause to communicate the cause of TypeIO errors.
Per Elvis' patch. Some additional changes were necessary to optimized's functools, but it ended up simplifying code by the removal of some PyErr_SetContext calls.
1 parent 8302e5c commit 37373f2

6 files changed

Lines changed: 59 additions & 81 deletions

File tree

postgresql/driver/pq3.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ def record_io_factory(self,
417417
funpack = tuple(map(get1, column_io))
418418
row_constructor = self.RowTypeFactory(attribute_map = attmap, composite_relid = composite_relid)
419419

420-
def raise_pack_tuple_error(procs, tup, itemnum):
420+
def raise_pack_tuple_error(cause, procs, tup, itemnum):
421421
data = repr(tup[itemnum])
422422
if len(data) > 80:
423423
# Be sure not to fill screen with noise.
@@ -428,9 +428,9 @@ def raise_pack_tuple_error(procs, tup, itemnum):
428428
(b'M', fmt_errmsg('pack', itemnum, attnames[itemnum], typnames[itemnum], composite_name),),
429429
(b'W', data,),
430430
(b'P', str(itemnum),)
431-
)))
431+
)), cause = cause)
432432

433-
def raise_unpack_tuple_error(procs, tup, itemnum):
433+
def raise_unpack_tuple_error(cause, procs, tup, itemnum):
434434
data = repr(tup[itemnum])
435435
if len(data) > 80:
436436
# Be sure not to fill screen with noise.
@@ -441,7 +441,7 @@ def raise_unpack_tuple_error(procs, tup, itemnum):
441441
(b'M', fmt_errmsg('unpack', itemnum, attnames[itemnum], typnames[itemnum], composite_name),),
442442
(b'W', data,),
443443
(b'P', str(itemnum),),
444-
)))
444+
)), cause = cause)
445445

446446
def unpack_a_record(data,
447447
unpack = io_lib.record_unpack,
@@ -731,12 +731,13 @@ def _process_tuple_chunk_Column(self, x, range = range):
731731
r = range(len(l))
732732
try:
733733
return [unpack(l[i]) for i in r]
734-
except:
735-
try:
736-
i = next(r)
737-
except StopIteration:
738-
i = len(l)
739-
self._raise_column_tuple_error(self._output_io, (l[i],), 0)
734+
except Exception:
735+
cause = sys.exc_info()[1]
736+
try:
737+
i = next(r)
738+
except StopIteration:
739+
i = len(l)
740+
self._raise_column_tuple_error(cause, self._output_io, (l[i],), 0)
740741

741742
# Process the element.Tuple message in x for rows()
742743
def _process_tuple_chunk_Row(self, x,
@@ -752,7 +753,7 @@ def _process_tuple_chunk_Row(self, x,
752753
def _process_tuple_chunk(self, x, proc = process_chunk):
753754
return proc(self._output_io, x, self._raise_column_tuple_error)
754755

755-
def _raise_column_tuple_error(self, procs, tup, itemnum):
756+
def _raise_column_tuple_error(self, cause, procs, tup, itemnum):
756757
'for column processing'
757758
# The element traceback will include the full list of parameters.
758759
data = repr(tup[itemnum])
@@ -775,7 +776,7 @@ def _raise_column_tuple_error(self, procs, tup, itemnum):
775776
(b'H', "Try casting the column to 'text'."),
776777
(b'P', str(itemnum)),
777778
))
778-
self.database.typio.raise_client_error(em, creator = self)
779+
self.database.typio.raise_client_error(em, creator = self, cause = cause)
779780

780781
@property
781782
def state(self):
@@ -1302,7 +1303,7 @@ def _pq_parameters(self, parameters, proc = process_tuple):
13021303
# process_tuple failed(exception). The parameters could not be packed.
13031304
# This function is called with the given information in the context
13041305
# of the original exception(to allow chaining).
1305-
def _raise_parameter_tuple_error(self, procs, tup, itemnum):
1306+
def _raise_parameter_tuple_error(self, cause, procs, tup, itemnum):
13061307
# Find the SQL type name. This should *not* hit the server.
13071308
typ = self.database.typio.sql_type_from_oid(
13081309
self.pg_parameter_types[itemnum]
@@ -1325,11 +1326,11 @@ def _raise_parameter_tuple_error(self, procs, tup, itemnum):
13251326
(b'H', "Try casting the parameter to 'text', then to the target type."),
13261327
(b'P', str(itemnum))
13271328
))
1328-
self.database.typio.raise_client_error(em, creator = self)
1329+
self.database.typio.raise_client_error(em, creator = self, cause = cause)
13291330

13301331
##
13311332
# Similar to the parameter variant.
1332-
def _raise_column_tuple_error(self, procs, tup, itemnum):
1333+
def _raise_column_tuple_error(self, cause, procs, tup, itemnum):
13331334
# Find the SQL type name. This should *not* hit the server.
13341335
typ = self.database.typio.sql_type_from_oid(
13351336
self.pg_column_types[itemnum]
@@ -1352,7 +1353,7 @@ def _raise_column_tuple_error(self, procs, tup, itemnum):
13521353
(b'H', "Try casting the column to 'text'."),
13531354
(b'P', str(itemnum)),
13541355
))
1355-
self.database.typio.raise_client_error(em, creator = self)
1356+
self.database.typio.raise_client_error(em, creator = self, cause = cause)
13561357

13571358
@property
13581359
def state(self) -> str:

postgresql/port/optimized/functools.c

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ _process_tuple(PyObject *procs, PyObject *tup, PyObject *fail)
100100
* processed and therefore a generalized exception raised in the
101101
* context of the original is *very* useful.
102102
*/
103-
104103
Py_DECREF(rob);
105104
rob = NULL;
106105

@@ -109,26 +108,28 @@ _process_tuple(PyObject *procs, PyObject *tup, PyObject *fail)
109108
*/
110109
if (PyErr_ExceptionMatches(PyExc_Exception))
111110
{
112-
PyObject *failargs, *failedat;
113-
PyObject *exc, *val, *tb;
114-
PyObject *oldexc, *oldval, *oldtb;
111+
PyObject *cause, *failargs, *failedat;
112+
PyObject *exc, *tb;
115113

116114
/* Store exception to set context after handler. */
117-
PyErr_Fetch(&oldexc, &oldval, &oldtb);
118-
PyErr_NormalizeException(&oldexc, &oldval, &oldtb);
115+
PyErr_Fetch(&exc, &cause, &tb);
116+
PyErr_NormalizeException(&exc, &cause, &tb);
117+
Py_XDECREF(exc);
118+
Py_XDECREF(tb);
119119

120120
failedat = PyLong_FromSsize_t(i);
121121
if (failedat != NULL)
122122
{
123-
failargs = PyTuple_New(3);
123+
failargs = PyTuple_New(4);
124124
if (failargs != NULL)
125125
{
126-
/* args for the exception "handler" */
127-
PyTuple_SET_ITEM(failargs, 0, procs);
126+
/* args for the exception "generalizer" */
127+
PyTuple_SET_ITEM(failargs, 0, cause);
128+
PyTuple_SET_ITEM(failargs, 1, procs);
128129
Py_INCREF(procs);
129-
PyTuple_SET_ITEM(failargs, 1, tup);
130+
PyTuple_SET_ITEM(failargs, 2, tup);
130131
Py_INCREF(tup);
131-
PyTuple_SET_ITEM(failargs, 2, failedat);
132+
PyTuple_SET_ITEM(failargs, 3, failedat);
132133

133134
r = PyObject_CallObject(fail, failargs);
134135
Py_DECREF(failargs);
@@ -145,33 +146,6 @@ _process_tuple(PyObject *procs, PyObject *tup, PyObject *fail)
145146
Py_DECREF(failedat);
146147
}
147148
}
148-
149-
PyErr_Fetch(&exc, &val, &tb);
150-
PyErr_NormalizeException(&exc, &val, &tb);
151-
152-
/*
153-
* Reference BaseException here as the condition is merely
154-
* *validating* that SetContext can be used.
155-
*/
156-
if (val != NULL && PyObject_IsInstance(val, PyExc_BaseException))
157-
{
158-
/* Steals oldval reference */
159-
PyException_SetContext(val, oldval);
160-
Py_XDECREF(oldexc);
161-
Py_XDECREF(oldtb);
162-
PyErr_Restore(exc, val, tb);
163-
}
164-
else
165-
{
166-
/*
167-
* Fetch & NormalizeException failed somehow.
168-
* Use the old exception...
169-
*/
170-
PyErr_Restore(oldexc, oldval, oldtb);
171-
Py_XDECREF(exc);
172-
Py_XDECREF(val);
173-
Py_XDECREF(tb);
174-
}
175149
}
176150

177151
/*

postgresql/python/functools.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
##
22
# python.functools
33
##
4+
import sys
45
from .decorlib import method
56

67
def rsetattr(attr, val, ob):
@@ -33,7 +34,7 @@ def __call__(self, r):
3334
# C implementation of the tuple processors.
3435
from ..port.optimized import process_tuple, process_chunk
3536
except ImportError:
36-
def process_tuple(procs, tup, exception_handler, len = len, tuple = tuple):
37+
def process_tuple(procs, tup, exception_handler, len = len, tuple = tuple, cause = None):
3738
"""
3839
Call each item in `procs` with the corresponding
3940
item in `tup` returning the result as `type`.
@@ -57,8 +58,10 @@ def process_tuple(procs, tup, exception_handler, len = len, tuple = tuple):
5758
continue
5859
r[i] = procs[i](ob)
5960
except Exception:
60-
# relying on __context__
61-
exception_handler(procs, tup, i)
61+
cause = sys.exc_info()[1]
62+
63+
if cause is not None:
64+
exception_handler(cause, procs, tup, i)
6265
raise RuntimeError("process_tuple exception handler failed to raise")
6366
return tuple(r)
6467

postgresql/test/test_driver.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,10 +1252,10 @@ def testTypeIOError(self):
12521252
# 'foo' is not a valid Decimal.
12531253
# Expecting a double TupleError here, one from the composite pack
12541254
# and one from the row pack.
1255-
self.assertTrue(isinstance(err.__context__, pg_exc.CompositeError))
1255+
self.assertTrue(isinstance(err.__cause__, pg_exc.CompositeError))
12561256
self.assertEqual(int(err.details['position']), 0)
12571257
# attribute number that the failure occurred on
1258-
self.assertEqual(int(err.__context__.details['position']), 0)
1258+
self.assertEqual(int(err.__cause__.details['position']), 0)
12591259
else:
12601260
self.fail("failed to raise TupleError")
12611261

@@ -1278,7 +1278,7 @@ def raise_ThisError(arg):
12781278
try:
12791279
ps(decimal.Decimal("101"))
12801280
except pg_exc.ColumnError as err:
1281-
self.assertTrue(isinstance(err.__context__, ThisError))
1281+
self.assertTrue(isinstance(err.__cause__, ThisError))
12821282
# might be too inquisitive....
12831283
self.assertEqual(int(err.details['position']), 0)
12841284
self.assertTrue('NUMERIC' in err.message)
@@ -1289,11 +1289,11 @@ def raise_ThisError(arg):
12891289
try:
12901290
ps((decimal.Decimal("101"),))
12911291
except pg_exc.ColumnError as err:
1292-
self.assertTrue(isinstance(err.__context__, pg_exc.CompositeError))
1293-
self.assertTrue(isinstance(err.__context__.__context__, ThisError))
1292+
self.assertTrue(isinstance(err.__cause__, pg_exc.CompositeError))
1293+
self.assertTrue(isinstance(err.__cause__.__cause__, ThisError))
12941294
# might be too inquisitive....
12951295
self.assertEqual(int(err.details['position']), 0)
1296-
self.assertEqual(int(err.__context__.details['position']), 0)
1296+
self.assertEqual(int(err.__cause__.details['position']), 0)
12971297
self.assertTrue('test_tuple_error' in err.message)
12981298
else:
12991299
self.fail("failed to raise TupleError from reception")

postgresql/test/test_protocol.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def test_getvalue(self):
101101
b.write(nd)
102102
self.assertEqual(b.getvalue(), nd)
103103
b.write(packl(4))
104-
self.assertEqual(b.read(), [(b'N', b'')])
104+
self.assertEqual(list(b.read()), [(b'N', b'')])
105105
self.assertEqual(b.getvalue(), b'')
106106
# partial; read one message to exercise
107107
# that the appropriate fragment of the first
@@ -112,20 +112,20 @@ def test_getvalue(self):
112112
second = b'z' + packl(len(second_body) + 4) + second_body
113113
b.write(first + second)
114114
self.assertEqual(b.getvalue(), first + second)
115-
self.assertEqual(b.read(1), [(b'v', first_body)])
115+
self.assertEqual(list(b.read(1)), [(b'v', first_body)])
116116
self.assertEqual(b.getvalue(), second)
117-
self.assertEqual(b.read(1), [(b'z', second_body)])
117+
self.assertEqual(list(b.read(1)), [(b'z', second_body)])
118118
# now, with a third full message in the next chunk
119119
third_body = (b'9876' * 10)
120120
third = b'3' + packl(len(third_body) + 4) + third_body
121121
b.write(first + second)
122122
b.write(third)
123123
self.assertEqual(b.getvalue(), first + second + third)
124-
self.assertEqual(b.read(1), [(b'v', first_body)])
124+
self.assertEqual(list(b.read(1)), [(b'v', first_body)])
125125
self.assertEqual(b.getvalue(), second + third)
126-
self.assertEqual(b.read(1), [(b'z', second_body)])
126+
self.assertEqual(list(b.read(1)), [(b'z', second_body)])
127127
self.assertEqual(b.getvalue(), third)
128-
self.assertEqual(b.read(1), [(b'3', third_body)])
128+
self.assertEqual(list(b.read(1)), [(b'3', third_body)])
129129
self.assertEqual(b.getvalue(), b'')
130130

131131
##

postgresql/test/test_types.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -340,27 +340,27 @@ def testExpectIO(self, samples):
340340
)
341341

342342
class test_io(unittest.TestCase):
343-
def test_process_tuple(self, pt = process_tuple):
344-
def funpass(procs, tup, col):
343+
def test_process_tuple(self):
344+
def funpass(cause, procs, tup, col):
345345
pass
346-
self.assertEqual(tuple(pt((),(), funpass)), ())
347-
self.assertEqual(tuple(pt((int,),("100",), funpass)), (100,))
348-
self.assertEqual(tuple(pt((int,int),("100","200"), funpass)), (100,200))
349-
self.assertEqual(tuple(pt((int,int),(None,"200"), funpass)), (None,200))
350-
self.assertEqual(tuple(pt((int,int,int),(None,None,"200"), funpass)), (None,None,200))
346+
self.assertEqual(tuple(process_tuple((),(), funpass)), ())
347+
self.assertEqual(tuple(process_tuple((int,),("100",), funpass)), (100,))
348+
self.assertEqual(tuple(process_tuple((int,int),("100","200"), funpass)), (100,200))
349+
self.assertEqual(tuple(process_tuple((int,int),(None,"200"), funpass)), (None,200))
350+
self.assertEqual(tuple(process_tuple((int,int,int),(None,None,"200"), funpass)), (None,None,200))
351351
# The exception handler must raise.
352-
self.assertRaises(RuntimeError, pt, (int,), ("foo",), funpass)
352+
self.assertRaises(RuntimeError, process_tuple, (int,), ("foo",), funpass)
353353

354354
class ThisError(Exception):
355355
pass
356356
data = []
357-
def funraise(procs, tup, col):
357+
def funraise(cause, procs, tup, col):
358358
data.append((procs, tup, col))
359-
raise ThisError
360-
self.assertRaises(ThisError, pt, (int,), ("foo",), funraise)
359+
raise ThisError from cause
360+
self.assertRaises(ThisError, process_tuple, (int,), ("foo",), funraise)
361361
self.assertEqual(data[0], ((int,), ("foo",), 0))
362362
del data[0]
363-
self.assertRaises(ThisError, pt, (int,int), ("100","bar"), funraise)
363+
self.assertRaises(ThisError, process_tuple, (int,int), ("100","bar"), funraise)
364364
self.assertEqual(data[0], ((int,int), ("100","bar"), 1))
365365

366366
def testExpectations(self):

0 commit comments

Comments
 (0)