formatters: Cast *all* columns before outputting

This is a continuation of a previous change, change
Ib7db6a25f2352a013cb2ce603e60ca48b6cc70e6 ("formatters: Cast columns
before outputting"). As noted in that change, libraries can return
non-primitive types for many operations which can break YAML output
formatting since PyYAML only works with primitive types in safe mode.
That change was not complete as it only handled the formatters for dicts
and lists, not dicts of lists or lists of dicts. Close this gap now.

While we're here, we improve test coverage and catch a number of bugs
with the existing tests.

Change-Id: Ifd8db5b725f46a1ca216986167a0bcde5aec1e16
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane 2022-12-20 11:14:27 +00:00
parent 58ff270a9c
commit 1deaaecc09
2 changed files with 62 additions and 26 deletions

View File

@ -46,9 +46,15 @@ class ListColumn(columns.FormattableColumn):
def human_readable(self):
return utils.format_list(self._value)
def machine_readable(self):
return [x for x in self._value or []]
class ListDictColumn(columns.FormattableColumn):
"""Format column for list of dict content"""
def human_readable(self):
return utils.format_list_of_dicts(self._value)
def machine_readable(self):
return [dict(x) for x in self._value or []]

View File

@ -22,60 +22,90 @@ from osc_lib.tests import utils
class TestDictColumn(utils.TestCase):
def test_dict_column(self):
dict_content = {
data = {
'key1': 'value1',
'key2': 'value2',
}
col = format_columns.DictColumn(dict_content)
self.assertEqual(dict_content, col.machine_readable())
col = format_columns.DictColumn(data)
self.assertEqual(data, col.machine_readable())
self.assertEqual("key1='value1', key2='value2'", col.human_readable())
def test_complex_object(self):
"""Non-primitive objects should be converted to a dict."""
dict_content = collections.OrderedDict(
[('key1', 'value1'), ('key2', 'value2')])
col = format_columns.DictColumn(dict_content)
self.assertIsInstance(col._value, dict)
"""Non-dict objects should be converted to a dict."""
data = collections.OrderedDict(
[('key1', 'value1'), ('key2', 'value2')]
)
col = format_columns.DictColumn(data)
# we explicitly check type rather than use isinstance since an
# OrderedDict is a subclass of dict and would inadvertently pass
self.assertEqual(type(col.machine_readable()), dict)
class TestDictListColumn(utils.TestCase):
def test_dict_list_column(self):
dict_list_content = {'public': ['2001:db8::8', '172.24.4.6'],
'private': ['2000:db7::7', '192.24.4.6']}
col = format_columns.DictListColumn(dict_list_content)
self.assertEqual(dict_list_content, col.machine_readable())
self.assertEqual('private=192.24.4.6, 2000:db7::7; '
'public=172.24.4.6, 2001:db8::8',
col.human_readable())
data = {
'public': ['2001:db8::8', '172.24.4.6'],
'private': ['2000:db7::7', '192.24.4.6'],
}
col = format_columns.DictListColumn(data)
self.assertEqual(data, col.machine_readable())
self.assertEqual(
'private=192.24.4.6, 2000:db7::7; public=172.24.4.6, 2001:db8::8',
col.human_readable(),
)
def test_complex_object(self):
"""Non-primitive objects should be converted to a dict."""
dict_content = collections.OrderedDict(
[('key1', ['value1']), ('key2', ['value2'])])
col = format_columns.DictListColumn(dict_content)
self.assertIsInstance(col._value, dict)
"""Non-dict-of-list objects should be converted to a dict-of-lists."""
data = collections.OrderedDict(
[('key1', ['value1']), ('key2', ['value2'])],
)
col = format_columns.DictListColumn(data)
# we explicitly check type rather than use isinstance since an
# OrderedDict is a subclass of dict and would inadvertently pass
self.assertEqual(type(col.machine_readable()), dict)
class TestListColumn(utils.TestCase):
def test_list_column(self):
list_content = [
data = [
'key1',
'key2',
]
col = format_columns.ListColumn(list_content)
self.assertEqual(list_content, col.machine_readable())
col = format_columns.ListColumn(data)
self.assertEqual(data, col.machine_readable())
self.assertEqual("key1, key2", col.human_readable())
def test_complex_object(self):
"""Non-list objects should be converted to a list."""
data = {'key1', 'key2'}
col = format_columns.ListColumn(data)
# we explicitly check type rather than use isinstance since an
# OrderedDict is a subclass of dict and would inadvertently pass
self.assertEqual(type(col.machine_readable()), list)
class TestListDictColumn(utils.TestCase):
def test_list_dict_column(self):
list_dict_content = [
data = [
{'key1': 'value1'},
{'key2': 'value2'},
]
col = format_columns.ListDictColumn(list_dict_content)
self.assertEqual(list_dict_content, col.machine_readable())
col = format_columns.ListDictColumn(data)
self.assertEqual(data, col.machine_readable())
self.assertEqual("key1='value1'\nkey2='value2'", col.human_readable())
def test_complex_object(self):
"""Non-list-of-dict objects should be converted to a list-of-dicts."""
# not actually a list (which is the point)
data = (
collections.OrderedDict([('key1', 'value1'), ('key2', 'value2')]),
)
col = format_columns.ListDictColumn(data)
# we explicitly check type rather than use isinstance since an
# OrderedDict is a subclass of dict and would inadvertently pass
self.assertEqual(type(col.machine_readable()), list)
for x in col.machine_readable():
self.assertEqual(type(x), dict)