Skip to content

Commit f990b92

Browse files
Improved detection of the signature used by output type handlers, in
particular those that that make use of __call__().
1 parent bf7756a commit f990b92

File tree

4 files changed

+81
-5
lines changed

4 files changed

+81
-5
lines changed

doc/src/release_notes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ Common Changes
4646
#) Added support for maintainers to specify optional compilation arguments
4747
when building python-oracledb. A new environment variable
4848
``PYO_COMPILE_ARGS`` can be set :ref:`before building <installsrc>`.
49+
#) Improved detection of the signature used by output type handlers, in
50+
particular those that that make use of ``__call__()``.
4951
#) Python wheel package binaries for Linux on `PyPI
5052
<https://pypi.org/project/oracledb/>`__ are now stripped to reduce their
5153
size.

src/oracledb/impl/base/cursor.pyx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -316,11 +316,11 @@ cdef class BaseCursorImpl:
316316
conn_impl = self._get_conn_impl()
317317
type_handler = conn_impl.outputtypehandler
318318
if type_handler is not None:
319-
uses_fetch_info[0] = \
320-
(inspect.isfunction(type_handler) and \
321-
type_handler.__code__.co_argcount == 2) or \
322-
(inspect.ismethod(type_handler) and \
323-
type_handler.__code__.co_argcount == 3)
319+
try:
320+
sig = inspect.signature(type_handler)
321+
uses_fetch_info[0] = (len(sig.parameters) == 2)
322+
except (ValueError, TypeError):
323+
uses_fetch_info[0] = False
324324
return type_handler
325325

326326
cdef int _init_fetch_vars(self, uint32_t num_columns) except -1:

tests/test_3800_typehandler.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
3800 - Module for testing the input and output type handlers.
2727
"""
2828

29+
import datetime
2930
import json
3031
import unittest
3132

@@ -286,6 +287,42 @@ def output_type_handler(cursor, metadata):
286287
expected_result = f"A{rc}B{rc}C{rc}"
287288
self.assertEqual(result, expected_result)
288289

290+
def test_3808(self):
291+
"3808 - output type handler with object implementing __call__()"
292+
293+
class TimestampOutputTypeHandler:
294+
295+
def __init__(self, unit="s"):
296+
if unit == "ms":
297+
self.factor = 1000
298+
else:
299+
self.factor = 1
300+
301+
def converter(self, d):
302+
return int(d.timestamp() * self.factor)
303+
304+
def __call__(self, cursor, metadata):
305+
if metadata.type_code is oracledb.DB_TYPE_TIMESTAMP:
306+
return cursor.var(
307+
metadata.type_code,
308+
arraysize=cursor.arraysize,
309+
outconverter=self.converter,
310+
)
311+
312+
d = datetime.datetime.today()
313+
with self.conn.cursor() as cursor:
314+
cursor.outputtypehandler = TimestampOutputTypeHandler("ms")
315+
cursor.setinputsizes(oracledb.DB_TYPE_TIMESTAMP)
316+
cursor.execute("select :d from dual", [d])
317+
(result,) = cursor.fetchone()
318+
self.assertEqual(result, int(d.timestamp() * 1000))
319+
with self.conn.cursor() as cursor:
320+
cursor.outputtypehandler = TimestampOutputTypeHandler("s")
321+
cursor.setinputsizes(oracledb.DB_TYPE_TIMESTAMP)
322+
cursor.execute("select :d from dual", [d])
323+
(result,) = cursor.fetchone()
324+
self.assertEqual(result, int(d.timestamp()))
325+
289326

290327
if __name__ == "__main__":
291328
test_env.run_test_cases()

tests/test_6000_typehandler_async.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
6000 - Module for testing the input and output type handlers with asyncio.
2727
"""
2828

29+
import datetime
2930
import json
3031
import unittest
3132

@@ -258,6 +259,42 @@ def output_type_handler(cursor, metadata):
258259
await self.cursor.execute("select * from TestJson")
259260
self.assertEqual(await self.cursor.fetchall(), data_to_insert)
260261

262+
async def test_6007(self):
263+
"6007 - output type handler with object implementing __call__()"
264+
265+
class TimestampOutputTypeHandler:
266+
267+
def __init__(self, unit="s"):
268+
if unit == "ms":
269+
self.factor = 1000
270+
else:
271+
self.factor = 1
272+
273+
def converter(self, d):
274+
return int(d.timestamp() * self.factor)
275+
276+
def __call__(self, cursor, metadata):
277+
if metadata.type_code is oracledb.DB_TYPE_TIMESTAMP:
278+
return cursor.var(
279+
metadata.type_code,
280+
arraysize=cursor.arraysize,
281+
outconverter=self.converter,
282+
)
283+
284+
d = datetime.datetime.today()
285+
with self.conn.cursor() as cursor:
286+
cursor.outputtypehandler = TimestampOutputTypeHandler("ms")
287+
cursor.setinputsizes(oracledb.DB_TYPE_TIMESTAMP)
288+
await cursor.execute("select :d from dual", [d])
289+
(result,) = await cursor.fetchone()
290+
self.assertEqual(result, int(d.timestamp() * 1000))
291+
with self.conn.cursor() as cursor:
292+
cursor.outputtypehandler = TimestampOutputTypeHandler("s")
293+
cursor.setinputsizes(oracledb.DB_TYPE_TIMESTAMP)
294+
await cursor.execute("select :d from dual", [d])
295+
(result,) = await cursor.fetchone()
296+
self.assertEqual(result, int(d.timestamp()))
297+
261298

262299
if __name__ == "__main__":
263300
test_env.run_test_cases()

0 commit comments

Comments
 (0)