diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -218,24 +218,26 @@ Module functions and constants .. function:: register_converter(typename, callable) Registers a callable to convert a bytestring from the database into a custom Python type. The callable will be invoked for all database values that are of the type *typename*. Confer the parameter *detect_types* of the :func:`connect` function for how the type detection works. Note that the case of *typename* and the name of the type in your query must match! + .. deprecated:: 3.6 .. function:: register_adapter(type, callable) Registers a callable to convert the custom Python type *type* into one of SQLite's supported types. The callable *callable* accepts as single parameter the Python value, and must return a value of the following types: int, float, str or bytes. + .. deprecated:: 3.6 .. function:: complete_statement(sql) Returns :const:`True` if the string *sql* contains one or more complete SQL statements terminated by semicolons. It does not verify that the SQL is syntactically correct, only that there are no unclosed string literals and the statement is terminated by a semicolon. @@ -786,32 +788,36 @@ to give your class a method ``__conform_ the converted value. The parameter *protocol* will be :class:`PrepareProtocol`. .. literalinclude:: ../includes/sqlite3/adapter_point_1.py Registering an adapter callable """"""""""""""""""""""""""""""" +.. deprecated:: 3.6 + The other possibility is to create a function that converts the type to the string representation and register the function with :meth:`register_adapter`. .. literalinclude:: ../includes/sqlite3/adapter_point_2.py The :mod:`sqlite3` module has two default adapters for Python's built-in :class:`datetime.date` and :class:`datetime.datetime` types. Now let's suppose we want to store :class:`datetime.datetime` objects not in ISO representation, but as a Unix timestamp. .. literalinclude:: ../includes/sqlite3/adapter_datetime.py Converting SQLite values to custom Python types ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. deprecated:: 3.6 + Writing an adapter lets you send custom Python types to SQLite. But to make it really useful we need to make the Python to SQLite to Python roundtrip work. Enter converters. Let's go back to the :class:`Point` class. We stored the x and y coordinates separated via semicolons as strings in SQLite. diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst --- a/Doc/whatsnew/3.6.rst +++ b/Doc/whatsnew/3.6.rst @@ -374,16 +374,18 @@ Deprecated Python modules, functions and * :meth:`importlib.machinery.SourceFileLoader` and :meth:`importlib.machinery.SourcelessFileLoader` are now deprecated. They were the only remaining implementations of :meth:`importlib.abc.Loader.load_module` in :mod:`importlib` that had not been deprecated in previous versions of Python in favour of :meth:`importlib.abc.Loader.exec_module`. +* :func:`sqlite3.register_converter` and :func:`sqlite3.register_adapter` are + now deprecated. Deprecated functions and types of the C API ------------------------------------------- * None yet. Deprecated features diff --git a/Lib/sqlite3/dbapi2.py b/Lib/sqlite3/dbapi2.py --- a/Lib/sqlite3/dbapi2.py +++ b/Lib/sqlite3/dbapi2.py @@ -18,16 +18,17 @@ # appreciated but is not required. # 2. Altered source versions must be plainly marked as such, and must not be # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. import datetime import time import collections.abc +import warnings from _sqlite3 import * paramstyle = "qmark" threadsafety = 1 apilevel = "2.0" @@ -71,19 +72,20 @@ def register_adapters_and_converters(): if len(timepart_full) == 2: microseconds = int('{:0<6.6}'.format(timepart_full[1].decode())) else: microseconds = 0 val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds) return val - - register_adapter(datetime.date, adapt_date) - register_adapter(datetime.datetime, adapt_datetime) - register_converter("date", convert_date) - register_converter("timestamp", convert_timestamp) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + register_adapter(datetime.date, adapt_date) + register_adapter(datetime.datetime, adapt_datetime) + register_converter("date", convert_date) + register_converter("timestamp", convert_timestamp) register_adapters_and_converters() # Clean up namespace del(register_adapters_and_converters) diff --git a/Lib/sqlite3/test/regression.py b/Lib/sqlite3/test/regression.py --- a/Lib/sqlite3/test/regression.py +++ b/Lib/sqlite3/test/regression.py @@ -19,16 +19,18 @@ # appreciated but is not required. # 2. Altered source versions must be plainly marked as such, and must not be # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. import datetime import unittest import sqlite3 as sqlite +import warnings + class RegressionTests(unittest.TestCase): def setUp(self): self.con = sqlite.connect(":memory:") def tearDown(self): self.con.close() @@ -145,17 +147,19 @@ class RegressionTests(unittest.TestCase) failure = "OperationalError did not have expected description text" if failure: self.fail(failure) def CheckRegisterAdapter(self): """ See issue 3312. """ - self.assertRaises(TypeError, sqlite.register_adapter, {}, None) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + self.assertRaises(TypeError, sqlite.register_adapter, {}, None) def CheckSetIsolationLevel(self): """ See issue 3312. """ con = sqlite.connect(":memory:") setattr(con, "isolation_level", "\xe9") diff --git a/Lib/sqlite3/test/types.py b/Lib/sqlite3/test/types.py --- a/Lib/sqlite3/test/types.py +++ b/Lib/sqlite3/test/types.py @@ -19,16 +19,17 @@ # appreciated but is not required. # 2. Altered source versions must be plainly marked as such, and must not be # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. import datetime import unittest import sqlite3 as sqlite +import warnings try: import zlib except ImportError: zlib = None class SqliteTypeTests(unittest.TestCase): def setUp(self): @@ -290,17 +291,19 @@ class ObjectAdaptationTests(unittest.Tes cast = staticmethod(cast) def setUp(self): self.con = sqlite.connect(":memory:") try: del sqlite.adapters[int] except: pass - sqlite.register_adapter(int, ObjectAdaptationTests.cast) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + sqlite.register_adapter(int, ObjectAdaptationTests.cast) self.cur = self.con.cursor() def tearDown(self): del sqlite.adapters[(int, sqlite.PrepareProtocol)] self.cur.close() self.con.close() def CheckCasterIsUsed(self): @@ -311,17 +314,19 @@ class ObjectAdaptationTests(unittest.Tes @unittest.skipUnless(zlib, "requires zlib") class BinaryConverterTests(unittest.TestCase): def convert(s): return zlib.decompress(s) convert = staticmethod(convert) def setUp(self): self.con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_COLNAMES) - sqlite.register_converter("bin", BinaryConverterTests.convert) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + sqlite.register_converter("bin", BinaryConverterTests.convert) def tearDown(self): self.con.close() def CheckBinaryInputForConverter(self): testdata = b"abcdefg" * 10 result = self.con.execute('select ? as "x [bin]"', (memoryview(zlib.compress(testdata)),)).fetchone()[0] self.assertEqual(testdata, result) @@ -372,23 +377,47 @@ class DateTimeTests(unittest.TestCase): def CheckDateTimeSubSecondsFloatingPoint(self): ts = sqlite.Timestamp(2004, 2, 14, 7, 15, 0, 510241) self.cur.execute("insert into test(ts) values (?)", (ts,)) self.cur.execute("select ts from test") ts2 = self.cur.fetchone()[0] self.assertEqual(ts, ts2) + +class DeprecationTests(unittest.TestCase): + + def test_deprecate_adapters(self): + class Point: + def __init__(self, x, y): + self.x, self.y = x, y + + def adapt_point(point): + return "%f;%f" % (point.x, point.y) + + with self.assertWarns(DeprecationWarning): + sqlite.register_adapter(Point, adapt_point) + + def test_deprecate_converters(self): + def convert(s): + return s + + with self.assertWarns(DeprecationWarning): + sqlite.register_converter('foo', convert) + + def suite(): sqlite_type_suite = unittest.makeSuite(SqliteTypeTests, "Check") decltypes_type_suite = unittest.makeSuite(DeclTypesTests, "Check") colnames_type_suite = unittest.makeSuite(ColNamesTests, "Check") adaptation_suite = unittest.makeSuite(ObjectAdaptationTests, "Check") bin_suite = unittest.makeSuite(BinaryConverterTests, "Check") date_suite = unittest.makeSuite(DateTimeTests, "Check") - return unittest.TestSuite((sqlite_type_suite, decltypes_type_suite, colnames_type_suite, adaptation_suite, bin_suite, date_suite)) + deprecation_suite = unittest.makeSuite(DeprecationTests) + return unittest.TestSuite((sqlite_type_suite, decltypes_type_suite, colnames_type_suite, adaptation_suite, bin_suite, date_suite, deprecation_suite)) + def test(): runner = unittest.TextTestRunner() runner.run(suite()) if __name__ == "__main__": test() diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -24,16 +24,20 @@ #include "connection.h" #include "statement.h" #include "cursor.h" #include "cache.h" #include "prepare_protocol.h" #include "microprotocols.h" #include "row.h" +#define DEPRECATE_ADAPTERS_MSG "Converters and adapters are deprecated. " \ + "Please use only supported SQLite types. Any " \ + "type mapping should happen in layer above this module." + #if SQLITE_VERSION_NUMBER >= 3003003 #define HAVE_SHARED_CACHE #endif /* static objects at module-level */ PyObject* pysqlite_Error, *pysqlite_Warning, *pysqlite_InterfaceError, *pysqlite_DatabaseError, *pysqlite_InternalError, *pysqlite_OperationalError, *pysqlite_ProgrammingError, @@ -156,16 +160,20 @@ static PyObject* module_register_adapter PyTypeObject* type; PyObject* caster; int rc; if (!PyArg_ParseTuple(args, "OO", &type, &caster)) { return NULL; } + if (PyErr_WarnEx(PyExc_DeprecationWarning, DEPRECATE_ADAPTERS_MSG, 1)) { + return NULL; + } + /* a basic type is adapted; there's a performance optimization if that's not the case * (99 % of all usages) */ if (type == &PyLong_Type || type == &PyFloat_Type || type == &PyUnicode_Type || type == &PyByteArray_Type) { pysqlite_BaseTypeAdapted = 1; } rc = pysqlite_microprotocols_add(type, (PyObject*)&pysqlite_PrepareProtocolType, caster); @@ -188,16 +196,20 @@ static PyObject* module_register_convert PyObject* callable; PyObject* retval = NULL; _Py_IDENTIFIER(upper); if (!PyArg_ParseTuple(args, "UO", &orig_name, &callable)) { return NULL; } + if (PyErr_WarnEx(PyExc_DeprecationWarning, DEPRECATE_ADAPTERS_MSG, 1)) { + return NULL; + } + /* convert the name to upper case */ name = _PyObject_CallMethodId(orig_name, &PyId_upper, ""); if (!name) { goto error; } if (PyDict_SetItem(converters, name, callable) != 0) { goto error;