bpo-43931: Export Python version as API data (GH-25577)



When Python is embedded in other applications, it is not easy to determine which version of Python is being used. This change exposes the Python version as part of the API data. Tools like Austin (https://github.com/P403n1x87/austin) can benefit from this data when targeting applications like uWSGI, as the Python version can then be inferred systematically by looking at the exported symbols rather than relying on unreliable pattern matching or other hacks (like remote code execution etc...).

Automerge-Triggered-By: GH:pablogsal
diff --git a/Doc/c-api/apiabiversion.rst b/Doc/c-api/apiabiversion.rst
index 04050f7..53a42e7 100644
--- a/Doc/c-api/apiabiversion.rst
+++ b/Doc/c-api/apiabiversion.rst
@@ -58,5 +58,14 @@
    Thus ``3.4.1a2`` is hexversion ``0x030401a2`` and ``3.10.0`` is
    hexversion ``0x030a00f0``.
 
+   This version is also available via the symbol :data:`Py_Version`.
+
+.. c:var:: const unsigned long Py_Version
+
+   The Python runtime version number encoded in a single constant integer, with
+   the same format as the c:macro:`PY_VERSION_HEX` macro.
+   This contains the Python version used at run time.
+
+   .. versionadded:: 3.11
 
 All the given macros are defined in :source:`Include/patchlevel.h`.
diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst
index 09dfc68..322b9e4 100644
--- a/Doc/c-api/init.rst
+++ b/Doc/c-api/init.rst
@@ -553,6 +553,8 @@
    period.  The returned string points into static storage; the caller should not
    modify its value.  The value is available to Python code as :data:`sys.version`.
 
+   See also the :data:`Py_Version` constant.
+
 
 .. c:function:: const char* Py_GetPlatform()
 
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index 64a0a2a..02e54e5 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -830,6 +830,7 @@
 macro,Py_UNBLOCK_THREADS,3.2,
 var,Py_UTF8Mode,3.8,
 function,Py_VaBuildValue,3.2,
+var,Py_Version,3.11,
 function,Py_XNewRef,3.10,
 type,Py_intptr_t,3.2,
 type,Py_ssize_t,3.2,
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index 7a82866..8d1f4eb 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -626,6 +626,10 @@
   fields of the result from the exception instance (the ``value`` field).
   (Contributed by Irit Katriel in :issue:`45711`.)
 
+* Added the :c:data:`Py_Version` constant which bears the same value as
+  :c:macro:`PY_VERSION_HEX`.
+  (Contributed by  Gabriele N. Tornetta in :issue:`43931`.)
+
 
 Porting to Python 3.11
 ----------------------
diff --git a/Include/pylifecycle.h b/Include/pylifecycle.h
index 4aecda2..e4c3b09 100644
--- a/Include/pylifecycle.h
+++ b/Include/pylifecycle.h
@@ -62,6 +62,10 @@
 PyAPI_FUNC(PyOS_sighandler_t) PyOS_getsig(int);
 PyAPI_FUNC(PyOS_sighandler_t) PyOS_setsig(int, PyOS_sighandler_t);
 
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030B0000
+PyAPI_DATA(const unsigned long) Py_Version;
+#endif
+
 #ifndef Py_LIMITED_API
 #  define Py_CPYTHON_PYLIFECYCLE_H
 #  include "cpython/pylifecycle.h"
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index d512470..ecf3aa3 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -840,6 +840,9 @@
     def test_widechar(self):
         _testcapi.test_widechar()
 
+    def test_version_api_data(self):
+        self.assertEqual(_testcapi.Py_Version, sys.hexversion)
+
 
 class Test_testinternalcapi(unittest.TestCase):
     locals().update((name, getattr(_testinternalcapi, name))
diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py
index d0cd5c2..9fd6b14 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -808,6 +808,7 @@
     "Py_SetRecursionLimit",
     "Py_UTF8Mode",
     "Py_VaBuildValue",
+    "Py_Version",
     "Py_XNewRef",
     "_PyArg_ParseTupleAndKeywords_SizeT",
     "_PyArg_ParseTuple_SizeT",
diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-04-24-15-39-23.bpo-43931.zpChDi.rst b/Misc/NEWS.d/next/Core and Builtins/2021-04-24-15-39-23.bpo-43931.zpChDi.rst
new file mode 100644
index 0000000..0375129
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2021-04-24-15-39-23.bpo-43931.zpChDi.rst
@@ -0,0 +1,2 @@
+Added the :c:data:`Py_Version` constant which bears the same value as
+:c:macro:`PY_VERSION_HEX`. Patch by Gabriele N. Tornetta.
\ No newline at end of file
diff --git a/Misc/stable_abi.txt b/Misc/stable_abi.txt
index de6caa8..9cb210c 100644
--- a/Misc/stable_abi.txt
+++ b/Misc/stable_abi.txt
@@ -2153,3 +2153,6 @@
 
 # (Detailed comments aren't really needed for further entries: from here on
 #  we can use version control logs.)
+
+data Py_Version
+    added 3.11
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 56d3949..6116365 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -7525,6 +7525,7 @@
     PyModule_AddObject(m, "PY_SSIZE_T_MAX", PyLong_FromSsize_t(PY_SSIZE_T_MAX));
     PyModule_AddObject(m, "PY_SSIZE_T_MIN", PyLong_FromSsize_t(PY_SSIZE_T_MIN));
     PyModule_AddObject(m, "SIZEOF_TIME_T", PyLong_FromSsize_t(sizeof(time_t)));
+    PyModule_AddObject(m, "Py_Version", PyLong_FromUnsignedLong(Py_Version));
     Py_INCREF(&PyInstanceMethod_Type);
     PyModule_AddObject(m, "instancemethod", (PyObject *)&PyInstanceMethod_Type);
 
diff --git a/PC/python3dll.c b/PC/python3dll.c
index 6e46935..b2bb170 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -725,6 +725,7 @@
 EXPORT_DATA(Py_GenericAliasType)
 EXPORT_DATA(Py_HasFileSystemDefaultEncoding)
 EXPORT_DATA(Py_UTF8Mode)
+EXPORT_DATA(Py_Version)
 EXPORT_DATA(PyBaseObject_Type)
 EXPORT_DATA(PyBool_Type)
 EXPORT_DATA(PyByteArray_Type)
diff --git a/Python/getversion.c b/Python/getversion.c
index c32b6f9..4691045 100644
--- a/Python/getversion.c
+++ b/Python/getversion.c
@@ -13,3 +13,6 @@
                   PY_VERSION, Py_GetBuildInfo(), Py_GetCompiler());
     return version;
 }
+
+// Export the Python hex version as a constant.
+const unsigned long Py_Version = PY_VERSION_HEX;