https://github.com/python/cpython/commit/5d1ab456ba3475fc1392b1fa7085fd6205ada562
commit: 5d1ab456ba3475fc1392b1fa7085fd6205ada562
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-09-29T15:48:39+03:00
summary:

gh-157470: Add structured version info for sqlite3 (GH-157471)

Add constants sqlite3.SQLITE_VERSION and sqlite3.SQLITE_VERSION_INFO
which provide information about the version of the SQLite library that
was used for building the module.

Make sqlite3.sqlite_version_info a named tuple.

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-09-14-07-49-51.gh-issue-157470.jgEHYK.rst
M Doc/library/sqlite3.rst
M Doc/whatsnew/3.16.rst
M Lib/sqlite3/dbapi2.py
M Lib/test/pythoninfo.py
M Lib/test/test_sqlite3/test_dbapi.py
M Modules/_sqlite/module.c

diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst
index 5aa3d2b0adcbf6e..130a84ae14230b1 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 <str>`.
 
+.. 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 <int>`.
+   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 460c2dfff9a1215..3808fac14f325cb 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -584,6 +584,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 0315760516edf82..2054b3a78e3f607 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 36b501a3a2492dd..205d6ff7b75b12a 100644
--- a/Lib/test/pythoninfo.py
+++ b/Lib/test/pythoninfo.py
@@ -687,7 +687,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 978227a8651e148..bbb7c1f8b615938 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 000000000000000..8e4caf84bf35830
--- /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 512d9744d57416e..5eebacaa79d4e60 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;
     }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to