-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Table format revisions #748
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,4 +13,5 @@ pylint==1.5.4 | |
| pyyaml==3.11 | ||
| requests==2.9.1 | ||
| six==1.10.0 | ||
| tabulate==0.7.5 | ||
| vcrpy==1.7.4 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,6 +68,7 @@ | |
| 'pyyaml', | ||
| 'requests', | ||
| 'six', | ||
| 'tabulate', | ||
| ] | ||
|
|
||
| if sys.version_info < (3, 4): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ | |
| from collections import OrderedDict | ||
| from six import StringIO, text_type, u | ||
| import colorama | ||
| from tabulate import tabulate | ||
|
|
||
| from azure.cli._util import CLIError | ||
| import azure.cli._logging as _logging | ||
|
|
@@ -40,31 +41,6 @@ def format_json_color(obj): | |
| from pygments import highlight, lexers, formatters | ||
| return highlight(format_json(obj), lexers.JsonLexer(), formatters.TerminalFormatter()) # pylint: disable=no-member | ||
|
|
||
| def format_table(obj): | ||
| result = obj.result | ||
| try: | ||
| if not obj.simple_output_query and not obj.is_query_active: | ||
| raise ValueError('No query specified and no built-in query available.') | ||
| if obj.simple_output_query and not obj.is_query_active: | ||
| if callable(obj.simple_output_query): | ||
| result = obj.simple_output_query(result) | ||
| else: | ||
| from jmespath import compile as compile_jmespath, search, Options | ||
| result = compile_jmespath(obj.simple_output_query).search(result, | ||
| Options(OrderedDict)) | ||
| obj_list = result if isinstance(result, list) else [result] | ||
| to = TableOutput() | ||
| for item in obj_list: | ||
| for item_key in item: | ||
| to.cell(item_key, item[item_key]) | ||
| to.end_row() | ||
| return to.dump() | ||
| except (ValueError, KeyError, TypeError): | ||
| logger.debug(traceback.format_exc()) | ||
| raise CLIError("Table output unavailable. "\ | ||
| "Change output type with --output or use "\ | ||
| "the --query option to specify an appropriate query. "\ | ||
| "Use --debug for more info.") | ||
|
|
||
| def format_text(obj): | ||
| result = obj.result | ||
|
|
@@ -78,6 +54,20 @@ def format_text(obj): | |
| except TypeError: | ||
| return '' | ||
|
|
||
| def format_table(obj): | ||
| result = obj.result | ||
| try: | ||
| if obj.simple_output_query and not obj.is_query_active: | ||
| if callable(obj.simple_output_query): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If simple output query is just the callable, why not rename it "table_transformer" or something. The old name was always pretty vague and now it's REALLY vague considering it only does that one thing. You shouldn't need the callable check on it now since we're saying that the callable is the only supported thing. If a dev passed a query string he should be given an error message he can debug rather than have it simply ignore him (or her). :) #Resolved
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep agreed but since I plan to remove the jmespath queries in another PR, I still needed the if statement. |
||
| result = obj.simple_output_query(result) | ||
| result_list = result if isinstance(result, list) else [result] | ||
| return TableOutput.dump(result_list) | ||
| except: | ||
| logger.debug(traceback.format_exc()) | ||
| raise CLIError("Table output unavailable. "\ | ||
| "Use the --query option to specify an appropriate query. "\ | ||
| "Use --debug for more info.") | ||
|
|
||
| def format_list(obj): | ||
| result = obj.result | ||
| result_list = result if isinstance(result, list) else [result] | ||
|
|
@@ -125,6 +115,42 @@ def out(self, obj): | |
| def get_formatter(format_type): | ||
| return OutputProducer.format_dict.get(format_type, format_list) | ||
|
|
||
| class TableOutput(object): #pylint: disable=too-few-public-methods | ||
|
|
||
| SKIP_KEYS = ['id', 'type'] | ||
|
|
||
| @staticmethod | ||
| def _capitalize_first_char(x): | ||
| return x[0].upper() + x[1:] if x and len(x) > 0 else x | ||
|
|
||
| @staticmethod | ||
| def _auto_table_item(item): | ||
| new_entry = OrderedDict() | ||
| for k in item.keys(): | ||
| if k in TableOutput.SKIP_KEYS: | ||
| continue | ||
| if item[k] and not isinstance(item[k], (list, dict, set)): | ||
| new_entry[TableOutput._capitalize_first_char(k)] = item[k] | ||
| return new_entry | ||
|
|
||
| @staticmethod | ||
| def _auto_table(result): | ||
| if isinstance(result, list): | ||
| new_result = [] | ||
| for item in result: | ||
| new_result.append(TableOutput._auto_table_item(item)) | ||
| return new_result | ||
| else: | ||
| return TableOutput._auto_table_item(result) | ||
|
|
||
| @staticmethod | ||
| def dump(data): | ||
| table_data = TableOutput._auto_table(data) | ||
| table_str = tabulate(table_data, headers="keys", tablefmt="simple") if table_data else '' | ||
| if table_str == '\n': | ||
| raise ValueError('Unable to extract fields for table.') | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this what we talked about at the meeting, where if there is data but the extracted table is empty we throw an error? #Resolved
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep #Resolved |
||
| return table_str + '\n' | ||
|
|
||
| class ListOutput(object): #pylint: disable=too-few-public-methods | ||
|
|
||
| # Match the capital letters in a camel case string | ||
|
|
@@ -196,53 +222,6 @@ def dump(self, data): | |
| io.close() | ||
| return result | ||
|
|
||
| class TableOutput(object): | ||
|
|
||
| unsupported_types = (list, dict, set) | ||
|
|
||
| def __init__(self): | ||
| self._rows = [{}] | ||
| self._columns = {} | ||
| self._column_order = [] | ||
|
|
||
| def dump(self): | ||
| if len(self._rows) == 1: | ||
| return | ||
|
|
||
| io = StringIO() | ||
| cols = [(c, self._columns[c]) for c in self._column_order] | ||
| io.write(' | '.join(c.center(w) for c, w in cols)) | ||
| io.write('\n') | ||
| io.write('-|-'.join('-' * w for c, w in cols)) | ||
| io.write('\n') | ||
| for r in self._rows[:-1]: | ||
| io.write(' | '.join(r.get(c, '-').ljust(w) for c, w in cols)) | ||
| io.write('\n') | ||
| result = io.getvalue() | ||
| io.close() | ||
| return result | ||
|
|
||
| @property | ||
| def any_rows(self): | ||
| return len(self._rows) > 1 | ||
|
|
||
| def cell(self, name, value): | ||
| if isinstance(value, TableOutput.unsupported_types): | ||
| raise TypeError('Table output does not support objects of type {}.\n'\ | ||
| 'Offending object name={} value={}'.format( | ||
| [ut.__name__ for ut in TableOutput.unsupported_types], name, value)) | ||
| n = str(name) | ||
| v = str(value) | ||
| max_width = self._columns.get(n) | ||
| if max_width is None: | ||
| self._column_order.append(n) | ||
| max_width = len(n) | ||
| self._rows[-1][n] = v | ||
| self._columns[n] = max(max_width, len(v)) | ||
|
|
||
| def end_row(self): | ||
| self._rows.append({}) | ||
|
|
||
| class TextOutput(object): | ||
|
|
||
| def __init__(self): | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are going to ignore the callable if query is specified, I would recommend a warning log at a minimum indicating that's what's happening. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still recommend this, but you can address it (or not) in the next PR since you'll be working in this vicinity anyways. :)
In reply to: 76473993 [](ancestors = 76473993)