Skip to content

gh-117404: Add structured version info for compression modules #117405

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Doc/library/bz2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,31 @@ One-shot (de)compression
.. versionchanged:: 3.3
Support for multi-stream inputs was added.


Miscellaneous
-------------

Information about the version of the bzlib library in use is available through
the following constants:


.. data:: BZLIB_VERSION

The version string of the bzlib library.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven’t seen bz2 called bzlib before. It’s always bzip2, bz2-lib or libbz2.


.. versionadded:: 3.13


.. data:: bzlib_version

A named tuple containing the three components of the bzlib library version:
*major*, *minor*, and *patch*. All values are integers.
The components can also be accessed by name, so ``bz2.bzlib_version[0]``
is equivalent to ``bz2.bzlib_version.major`` and so on.

.. versionadded:: 3.13


.. _bz2-usage-examples:

Examples of usage
Expand Down
42 changes: 42 additions & 0 deletions Doc/library/lzma.rst
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,48 @@ 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_RUNTIME_VERSION`.

.. versionadded:: 3.13


.. data:: LZMA_RUNTIME_VERSION

The version string of the lzma library actually loaded by the interpreter.

.. versionadded:: 3.13


.. data:: lzma_version

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[0]``
is equivalent to ``lzma.lzma_version.major`` and so on.
This may be different from the lzma library actually used at runtime, which
is available as :const:`lzma_runtime_version`.

.. versionadded:: 3.13


.. data:: lzma_runtime_version

A named tuple containing the lzma library version actually loaded by the interpreter.

.. versionadded:: 3.13


.. _filter-chain-specs:

Specifying custom filter chains
Expand Down
21 changes: 21 additions & 0 deletions Doc/library/zlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,27 @@ the following constants:
.. versionadded:: 3.3


.. data:: zlib_version

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[0]``
is equivalent to ``zlib.zlib_version.major`` and so on.
This may be different from the zlib library actually used at runtime, which
is available as :const:`zlib_runtime_version`.

.. versionadded:: 3.13


.. data:: zlib_runtime_version

A named tuple containing the zlib library version actually loaded by the interpreter.

.. versionadded:: 3.13


.. seealso::

Module :mod:`gzip`
Expand Down
4 changes: 2 additions & 2 deletions Lib/bz2.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""

__all__ = ["BZ2File", "BZ2Compressor", "BZ2Decompressor",
"open", "compress", "decompress"]
"open", "compress", "decompress", "BZLIB_VERSION", "bzlib_version"]

__author__ = "Nadeem Vawda <nadeem.vawda@gmail.com>"

Expand All @@ -14,7 +14,7 @@
import os
import _compression

from _bz2 import BZ2Compressor, BZ2Decompressor
from _bz2 import BZ2Compressor, BZ2Decompressor, BZLIB_VERSION, bzlib_version


_MODE_CLOSED = 0
Expand Down
1 change: 1 addition & 0 deletions Lib/lzma.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

"LZMACompressor", "LZMADecompressor", "LZMAFile", "LZMAError",
"open", "compress", "decompress", "is_check_supported",
"LZMA_VERSION", "lzma_version", "LZMA_RUNTIME_VERSION", "lzma_runtime_version",
]

import builtins
Expand Down
25 changes: 25 additions & 0 deletions Lib/test/test_bz2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,31 @@ 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 = {bz2.bzlib_version}', flush=True)
v = bz2.bzlib_version
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(bz2.BZLIB_VERSION.split(',')[0], '%d.%d.%d' % v)


def tearDownModule():
support.reap_children()

Expand Down
37 changes: 37 additions & 0 deletions Lib/test/test_lzma.py
Original file line number Diff line number Diff line change
Expand Up @@ -1446,6 +1446,43 @@ 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.assertTrue(string.startswith('%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)
self._test_lzma_version(lzma.lzma_version, lzma.LZMA_VERSION)

def test_lzma_runtime_version(self):
if support.verbose:
print(f'LZMA_RUNTIME_VERSION = {lzma.LZMA_RUNTIME_VERSION}', flush=True)
print(f'lzma_runtime_version = {lzma.lzma_runtime_version}', flush=True)
self._test_lzma_version(lzma.lzma_runtime_version, lzma.LZMA_RUNTIME_VERSION)

def test_is_check_supported(self):
# CHECK_NONE and CHECK_CRC32 should always be supported,
# regardless of the options liblzma was compiled with.
Expand Down
53 changes: 43 additions & 10 deletions Lib/test/test_zlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: On s390x, when a hardware accelerator is used, using different
# ways to compress data with zlib can produce different compressed data.
# Simplified test_pair() code:
Expand Down Expand Up @@ -69,6 +66,42 @@ def test_library_version(self):
# 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_runtime_version[0], zlib.zlib_version[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)
self._test_zlib_version(zlib.zlib_version)
self.assertEqual(zlib.zlib_version, _parse_version(zlib.ZLIB_VERSION))

def test_zlib_runtime_version(self):
if support.verbose:
print(f'ZLIB_RUNTIME_VERSION = {zlib.ZLIB_RUNTIME_VERSION}', flush=True)
print(f'zlib_runtime_version = {zlib.zlib_runtime_version}', flush=True)
self._test_zlib_version(zlib.zlib_runtime_version)
self.assertEqual(zlib.zlib_runtime_version,
_parse_version(zlib.ZLIB_RUNTIME_VERSION))


class ChecksumTestCase(unittest.TestCase):
Expand Down Expand Up @@ -489,7 +522,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 >= (1, 2, 5, 3):
sync_opt.append('Z_BLOCK')

sync_opt = [getattr(zlib, opt) for opt in sync_opt
Expand Down Expand Up @@ -807,7 +840,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 >= (1, 2, 3, 5)

co = zlib.compressobj(level=1, wbits=15)
zlib15 = co.compress(HAMLET_SCENE) + co.flush()
Expand Down
68 changes: 68 additions & 0 deletions Modules/_bz2module.c
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,56 @@ static PyType_Spec bz2_decompressor_type_spec = {
.slots = bz2_decompressor_type_slots,
};


PyDoc_STRVAR(bzlib_version__doc__,
"_bz2.bzlib_version\n\
\n\
Bzlib version information as a named tuple.");

static PyStructSequence_Field bzlib_version_fields[] = {
{"major", "Major release number"},
{"minor", "Minor release number"},
{"patch", "Patch release number"},
{0}
};

static PyStructSequence_Desc zlib_version_desc = {
"_bz2.bzlib_version", /* name */
bzlib_version__doc__, /* doc */
bzlib_version_fields, /* fields */
3
};

static PyObject *
make_bzlib_version(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
Expand All @@ -772,6 +822,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(&zlib_version_desc);
if (version_type == NULL) {
return -1;
}
if (PyModule_Add(module, "bzlib_version",
make_bzlib_version(version_type, BZ2_bzlibVersion())) < 0)
{
Py_DECREF(version_type);
return -1;
}
Py_DECREF(version_type);
return 0;
}

Expand Down
Loading
Loading