From 8e0fc40eebe9bb30369f61c9566a2ee41248c145 Mon Sep 17 00:00:00 2001 From: Roland Walker Date: Fri, 20 Mar 2026 17:27:12 -0400 Subject: [PATCH] probe tabulate version for preserve_whitespace Somehow, users are able to have installations of newer mycli/cli_helpers with older versions of tabulate, before a breaking change in the way preserve_whitespace is specified for tabulate. Here we probe the tabulate version to determine how to specify the value. --- CHANGELOG | 6 +++++ .../tabular_output/tabulate_adapter.py | 11 ++++++-- cli_helpers/utils.py | 14 ++++++++++- tests/test_utils.py | 25 +++++++++++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b3139b8..7b1e73f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,11 @@ # Changelog +## Version 2.12.0 + +(released on 2025-03-21) + +- Probe tabulate version for `preserve_whitespace` keyword support. + ## Version 2.11.0 (released on 2025-03-05) diff --git a/cli_helpers/tabular_output/tabulate_adapter.py b/cli_helpers/tabular_output/tabulate_adapter.py index cf32da5..0b638df 100644 --- a/cli_helpers/tabular_output/tabulate_adapter.py +++ b/cli_helpers/tabular_output/tabulate_adapter.py @@ -5,7 +5,7 @@ import os -from cli_helpers.utils import filter_dict_by_key +from cli_helpers.utils import filter_dict_by_key, version_as_tuple from cli_helpers.compat import ( Terminal256Formatter, TerminalTrueColorFormatter, @@ -141,6 +141,10 @@ headless_formats = ("minimal",) +def has_new_preserve_whitespace_arg(): + return version_as_tuple(tabulate.__version__) >= (0, 10, 0) + + def get_preprocessors(format_name): common_formatters = ( override_missing_value, @@ -249,7 +253,10 @@ def adapter(data, headers, table_format=None, preserve_whitespace=False, **kwarg if table_format in supported_markup_formats: tkwargs.update(numalign=None, stralign=None) - tkwargs.update(preserve_whitespace=preserve_whitespace) + if has_new_preserve_whitespace_arg(): + tkwargs.update(preserve_whitespace=preserve_whitespace) + else: + tabulate.PRESERVE_WHITESPACE = preserve_whitespace tkwargs.update(default_kwargs.get(table_format, {})) if table_format in headless_formats: diff --git a/cli_helpers/utils.py b/cli_helpers/utils.py index 5e6646f..f386929 100644 --- a/cli_helpers/utils.py +++ b/cli_helpers/utils.py @@ -5,7 +5,7 @@ import os import re from functools import lru_cache -from typing import Dict, Union +from typing import Dict, Tuple, Union from typing import TYPE_CHECKING @@ -146,3 +146,15 @@ def filter_style_table(style: "StyleMeta", *relevant_styles: str) -> Dict: _styles_iter = ((key, val) for key, val in getattr(style, "styles", {}).items()) _relevant_styles_iter = filter(lambda tpl: tpl[0] in relevant_styles, _styles_iter) return {key: val for key, val in _relevant_styles_iter} + + +@lru_cache() +def version_as_tuple(version: str) -> Tuple: + try: + list_s = [re.sub(r'(rc|alpha|beta|test|dev|post).*$', '', x) for x in version.split('.')] + list_s = [re.sub(r'[^\d]', '', x) for x in list_s] + list_i = [int(x) for x in list_s if x] + list_i.extend([0, 0, 0]) + return tuple(list_i[:3]) + except ValueError: + return (0, 0, 0) diff --git a/tests/test_utils.py b/tests/test_utils.py index ba43937..3db8b0b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -75,3 +75,28 @@ def test_filter_dict_by_key(): fd = utils.filter_dict_by_key(d, keys) assert len(fd) == 1 assert all([k in keys for k in fd]) + + +def test_version_as_tuple_01(): + """Test version_as_tuple() in the usual case.""" + assert utils.version_as_tuple('0.10.0') == (0, 10, 0) + + +def test_version_as_tuple_02(): + """Test version_as_tuple() with leading v.""" + assert utils.version_as_tuple('v0.10.0') == (0, 10, 0) + + +def test_version_as_tuple_03(): + """Test version_as_tuple() trailing padding.""" + assert utils.version_as_tuple('0.10') == (0, 10, 0) + + +def test_version_as_tuple_04(): + """Test version_as_tuple() stripping rc suffixes.""" + assert utils.version_as_tuple('0.10.0rc1') == (0, 10, 0) + + +def test_version_as_tuple_05(): + """Test version_as_tuple() deleting post1 elements.""" + assert utils.version_as_tuple('0.10.0.post1') == (0, 10, 0)