Skip to content

Commit

Permalink
Replace deprecated PyUnicode_FromUnicode(NULL, size) calls (mkleehamm…
Browse files Browse the repository at this point in the history
…er#998)

Current versions of Python write a deprecation warning message to
stderr, which breaks CGI scripts running under web servers which
fold stderr into stdout. Likely breaks other software. This change
replaces the deprecated calls with PyUnicode_New(size, 0x10ffff).
The second argument represents the highest code point for Unicode
characters, which is used because the calls being replaced have
no string value from which we can determine the smallest number
of bytes needed to represent any value which can be stored in the
newly-created object.
  • Loading branch information
bkline committed Jan 2, 2022
1 parent b1cfb75 commit e5e54b8
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 2 deletions.
6 changes: 5 additions & 1 deletion src/pyodbccompat.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,17 @@ inline void PyString_ConcatAndDel(PyObject** lhs, PyObject* rhs)

#endif

#ifndef MAX_UNICODE
#define MAX_UNICODE 0x10ffff
#endif

inline PyObject* Text_New(Py_ssize_t length)
{
// Returns a new, uninitialized String (Python 2) or Unicode object (Python 3) object.
#if PY_MAJOR_VERSION < 3
return PyString_FromStringAndSize(0, length);
#else
return PyUnicode_FromUnicode(0, length);
return PyUnicode_New(length, MAX_UNICODE);
#endif
}

Expand Down
2 changes: 1 addition & 1 deletion src/pyodbcmodule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1357,7 +1357,7 @@ static PyObject* MakeConnectionString(PyObject* existing, PyObject* parts)
length += Text_Size(key) + 1 + Text_Size(value) + 1; // key=value;
}

PyObject* result = PyUnicode_FromUnicode(0, length);
PyObject* result = PyUnicode_New(length, MAX_UNICODE);
if (!result)
return 0;

Expand Down
78 changes: 78 additions & 0 deletions tests3/issue998.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""
Verify that no warning is emitted for `PyUnicode_FromUnicode(NULL, size)`.
See https://github.com/mkleehammer/pyodbc/issues/998.
See also https://bugs.python.org/issue36346.
"""

import io
import os
import sys
import unittest

# pylint: disable-next=import-error
from tests3.testutils import add_to_path, load_setup_connection_string

add_to_path()
import pyodbc # pylint: disable=wrong-import-position

KB = 1024
MB = KB * 1024

CONNECTION_STRING = None

CONNECTION_STRING_ERROR_MESSAGE = (
"Please create tmp/setup.cfg file or "
"set a valid value to CONNECTION_STRING."
)
NO_ERROR = None


class SQLPutDataUnicodeToBytesMemoryLeakTestCase(unittest.TestCase):
"""Test case for issue998 bug fix."""

driver = pyodbc

@classmethod
def setUpClass(cls):
"""Set the connection string."""

filename = os.path.splitext(os.path.basename(__file__))[0]
cls.connection_string = (
load_setup_connection_string(filename) or CONNECTION_STRING
)

if cls.connection_string:
return NO_ERROR
return ValueError(CONNECTION_STRING_ERROR_MESSAGE)

def test_use_correct_unicode_factory_function(self):
"""Verify that the obsolete function call has been replaced."""

# Create a results set.
with pyodbc.connect(self.connection_string, autocommit=True) as cnxn:
cursor = cnxn.cursor()
cursor.execute("SELECT 1 AS a, 2 AS b")
rows = cursor.fetchall()

# Redirect stderr so we can detect the warning.
sys.stderr = redirected_stderr = io.StringIO()

# Convert the results object to a string.
self.assertGreater(len(str(rows)), 0)

# Restore stderr to the original stream.
sys.stderr = sys.__stderr__

# If the bug has been fixed, nothing will have been written to stderr.
self.assertEqual(len(redirected_stderr.getvalue()), 0)


def main():
"""Top-level driver for the test."""
unittest.main()


if __name__ == "__main__":
main()

0 comments on commit e5e54b8

Please sign in to comment.