diff --git a/orchestrator/cli/commands/context.py b/orchestrator/cli/commands/context.py index 20d693d2e..9516e13b4 100644 --- a/orchestrator/cli/commands/context.py +++ b/orchestrator/cli/commands/context.py @@ -115,6 +115,7 @@ def list_contexts( matching_space_id=None, matching_space=None, minimize_output=True, + no_trunc=False, output_format=AdoGetSupportedOutputFormats.DEFAULT, resource_id=None, resource_type=AdoGetSupportedResourceTypes.CONTEXT, diff --git a/orchestrator/cli/commands/get.py b/orchestrator/cli/commands/get.py index 4c54f999e..4648f89a5 100644 --- a/orchestrator/cli/commands/get.py +++ b/orchestrator/cli/commands/get.py @@ -173,6 +173,17 @@ def get_resource( rich_help_panel=OUTPUT_CONFIGURATION_OPTIONS, ), ] = False, + no_trunc: Annotated[ + bool, + typer.Option( + "--no-trunc", + help=""" + Prevent truncation of table content. When enabled, columns will be sized to fit all content + without truncation. Only applies to default (table) output format. + """, + rich_help_panel=OUTPUT_CONFIGURATION_OPTIONS, + ), + ] = False, show_deprecated: Annotated[ bool, typer.Option( @@ -361,6 +372,7 @@ def get_resource( matching_space_id=matching_space_id, matching_space=matching_space, minimize_output=minimize_output, + no_trunc=no_trunc, output_format=output_format, resource_id=resource_id, resource_type=resource_type, diff --git a/orchestrator/cli/commands/show_entities.py b/orchestrator/cli/commands/show_entities.py index 6e75860b5..d4377a61a 100644 --- a/orchestrator/cli/commands/show_entities.py +++ b/orchestrator/cli/commands/show_entities.py @@ -121,6 +121,16 @@ def show_entities_for_resources( rich_help_panel=SPACE_PANEL_NAME, ), ] = None, + no_trunc: Annotated[ + bool, + typer.Option( + "--no-trunc", + help=""" + Prevent truncation of table content. When enabled, columns will be sized to fit all content + without truncation. Only applies to console output format. + """, + ), + ] = False, ) -> None: """ Show entities related to a space or an operation and their measurements. @@ -184,6 +194,7 @@ def show_entities_for_resources( entities_output_format=output_format, entities_property_format=property_format, entities_type=entity_type, + no_trunc=no_trunc, properties=properties, resource_configuration=resource_configuration, resource_id=resource_id, diff --git a/orchestrator/cli/commands/show_requests.py b/orchestrator/cli/commands/show_requests.py index db7dcf073..5dcbd0bee 100644 --- a/orchestrator/cli/commands/show_requests.py +++ b/orchestrator/cli/commands/show_requests.py @@ -66,6 +66,16 @@ def show_requests_for_resources( """, ), ] = None, + no_trunc: Annotated[ + bool, + typer.Option( + "--no-trunc", + help=""" + Prevent truncation of table content. When enabled, columns will be sized to fit all content + without truncation. Only applies to console output format. + """, + ), + ] = False, ) -> None: """ Show the timeseries of requests for an operation. @@ -114,6 +124,7 @@ def show_requests_for_resources( parameters = AdoShowRequestsCommandParameters( ado_configuration=ado_configuration, hide_fields=hide_fields, + no_trunc=no_trunc, output_format=output_format, resource_id=resource_id, ) diff --git a/orchestrator/cli/commands/show_results.py b/orchestrator/cli/commands/show_results.py index 6d4fb7815..4e3b0f642 100644 --- a/orchestrator/cli/commands/show_results.py +++ b/orchestrator/cli/commands/show_results.py @@ -61,6 +61,16 @@ def show_results_for_resources( "Different resource types might support different fields.", ), ] = None, + no_trunc: Annotated[ + bool, + typer.Option( + "--no-trunc", + help=""" + Prevent truncation of table content. When enabled, columns will be sized to fit all content + without truncation. Only applies to console output format. + """, + ), + ] = False, ) -> None: """ Show the timeseries of results for an operation. @@ -109,6 +119,7 @@ def show_results_for_resources( parameters = AdoShowResultsCommandParameters( ado_configuration=ado_configuration, hide_fields=hide_fields, + no_trunc=no_trunc, output_format=output_format, resource_id=resource_id, ) diff --git a/orchestrator/cli/models/parameters.py b/orchestrator/cli/models/parameters.py index 5a519ed8d..404acb036 100644 --- a/orchestrator/cli/models/parameters.py +++ b/orchestrator/cli/models/parameters.py @@ -38,6 +38,7 @@ class AdoGetCommandParameters(pydantic.BaseModel): matching_space_id: str | None matching_space: pathlib.Path | None minimize_output: bool + no_trunc: bool output_format: AdoGetSupportedOutputFormats resource_id: str | None resource_type: AdoGetSupportedResourceTypes @@ -88,6 +89,7 @@ class AdoShowEntitiesCommandParameters(pydantic.BaseModel): entities_output_format: AdoShowEntitiesSupportedOutputFormats entities_property_format: AdoShowEntitiesSupportedPropertyFormats entities_type: AdoShowEntitiesSupportedEntityTypes + no_trunc: bool properties: list[str] | None resource_configuration: Path | None resource_id: str | None @@ -101,6 +103,7 @@ class AdoShowRelatedCommandParameters(pydantic.BaseModel): class AdoShowRequestsCommandParameters(pydantic.BaseModel): ado_configuration: AdoConfiguration hide_fields: list[str] | None + no_trunc: bool output_format: AdoShowRequestsSupportedOutputFormats resource_id: str @@ -108,6 +111,7 @@ class AdoShowRequestsCommandParameters(pydantic.BaseModel): class AdoShowResultsCommandParameters(pydantic.BaseModel): ado_configuration: AdoConfiguration hide_fields: list[str] | None + no_trunc: bool output_format: AdoShowResultsSupportedOutputFormats resource_id: str diff --git a/orchestrator/cli/resources/actuator/get.py b/orchestrator/cli/resources/actuator/get.py index bd47115b6..14407c310 100644 --- a/orchestrator/cli/resources/actuator/get.py +++ b/orchestrator/cli/resources/actuator/get.py @@ -97,5 +97,10 @@ def get_actuator(parameters: AdoGetCommandParameters) -> None: return console_print( - dataframe_to_rich_table(output_df, box=rich.box.SQUARE, show_edge=True) + dataframe_to_rich_table( + output_df, + box=rich.box.SQUARE, + show_edge=True, + do_not_truncate_column_content=parameters.no_trunc, + ) ) diff --git a/orchestrator/cli/resources/context/get.py b/orchestrator/cli/resources/context/get.py index 6e09ff1c5..76fa97ca9 100644 --- a/orchestrator/cli/resources/context/get.py +++ b/orchestrator/cli/resources/context/get.py @@ -76,7 +76,11 @@ def get_context( console_print( dataframe_to_rich_table( - contexts_df, show_edge=True, show_index=True, box=rich.box.SQUARE + contexts_df, + show_edge=True, + show_index=True, + box=rich.box.SQUARE, + do_not_truncate_column_content=parameters.no_trunc, ) ) return diff --git a/orchestrator/cli/resources/discovery_space/get.py b/orchestrator/cli/resources/discovery_space/get.py index 5650a2fc5..258df4faa 100644 --- a/orchestrator/cli/resources/discovery_space/get.py +++ b/orchestrator/cli/resources/discovery_space/get.py @@ -61,7 +61,10 @@ def get_discovery_space(parameters: AdoGetCommandParameters) -> None: else: console_print( dataframe_to_rich_table( - output_df, show_edge=True, box=rich.box.SQUARE + output_df, + show_edge=True, + box=rich.box.SQUARE, + do_not_truncate_column_content=parameters.no_trunc, ) ) @@ -88,7 +91,10 @@ def get_discovery_space(parameters: AdoGetCommandParameters) -> None: else: console_print( dataframe_to_rich_table( - output_df, show_edge=True, box=rich.box.SQUARE + output_df, + show_edge=True, + box=rich.box.SQUARE, + do_not_truncate_column_content=parameters.no_trunc, ) ) diff --git a/orchestrator/cli/resources/discovery_space/show_entities.py b/orchestrator/cli/resources/discovery_space/show_entities.py index c2d9f0244..15bd8b46e 100644 --- a/orchestrator/cli/resources/discovery_space/show_entities.py +++ b/orchestrator/cli/resources/discovery_space/show_entities.py @@ -192,6 +192,7 @@ def show_discovery_space_entities(parameters: AdoShowEntitiesCommandParameters) df=output_df, output_format=parameters.entities_output_format.value, file_name=file_name, + no_trunc=parameters.no_trunc, ) diff --git a/orchestrator/cli/resources/experiment/get.py b/orchestrator/cli/resources/experiment/get.py index 77cfeb950..afcbe9a50 100644 --- a/orchestrator/cli/resources/experiment/get.py +++ b/orchestrator/cli/resources/experiment/get.py @@ -121,5 +121,10 @@ def get_experiment(parameters: AdoGetCommandParameters) -> None: ) console_print( - dataframe_to_rich_table(output_df, box=rich.box.SQUARE, show_edge=True) + dataframe_to_rich_table( + output_df, + box=rich.box.SQUARE, + show_edge=True, + do_not_truncate_column_content=parameters.no_trunc, + ) ) diff --git a/orchestrator/cli/resources/operation/show_entities.py b/orchestrator/cli/resources/operation/show_entities.py index 68c8753b8..1184fdee1 100644 --- a/orchestrator/cli/resources/operation/show_entities.py +++ b/orchestrator/cli/resources/operation/show_entities.py @@ -48,4 +48,5 @@ def show_operation_entities(parameters: AdoShowEntitiesCommandParameters) -> Non df=output_df, output_format=parameters.entities_output_format.value, file_name=file_name, + no_trunc=parameters.no_trunc, ) diff --git a/orchestrator/cli/resources/operation/show_requests.py b/orchestrator/cli/resources/operation/show_requests.py index 204c8c1e3..3ac9aba54 100644 --- a/orchestrator/cli/resources/operation/show_requests.py +++ b/orchestrator/cli/resources/operation/show_requests.py @@ -123,5 +123,8 @@ class _COLUMN(enum.Enum): df = df.drop(parameters.hide_fields, axis="columns") df_to_output( - df=df, output_format=parameters.output_format.value, file_name=file_name + df=df, + output_format=parameters.output_format.value, + file_name=file_name, + no_trunc=parameters.no_trunc, ) diff --git a/orchestrator/cli/resources/operation/show_results.py b/orchestrator/cli/resources/operation/show_results.py index 7109e8c13..aa9aac076 100644 --- a/orchestrator/cli/resources/operation/show_results.py +++ b/orchestrator/cli/resources/operation/show_results.py @@ -122,5 +122,8 @@ class _COLUMN(enum.Enum): df = df.drop(parameters.hide_fields, axis="columns") df_to_output( - df=df, output_format=parameters.output_format.value, file_name=file_name + df=df, + output_format=parameters.output_format.value, + file_name=file_name, + no_trunc=parameters.no_trunc, ) diff --git a/orchestrator/cli/resources/operator/get.py b/orchestrator/cli/resources/operator/get.py index 5ca180ccd..bc3a2398f 100644 --- a/orchestrator/cli/resources/operator/get.py +++ b/orchestrator/cli/resources/operator/get.py @@ -79,6 +79,10 @@ def get_operator(parameters: AdoGetCommandParameters) -> None: operators = operators.sort_values(by=["TYPE", "OPERATOR"]).reset_index(drop=True) console_print( dataframe_to_rich_table( - operators, show_edge=True, show_index=True, box=rich.box.SQUARE + operators, + show_edge=True, + show_index=True, + box=rich.box.SQUARE, + do_not_truncate_column_content=parameters.no_trunc, ) ) diff --git a/orchestrator/cli/utils/output/dataframes.py b/orchestrator/cli/utils/output/dataframes.py index ac90fa07b..ee9388671 100644 --- a/orchestrator/cli/utils/output/dataframes.py +++ b/orchestrator/cli/utils/output/dataframes.py @@ -28,6 +28,7 @@ def df_to_output( df: "pd.DataFrame", output_format: Literal["console", "json", "csv"], file_name: str | None = None, + no_trunc: bool = False, ) -> None: if output_format != "console" and not file_name: console_print( @@ -43,7 +44,11 @@ def df_to_output( if output_format == "console": console_print( dataframe_to_rich_table( - df, show_edge=True, show_index=True, box=rich.box.SQUARE + df, + show_edge=True, + show_index=True, + box=rich.box.SQUARE, + do_not_truncate_column_content=no_trunc, ) ) if ( diff --git a/orchestrator/cli/utils/resources/handlers.py b/orchestrator/cli/utils/resources/handlers.py index 5aad159a0..11d49b683 100644 --- a/orchestrator/cli/utils/resources/handlers.py +++ b/orchestrator/cli/utils/resources/handlers.py @@ -134,7 +134,11 @@ def handle_ado_get_default_format( console_print( dataframe_to_rich_table( - output_df, box=rich.box.SQUARE, show_index=True, show_edge=True + output_df, + box=rich.box.SQUARE, + show_index=True, + show_edge=True, + do_not_truncate_column_content=parameters.no_trunc, ) ) return @@ -154,7 +158,12 @@ def handle_ado_get_default_format( ) console_print( - dataframe_to_rich_table(output_df, box=rich.box.SQUARE, show_edge=True) + dataframe_to_rich_table( + output_df, + box=rich.box.SQUARE, + show_edge=True, + do_not_truncate_column_content=parameters.no_trunc, + ) ) diff --git a/orchestrator/utilities/rich.py b/orchestrator/utilities/rich.py index e8d3b8f53..8815494ed 100644 --- a/orchestrator/utilities/rich.py +++ b/orchestrator/utilities/rich.py @@ -10,7 +10,7 @@ if typing.TYPE_CHECKING: import pandas as pd - from rich.console import RenderableType + from rich.console import OverflowMethod, RenderableType def get_rich_repr(obj: typing.Any) -> "RenderableType": # noqa: ANN401 @@ -38,6 +38,9 @@ def dataframe_to_rich_table( show_edge: bool = False, box: rich.box.Box = rich.box.HEAVY, show_index: bool = False, + overflow: "OverflowMethod" = "ellipsis", + no_wrap: bool = False, + do_not_truncate_column_content: bool = False, ) -> Table: """Convert a pandas DataFrame to a rich Table. @@ -50,28 +53,81 @@ def dataframe_to_rich_table( box: Box style for the table show_index: Whether to include the DataFrame's index as the first column. If True, the index will be displayed with a header label using the - DataFrame's index name if available, or "Index" as a default. + DataFrame's index name if available, or "INDEX" as a default. Default is False for backward compatibility. + overflow: How to handle content that exceeds column width. Options include + "ellipsis" (default), "ignore", "fold", "crop". When do_not_truncate_column_content + is True, this is automatically set to "ignore". + no_wrap: Whether to disable text wrapping in cells. When do_not_truncate_column_content + is True, this is automatically set to True. + do_not_truncate_column_content: If True, automatically calculates column widths + to fit all content without truncation. This sets overflow="ignore" and no_wrap=True, + and calculates the minimum table width needed to display all content. Returns: A rich Table object ready for rendering """ + index_name = str(df.index.name) if df.index.name is not None else "INDEX" + + # Initialize variables for column width control + table_width = None + column_width = {} + + if do_not_truncate_column_content: + # Override overflow and no_wrap settings to prevent truncation + overflow = "ignore" + no_wrap = True + + # Calculate column name lengths + column_names_length = {col: len(col) for col in df.columns} + column_names_length[index_name] = len(index_name) + + # Calculate longest string in each column + longest_string_in_column = df.apply( + lambda col: col.astype(str).str.len().max() + ).to_dict() + longest_string_in_column[index_name] = len(str(df.index.max())) + + # Determine column widths (max of column name length and longest content) + column_width = { + col: max(column_names_length[col], longest_string_in_column[col]) + for col in column_names_length + } + + # Calculate total table width: + # sum of column widths + # + padding (2 per column) + # + separators (1 per column + 1 for borders) + table_width = sum(column_width.values()) + len(column_width) * 3 + 1 + table = Table( title=title, show_header=show_header, show_lines=show_lines, show_edge=show_edge, box=box, + width=table_width, ) # Add index column if requested if show_index: - index_name = df.index.name or "INDEX" - table.add_column(str(index_name)) + table.add_column( + index_name, + overflow=overflow, + no_wrap=no_wrap, + min_width=column_width.get(index_name), + width=column_width.get(index_name), + ) # Add columns for column in df.columns: - table.add_column(str(column)) + table.add_column( + column, + overflow=overflow, + no_wrap=no_wrap, + min_width=column_width.get(column), + width=column_width.get(column), + ) # Add rows for idx, row in df.iterrows():