Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changes/unreleased/fixed-20251112-081104.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: fixed
body: refactor output format - adding support for print in key-value list style
time: 2025-11-12T08:11:04.00625845Z
7 changes: 7 additions & 0 deletions src/fabric_cli/core/fab_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def __init__(
error_code: Optional[str] = None,
data: Optional[Any] = None,
hidden_data: Optional[Any] = None,
show_key_value_list: bool = False,
):
"""Initialize a new FabricCLIOutput instance.

Expand All @@ -89,6 +90,7 @@ def __init__(
error_code: Optional error code. Only included when status is Failed.
data: The main output data to be displayed
hidden_data: Additional data shown only when --all flag or FAB_SHOW_HIDDEN is true
show_key_value_list: Whether to show output in key-value list format

Note:
The data parameter is always converted to a list format internally.
Expand All @@ -100,6 +102,7 @@ def __init__(
self._subcommand = subcommand
self._output_format_type = output_format_type
self._show_headers = show_headers
self._show_key_value_list = show_key_value_list

self._result = OutputResult(
data=data,
Expand All @@ -124,6 +127,10 @@ def result(self) -> OutputResult:
def show_headers(self) -> bool:
return self._show_headers

@property
def show_key_value_list(self) -> bool:
return self._show_key_value_list

def to_json(self, indent: int = 4) -> str:
try:
from fabric_cli.utils.fab_util import dumps
Expand Down
4 changes: 4 additions & 0 deletions src/fabric_cli/errors/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

class CommonErrors:

@staticmethod
def invalid_entries_format() -> str:
return "Invalid entries format"

@staticmethod
def invalid_jmespath_query() -> str:
return f"Invalid jmespath query (https://jmespath.org)"
Expand Down
80 changes: 78 additions & 2 deletions src/fabric_cli/utils/fab_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import builtins
import html
import json
import sys
import unicodedata
from argparse import Namespace
Expand Down Expand Up @@ -95,7 +94,7 @@ def print_output_format(
data: Optional[Any] = None,
hidden_data: Optional[Any] = None,
show_headers: bool = False,
# print_callback: bool = True,
show_key_value_list: bool = False,
) -> None:
"""Create a FabricCLIOutput instance and print it depends on the format.

Expand All @@ -105,6 +104,7 @@ def print_output_format(
data: Optional data to include in output
hidden_data: Optional hidden data to include in output
show_headers: Whether to show headers in the output (default: False)
show_key_value_list: Whether to show output in key-value list format (default: False)

Returns:
FabricCLIOutput: Configured output instance ready for printing
Expand All @@ -121,6 +121,7 @@ def print_output_format(
data=data,
hidden_data=hidden_data,
show_headers=show_headers,
show_key_value_list=show_key_value_list,
)

# Get format from output or config
Expand Down Expand Up @@ -355,6 +356,8 @@ def _print_output_format_result_text(output: FabricCLIOutput) -> None:
):
data_keys = output.result.get_data_keys() if output_result.data else []
print_entries_unix_style(output_result.data, data_keys, header=show_headers)
elif output.show_key_value_list:
_print_entries_key_value_list_style(output_result.data)
else:
_print_raw_data(output_result.data)

Expand Down Expand Up @@ -486,3 +489,76 @@ def _get_visual_length(string: str) -> int:
else:
length += 1
return length


def _print_entries_key_value_list_style(entries: Any) -> None:
"""Print entries in a key-value list format with formatted keys.

Args:
entries: Dictionary or list of dictionaries to print

Example output:
Logged In: true
Account: johndoe@example.com
"""
if isinstance(entries, dict):
_entries = [entries]
elif isinstance(entries, list):
if not entries:
return
_entries = entries
else:
raise FabricCLIError(
ErrorMessages.Common.invalid_entries_format(),
fab_constant.ERROR_INVALID_ENTRIES_FORMAT,
)

for i, entry in enumerate(_entries):
for key, value in entry.items():
pretty_key = _format_key_to_convert_to_title_case(key)
print_grey(f"{pretty_key}: {value}", to_stderr=False)
if i < len(_entries) - 1:
print_grey("", to_stderr=False) # Empty line between entries


def _format_key_to_convert_to_title_case(key: str) -> str:
"""Convert a snake_case key to a Title Case name.

Args:
key: The key to format in snake_case format (e.g. 'user_id', 'account_name')

Returns:
str: Formatted to title case name (e.g. 'User ID', 'Account Name')

Raises:
ValueError: If the key is not in the expected underscore-separated format
"""
# Allow letters, numbers, and underscores only
if not key.replace('_', '').replace(' ', '').isalnum():
raise ValueError(f"Invalid key format: '{key}'. Only underscore-separated words are allowed.")

# Check for invalid patterns (camelCase, spaces mixed with underscores, etc.)
if ' ' in key and '_' in key:
raise ValueError(f"Invalid key format: '{key}'. Only underscore-separated words are allowed.")

# Check for camelCase pattern (uppercase letters not at the start)
if any(char.isupper() for char in key[1:]) and '_' not in key:
raise ValueError(f"Invalid key format: '{key}'. Only underscore-separated words are allowed.")

pretty = key.replace('_', ' ').title().strip()

return _check_special_cases(pretty)


def _check_special_cases(pretty: str) -> str:
"""Check for special cases and replace them with the correct value."""
# Here add special cases for specific keys that need to be formatted differently
special_cases = {
"Id": "ID",
"Powerbi": "PowerBI",
}

for case_key, case_value in special_cases.items():
pretty = pretty.replace(case_key.title(), case_value)

return pretty
15 changes: 15 additions & 0 deletions tests/test_core/test_fab_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,18 @@ def test_fabric_cli_output_error_handling_success():

json_output = json.loads(output.to_json())
assert json_output["result"]["error_code"] == "UnexpectedError"


def test_fabric_cli_output_show_key_value_list_success():
"""Test show_key_value_list property is handled correctly."""
# Test with show_key_value_list True
output = FabricCLIOutput(data={"test": "data"}, show_key_value_list=True)
assert output.show_key_value_list is True

# Test with show_key_value_list False (default)
output = FabricCLIOutput(data={"test": "data"})
assert output.show_key_value_list is False

# Test with explicit False
output = FabricCLIOutput(data={"test": "data"}, show_key_value_list=False)
assert output.show_key_value_list is False
Loading