Add utils for better column handling

In most OSC and plugin codes, we have column definitions for
list operations but some better way of column definitions helps
developers. This commit proposes some utility functions.
We can define column definitions in more meaningful way.

It also potentially helps us if we would like to show same
field names in the list and show operations. At now,
the list operation replaces API attribute names, but the show
operation shows API attribute names without modification.

A sample usage of these functions are found at:
https://github.com/openstack/python-neutronclient/blob/master/neutronclient/osc/v2/fwaas/firewallgroup.py

This commit moves osc_lib/utils.py to a subdirectory.
The file is now a bit long and it would be nice if the module
is split into meaningful pieces.

Change-Id: Ife59ea35f5fad05173d3405718c9615ca97915fa
This commit is contained in:
Akihiro Motoki 2017-11-24 19:50:17 +00:00
parent 58a4d3cb6e
commit 75700a7b42
3 changed files with 143 additions and 0 deletions

View File

@ -24,6 +24,7 @@ from osc_lib import exceptions
from osc_lib.tests import fakes
from osc_lib.tests import utils as test_utils
from osc_lib import utils
from osc_lib.utils import columns as column_utils
PASSWORD = "Pa$$w0rd"
WASSPORD = "Wa$$p0rd"
@ -707,6 +708,49 @@ class TestFindResource(test_utils.TestCase):
self.assertEqual(expected, actual_unsorted)
class TestColumnUtils(test_utils.TestCase):
def test_get_column_definitions(self):
attr_map = (
('id', 'ID', column_utils.LIST_BOTH),
('tenant_id', 'Project', column_utils.LIST_LONG_ONLY),
('name', 'Name', column_utils.LIST_BOTH),
('summary', 'Summary', column_utils.LIST_SHORT_ONLY),
)
headers, columns = column_utils.get_column_definitions(
attr_map, long_listing=False)
self.assertEqual(['id', 'name', 'summary'], columns)
self.assertEqual(['ID', 'Name', 'Summary'], headers)
def test_get_column_definitions_long(self):
attr_map = (
('id', 'ID', column_utils.LIST_BOTH),
('tenant_id', 'Project', column_utils.LIST_LONG_ONLY),
('name', 'Name', column_utils.LIST_BOTH),
('summary', 'Summary', column_utils.LIST_SHORT_ONLY),
)
headers, columns = column_utils.get_column_definitions(
attr_map, long_listing=True)
self.assertEqual(['id', 'tenant_id', 'name'], columns)
self.assertEqual(['ID', 'Project', 'Name'], headers)
def test_get_columns(self):
item = {
'id': 'test-id',
'tenant_id': 'test-tenant_id',
# 'name' is not included
'foo': 'bar', # unknown attribute
}
attr_map = (
('id', 'ID', column_utils.LIST_BOTH),
('tenant_id', 'Project', column_utils.LIST_LONG_ONLY),
('name', 'Name', column_utils.LIST_BOTH),
)
columns, display_names = column_utils.get_columns(item, attr_map)
self.assertEqual(tuple(['id', 'tenant_id', 'foo']), columns)
self.assertEqual(tuple(['ID', 'Project', 'foo']), display_names)
class TestAssertItemEqual(test_utils.TestCommand):
def test_assert_normal_item(self):

99
osc_lib/utils/columns.py Normal file
View File

@ -0,0 +1,99 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import operator
LIST_BOTH = 'both'
LIST_SHORT_ONLY = 'short_only'
LIST_LONG_ONLY = 'long_only'
def get_column_definitions(attr_map, long_listing):
"""Return table headers and column names for a listing table.
An attribute map (attr_map) is a list of table entry definitions
and the format of the map is as follows:
:param attr_map: a list of table entry definitions.
Each entry should be a tuple consisting of
(API attribute name, header name, listing mode). For example:
.. code-block:: python
(('id', 'ID', LIST_BOTH),
('name', 'Name', LIST_BOTH),
('tenant_id', 'Project', LIST_LONG_ONLY))
The third field of each tuple must be one of LIST_BOTH,
LIST_LONG_ONLY (a corresponding column is shown only in a long mode),
or LIST_SHORT_ONLY (a corresponding column is shown only
in a short mode).
:param long_listing: A boolean value which indicates a long listing
or not. In most cases, parsed_args.long is passed to this argument.
:return: A tuple of a list of table headers and a list of column names.
"""
if long_listing:
headers = [hdr for col, hdr, listing_mode in attr_map
if listing_mode in (LIST_BOTH, LIST_LONG_ONLY)]
columns = [col for col, hdr, listing_mode in attr_map
if listing_mode in (LIST_BOTH, LIST_LONG_ONLY)]
else:
headers = [hdr for col, hdr, listing_mode in attr_map if listing_mode
if listing_mode in (LIST_BOTH, LIST_SHORT_ONLY)]
columns = [col for col, hdr, listing_mode in attr_map if listing_mode
if listing_mode in (LIST_BOTH, LIST_SHORT_ONLY)]
return headers, columns
def get_columns(item, attr_map=None):
"""Return pair of resource attributes and corresponding display names.
:param item: a dictionary which represents a resource.
Keys of the dictionary are expected to be attributes of the resource.
Values are not referred to by this method.
.. code-block:: python
{'id': 'myid', 'name': 'myname',
'foo': 'bar', 'tenant_id': 'mytenan'}
:param attr_map: a list of mapping from attribute to display name.
The same format is used as for get_column_definitions attr_map.
.. code-block:: python
(('id', 'ID', LIST_BOTH),
('name', 'Name', LIST_BOTH),
('tenant_id', 'Project', LIST_LONG_ONLY))
:return: A pair of tuple of attributes and tuple of display names.
.. code-block:: python
(('id', 'name', 'tenant_id', 'foo'), # attributes
('ID', 'Name', 'Project', 'foo') # display names
Both tuples of attributes and display names are sorted by display names
in the alphabetical order.
Attributes not found in a given attr_map are kept as-is.
"""
attr_map = attr_map or tuple([])
_attr_map_dict = dict((col, hdr) for col, hdr, listing_mode in attr_map)
columns = [(column, _attr_map_dict.get(column, column))
for column in item.keys()]
columns = sorted(columns, key=operator.itemgetter(1))
return (tuple(col[0] for col in columns),
tuple(col[1] for col in columns))