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

gh-157513: Add structured version info for ssl (GH-157515)

ssl.OPENSSL_VERSION_INFO is now a named tuple.

Add ssl.OPENSSL_API_VERSION_INFO which provides information about the
version of the OpenSSL library that was used for building the module.
The private name _OPENSSL_API_VERSION is kept as an alias.

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

files:
A Misc/NEWS.d/next/Library/2026-09-14-18-36-31.gh-issue-157513.EW4QOC.rst
M Doc/library/ssl.rst
M Doc/whatsnew/3.16.rst
M Lib/ssl.py
M Lib/test/pythoninfo.py
M Lib/test/test_ssl.py
M Modules/_ssl.c

diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index 66fe6c7aee4862..c38763037d67c5 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -982,14 +982,26 @@ Constants
 
 .. data:: OPENSSL_VERSION_INFO
 
-   A tuple of five integers representing version information about the
-   OpenSSL library::
+   A named tuple of five integers representing version information about the
+   OpenSSL library loaded by the interpreter:
+   *major*, *minor*, *fix*, *patch* and *status*::
 
     >>> ssl.OPENSSL_VERSION_INFO
-    (1, 0, 2, 11, 15)
+    ssl.OPENSSL_VERSION_INFO(major=3, minor=0, fix=0, patch=13, status=0)
 
    .. versionadded:: 3.2
 
+   .. versionchanged:: next
+      It is now a named tuple.
+
+.. data:: OPENSSL_API_VERSION_INFO
+
+   A named tuple containing the version of the OpenSSL library that was used
+   for building the module, with the same fields as 
:const:`OPENSSL_VERSION_INFO`.
+   This may be different from the OpenSSL library actually used at runtime.
+
+   .. versionadded:: next
+
 .. data:: OPENSSL_VERSION_NUMBER
 
    The raw version number of the OpenSSL library, as a single integer::
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index 3808fac14f325c..1253e30ee1d3a8 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -591,6 +591,16 @@ sqlite3
   (Contributed by Serhiy Storchaka in :gh:`157470`.)
 
 
+ssl
+---
+
+* :const:`ssl.OPENSSL_VERSION_INFO` is now a named tuple.
+  Added :const:`~ssl.OPENSSL_API_VERSION_INFO` which provides information
+  about the version of the OpenSSL library that was used for building
+  the module.
+  (Contributed by Serhiy Storchaka in :gh:`157513`.)
+
+
 symtable
 --------
 
diff --git a/Lib/ssl.py b/Lib/ssl.py
index dc957121728f28..44dc0b046f4518 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -103,6 +103,7 @@
 import _ssl             # if we can't import it, let the error propagate
 
 from _ssl import OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_INFO, OPENSSL_VERSION
