leapp/0001-introduce-the-format_list-function.patch
Petr Stodulka 2a6064c28d IPU 9.9 -> 10.3: CTC1 candidate 1
- Bump leapp-framework to 6.5
- Change how commands are converted for text based report from the list representation
- Introduce the format_list function for unified presentation of lists in reports and logs
- Resolves: RHEL-156521
2026-04-17 15:58:18 +02:00

195 lines
7.6 KiB
Diff

From d67332c2234e437113493bfdce37594c1e2ae417 Mon Sep 17 00:00:00 2001
From: Petr Stodulka <pstodulk@redhat.com>
Date: Wed, 3 Dec 2025 08:48:08 +0100
Subject: [PATCH 01/10] introduce the format_list function
The construction of report messages involving lists (e.g., package
names, file paths) lacked a consistent formatting which led to
inconsistencies and redefinitions when creating reports.
This patch introduces the format_list function to provide a consistent
formatting option across all reports. The function is available from both
leapp.libraries.stdlib and leapp.reporting. It supports custom sorting,
item limits, and configurable separator.
Jira-ref: RHEL-126447
---
docs/source/best-practices.md | 17 +++++++++++
leapp/libraries/stdlib/__init__.py | 36 +++++++++++++++++++++--
leapp/reporting/__init__.py | 5 +++-
packaging/leapp.spec | 2 +-
tests/scripts/test_format_list.py | 47 ++++++++++++++++++++++++++++++
5 files changed, 103 insertions(+), 4 deletions(-)
create mode 100644 tests/scripts/test_format_list.py
diff --git a/docs/source/best-practices.md b/docs/source/best-practices.md
index 11f861a..913dcbd 100644
--- a/docs/source/best-practices.md
+++ b/docs/source/best-practices.md
@@ -155,6 +155,23 @@ In case of [StopActorExecutionError](leapp.exceptions.StopActorExecutionError) t
You can also use the [StopActorExecution](leapp.exceptions.StopActorExecution) and [StopActorExecutionError](leapp.exceptions.StopActorExecutionError) exceptions inside a private or shared library.
+## Consistent list formatting in reports
+
+When constructing report messages that include lists of items (e.g. package names, file paths), use the `format_list` function to ensure consistent formatting across all reports. The function is available from both `leapp.libraries.stdlib` and `leapp.reporting`. It supports custom sorting, item limits, and configurable separators.
+
+```python
+from leapp.reporting import format_list
+
+pkgs = ['kernel', 'bash', 'glibc']
+msg = 'The following packages will be removed:{}'.format(format_list(pkgs))
+
+# Output:
+#The following packages will be removed:
+# - bash
+# - glibc
+# - kernel
+```
+
## Use the LEAPP and LEAPP\_DEVEL prefixes for new envars
In case you need to change a behaviour of actor(s) for testing or development purposes - e.g. be able to skip a functionality in your actor - use environment variables. Such environment variables should start with prefix *LEAPP\_DEVEL*. Such variables are not possible to use on production systems without special *LEAPP\_UNSUPPORTED* variable. This prevents users to break their systems by a mistake.
diff --git a/leapp/libraries/stdlib/__init__.py b/leapp/libraries/stdlib/__init__.py
index 89e59b2..efe2029 100644
--- a/leapp/libraries/stdlib/__init__.py
+++ b/leapp/libraries/stdlib/__init__.py
@@ -8,12 +8,15 @@ import logging
import os
import sys
import uuid
+from itertools import islice
from leapp.exceptions import LeappError
-from leapp.utils.audit import create_audit_entry
from leapp.libraries.stdlib import api
-from leapp.libraries.stdlib.call import _call, STDOUT
+from leapp.libraries.stdlib.call import STDOUT, _call
from leapp.libraries.stdlib.config import is_debug
+from leapp.utils.audit import create_audit_entry
+
+FMT_LIST_SEPARATOR = '\n - '
class CalledProcessError(LeappError):
@@ -214,3 +217,32 @@ def run(args, split=False, callback_raw=_console_logging_handler, callback_lineb
)
api.current_logger().debug('External command has finished: {0}'.format(str(args)))
return result
+
+
+def format_list(data, sep=FMT_LIST_SEPARATOR, callback_sort=sorted, limit=0):
+ """
+ Format an iterable into a string using a specified separator that is prepended to every item.
+
+ This function can be used to consistently format lists in reports, logs, and error messages.
+
+ :param data: Iterable of items to format.
+ :type data: Iterable
+ :param sep: Separator prepended to each item. Defaults to FMT_LIST_SEPARATOR.
+ :type sep: str
+ :param callback_sort: Callable returning a new list, called before the limit is applied.
+ Set to None to preserve original order. Defaults to sorted.
+ :type callback_sort: Callable or None
+ :param limit: Maximum number of items to include. Defaults to 0 (no limit).
+ :type limit: int
+ :returns: A string with each item prefixed by the specified separator.
+ :rtype: str
+ """
+ items = data
+ if callback_sort is not None:
+ items = callback_sort(data)
+
+ if limit > 0:
+ items = islice(items, limit)
+
+ res = ['{}{}'.format(sep, item) for item in items]
+ return ''.join(res)
diff --git a/leapp/reporting/__init__.py b/leapp/reporting/__init__.py
index 7a0e223..34af17a 100644
--- a/leapp/reporting/__init__.py
+++ b/leapp/reporting/__init__.py
@@ -6,9 +6,12 @@ import os
import six
from leapp.compat import string_types
+# NOTE(pstodulk): the format_list is imported to provide the function
+# also in this library. Its use is not planned here however.
+from leapp.libraries.stdlib import format_list
+from leapp.libraries.stdlib.api import produce
from leapp.models import fields, Model, ErrorModel
from leapp.topics import ReportTopic
-from leapp.libraries.stdlib.api import produce
from leapp.utils.deprecation import deprecated
diff --git a/packaging/leapp.spec b/packaging/leapp.spec
index 1e32cf6..a06b141 100644
--- a/packaging/leapp.spec
+++ b/packaging/leapp.spec
@@ -13,7 +13,7 @@
# This is kind of help for more flexible development of leapp repository,
# so people do not have to wait for new official release of leapp to ensure
# it is installed/used the compatible one.
-%global framework_version 6.2
+%global framework_version 6.3
# IMPORTANT: everytime the requirements are changed, increment number by one
# - same for Provides in deps subpackage
diff --git a/tests/scripts/test_format_list.py b/tests/scripts/test_format_list.py
new file mode 100644
index 0000000..1b0ca24
--- /dev/null
+++ b/tests/scripts/test_format_list.py
@@ -0,0 +1,47 @@
+import pytest
+
+from leapp.libraries.stdlib import FMT_LIST_SEPARATOR, format_list
+
+SEP = ', '
+
+
+@pytest.mark.parametrize('data, kwargs, expected', [
+ # Basic usage
+ ([], {}, ''),
+ (['c', 'a', 'b'], {}, '{0}a{0}b{0}c'.format(FMT_LIST_SEPARATOR)),
+ (['c', 'a', 'b'], {'sep': SEP}, ', a, b, c'),
+ (['a'], {'sep': SEP}, ', a'),
+ # Sorting
+ (['c', 'a', 'b'], {'sep': SEP, 'callback_sort': None}, ', c, a, b'),
+ (['c', 'a', 'b'], {'sep': SEP, 'callback_sort': lambda d: sorted(d, reverse=True)}, ', c, b, a'),
+ # Limit
+ (['c', 'a', 'b'], {'sep': SEP, 'limit': 2}, ', a, b'),
+ (['c', 'a', 'b'], {'sep': SEP, 'limit': 0}, ', a, b, c'),
+ (['b', 'a'], {'sep': SEP, 'limit': 10}, ', a, b'),
+ (['c', 'a', 'b'], {'sep': SEP, 'limit': -1}, ', a, b, c'),
+ # Non-list iterables
+ ({'a', 'b', 'c'}, {'sep': SEP, 'limit': 2}, ', a, b'),
+ (('a', 'b'), {'sep': SEP}, ', a, b'),
+ ({'b': 1, 'a': 2}, {'sep': SEP}, ', a, b'),
+ # Generators
+ ((x for x in ['c', 'a', 'b']), {'sep': SEP}, ', a, b, c'),
+ ((x for x in ['c', 'a', 'b']), {'sep': SEP, 'callback_sort': None, 'limit': 2}, ', c, a'),
+], ids=[
+ 'empty_data',
+ 'single_item',
+ 'default_separator',
+ 'custom_separator',
+ 'no_sort',
+ 'reverse_sort',
+ 'limit',
+ 'limit_zero',
+ 'limit_larger_than_data',
+ 'negative_limit_ignored',
+ 'set_input',
+ 'tuple_input',
+ 'dict_keys_input',
+ 'generator_sorted',
+ 'generator_unsorted_with_limit',
+])
+def test_format_list(data, kwargs, expected):
+ assert format_list(data, **kwargs) == expected
--
2.53.0