From 0be1820eae455015b18a7c8e5afa1ede785a638a Mon Sep 17 00:00:00 2001 From: Bernd Hufmann Date: Wed, 20 Aug 2025 13:41:59 -0400 Subject: [PATCH 1/2] Print virtual table lines as table using panda data frame Signed-off-by: Bernd Hufmann --- table_model.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ tsp_cli_client | 18 +++++++++++-- 2 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 table_model.py diff --git a/table_model.py b/table_model.py new file mode 100644 index 0000000..20b6bc4 --- /dev/null +++ b/table_model.py @@ -0,0 +1,70 @@ + +# The MIT License (MIT) +# +# Copyright (C) 2025 - Ericsson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""TableModel class file.""" + +from tabulate import tabulate + +import pandas as pd + + +# pylint: disable=too-few-public-methods +class TableModel: + """TreeModel class implementation.""" + + def __init__(self, model, headers=None): + self._headers = headers + self._model = model + + def print(self): + """Render this tree model.""" + frame = {} + data = [] + low_index = self._model.low_index + for line in self._model.lines: + row = [] + row.append(low_index) + low_index += 1 + for cell in line.cells: + row.append(cell.content) + data.append(row) + + headers = [] + headers.append("Index") + + if self._headers is not None: + for col_id in self._model.column_ids: + headers.append(self._headers[col_id].name) + + # for header in self._headers: + # headers.append(header.name) + if len(headers) == len(data[0]): + frame = pd.DataFrame(data, columns=headers) + else: + frame = pd.DataFrame(data) + else: + frame = pd.DataFrame(data) + # print(frame.to_string()) + #frame.to_csv('output.csv', index=False, sep='\t') + print(tabulate(frame.values, headers, tablefmt="fancy_grid")) + diff --git a/tsp_cli_client b/tsp_cli_client index 19b4c38..01c9548 100755 --- a/tsp_cli_client +++ b/tsp_cli_client @@ -38,6 +38,7 @@ import requests from termcolor import colored from tree_model import TreeModel +from table_model import TableModel from tsp.tsp_client import TspClient TRACE_MISSING = "Trace UUID is missing" @@ -471,8 +472,21 @@ if __name__ == "__main__": response = tsp_client.fetch_virtual_table_lines(options.uuid, options.get_virtual_table_lines, parameters) if response.status_code == 200: - model = response.model.model - model.print() + print('Successfully fetched virtual table lines') + print('----------------------------------------') + + columns_response = tsp_client.fetch_virtual_table_columns( + options.uuid, options.get_virtual_table_lines) + + headers = {} + if response.status_code == 200: + headers = columns_response.model.model.columns + + table_model = TableModel(response.model.model, headers) + table_model.print() + +# python_object = json.loads(response.status_text) +# print(json.dumps(python_object, indent=4)) sys.exit(0) else: sys.exit(1) From beae082edb95242c3b7c1b74161808b94345d63e Mon Sep 17 00:00:00 2001 From: Bernd Hufmann Date: Thu, 2 Oct 2025 16:01:02 -0400 Subject: [PATCH 2/2] Introduce --pretty option to pretty print result models By default, the models will be printed as JSON object. Using --pretty the result will be pretty printed, for example, the tables will be printed as tables or trees as table tree. Add json encoder for virtual table lines and headers Signed-off-by: Bernd Hufmann --- README.md | 2 ++ tsp/entry_model.py | 7 ++++-- tsp/virtual_table_header_model.py | 29 ++++++++++++++++++++- tsp/virtual_table_model.py | 42 ++++++++++++++++++++++++++++++- tsp_cli_client | 32 +++++++++++++++++++---- 5 files changed, 103 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7e8f7bd..15e9e6c 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,8 @@ optional arguments: The output ID --json-file JSON_FILE JSON file with parameter + --pretty + Pretty print result (if supported) else plain JSON ``` Examples: diff --git a/tsp/entry_model.py b/tsp/entry_model.py index a74b3ae..63ebf93 100644 --- a/tsp/entry_model.py +++ b/tsp/entry_model.py @@ -60,12 +60,15 @@ def __init__(self, params, model_type=ModelType.XY_TREE): def __repr__(self) -> str: return 'EntryModel({})'.format(', '.join(str(entry) for entry in self.entries)) + + def to_json(self): + return json.dumps(self, cls=EntryModelEncoder, indent=4) class EntryModelEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, EntryModel): return { - 'headers': [EntryHeaderEncoder().default(header) for header in obj.headers], - 'entries': [TimeGraphEntryEncoder().default(entry) if isinstance(entry, TimeGraphEntry) else EntryEncoder().default(entry) for entry in obj.entries] + HEADER_KEY: [EntryHeaderEncoder().default(header) for header in obj.headers], + ENTRIES_KEY: [TimeGraphEntryEncoder().default(entry) if isinstance(entry, TimeGraphEntry) else EntryEncoder().default(entry) for entry in obj.entries] } return super().default(obj) diff --git a/tsp/virtual_table_header_model.py b/tsp/virtual_table_header_model.py index 444e52c..7b3a50d 100644 --- a/tsp/virtual_table_header_model.py +++ b/tsp/virtual_table_header_model.py @@ -22,6 +22,8 @@ """Virtual table header model file.""" +import json + COLUMN_ID_KEY = "id" COLUMN_NAME_KEY = "name" COLUMN_DESCRIPTION_KEY = "description" @@ -49,6 +51,17 @@ def print(self): for column in self.columns: column.print() + def to_json(self): + return json.dumps(self, cls=VirtualTableHeaderModelEncoder, indent=4) + +class VirtualTableHeaderModelEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, VirtualTableHeaderModel): + # result = {} + # result['model'] = [ VirtualTableHeaderColumnModelEncoder().default(column) for column in obj.columns ] + return [ VirtualTableHeaderColumnModelEncoder().default(column) for column in obj.columns ] + return super().default(obj) + class VirtualTableHeaderColumnModel: ''' Virtual table header column model that will be returned by the server @@ -87,4 +100,18 @@ def print(self): print(" name: " + str(self.name)) print(" description: " + str(self.description)) print(" type: " + str(self.type)) - print("-" * 50) \ No newline at end of file + print("-" * 50) + + def to_json(self): + return json.dumps(self, cls=VirtualTableHeaderColumnModelEncoder, indent=4) + +class VirtualTableHeaderColumnModelEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, VirtualTableHeaderColumnModel): + result = {} + result[COLUMN_ID_KEY] = obj.id + result[COLUMN_NAME_KEY] = obj.name + result[COLUMN_DESCRIPTION_KEY] = obj.description + result[COLUMN_TYPE_KEY] = obj.type + return result + return super().default(obj) \ No newline at end of file diff --git a/tsp/virtual_table_model.py b/tsp/virtual_table_model.py index 2ccd460..2fdcc23 100644 --- a/tsp/virtual_table_model.py +++ b/tsp/virtual_table_model.py @@ -22,6 +22,7 @@ """VirtualTableModel class file.""" +import json from tsp.virtual_table_tag import VirtualTableTag SIZE_KEY = "size" @@ -71,6 +72,9 @@ def __init__(self, params): self.lines.append(VirtualTableLine(line)) del params[LINES_KEY] + def to_json(self): + return json.dumps(self, cls=VirtualTableModelEncoder, indent=4) + def print(self): print("VirtualTableModel:") print(f" size: {self.size}") @@ -81,6 +85,17 @@ def print(self): for i, line in enumerate(self.lines): line.print() +class VirtualTableModelEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, VirtualTableModel): + result = {} + result[SIZE_KEY] = obj.size + result[LOW_INDEX_KEY] = obj.low_index + result[COLUMN_IDS_KEY] = obj.column_ids + result[LINES_KEY] = [ VirtualTableLineEncoder().default(line) for line in obj.lines ] + return result + return super().default(obj) + class VirtualTableLine: ''' Virtual table line that will be returned by the server @@ -122,6 +137,9 @@ def __init__(self, params): def has_tag(self, tag): return bool(self.tags & tag) + def to_json(self): + return json.dumps(self, cls=VirtualTableLineEncoder, indent=4) + def print(self): print(f" index: {self.index}") @@ -138,6 +156,16 @@ def print(self): cell.print() print(f" {'-' * 30}") +class VirtualTableLineEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, VirtualTableLine): + result = {} + result[TABLE_LINE_INDEX_KEY] = obj.index + result[TAGS_KEY] = obj.tags.value + result[TABLE_LINE_CELLS_KEY] = [ VirtualTableLineCellEncoder().default(cell) for cell in obj.cells ] + return result + return super().default(obj) + class VirtualTableLineCell: ''' Virtual table line cell that will be returned by the server @@ -180,4 +208,16 @@ def print(self): tags_str = " | ".join(active_tags) print(f" \"tags\": \"{tags_str}\"") - print(f" {'-' * 10}") \ No newline at end of file + print(f" {'-' * 10}") + + def to_json(self): + return json.dumps(self, cls=VirtualTableLineCell, indent=4) + +class VirtualTableLineCellEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, VirtualTableLineCell): + return { + TABLE_LINE_CELL_CONTENT_KEY: obj.content, + TAGS_KEY: obj.tags.value + } + return super().default(obj) diff --git a/tsp_cli_client b/tsp_cli_client index 01c9548..941288d 100755 --- a/tsp_cli_client +++ b/tsp_cli_client @@ -86,10 +86,18 @@ def __get_tree(uuid, outputid, treetype): if tree is None: print("Tree had no model; retry?") sys.exit(1) + + print('Successfully fetched tree') + print('-------------------------') - tree_model = TreeModel(tree.entries, tree.headers) - tree_model.print() + if (options.pretty): + tree_model = TreeModel(tree.entries, tree.headers) + tree_model.print() + sys.exit(0) + + print(tree.to_json()) sys.exit(0) + else: sys.exit(1) else: @@ -221,6 +229,8 @@ if __name__ == "__main__": parser.add_argument("--delete-output", dest="delete_output", help="Delete derived output", metavar="DERIVED_OUTPUT_ID") parser.add_argument("--output-id", dest="output_id", help="The output ID") parser.add_argument("--json-file", dest="json_file", help="JSON file with parameter") + parser.add_argument("--pretty", action='store_true', dest="pretty", help="Pretty print result (if supported) else plain JSON") + argcomplete.autocomplete(parser) options = parser.parse_args() @@ -432,8 +442,15 @@ if __name__ == "__main__": options.uuid, options.get_virtual_table_columns) if response.status_code == 200: + print('Successfully fetched virtual table columns') + print('------------------------------------------') + model = response.model.model - model.print() + if (options.pretty): + model.print() + sys.exit(0) + + print(model.to_json()) sys.exit(0) else: sys.exit(1) @@ -482,8 +499,13 @@ if __name__ == "__main__": if response.status_code == 200: headers = columns_response.model.model.columns - table_model = TableModel(response.model.model, headers) - table_model.print() + if (options.pretty): + table_model = TableModel(response.model.model, headers) + table_model.print() + sys.exit(0) + + print(response.model.model.to_json()) + sys.exit(0) # python_object = json.loads(response.status_text) # print(json.dumps(python_object, indent=4))