From 0b0c1d046cac540deefc56ab3c38732bc76f6c56 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 29 Aug 2023 22:02:12 +0200 Subject: gh-108278: Deprecate passing the first param of sqlite3.Connection callback APIs by keyword (#108632) Deprecate passing the callback callable by keyword for the following sqlite3.Connection APIs: - set_authorizer(authorizer_callback) - set_progress_handler(progress_handler, ...) - set_trace_callback(trace_callback) The affected parameters will become positional-only in Python 3.15. --- Doc/library/sqlite3.rst | 27 ++++-- Doc/whatsnew/3.13.rst | 7 ++ Lib/test/test_sqlite3/test_hooks.py | 24 +++++ Lib/test/test_sqlite3/test_userfunctions.py | 33 +++++++ .../2023-08-29-11-29-15.gh-issue-108278.-UhsnJ.rst | 10 ++ Modules/_sqlite/clinic/connection.c.h | 102 +++++++++++++++++++-- Modules/_sqlite/connection.c | 25 +++-- 7 files changed, 206 insertions(+), 22 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-08-29-11-29-15.gh-issue-108278.-UhsnJ.rst diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 5137e17..0abdab5 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -763,10 +763,10 @@ Connection objects ... print(row) ('acbd18db4cc2f85cedef654fccc4a4d8',) - .. versionchanged:: 3.13 + .. versionchanged:: 3.13 - Passing *name*, *narg*, and *func* as keyword arguments is deprecated. - These parameters will become positional-only in Python 3.15. + Passing *name*, *narg*, and *func* as keyword arguments is deprecated. + These parameters will become positional-only in Python 3.15. .. method:: create_aggregate(name, n_arg, aggregate_class) @@ -822,10 +822,10 @@ Connection objects 3 - .. versionchanged:: 3.13 + .. versionchanged:: 3.13 - Passing *name*, *n_arg*, and *aggregate_class* as keyword arguments is deprecated. - These parameters will become positional-only in Python 3.15. + Passing *name*, *n_arg*, and *aggregate_class* as keyword arguments is deprecated. + These parameters will become positional-only in Python 3.15. .. method:: create_window_function(name, num_params, aggregate_class, /) @@ -991,6 +991,11 @@ Connection objects .. versionchanged:: 3.11 Added support for disabling the authorizer using ``None``. + .. versionchanged:: 3.13 + + Passing *authorizer_callback* as a keyword argument to is deprecated. + The parameter will become positional-only in Python 3.15. + .. method:: set_progress_handler(progress_handler, n) @@ -1006,6 +1011,11 @@ Connection objects currently executing query and cause it to raise a :exc:`DatabaseError` exception. + .. versionchanged:: 3.13 + + Passing *progress_handler* as a keyword argument to is deprecated. + The parameter will become positional-only in Python 3.15. + .. method:: set_trace_callback(trace_callback) @@ -1030,6 +1040,11 @@ Connection objects .. versionadded:: 3.3 + .. versionchanged:: 3.13 + + Passing *trace_callback* as a keyword argument to is deprecated. + The parameter will become positional-only in Python 3.15. + .. method:: enable_load_extension(enabled, /) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 1c94da2..be5bf9a 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -266,6 +266,13 @@ Deprecated * :meth:`~sqlite3.Connection.create_function` * :meth:`~sqlite3.Connection.create_aggregate` + Deprecate passing the callback callable by keyword for the following + :class:`sqlite3.Connection` APIs: + + * :meth:`~sqlite3.Connection.set_authorizer` + * :meth:`~sqlite3.Connection.set_progress_handler` + * :meth:`~sqlite3.Connection.set_trace_callback` + The affected parameters will become positional-only in Python 3.15. (Contributed by Erlend E. Aasland in :gh:`107948` and :gh:`108278`.) diff --git a/Lib/test/test_sqlite3/test_hooks.py b/Lib/test/test_sqlite3/test_hooks.py index 33f0af9..49e72f8 100644 --- a/Lib/test/test_sqlite3/test_hooks.py +++ b/Lib/test/test_sqlite3/test_hooks.py @@ -219,6 +219,18 @@ class ProgressTests(MemoryDatabaseMixin, unittest.TestCase): create table foo(a, b) """) + def test_progress_handler_keyword_args(self): + regex = ( + r"Passing keyword argument 'progress_handler' to " + r"_sqlite3.Connection.set_progress_handler\(\) is deprecated. " + r"Parameter 'progress_handler' will become positional-only in " + r"Python 3.15." + ) + + with self.assertWarnsRegex(DeprecationWarning, regex) as cm: + self.con.set_progress_handler(progress_handler=lambda: None, n=1) + self.assertEqual(cm.filename, __file__) + class TraceCallbackTests(MemoryDatabaseMixin, unittest.TestCase): @@ -340,6 +352,18 @@ class TraceCallbackTests(MemoryDatabaseMixin, unittest.TestCase): cx.set_trace_callback(lambda stmt: 5/0) cx.execute("select 1") + def test_trace_keyword_args(self): + regex = ( + r"Passing keyword argument 'trace_callback' to " + r"_sqlite3.Connection.set_trace_callback\(\) is deprecated. " + r"Parameter 'trace_callback' will become positional-only in " + r"Python 3.15." + ) + + with self.assertWarnsRegex(DeprecationWarning, regex) as cm: + self.con.set_trace_callback(trace_callback=lambda: None) + self.assertEqual(cm.filename, __file__) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_sqlite3/test_userfunctions.py b/Lib/test/test_sqlite3/test_userfunctions.py index d86b8c6..0901949 100644 --- a/Lib/test/test_sqlite3/test_userfunctions.py +++ b/Lib/test/test_sqlite3/test_userfunctions.py @@ -737,6 +737,27 @@ class AggregateTests(unittest.TestCase): val = cur.fetchone()[0] self.assertEqual(val, txt) + def test_agg_keyword_args(self): + regex = ( + r"Passing keyword arguments 'name', 'n_arg' and 'aggregate_class' to " + r"_sqlite3.Connection.create_aggregate\(\) is deprecated. " + r"Parameters 'name', 'n_arg' and 'aggregate_class' will become " + r"positional-only in Python 3.15." + ) + + with self.assertWarnsRegex(DeprecationWarning, regex) as cm: + self.con.create_aggregate("test", 1, aggregate_class=AggrText) + self.assertEqual(cm.filename, __file__) + + with self.assertWarnsRegex(DeprecationWarning, regex) as cm: + self.con.create_aggregate("test", n_arg=1, aggregate_class=AggrText) + self.assertEqual(cm.filename, __file__) + + with self.assertWarnsRegex(DeprecationWarning, regex) as cm: + self.con.create_aggregate(name="test", n_arg=0, + aggregate_class=AggrText) + self.assertEqual(cm.filename, __file__) + class AuthorizerTests(unittest.TestCase): @staticmethod @@ -779,6 +800,18 @@ class AuthorizerTests(unittest.TestCase): self.con.execute("select * from t2") self.con.execute("select c2 from t1") + def test_authorizer_keyword_args(self): + regex = ( + r"Passing keyword argument 'authorizer_callback' to " + r"_sqlite3.Connection.set_authorizer\(\) is deprecated. " + r"Parameter 'authorizer_callback' will become positional-only in " + r"Python 3.15." + ) + + with self.assertWarnsRegex(DeprecationWarning, regex) as cm: + self.con.set_authorizer(authorizer_callback=lambda: None) + self.assertEqual(cm.filename, __file__) + class AuthorizerRaiseExceptionTests(AuthorizerTests): @staticmethod diff --git a/Misc/NEWS.d/next/Library/2023-08-29-11-29-15.gh-issue-108278.-UhsnJ.rst b/Misc/NEWS.d/next/Library/2023-08-29-11-29-15.gh-issue-108278.-UhsnJ.rst new file mode 100644 index 0000000..fa2dbdf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-08-29-11-29-15.gh-issue-108278.-UhsnJ.rst @@ -0,0 +1,10 @@ +Deprecate passing the callback callable by keyword for the following +:class:`sqlite3.Connection` APIs: + +* :meth:`~sqlite3.Connection.set_authorizer` +* :meth:`~sqlite3.Connection.set_progress_handler` +* :meth:`~sqlite3.Connection.set_trace_callback` + +The affected parameters will become positional-only in Python 3.15. + +Patch by Erlend E. Aasland. diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index 992b2e6..f9510f4 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -712,7 +712,12 @@ PyDoc_STRVAR(pysqlite_connection_set_authorizer__doc__, "set_authorizer($self, /, authorizer_callback)\n" "--\n" "\n" -"Sets authorizer callback."); +"Set authorizer callback.\n" +"\n" +"Note: Passing keyword argument \'authorizer_callback\' to\n" +"_sqlite3.Connection.set_authorizer() is deprecated. Parameter\n" +"\'authorizer_callback\' will become positional-only in Python 3.15.\n" +""); #define PYSQLITE_CONNECTION_SET_AUTHORIZER_METHODDEF \ {"set_authorizer", _PyCFunction_CAST(pysqlite_connection_set_authorizer), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_set_authorizer__doc__}, @@ -722,13 +727,24 @@ pysqlite_connection_set_authorizer_impl(pysqlite_Connection *self, PyTypeObject *cls, PyObject *callable); +// Emit compiler warnings when we get to Python 3.15. +#if PY_VERSION_HEX >= 0x030f00C0 +# error "Update the clinic input of '_sqlite3.Connection.set_authorizer'." +#elif PY_VERSION_HEX >= 0x030f00A0 +# ifdef _MSC_VER +# pragma message ("Update the clinic input of '_sqlite3.Connection.set_authorizer'.") +# else +# warning "Update the clinic input of '_sqlite3.Connection.set_authorizer'." +# endif +#endif + static PyObject * pysqlite_connection_set_authorizer(pysqlite_Connection *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 1 + #define NUM_KEYWORDS 2 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -758,6 +774,16 @@ pysqlite_connection_set_authorizer(pysqlite_Connection *self, PyTypeObject *cls, if (!args) { goto exit; } + if (nargs < 1) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword argument 'authorizer_callback' to " + "_sqlite3.Connection.set_authorizer() is deprecated. Parameter " + "'authorizer_callback' will become positional-only in Python " + "3.15.", 1)) + { + goto exit; + } + } callable = args[0]; return_value = pysqlite_connection_set_authorizer_impl(self, cls, callable); @@ -769,7 +795,22 @@ PyDoc_STRVAR(pysqlite_connection_set_progress_handler__doc__, "set_progress_handler($self, /, progress_handler, n)\n" "--\n" "\n" -"Sets progress handler callback."); +"Set progress handler callback.\n" +"\n" +" progress_handler\n" +" A callable that takes no arguments.\n" +" If the callable returns non-zero, the current query is terminated,\n" +" and an exception is raised.\n" +" n\n" +" The number of SQLite virtual machine instructions that are\n" +" executed between invocations of \'progress_handler\'.\n" +"\n" +"If \'progress_handler\' is None or \'n\' is 0, the progress handler is disabled.\n" +"\n" +"Note: Passing keyword argument \'progress_handler\' to\n" +"_sqlite3.Connection.set_progress_handler() is deprecated. Parameter\n" +"\'progress_handler\' will become positional-only in Python 3.15.\n" +""); #define PYSQLITE_CONNECTION_SET_PROGRESS_HANDLER_METHODDEF \ {"set_progress_handler", _PyCFunction_CAST(pysqlite_connection_set_progress_handler), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_set_progress_handler__doc__}, @@ -779,13 +820,24 @@ pysqlite_connection_set_progress_handler_impl(pysqlite_Connection *self, PyTypeObject *cls, PyObject *callable, int n); +// Emit compiler warnings when we get to Python 3.15. +#if PY_VERSION_HEX >= 0x030f00C0 +# error "Update the clinic input of '_sqlite3.Connection.set_progress_handler'." +#elif PY_VERSION_HEX >= 0x030f00A0 +# ifdef _MSC_VER +# pragma message ("Update the clinic input of '_sqlite3.Connection.set_progress_handler'.") +# else +# warning "Update the clinic input of '_sqlite3.Connection.set_progress_handler'." +# endif +#endif + static PyObject * pysqlite_connection_set_progress_handler(pysqlite_Connection *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 2 + #define NUM_KEYWORDS 3 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -816,6 +868,16 @@ pysqlite_connection_set_progress_handler(pysqlite_Connection *self, PyTypeObject if (!args) { goto exit; } + if (nargs < 1) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword argument 'progress_handler' to " + "_sqlite3.Connection.set_progress_handler() is deprecated. " + "Parameter 'progress_handler' will become positional-only in " + "Python 3.15.", 1)) + { + goto exit; + } + } callable = args[0]; n = PyLong_AsInt(args[1]); if (n == -1 && PyErr_Occurred()) { @@ -831,7 +893,12 @@ PyDoc_STRVAR(pysqlite_connection_set_trace_callback__doc__, "set_trace_callback($self, /, trace_callback)\n" "--\n" "\n" -"Sets a trace callback called for each SQL statement (passed as unicode)."); +"Set a trace callback called for each SQL statement (passed as unicode).\n" +"\n" +"Note: Passing keyword argument \'trace_callback\' to\n" +"_sqlite3.Connection.set_trace_callback() is deprecated. Parameter\n" +"\'trace_callback\' will become positional-only in Python 3.15.\n" +""); #define PYSQLITE_CONNECTION_SET_TRACE_CALLBACK_METHODDEF \ {"set_trace_callback", _PyCFunction_CAST(pysqlite_connection_set_trace_callback), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_set_trace_callback__doc__}, @@ -841,13 +908,24 @@ pysqlite_connection_set_trace_callback_impl(pysqlite_Connection *self, PyTypeObject *cls, PyObject *callable); +// Emit compiler warnings when we get to Python 3.15. +#if PY_VERSION_HEX >= 0x030f00C0 +# error "Update the clinic input of '_sqlite3.Connection.set_trace_callback'." +#elif PY_VERSION_HEX >= 0x030f00A0 +# ifdef _MSC_VER +# pragma message ("Update the clinic input of '_sqlite3.Connection.set_trace_callback'.") +# else +# warning "Update the clinic input of '_sqlite3.Connection.set_trace_callback'." +# endif +#endif + static PyObject * pysqlite_connection_set_trace_callback(pysqlite_Connection *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 1 + #define NUM_KEYWORDS 2 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -877,6 +955,16 @@ pysqlite_connection_set_trace_callback(pysqlite_Connection *self, PyTypeObject * if (!args) { goto exit; } + if (nargs < 1) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword argument 'trace_callback' to " + "_sqlite3.Connection.set_trace_callback() is deprecated. " + "Parameter 'trace_callback' will become positional-only in Python" + " 3.15.", 1)) + { + goto exit; + } + } callable = args[0]; return_value = pysqlite_connection_set_trace_callback_impl(self, cls, callable); @@ -1734,4 +1822,4 @@ exit: #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=f80eb1d02cf698e4 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=f06b254bc5c2bcaf input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 45ec0f9..24090b0 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -1499,17 +1499,17 @@ exit: _sqlite3.Connection.set_authorizer as pysqlite_connection_set_authorizer cls: defining_class - / authorizer_callback as callable: object + / [from 3.15] -Sets authorizer callback. +Set authorizer callback. [clinic start generated code]*/ static PyObject * pysqlite_connection_set_authorizer_impl(pysqlite_Connection *self, PyTypeObject *cls, PyObject *callable) -/*[clinic end generated code: output=75fa60114fc971c3 input=605d32ba92dd3eca]*/ +/*[clinic end generated code: output=75fa60114fc971c3 input=a52bd4937c588752]*/ { if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; @@ -1541,18 +1541,25 @@ pysqlite_connection_set_authorizer_impl(pysqlite_Connection *self, _sqlite3.Connection.set_progress_handler as pysqlite_connection_set_progress_handler cls: defining_class - / progress_handler as callable: object + A callable that takes no arguments. + If the callable returns non-zero, the current query is terminated, + and an exception is raised. + / [from 3.15] n: int + The number of SQLite virtual machine instructions that are + executed between invocations of 'progress_handler'. -Sets progress handler callback. +Set progress handler callback. + +If 'progress_handler' is None or 'n' is 0, the progress handler is disabled. [clinic start generated code]*/ static PyObject * pysqlite_connection_set_progress_handler_impl(pysqlite_Connection *self, PyTypeObject *cls, PyObject *callable, int n) -/*[clinic end generated code: output=0739957fd8034a50 input=f7c1837984bd86db]*/ +/*[clinic end generated code: output=0739957fd8034a50 input=b4d6e2ef8b4d32f9]*/ { if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; @@ -1578,17 +1585,17 @@ pysqlite_connection_set_progress_handler_impl(pysqlite_Connection *self, _sqlite3.Connection.set_trace_callback as pysqlite_connection_set_trace_callback cls: defining_class - / trace_callback as callable: object + / [from 3.15] -Sets a trace callback called for each SQL statement (passed as unicode). +Set a trace callback called for each SQL statement (passed as unicode). [clinic start generated code]*/ static PyObject * pysqlite_connection_set_trace_callback_impl(pysqlite_Connection *self, PyTypeObject *cls, PyObject *callable) -/*[clinic end generated code: output=d91048c03bfcee05 input=351a94210c5f81bb]*/ +/*[clinic end generated code: output=d91048c03bfcee05 input=d705d592ec03cf28]*/ { if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; -- cgit v0.12