+from _ssl import OPENSSL_API_VERSION_INFO
 from _ssl import _SSLContext, MemoryBIO, SSLSession
 from _ssl import (
     SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError,
diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py
index 205d6ff7b75b12..54f662c32edfa9 100644
--- a/Lib/test/pythoninfo.py
+++ b/Lib/test/pythoninfo.py
@@ -632,6 +632,7 @@ def format_attr(attr, value):
     attributes = (
         'OPENSSL_VERSION',
         'OPENSSL_VERSION_INFO',
+        'OPENSSL_API_VERSION_INFO',
         'HAS_SNI',
         'OP_ALL',
         'OP_NO_TLSv1_1',
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index abd7710a1d570a..9a6118d94fbbb2 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -545,7 +545,7 @@ def test_parse_cert_CVE_2013_4238(self):
                    (('emailAddress', '[email protected]'),))
         self.assertEqual(p['subject'], subject)
         self.assertEqual(p['issuer'], subject)
-        if ssl._OPENSSL_API_VERSION >= (0, 9, 8):
+        if ssl.OPENSSL_API_VERSION_INFO >= (0, 9, 8):
             san = (('DNS', 'altnull.python.org\x00example.com'),
                    ('email', '[email protected]\[email protected]'),
                    ('URI', 'http://null.python.org\x00http://example.org'),
@@ -601,6 +601,14 @@ def test_openssl_version(self):
         self.assertIsInstance(n, int)
         self.assertIsInstance(t, tuple)
         self.assertIsInstance(s, str)
+        self.assertEqual(len(t), 5)
+        self.assertEqual(t, (t.major, t.minor, t.fix, t.patch, t.status))
+        a = ssl.OPENSSL_API_VERSION_INFO
+        self.assertIsInstance(a, tuple)
+        self.assertEqual(len(a), 5)
+        self.assertEqual(a, (a.major, a.minor, a.fix, a.patch, a.status))
+        self.assertIs(ssl._OPENSSL_API_VERSION, a)
+        self.assertEqual(a.major, t.major)
         # Some sanity checks follow
         # >= 1.1.1
         self.assertGreaterEqual(n, 0x10101000)
diff --git 
a/Misc/NEWS.d/next/Library/2026-09-14-18-36-31.gh-issue-157513.EW4QOC.rst 
b/Misc/NEWS.d/next/Library/2026-09-14-18-36-31.gh-issue-157513.EW4QOC.rst
new file mode 100644
index 00000000000000..c9d19314a7604d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-14-18-36-31.gh-issue-157513.EW4QOC.rst
@@ -0,0 +1,3 @@
+:const:`ssl.OPENSSL_VERSION_INFO` is now a named tuple.
+Add :const:`ssl.OPENSSL_API_VERSION_INFO` which provides information about
+the version of the OpenSSL library that was used for building the module.
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index 4fd8bf0ac3b957..360aa3e2454f8b 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -7229,12 +7229,62 @@ parse_openssl_version(unsigned long libver,
     *major = libver & 0xFF;
 }
 
+PyDoc_STRVAR(openssl_version_info__doc__,
+"ssl.OPENSSL_VERSION_INFO\n\
+\n\
+OpenSSL version information as a named tuple.");
+
+static PyStructSequence_Field openssl_version_info_fields[] = {
+    {"major", "Major release number"},
+    {"minor", "Minor release number"},
+    {"fix", "Fix release number"},
+    {"patch", "Patch release number"},
+    {"status", "Release status"},
+    {0}
+};
+
+static PyStructSequence_Desc openssl_version_info_desc = {
+    "ssl.OPENSSL_VERSION_INFO",     /* name */
+    openssl_version_info__doc__,    /* doc */
+    openssl_version_info_fields,    /* fields */
+    5
+};
+
+static PyObject *
+make_openssl_version_info(PyTypeObject *type, unsigned long libver)
+{
+    PyObject *version;
+    int pos = 0;
+    unsigned int major, minor, fix, patch, status;
+
+    parse_openssl_version(libver, &major, &minor, &fix, &patch, &status);
+    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_FromUnsignedLong(major))
+    SetItem(PyLong_FromUnsignedLong(minor))
+    SetItem(PyLong_FromUnsignedLong(fix))
+    SetItem(PyLong_FromUnsignedLong(patch))
+    SetItem(PyLong_FromUnsignedLong(status))
+#undef SetItem
+
+    return version;
+}
+
 static int
 sslmodule_init_versioninfo(PyObject *m)
 {
     PyObject *r;
     unsigned long libver;
-    unsigned int major, minor, fix, patch, status;
 
     /* OpenSSL version */
     /* SSLeay() gives us the version of the library linked against,
@@ -7245,20 +7295,33 @@ sslmodule_init_versioninfo(PyObject *m)
     if (PyModule_Add(m, "OPENSSL_VERSION_NUMBER", r) < 0)
         return -1;
 
-    parse_openssl_version(libver, &major, &minor, &fix, &patch, &status);
-    r = Py_BuildValue("IIIII", major, minor, fix, patch, status);
-    if (PyModule_Add(m, "OPENSSL_VERSION_INFO", r) < 0)
-        return -1;
-
     r = PyUnicode_FromString(OpenSSL_version(OPENSSL_VERSION));
     if (PyModule_Add(m, "OPENSSL_VERSION", r) < 0)
         return -1;
 
-    libver = OPENSSL_VERSION_NUMBER;
-    parse_openssl_version(libver, &major, &minor, &fix, &patch, &status);
-    r = Py_BuildValue("IIIII", major, minor, fix, patch, status);
-    if (PyModule_Add(m, "_OPENSSL_API_VERSION", r) < 0)
+    PyTypeObject *version_type;
+    version_type = PyStructSequence_NewType(&openssl_version_info_desc);
+    if (version_type == NULL) {
+        return -1;
+    }
+    if (PyModule_Add(m, "OPENSSL_VERSION_INFO",
+            make_openssl_version_info(version_type, libver)) < 0)
+    {
+        Py_DECREF(version_type);
+        return -1;
+    }
+    r = make_openssl_version_info(version_type, OPENSSL_VERSION_NUMBER);
+    Py_DECREF(version_type);
+    if (r == NULL) {
         return -1;
+    }
+    if (PyModule_AddObjectRef(m, "OPENSSL_API_VERSION_INFO", r) < 0 ||
+        PyModule_AddObjectRef(m, "_OPENSSL_API_VERSION", r) < 0)
+    {
+        Py_DECREF(r);
+        return -1;
+    }
+    Py_DECREF(r);
 
     return 0;
 }

_______________________________________________
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