diff --git a/metricflow-semantics/metricflow_semantics/mf_logging/pretty_print.py b/metricflow-semantics/metricflow_semantics/mf_logging/pretty_print.py index 3e6c5be27..14d398bd5 100644 --- a/metricflow-semantics/metricflow_semantics/mf_logging/pretty_print.py +++ b/metricflow-semantics/metricflow_semantics/mf_logging/pretty_print.py @@ -4,7 +4,7 @@ import pprint from dataclasses import fields, is_dataclass from enum import Enum -from typing import Any, List, Mapping, Optional, Sized, Tuple, Union +from typing import Any, Dict, List, Mapping, Optional, Sized, Tuple, Union from metricflow_semantics.mf_logging.formatting import indent from metricflow_semantics.mf_logging.pretty_formattable import MetricFlowPrettyFormattable @@ -454,6 +454,8 @@ def mf_pformat_dict( # type: ignore description_lines: List[str] = [description] if description is not None else [] obj_dict = obj_dict or {} item_sections = [] + + str_converted_dict: Dict[str, str] = {} for key, value in obj_dict.items(): if preserve_raw_strings and isinstance(value, str): value_str = value @@ -466,10 +468,10 @@ def mf_pformat_dict( # type: ignore include_none_object_fields=include_none_object_fields, include_empty_object_fields=include_empty_object_fields, ) + str_converted_dict[str(key)] = value_str - lines_in_value_str = len(value_str.split("\n")) item_section_lines: Tuple[str, ...] - if lines_in_value_str > 1: + if "\n" in value_str: item_section_lines = ( f"{key}:", indent( @@ -486,7 +488,37 @@ def mf_pformat_dict( # type: ignore else: item_sections.append(indent(item_section)) + result_as_one_line = _as_one_line( + description=description, str_converted_dict=str_converted_dict, max_line_length=max_line_length + ) + if result_as_one_line is not None: + return result_as_one_line + if pad_items_with_newlines: return "\n\n".join(description_lines + item_sections) else: return "\n".join(description_lines + item_sections) + + +def _as_one_line(description: Optional[str], str_converted_dict: Dict[str, str], max_line_length: int) -> Optional[str]: + """See if the result can be returned in a compact, one-line representation. + + e.g. for: + mf_pformat_dict("Example output", {"a": 1, "b": 2}) + + Compact output: + Example output (a=1, b=2) + + Normal output: + Example output + a: 1 + b: 2 + """ + items = tuple(f"{key_str}={value_str}" for key_str, value_str in str_converted_dict.items()) + value_in_parenthesis = ", ".join(items) + result = f"{description}" + (f" ({value_in_parenthesis})" if len(value_in_parenthesis) > 0 else "") + + if "\n" in result or len(result) > max_line_length: + return None + + return result diff --git a/metricflow-semantics/tests_metricflow_semantics/mf_logging/test_lazy_format.py b/metricflow-semantics/tests_metricflow_semantics/mf_logging/test_lazy_format.py index 62595d59f..df7ec0543 100644 --- a/metricflow-semantics/tests_metricflow_semantics/mf_logging/test_lazy_format.py +++ b/metricflow-semantics/tests_metricflow_semantics/mf_logging/test_lazy_format.py @@ -2,7 +2,6 @@ import logging -from metricflow_semantics.formatting.formatting_helpers import mf_dedent from metricflow_semantics.mf_logging.lazy_formattable import LazyFormat from typing_extensions import override @@ -19,12 +18,8 @@ def test_log_kwargs() -> None: recorded_logger.debug( LazyFormat("Found candidates.", matches=[1, 2, 3], parameters={"field_0": "value_0", "field_1": "value_1"}) ) - assert handler.get_last_message() == mf_dedent( - """ - Found candidates. - matches: [1, 2, 3] - parameters: {'field_0': 'value_0', 'field_1': 'value_1'} - """ + assert handler.get_last_message() == ( + "Found candidates. (matches=[1, 2, 3], parameters={'field_0': 'value_0', 'field_1': 'value_1'})" ) diff --git a/metricflow-semantics/tests_metricflow_semantics/mf_logging/test_pformat_dict.py b/metricflow-semantics/tests_metricflow_semantics/mf_logging/test_pformat_dict.py index f52c9bd4e..4732e69ef 100644 --- a/metricflow-semantics/tests_metricflow_semantics/mf_logging/test_pformat_dict.py +++ b/metricflow-semantics/tests_metricflow_semantics/mf_logging/test_pformat_dict.py @@ -10,7 +10,9 @@ def test_pformat_many() -> None: # noqa: D103 - result = mf_pformat_dict("Example description:", obj_dict={"object_0": (1, 2, 3), "object_1": {4: 5}}) + result = mf_pformat_dict( + "Example description:", obj_dict={"object_0": (1, 2, 3), "object_1": {4: 5}}, max_line_length=30 + ) assert ( textwrap.dedent( @@ -25,7 +27,9 @@ def test_pformat_many() -> None: # noqa: D103 def test_pformat_many_with_raw_strings() -> None: # noqa: D103 - result = mf_pformat_dict("Example description:", obj_dict={"object_0": "foo\nbar"}, preserve_raw_strings=True) + result = mf_pformat_dict( + "Example description:", obj_dict={"object_0": "foo\nbar"}, preserve_raw_strings=True, max_line_length=30 + ) assert ( textwrap.dedent( @@ -42,7 +46,7 @@ def test_pformat_many_with_raw_strings() -> None: # noqa: D103 def test_pformat_dict_with_empty_message() -> None: """Test `mf_pformat_dict` without a description.""" - result = mf_pformat_dict(obj_dict={"object_0": (1, 2, 3), "object_1": {4: 5}}) + result = mf_pformat_dict(obj_dict={"object_0": (1, 2, 3), "object_1": {4: 5}}, max_line_length=30) assert ( mf_dedent( @@ -57,7 +61,9 @@ def test_pformat_dict_with_empty_message() -> None: def test_pformat_dict_with_pad_sections_with_newline() -> None: """Test `mf_pformat_dict` with new lines between sections.""" - result = mf_pformat_dict(obj_dict={"object_0": (1, 2, 3), "object_1": {4: 5}}, pad_items_with_newlines=True) + result = mf_pformat_dict( + obj_dict={"object_0": (1, 2, 3), "object_1": {4: 5}}, pad_items_with_newlines=True, max_line_length=30 + ) assert ( mf_dedent( @@ -72,7 +78,7 @@ def test_pformat_dict_with_pad_sections_with_newline() -> None: def test_pformat_many_with_strings() -> None: # noqa: D103 - result = mf_pformat_dict("Example description:", obj_dict={"object_0": "foo\nbar"}) + result = mf_pformat_dict("Example description:", obj_dict={"object_0": "foo\nbar"}, max_line_length=30) assert ( textwrap.dedent( """\ @@ -92,3 +98,8 @@ def test_minimal_length() -> None: foo: 'bar' """ ) + + +def test_one_line() -> None: + """Test formatting as a one-line string if possible.""" + assert mf_pformat_dict("Example output", {"a": 1, "b": 2}, max_line_length=80) == "Example output (a=1, b=2)"