diff --git a/Doc/library/bz2.rst b/Doc/library/bz2.rst index 6c20e9c94a3eae..d5f367d2d19e90 100644 --- a/Doc/library/bz2.rst +++ b/Doc/library/bz2.rst @@ -307,6 +307,36 @@ One-shot (de)compression .. versionchanged:: 3.3 Support for multi-stream inputs was added. + +Miscellaneous +------------- + +Information about the version of the bzip2 compression library +actually loaded by the interpreter is available through the following +constants. +The version used for building the module is not available, +because the bzip2 library does not provide it. + + +.. data:: bzlib_version + + The version string of the bzip2 compression library + actually loaded by the interpreter. + + .. versionadded:: next + + +.. data:: bzlib_version_info + + A named tuple containing the three components of the bzip2 compression + library version actually loaded by the interpreter: + *major*, *minor*, and *patch*. All values are integers. + The components can also be accessed by name, so ``bz2.bzlib_version_info[0]`` + is equivalent to ``bz2.bzlib_version_info.major`` and so on. + + .. versionadded:: next + + .. _bz2-usage-examples: Examples of usage diff --git a/Doc/library/compression.zstd.rst b/Doc/library/compression.zstd.rst index 6618ccf7d559e7..4ab47b02b3d181 100644 --- a/Doc/library/compression.zstd.rst +++ b/Doc/library/compression.zstd.rst @@ -827,10 +827,45 @@ Miscellaneous The default compression level for Zstandard: ``3``. -.. attribute:: zstd_version_info +Information about the version of the zstd library in use is available through +the following constants: - Version number of the runtime zstd library as a tuple of integers - (major, minor, release). + +.. data:: ZSTD_VERSION + + The version string of the zstd library that was used for building the module. + This may be different from the zstd library actually used at runtime, which + is available as :const:`zstd_version`. + + .. versionadded:: next + + +.. data:: zstd_version + + The version string of the zstd library actually loaded by the interpreter. + + +.. data:: ZSTD_VERSION_INFO + + A named tuple containing the three components of the zstd 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 ``zstd.ZSTD_VERSION_INFO[0]`` + is equivalent to ``zstd.ZSTD_VERSION_INFO.major`` and so on. + This may be different from the zstd library actually used at runtime, which + is available as :const:`zstd_version_info`. + + .. versionadded:: next + + +.. data:: zstd_version_info + + A named tuple containing the version of the zstd library + actually loaded by the interpreter, + with the same fields as :const:`ZSTD_VERSION_INFO`. + + .. versionchanged:: next + It is now a named tuple. Examples diff --git a/Doc/library/lzma.rst b/Doc/library/lzma.rst index 580829cfbb46e0..49ba6d9b7ed4f2 100644 --- a/Doc/library/lzma.rst +++ b/Doc/library/lzma.rst @@ -314,6 +314,50 @@ Miscellaneous feature set. +Information about the version of the lzma library in use is available through +the following constants: + + +.. data:: LZMA_VERSION + + The version string of the lzma library that was used for building the module. + This may be different from the lzma library actually used at runtime, which + is available as :const:`lzma_version`. + + .. versionadded:: next + + +.. data:: lzma_version + + The version string of the lzma library actually loaded by the interpreter. + + .. versionadded:: next + + +.. data:: LZMA_VERSION_INFO + + A named tuple containing the four components of the lzma library + version that was used for building the module: + *major*, *minor*, *patch*, and *stability*. + All values except *stability* are integers; *stability* is ``'alpha'``, + ``'beta'``, or ``'stable'``. + The components can also be accessed by name, so ``lzma.LZMA_VERSION_INFO[0]`` + is equivalent to ``lzma.LZMA_VERSION_INFO.major`` and so on. + This may be different from the lzma library actually used at runtime, which + is available as :const:`lzma_version_info`. + + .. versionadded:: next + + +.. data:: lzma_version_info + + A named tuple containing the version of the lzma library + actually loaded by the interpreter, + with the same fields as :const:`LZMA_VERSION_INFO`. + + .. versionadded:: next + + .. _filter-chain-specs: Specifying custom filter chains diff --git a/Doc/library/zlib.rst b/Doc/library/zlib.rst index f043915c0f4b94..e2affe6e59795a 100644 --- a/Doc/library/zlib.rst +++ b/Doc/library/zlib.rst @@ -479,27 +479,65 @@ the following constants: The version string of the zlib library that was used for building the module. This may be different from the zlib library actually used at runtime, which - is available as :const:`ZLIB_RUNTIME_VERSION`. + is available as :const:`zlib_version`. .. data:: ZLIB_RUNTIME_VERSION + zlib_version The version string of the zlib library actually loaded by the interpreter. .. versionadded:: 3.3 + The :const:`!ZLIB_RUNTIME_VERSION` constant. + + .. versionadded:: next + The :const:`!zlib_version` alias. + + +.. data:: ZLIB_VERSION_INFO + + A named tuple containing the four components of the zlib library + version that was used for building the module: + *major*, *minor*, *revision*, and *subversion*. + All values are integers. + The components can also be accessed by name, so ``zlib.ZLIB_VERSION_INFO[0]`` + is equivalent to ``zlib.ZLIB_VERSION_INFO.major`` and so on. + This may be different from the zlib library actually used at runtime, which + is available as :const:`zlib_version_info`. + + .. versionadded:: next + + +.. data:: zlib_version_info + + A named tuple containing the version of the zlib library + actually loaded by the interpreter, + with the same fields as :const:`ZLIB_VERSION_INFO`. + + .. versionadded:: next + + +The following constants are only present if zlib-ng was used to build +the module: .. data:: ZLIBNG_VERSION The version string of the zlib-ng library that was used for building the - module if zlib-ng was used. When present, the :data:`ZLIB_VERSION` and - :data:`ZLIB_RUNTIME_VERSION` constants reflect the version of the zlib API + module if zlib-ng was used. When present, the :const:`ZLIB_VERSION` and + :const:`zlib_version` constants reflect the version of the zlib API provided by zlib-ng. - If zlib-ng was not used to build the module, this constant will be absent. - .. versionadded:: 3.14 +.. data:: ZLIBNG_VERSION_INFO + + A named tuple containing the three components of the zlib-ng library + version that was used for building the module: + *major*, *minor*, and *revision*. All values are integers. + + .. versionadded:: next + .. seealso:: diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 90ab6758a264cd..58914e2023f876 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -109,6 +109,13 @@ asyncio socket file created for *path*. (Contributed by Sam Bull in :gh:`94984`.) +bz2 +--- + +* Added constants :const:`~bz2.bzlib_version` and + :const:`~bz2.bzlib_version_info` which provide information about + the version of the bzip2 compression library in use. + (Contributed by Serhiy Storchaka in :gh:`117404`.) codecs ------ @@ -120,6 +127,15 @@ codecs even when a built-in codec of the same name exists. (Contributed by Serhiy Storchaka in :gh:`152997`.) +compression.zstd +---------------- + +* Added constants :const:`~compression.zstd.ZSTD_VERSION` and + :const:`~compression.zstd.ZSTD_VERSION_INFO` which provide information + about the version of the zstd library that was used for building the module. + :const:`~compression.zstd.zstd_version_info` is now a named tuple. + (Contributed by Serhiy Storchaka in :gh:`117404`.) + csv --- @@ -378,6 +394,11 @@ lzma requires ``lzma`` 5.4.0 or newer while RISC-V requires 5.6.0 or newer. (Contributed by Chien Wong in :gh:`115988`.) +* Added constants :const:`~lzma.LZMA_VERSION`, :const:`~lzma.lzma_version`, + :const:`~lzma.LZMA_VERSION_INFO`, and :const:`~lzma.lzma_version_info`, + which provide information about the version of the lzma library in use. + (Contributed by Serhiy Storchaka in :gh:`117404`.) + math ---- @@ -613,6 +634,17 @@ zipfile by the local file entries of removed members. (Contributed by Danny Lin in :gh:`51067`.) +zlib +---- + +* Added constants :const:`~zlib.ZLIB_VERSION_INFO`, + :const:`~zlib.zlib_version_info`, and :const:`~zlib.ZLIBNG_VERSION_INFO`, + which provide information about the version of the zlib and the zlib-ng + libraries in use. + Added :const:`~zlib.zlib_version` as an alias of + :const:`~zlib.ZLIB_RUNTIME_VERSION`. + (Contributed by Serhiy Storchaka in :gh:`117404`.) + .. Add improved modules above alphabetically, not here at the end. Optimizations diff --git a/Lib/bz2.py b/Lib/bz2.py index eb58f4da596ea1..79305669efc38c 100644 --- a/Lib/bz2.py +++ b/Lib/bz2.py @@ -5,7 +5,8 @@ """ __all__ = ["BZ2File", "BZ2Compressor", "BZ2Decompressor", - "open", "compress", "decompress"] + "open", "compress", "decompress", + "bzlib_version", "bzlib_version_info"] __author__ = "Nadeem Vawda " @@ -14,7 +15,8 @@ import io import os -from _bz2 import BZ2Compressor, BZ2Decompressor +from _bz2 import (BZ2Compressor, BZ2Decompressor, + bzlib_version, bzlib_version_info) # Value 0 no longer used diff --git a/Lib/compression/zstd/__init__.py b/Lib/compression/zstd/__init__.py index 5326cf9b19cf88..22ce028c435caa 100644 --- a/Lib/compression/zstd/__init__.py +++ b/Lib/compression/zstd/__init__.py @@ -20,6 +20,8 @@ 'get_frame_size', 'zstd_version', 'zstd_version_info', + 'ZSTD_VERSION', + 'ZSTD_VERSION_INFO', 'ZstdCompressor', 'ZstdDecompressor', 'ZstdDict', @@ -29,13 +31,10 @@ import _zstd import enum from _zstd import (ZstdCompressor, ZstdDecompressor, ZstdDict, ZstdError, - get_frame_size, zstd_version) + get_frame_size, zstd_version, ZSTD_VERSION, + zstd_version_info, ZSTD_VERSION_INFO) from compression.zstd._zstdfile import ZstdFile, open, _nbytes -# zstd_version_number is (MAJOR * 100 * 100 + MINOR * 100 + RELEASE) -zstd_version_info = (*divmod(_zstd.zstd_version_number // 100, 100), - _zstd.zstd_version_number % 100) -"""Version number of the runtime zstd library as a tuple of integers.""" COMPRESSION_LEVEL_DEFAULT = _zstd.ZSTD_CLEVEL_DEFAULT """The default compression level for Zstandard, currently '3'.""" diff --git a/Lib/lzma.py b/Lib/lzma.py index fb4bbf650f849a..73a7c702e03711 100644 --- a/Lib/lzma.py +++ b/Lib/lzma.py @@ -20,6 +20,7 @@ "LZMACompressor", "LZMADecompressor", "LZMAFile", "LZMAError", "open", "compress", "decompress", "is_check_supported", + "lzma_version", "lzma_version_info", "LZMA_VERSION", "LZMA_VERSION_INFO", ] import builtins diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index ea7edb79805156..31505cec112608 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -719,17 +719,37 @@ def collect_zlib(info_add): except ImportError: return - attributes = ('ZLIB_VERSION', 'ZLIB_RUNTIME_VERSION', 'ZLIBNG_VERSION') + attributes = ('ZLIB_VERSION', 'zlib_version', 'ZLIBNG_VERSION') copy_attributes(info_add, zlib, 'zlib.%s', attributes) +def collect_bz2(info_add): + try: + import _bz2 + except ImportError: + return + + attributes = ('bzlib_version',) + copy_attributes(info_add, _bz2, 'bz2.%s', attributes) + + +def collect_lzma(info_add): + try: + import _lzma + except ImportError: + return + + attributes = ('LZMA_VERSION', 'lzma_version') + copy_attributes(info_add, _lzma, 'lzma.%s', attributes) + + def collect_zstd(info_add): try: import _zstd except ImportError: return - attributes = ('zstd_version',) + attributes = ('ZSTD_VERSION', 'zstd_version') copy_attributes(info_add, _zstd, 'zstd.%s', attributes) @@ -1334,6 +1354,7 @@ def collect_info(info): collect_urandom, collect_builtins, + collect_bz2, collect_cc, collect_curses, collect_datetime, @@ -1344,6 +1365,7 @@ def collect_info(info): collect_gdbm, collect_get_config, collect_locale, + collect_lzma, collect_os, collect_platform, collect_pwd, diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py index 64293d757331d7..2c95a8eec510b0 100644 --- a/Lib/test/test_bz2.py +++ b/Lib/test/test_bz2.py @@ -1222,6 +1222,33 @@ def test_newline(self): self.assertEqual(f.readlines(), [text]) +class MiscTests(unittest.TestCase): + + def test_bzlib_version(self): + if support.verbose: + print(f'bzlib_version = {bz2.bzlib_version}', flush=True) + print(f'bzlib_version_info = {bz2.bzlib_version_info}', flush=True) + v = bz2.bzlib_version_info + 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, 0) + self.assertGreaterEqual(v.minor, 0) + self.assertGreaterEqual(v.patch, 0) + + # The version string can have a suffix, e.g. "1.0.8, 13-Jul-2019" + # for bzip2 or "1.1.0-libbz2-rs-sys-0.2.5" for libbz2-rs. + self.assertStartsWith(bz2.bzlib_version, '%d.%d.%d' % v) + + def tearDownModule(): support.reap_children() diff --git a/Lib/test/test_lzma.py b/Lib/test/test_lzma.py index d4f954b34c1252..df0b66039d77a1 100644 --- a/Lib/test/test_lzma.py +++ b/Lib/test/test_lzma.py @@ -1513,6 +1513,41 @@ def test_x_mode(self): class MiscellaneousTestCase(unittest.TestCase): + def _test_lzma_version(self, v, string): + self.assertIsInstance(v[:], tuple) + self.assertEqual(len(v), 4) + self.assertIsInstance(v[0], int) + self.assertIsInstance(v[1], int) + self.assertIsInstance(v[2], int) + self.assertIsInstance(v[3], str) + self.assertIsInstance(v.major, int) + self.assertIsInstance(v.minor, int) + self.assertIsInstance(v.patch, int) + self.assertIsInstance(v.stability, str) + self.assertEqual(v[0], v.major) + self.assertEqual(v[1], v.minor) + self.assertEqual(v[2], v.patch) + self.assertEqual(v[3], v.stability) + self.assertGreaterEqual(v.major, 0) + self.assertGreaterEqual(v.minor, 0) + self.assertGreaterEqual(v.patch, 0) + self.assertIn(v.stability, {'alpha', 'beta', 'stable'}) + + if v.stability == 'stable': + self.assertEqual(string, '%d.%d.%d' % v[:3]) + else: + self.assertStartsWith(string, '%d.%d.%d%s' % v) + + def test_lzma_version(self): + if support.verbose: + print(f'LZMA_VERSION = {lzma.LZMA_VERSION}', flush=True) + print(f'lzma_version = {lzma.lzma_version}', flush=True) + print(f'LZMA_VERSION_INFO = {lzma.LZMA_VERSION_INFO}', flush=True) + print(f'lzma_version_info = {lzma.lzma_version_info}', flush=True) + self._test_lzma_version(lzma.LZMA_VERSION_INFO, lzma.LZMA_VERSION) + self._test_lzma_version(lzma.lzma_version_info, lzma.lzma_version) + self.assertEqual(lzma.LZMA_VERSION_INFO[0], lzma.lzma_version_info[0]) + def test_is_check_supported(self): # CHECK_NONE and CHECK_CRC32 should always be supported, # regardless of the options liblzma was compiled with. diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py index ed9d85408159b2..45f75d3a8a60eb 100644 --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -19,20 +19,17 @@ 'requires Decompress.copy()') -def _zlib_runtime_version_tuple(zlib_version=zlib.ZLIB_RUNTIME_VERSION): +def _parse_version(version): # Register "1.2.3" as "1.2.3.0" # or "1.2.0-linux","1.2.0.f","1.2.0.f-linux" - v = zlib_version.split('-', 1)[0].split('.') - if len(v) < 4: + v = version.split('-', 1)[0].split('.') + if not v[-1].isnumeric(): + v.pop() + while len(v) < 4: v.append('0') - elif not v[-1].isnumeric(): - v[-1] = '0' return tuple(map(int, v)) -ZLIB_RUNTIME_VERSION_TUPLE = _zlib_runtime_version_tuple() - - # bpo-46623: When a hardware accelerator is used (currently only on s390x), # using different ways to compress data with zlib can produce different # compressed data. @@ -71,7 +68,70 @@ def test_library_version(self): # the minor versions will match (even on the machine on which the module # was compiled), and the API is stable between minor versions, so # testing only the major versions avoids spurious failures. - self.assertEqual(zlib.ZLIB_RUNTIME_VERSION[0], zlib.ZLIB_VERSION[0]) + self.assertEqual(zlib.zlib_version.split('.')[0], + zlib.ZLIB_VERSION.split('.')[0]) + self.assertEqual(zlib.zlib_version_info[0], + zlib.ZLIB_VERSION_INFO[0]) + + def _test_zlib_version(self, v): + self.assertIsInstance(v[:], tuple) + self.assertEqual(len(v), 4) + self.assertIsInstance(v[0], int) + self.assertIsInstance(v[1], int) + self.assertIsInstance(v[2], int) + self.assertIsInstance(v[3], int) + self.assertIsInstance(v.major, int) + self.assertIsInstance(v.minor, int) + self.assertIsInstance(v.revision, int) + self.assertIsInstance(v.subversion, int) + self.assertEqual(v[0], v.major) + self.assertEqual(v[1], v.minor) + self.assertEqual(v[2], v.revision) + self.assertEqual(v[3], v.subversion) + self.assertGreaterEqual(v.major, 0) + self.assertGreaterEqual(v.minor, 0) + self.assertGreaterEqual(v.revision, 0) + self.assertGreaterEqual(v.subversion, 0) + + def test_zlib_version(self): + if support.verbose: + print(f'ZLIB_VERSION = {zlib.ZLIB_VERSION}', flush=True) + print(f'zlib_version = {zlib.zlib_version}', flush=True) + print(f'ZLIB_VERSION_INFO = {zlib.ZLIB_VERSION_INFO}', flush=True) + print(f'zlib_version_info = {zlib.zlib_version_info}', flush=True) + self._test_zlib_version(zlib.ZLIB_VERSION_INFO) + self.assertEqual(zlib.ZLIB_VERSION_INFO, + _parse_version(zlib.ZLIB_VERSION)) + self._test_zlib_version(zlib.zlib_version_info) + self.assertEqual(zlib.zlib_version_info, + _parse_version(zlib.zlib_version)) + self.assertEqual(zlib.ZLIB_RUNTIME_VERSION, zlib.zlib_version) + + def _test_zlibng_version(self, v): + 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.revision, int) + self.assertEqual(v[0], v.major) + self.assertEqual(v[1], v.minor) + self.assertEqual(v[2], v.revision) + self.assertGreaterEqual(v.major, 0) + self.assertGreaterEqual(v.minor, 0) + self.assertGreaterEqual(v.revision, 0) + + @unittest.skipUnless(hasattr(zlib, 'ZLIBNG_VERSION'), 'requires zlib-ng') + def test_zlibng_version(self): + if support.verbose: + print(f'ZLIBNG_VERSION = {zlib.ZLIBNG_VERSION}', flush=True) + print(f'ZLIBNG_VERSION_INFO = {zlib.ZLIBNG_VERSION_INFO}', + flush=True) + self._test_zlibng_version(zlib.ZLIBNG_VERSION_INFO) + self.assertEqual(zlib.ZLIBNG_VERSION, + '%d.%d.%d' % zlib.ZLIBNG_VERSION_INFO) class ChecksumTestCase(unittest.TestCase): @@ -604,7 +664,7 @@ def test_flushes(self): 'Z_PARTIAL_FLUSH'] # Z_BLOCK has a known failure prior to 1.2.5.3 - if ZLIB_RUNTIME_VERSION_TUPLE >= (1, 2, 5, 3): + if zlib.zlib_version_info >= (1, 2, 5, 3): sync_opt.append('Z_BLOCK') sync_opt = [getattr(zlib, opt) for opt in sync_opt @@ -918,7 +978,7 @@ def test_large_unconsumed_tail(self, size): def test_wbits(self): # wbits=0 only supported since zlib v1.2.3.5 - supports_wbits_0 = ZLIB_RUNTIME_VERSION_TUPLE >= (1, 2, 3, 5) + supports_wbits_0 = zlib.zlib_version_info >= (1, 2, 3, 5) co = zlib.compressobj(level=1, wbits=15) zlib15 = co.compress(HAMLET_SCENE) + co.flush() diff --git a/Lib/test/test_zstd.py b/Lib/test/test_zstd.py index 9225c42e0e711a..372326a26c71ea 100644 --- a/Lib/test/test_zstd.py +++ b/Lib/test/test_zstd.py @@ -9,6 +9,7 @@ import tempfile import threading +from test import support from test.support.import_helper import import_module from test.support import threading_helper from test.support import _1M @@ -24,8 +25,6 @@ ZstdDecompressor, ZstdDict, ZstdError, - zstd_version, - zstd_version_info, COMPRESSION_LEVEL_DEFAULT, get_frame_info, get_frame_size, @@ -133,9 +132,33 @@ def setUpModule(): class FunctionsTestCase(unittest.TestCase): - def test_version(self): - s = ".".join((str(i) for i in zstd_version_info)) - self.assertEqual(s, zstd_version) + def _test_zstd_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, 0) + self.assertGreaterEqual(v.minor, 0) + self.assertGreaterEqual(v.patch, 0) + + self.assertEqual(string, '%d.%d.%d' % v) + + def test_zstd_version(self): + if support.verbose: + print(f'ZSTD_VERSION = {zstd.ZSTD_VERSION}', flush=True) + print(f'zstd_version = {zstd.zstd_version}', flush=True) + print(f'ZSTD_VERSION_INFO = {zstd.ZSTD_VERSION_INFO}', flush=True) + print(f'zstd_version_info = {zstd.zstd_version_info}', flush=True) + self._test_zstd_version(zstd.ZSTD_VERSION_INFO, zstd.ZSTD_VERSION) + self._test_zstd_version(zstd.zstd_version_info, zstd.zstd_version) + self.assertEqual(zstd.ZSTD_VERSION_INFO[0], zstd.zstd_version_info[0]) def test_compressionLevel_values(self): min, max = CompressionParameter.compression_level.bounds() diff --git a/Misc/NEWS.d/next/Library/2026-05-29-10-11-34.gh-issue-117404.EeP6xe.rst b/Misc/NEWS.d/next/Library/2026-05-29-10-11-34.gh-issue-117404.EeP6xe.rst new file mode 100644 index 00000000000000..3f1942cf7e143c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-29-10-11-34.gh-issue-117404.EeP6xe.rst @@ -0,0 +1,5 @@ +Add a number of constants in modules :mod:`zlib`, :mod:`bz2`, :mod:`lzma`, +and :mod:`compression.zstd`, which provide information about the version of +libraries that were used for building the module and that are actually +loaded. +Make :const:`compression.zstd.zstd_version_info` a named tuple. diff --git a/Modules/_bz2module.c b/Modules/_bz2module.c index 9db3ac39da5209..e0d85b78741a91 100644 --- a/Modules/_bz2module.c +++ b/Modules/_bz2module.c @@ -747,6 +747,61 @@ static PyType_Spec bz2_decompressor_type_spec = { .slots = bz2_decompressor_type_slots, }; + +PyDoc_STRVAR(bzlib_version_info__doc__, +"_bz2.bzlib_version_info\n\ +\n\ +Bzlib version information as a named tuple."); + +static PyStructSequence_Field bzlib_version_info_fields[] = { + {"major", "Major release number"}, + {"minor", "Minor release number"}, + {"patch", "Patch release number"}, + {0} +}; + +static PyStructSequence_Desc bzlib_version_info_desc = { + "_bz2.bzlib_version_info", /* name */ + bzlib_version_info__doc__, /* doc */ + bzlib_version_info_fields, /* fields */ + 3 +}; + +/* BZ2_bzlibVersion() returns a string with a trailing suffix, for example + "1.0.8, 13-Jul-2019" for bzip2 or "1.1.0-libbz2-rs-sys-0.2.5" for + libbz2-rs. sscanf() stops at the suffix; components which were not + parsed are left zero. This is deliberate -- a zero is more useful + here than a hard error. */ +static PyObject * +make_bzlib_version_info(PyTypeObject *type, const char *string) +{ + PyObject *version; + int pos = 0; + unsigned int major = 0, minor = 0, patch = 0; + + sscanf(string, "%u.%u.%u", &major, &minor, &patch); + + version = PyStructSequence_New(type); + if (version == NULL) { + return NULL; + } + +#define SetIntItem(VALUE) \ + PyStructSequence_SET_ITEM(version, pos++, PyLong_FromUnsignedLong(VALUE)); \ + if (PyErr_Occurred()) { \ + Py_DECREF(version); \ + return NULL; \ + } + + SetIntItem(major) + SetIntItem(minor) + SetIntItem(patch) +#undef SetIntItem + + return version; +} + + /* Module initialization. */ static int @@ -771,6 +826,24 @@ _bz2_exec(PyObject *module) return -1; } + /* bzlib_version */ + if (PyModule_Add(module, "bzlib_version", + PyUnicode_FromString(BZ2_bzlibVersion())) < 0) + { + return -1; + } + PyTypeObject *version_type; + version_type = PyStructSequence_NewType(&bzlib_version_info_desc); + if (version_type == NULL) { + return -1; + } + if (PyModule_Add(module, "bzlib_version_info", + make_bzlib_version_info(version_type, BZ2_bzlibVersion())) < 0) + { + Py_DECREF(version_type); + return -1; + } + Py_DECREF(version_type); return 0; } diff --git a/Modules/_lzmamodule.c b/Modules/_lzmamodule.c index 4335a8bb162414..92784904f3c993 100644 --- a/Modules/_lzmamodule.c +++ b/Modules/_lzmamodule.c @@ -1509,6 +1509,62 @@ _lzma__decode_filter_properties_impl(PyObject *module, lzma_vli filter_id, return result; } + +PyDoc_STRVAR(lzma_version_info__doc__, +"_lzma.lzma_version_info\n\ +\n\ +Lzma version information as a named tuple."); + +static PyStructSequence_Field lzma_version_info_fields[] = { + {"major", "Major release number"}, + {"minor", "Minor release number"}, + {"patch", "Patch release number"}, + {"stability", "'alpha', 'beta', or 'stable'"}, + {0} +}; + +static PyStructSequence_Desc lzma_version_info_desc = { + "_lzma.lzma_version_info", /* name */ + lzma_version_info__doc__, /* doc */ + lzma_version_info_fields, /* fields */ + 4 +}; + +static PyObject * +make_lzma_version_info(PyTypeObject *type, unsigned int number) +{ + PyObject *version; + int pos = 0; + unsigned int major = number / 10000000u; + unsigned int minor = (number % 10000000u) / 10000u; + unsigned int patch = (number % 10000u) / 10u; + unsigned int stability = number % 10u; + const char *stability_string = (stability == 0) ? "alpha" + : (stability == 1) ? "beta" + : "stable"; + + 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(patch)) + SetItem(PyUnicode_FromString(stability_string)) +#undef SetItem + + return version; +} + + /* Some of our constants are more than 32 bits wide, so PyModule_AddIntConstant would not work correctly on platforms with 32-bit longs. */ static int @@ -1603,6 +1659,33 @@ lzma_exec(PyObject *module) return -1; } + /* lzma_version */ + if (PyModule_Add(module, "LZMA_VERSION", + PyUnicode_FromString(LZMA_VERSION_STRING)) < 0) { + return -1; + } + if (PyModule_Add(module, "lzma_version", + PyUnicode_FromString(lzma_version_string())) < 0) { + return -1; + } + PyTypeObject *version_type; + version_type = PyStructSequence_NewType(&lzma_version_info_desc); + if (version_type == NULL) { + return -1; + } + if (PyModule_Add(module, "LZMA_VERSION_INFO", + make_lzma_version_info(version_type, LZMA_VERSION)) < 0) + { + Py_DECREF(version_type); + return -1; + } + if (PyModule_Add(module, "lzma_version_info", + make_lzma_version_info(version_type, lzma_version_number())) < 0) + { + Py_DECREF(version_type); + return -1; + } + Py_DECREF(version_type); return 0; } diff --git a/Modules/_zstd/_zstdmodule.c b/Modules/_zstd/_zstdmodule.c index 94246dd93b17de..9b885bc616ba27 100644 --- a/Modules/_zstd/_zstdmodule.c +++ b/Modules/_zstd/_zstdmodule.c @@ -571,6 +571,55 @@ static PyMethodDef _zstd_methods[] = { {NULL, NULL} }; +PyDoc_STRVAR(zstd_version_info__doc__, +"_zstd.zstd_version_info\n\ +\n\ +Zstd version information as a named tuple."); + +static PyStructSequence_Field zstd_version_info_fields[] = { + {"major", "Major release number"}, + {"minor", "Minor release number"}, + {"patch", "Patch release number"}, + {0} +}; + +static PyStructSequence_Desc zstd_version_info_desc = { + "_zstd.zstd_version_info", /* name */ + zstd_version_info__doc__, /* doc */ + zstd_version_info_fields, /* fields */ + 3 +}; + +static PyObject * +make_zstd_version_info(PyTypeObject *type, unsigned int number) +{ + PyObject *version; + int pos = 0; + unsigned int major = number / 10000u; + unsigned int minor = (number % 10000u) / 100u; + unsigned int patch = number % 100u; + + version = PyStructSequence_New(type); + if (version == NULL) { + return NULL; + } + +#define SetIntItem(VALUE) \ + PyStructSequence_SET_ITEM(version, pos++, PyLong_FromUnsignedLong(VALUE)); \ + if (PyErr_Occurred()) { \ + Py_DECREF(version); \ + return NULL; \ + } + + SetIntItem(major) + SetIntItem(minor) + SetIntItem(patch) +#undef SetIntItem + + return version; +} + + static int _zstd_exec(PyObject *m) { @@ -623,15 +672,32 @@ do { \ } /* Add constants */ - if (PyModule_AddIntConstant(m, "zstd_version_number", - ZSTD_versionNumber()) < 0) { + if (PyModule_AddStringConstant(m, "ZSTD_VERSION", + ZSTD_VERSION_STRING) < 0) { return -1; } - if (PyModule_AddStringConstant(m, "zstd_version", ZSTD_versionString()) < 0) { return -1; } + PyTypeObject *version_type; + version_type = PyStructSequence_NewType(&zstd_version_info_desc); + if (version_type == NULL) { + return -1; + } + if (PyModule_Add(m, "ZSTD_VERSION_INFO", + make_zstd_version_info(version_type, ZSTD_VERSION_NUMBER)) < 0) + { + Py_DECREF(version_type); + return -1; + } + if (PyModule_Add(m, "zstd_version_info", + make_zstd_version_info(version_type, ZSTD_versionNumber())) < 0) + { + Py_DECREF(version_type); + return -1; + } + Py_DECREF(version_type); #if ZSTD_VERSION_NUMBER >= 10500 if (PyModule_AddIntConstant(m, "ZSTD_CLEVEL_DEFAULT", diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index 0a6732835eb51f..75fab7c675417b 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -2060,6 +2060,77 @@ zlib_getattr(PyObject *self, PyObject *args) return NULL; } +PyDoc_STRVAR(zlib_version_info__doc__, +"zlib.zlib_version_info\n\ +\n\ +Zlib version information as a named tuple."); + +static PyStructSequence_Field zlib_version_info_fields[] = { + {"major", "Major release number"}, + {"minor", "Minor release number"}, + {"revision", "Revision release number"}, + {"subversion", "Subversion release number"}, + {0} +}; + +static PyStructSequence_Desc zlib_version_info_desc = { + "zlib.zlib_version_info", /* name */ + zlib_version_info__doc__, /* doc */ + zlib_version_info_fields, /* fields */ + 4 +}; + +#ifdef ZLIBNG_VERSION +PyDoc_STRVAR(zlibng_version_info__doc__, +"zlib.zlibng_version_info\n\ +\n\ +Zlib-ng version information as a named tuple."); + +static PyStructSequence_Field zlibng_version_info_fields[] = { + {"major", "Major release number"}, + {"minor", "Minor release number"}, + {"revision", "Revision release number"}, + {0} +}; + +static PyStructSequence_Desc zlibng_version_info_desc = { + "zlib.zlibng_version_info", /* name */ + zlibng_version_info__doc__, /* doc */ + zlibng_version_info_fields, /* fields */ + 3 +}; +#endif // ZLIBNG_VERSION + +/* Create a named tuple from the first *size* components of a version string + like "1.2.11" or "1.2.11.1". sscanf() is expected to fail on trailing + garbage and on versions with fewer components (for example "1.2.0.f" or + "1.3.1.zlib-ng"); the components which were not parsed are left zero. + This is deliberate -- a zero is more useful here than a hard error. */ +static PyObject * +make_version_info(PyTypeObject *type, const char *string, Py_ssize_t size) +{ + unsigned int components[4] = {0, 0, 0, 0}; + assert(size <= (Py_ssize_t)Py_ARRAY_LENGTH(components)); + + sscanf(string, "%u.%u.%u.%u", + &components[0], &components[1], &components[2], &components[3]); + + PyObject *version = PyStructSequence_New(type); + if (version == NULL) { + return NULL; + } + for (Py_ssize_t i = 0; i < size; i++) { + PyObject *item = PyLong_FromUnsignedLong(components[i]); + if (item == NULL) { + Py_DECREF(version); + return NULL; + } + PyStructSequence_SET_ITEM(version, i, item); + } + return version; +} + + static PyMethodDef zlib_methods[] = { ZLIB_ADLER32_METHODDEF @@ -2255,10 +2326,16 @@ zlib_exec(PyObject *mod) #ifdef Z_TREES // 1.2.3.4, only for inflate ZLIB_ADD_INT_MACRO(Z_TREES); #endif + + /* zlib_version */ if (PyModule_Add(mod, "ZLIB_VERSION", PyUnicode_FromString(ZLIB_VERSION)) < 0) { return -1; } + if (PyModule_Add(mod, "zlib_version", + PyUnicode_FromString(zlibVersion())) < 0) { + return -1; + } if (PyModule_Add(mod, "ZLIB_RUNTIME_VERSION", PyUnicode_FromString(zlibVersion())) < 0) { return -1; @@ -2268,6 +2345,37 @@ zlib_exec(PyObject *mod) PyUnicode_FromString(ZLIBNG_VERSION)) < 0) { return -1; } +#endif + PyTypeObject *version_type; + version_type = PyStructSequence_NewType(&zlib_version_info_desc); + if (version_type == NULL) { + return -1; + } + if (PyModule_Add(mod, "ZLIB_VERSION_INFO", + make_version_info(version_type, ZLIB_VERSION, 4)) < 0) + { + Py_DECREF(version_type); + return -1; + } + if (PyModule_Add(mod, "zlib_version_info", + make_version_info(version_type, zlibVersion(), 4)) < 0) + { + Py_DECREF(version_type); + return -1; + } + Py_DECREF(version_type); +#ifdef ZLIBNG_VERSION + version_type = PyStructSequence_NewType(&zlibng_version_info_desc); + if (version_type == NULL) { + return -1; + } + if (PyModule_Add(mod, "ZLIBNG_VERSION_INFO", + make_version_info(version_type, ZLIBNG_VERSION, 3)) < 0) + { + Py_DECREF(version_type); + return -1; + } + Py_DECREF(version_type); #endif return 0; }