Skip to content

Commit bac88ad

Browse files
committed
/* PR_START p--misc 04 */ Allow lazy evaluation of arguments in LazyFormat.
1 parent d7d8be9 commit bac88ad

File tree

2 files changed

+49
-7
lines changed

2 files changed

+49
-7
lines changed

metricflow-semantics/metricflow_semantics/mf_logging/lazy_formattable.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
from __future__ import annotations
22

3+
import logging
34
from functools import cached_property
45
from typing import Callable, Union
56

67
from typing_extensions import override
78

89
from metricflow_semantics.mf_logging.pretty_print import mf_pformat_dict
910

11+
logger = logging.getLogger(__name__)
12+
1013

1114
class LazyFormat:
1215
"""Lazily formats the given objects into a string representation for logging.
@@ -40,7 +43,8 @@ def __init__(self, message: Union[str, Callable[[], str]], **kwargs) -> None: #
4043
4144
Args:
4245
message: The message or a function that returns a message.
43-
**kwargs: A dictionary of objects to format to text when `__str__` is called on this object.
46+
**kwargs: A dictionary of objects to format to text when `__str__` is called on this object. To allow for
47+
lazy evaluation of argument values, a callable that takes no arguments can be supplied for a key's value.
4448
"""
4549
self._message = message
4650
self._kwargs = kwargs
@@ -52,7 +56,23 @@ def _str_value(self) -> str:
5256
message = self._message()
5357
else:
5458
message = self._message
55-
return mf_pformat_dict(message, self._kwargs, preserve_raw_strings=True)
59+
60+
evaluated_args = {}
61+
for arg_name, arg_value in self._kwargs.items():
62+
if callable(arg_value):
63+
try:
64+
evaluated_args[arg_name] = arg_value()
65+
continue
66+
except Exception:
67+
logger.warning(
68+
f"Got an exception while evaluating {arg_name=} {arg_value=}. Since this is an error with "
69+
f"formatting log output, this should not result in system issues. However, the exception "
70+
f"indicates a bug with how the logging call is made and should be investigated.",
71+
exc_info=True,
72+
)
73+
74+
evaluated_args[arg_name] = arg_value
75+
return mf_pformat_dict(message, evaluated_args, preserve_raw_strings=True)
5676

5777
@override
5878
def __str__(self) -> str:

metricflow-semantics/tests_metricflow_semantics/mf_logging/test_lazy_format.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
logger = logging.getLogger(__name__)
1111

12+
_NOT_FORMATTED_ASSERTION_MESSAGE = "This should not have been formatted as a string."
13+
1214

1315
def test_log_kwargs() -> None:
1416
"""Test that objects included via keyword args are formatted."""
@@ -36,12 +38,11 @@ def test_lambda() -> None:
3638

3739
def test_lazy_object() -> None:
3840
"""Test that formatting of objects are done lazily and not when the logging level is not appropriate."""
39-
assertion_message = "This should not have been formatted as a string."
4041

4142
class _NotFormattedObject:
4243
@override
4344
def __repr__(self) -> str:
44-
raise AssertionError(assertion_message)
45+
raise AssertionError(_NOT_FORMATTED_ASSERTION_MESSAGE)
4546

4647
# Logging level is INFO, so DEBUG messages shouldn't be logged / formatted.
4748
recorded_logger: logging.Logger
@@ -51,14 +52,35 @@ def __repr__(self) -> str:
5152

5253

5354
def test_lazy_lambda() -> None:
54-
"""Test that a lambda input is not evaluated when the logging level is not appropriate."""
55-
assertion_message = "This should not have been formatted as a string."
55+
"""Test that a lambda message is not evaluated when the logging level is not appropriate."""
5656

5757
def _should_not_be_called() -> str:
58-
raise AssertionError(assertion_message)
58+
raise AssertionError(_NOT_FORMATTED_ASSERTION_MESSAGE)
5959

6060
# Logging level is INFO, so DEBUG messages shouldn't be logged / formatted.
6161
recorded_logger: logging.Logger
6262
handler: RecordingLogHandler
6363
with recorded_logging_context(logger, logging.INFO) as (recorded_logger, handler):
6464
recorded_logger.debug(LazyFormat(lambda: f"{_should_not_be_called()}"))
65+
66+
67+
def test_lambda_argument() -> None:
68+
"""Tests that a callable that's supplied as an argument value is evaluated."""
69+
recorded_logger: logging.Logger
70+
handler: RecordingLogHandler
71+
with recorded_logging_context(logger, logging.INFO) as (recorded_logger, handler):
72+
recorded_logger.info(LazyFormat("Example message", arg_0=lambda: 1))
73+
assert handler.get_last_message() == "Example message (arg_0=1)"
74+
75+
76+
def test_lazy_lambda_argument() -> None:
77+
"""Test that a lambda input is not evaluated when the logging level is not appropriate."""
78+
79+
def _should_not_be_called() -> str:
80+
raise AssertionError(_NOT_FORMATTED_ASSERTION_MESSAGE)
81+
82+
# Logging level is INFO, so DEBUG messages shouldn't be logged / formatted.
83+
recorded_logger: logging.Logger
84+
handler: RecordingLogHandler
85+
with recorded_logging_context(logger, logging.INFO) as (recorded_logger, handler):
86+
recorded_logger.debug(LazyFormat("Example message", arg_0=lambda: f"{_should_not_be_called()}"))

0 commit comments

Comments
 (0)