diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 5aa3d2b0adcbf6..130a84ae14230b 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -498,14 +498,40 @@ Module constants The ``named`` DB-API parameter style is also supported. +.. data:: SQLITE_VERSION + + The version string of the SQLite library that was used for building + the module. + This may be different from the SQLite library actually used at runtime, + which is available as :const:`sqlite_version`. + + .. versionadded:: next + .. data:: sqlite_version Version number of the runtime SQLite library as a :class:`string `. +.. data:: SQLITE_VERSION_INFO + + A named tuple containing the three components of the SQLite library + version that was used for building the module: + *major*, *minor*, and *patch*. + All values are integers. + The components can also be accessed by name, + so ``sqlite3.SQLITE_VERSION_INFO[0]`` is equivalent to + ``sqlite3.SQLITE_VERSION_INFO.major`` and so on. + This may be different from the SQLite library actually used at runtime, + which is available as :const:`sqlite_version_info`. + + .. versionadded:: next + .. data:: sqlite_version_info - Version number of the runtime SQLite library as a :class:`tuple` of - :class:`integers `. + A named tuple containing the version of the runtime SQLite library, + with the same fields as :const:`SQLITE_VERSION_INFO`. + + .. versionchanged:: next + It is now a named tuple. .. data:: SQLITE_KEYWORDS diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 53983637f520c8..890521271b8ebb 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -558,6 +558,12 @@ sqlite3 :exc:`SystemError` or :exc:`ValueError`. (Contributed by Jiseok CHOI in :gh:`150449`.) +* Added constants :const:`~sqlite3.SQLITE_VERSION` and + :const:`~sqlite3.SQLITE_VERSION_INFO` which provide information + about the version of the SQLite library that was used for building the module. + :const:`~sqlite3.sqlite_version_info` is now a named tuple. + (Contributed by Serhiy Storchaka in :gh:`157470`.) + symtable -------- diff --git a/Lib/sqlite3/dbapi2.py b/Lib/sqlite3/dbapi2.py index 0315760516edf8..2054b3a78e3f60 100644 --- a/Lib/sqlite3/dbapi2.py +++ b/Lib/sqlite3/dbapi2.py @@ -46,8 +46,6 @@ def TimestampFromTicks(ticks): return Timestamp(*time.localtime(ticks)[:6]) -sqlite_version_info = tuple([int(x) for x in sqlite_version.split(".")]) - Binary = memoryview collections.abc.Sequence.register(Row) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index b59e2acb9376f1..a622ca080be906 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -701,7 +701,7 @@ def collect_sqlite(info_add): except ImportError: return - attributes = ('sqlite_version',) + attributes = ('SQLITE_VERSION', 'sqlite_version') copy_attributes(info_add, sqlite3, 'sqlite3.%s', attributes) diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index 978227a8651e14..bbb7c1f8b61593 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -31,6 +31,7 @@ import urllib.parse import warnings +from test import support from test.support import ( SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess ) @@ -88,6 +89,33 @@ def test_programming_error(self): def test_not_supported_error(self): self.assertIsSubclass(sqlite.NotSupportedError, sqlite.DatabaseError) + def _test_sqlite_version(self, v, string): + self.assertIsInstance(v[:], tuple) + self.assertEqual(len(v), 3) + self.assertIsInstance(v[0], int) + self.assertIsInstance(v[1], int) + self.assertIsInstance(v[2], int) + self.assertIsInstance(v.major, int) + self.assertIsInstance(v.minor, int) + self.assertIsInstance(v.patch, int) + self.assertEqual(v[0], v.major) + self.assertEqual(v[1], v.minor) + self.assertEqual(v[2], v.patch) + self.assertGreaterEqual(v.major, 3) + self.assertGreaterEqual(v.minor, 0) + self.assertGreaterEqual(v.patch, 0) + self.assertEqual(string, '%d.%d.%d' % v) + + def test_sqlite_version(self): + if support.verbose: + print(f'SQLITE_VERSION = {sqlite.SQLITE_VERSION}', flush=True) + print(f'sqlite_version = {sqlite.sqlite_version}', flush=True) + print(f'SQLITE_VERSION_INFO = {sqlite.SQLITE_VERSION_INFO}', flush=True) + print(f'sqlite_version_info = {sqlite.sqlite_version_info}', flush=True) + self._test_sqlite_version(sqlite.SQLITE_VERSION_INFO, sqlite.SQLITE_VERSION) + self._test_sqlite_version(sqlite.sqlite_version_info, sqlite.sqlite_version) + self.assertEqual(sqlite.SQLITE_VERSION_INFO[0], sqlite.sqlite_version_info[0]) + def test_module_constants(self): consts = [ "SQLITE_ABORT", diff --git a/Misc/NEWS.d/next/Library/2026-09-14-07-49-51.gh-issue-157470.jgEHYK.rst b/Misc/NEWS.d/next/Library/2026-09-14-07-49-51.gh-issue-157470.jgEHYK.rst new file mode 100644 index 00000000000000..8e4caf84bf3583 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-14-07-49-51.gh-issue-157470.jgEHYK.rst @@ -0,0 +1,4 @@ +Add constants :const:`sqlite3.SQLITE_VERSION` and +:const:`sqlite3.SQLITE_VERSION_INFO` which provide information about +the version of the SQLite library that was used for building the module. +:const:`sqlite3.sqlite_version_info` is now a named tuple. diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index 512d9744d57416..5eebacaa79d4e6 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -439,6 +439,87 @@ add_keyword_tuple(PyObject *module) #endif } +PyDoc_STRVAR(sqlite_version_info__doc__, +"_sqlite3.sqlite_version_info\n\ +\n\ +SQLite version information as a named tuple."); + +static PyStructSequence_Field sqlite_version_info_fields[] = { + {"major", "Major release number"}, + {"minor", "Minor release number"}, + {"patch", "Patch release number"}, + {0} +}; + +static PyStructSequence_Desc sqlite_version_info_desc = { + "_sqlite3.sqlite_version_info", /* name */ + sqlite_version_info__doc__, /* doc */ + sqlite_version_info_fields, /* fields */ + 3 +}; + +static PyObject * +make_sqlite_version_info(PyTypeObject *type, int number) +{ + PyObject *version; + int pos = 0; + int major = number / 1000000; + int minor = (number % 1000000) / 1000; + int patch = number % 1000; + + version = PyStructSequence_New(type); + if (version == NULL) { + return NULL; + } + +#define SetItem(VALUE) \ + PyStructSequence_SET_ITEM(version, pos++, VALUE); \ + if (PyErr_Occurred()) { \ + Py_DECREF(version); \ + return NULL; \ + } + + SetItem(PyLong_FromLong(major)) + SetItem(PyLong_FromLong(minor)) + SetItem(PyLong_FromLong(patch)) +#undef SetItem + + return version; +} + +static int +add_version_constants(PyObject *module) +{ + if (PyModule_AddStringMacro(module, SQLITE_VERSION) < 0) { + return -1; + } + if (PyModule_AddStringConstant(module, "sqlite_version", + sqlite3_libversion()) < 0) + { + return -1; + } + PyTypeObject *version_type; + version_type = PyStructSequence_NewType(&sqlite_version_info_desc); + if (version_type == NULL) { + return -1; + } + if (PyModule_Add(module, "SQLITE_VERSION_INFO", + make_sqlite_version_info(version_type, SQLITE_VERSION_NUMBER)) < 0) + { + Py_DECREF(version_type); + return -1; + } + if (PyModule_Add(module, "sqlite_version_info", + make_sqlite_version_info(version_type, + sqlite3_libversion_number())) < 0) + { + Py_DECREF(version_type); + return -1; + } + Py_DECREF(version_type); + return 0; +} + static int add_integer_constants(PyObject *module) { #define ADD_INT(ival) \ @@ -741,7 +822,7 @@ module_exec(PyObject *module) goto error; } - if (PyModule_AddStringConstant(module, "sqlite_version", sqlite3_libversion())) { + if (add_version_constants(module) < 0) { goto error; }