|
2 | 2 |
|
3 | 3 | import logging
|
4 | 4 | import pprint
|
5 |
| -from collections.abc import Mapping |
6 | 5 | from dataclasses import fields, is_dataclass
|
7 | 6 | from enum import Enum
|
8 |
| -from typing import Any, List, Optional, Sized, Tuple, Union |
9 |
| - |
10 |
| -from dsi_pydantic_shim import BaseModel |
| 7 | +from typing import Any, List, Mapping, Optional, Sized, Tuple, Union |
11 | 8 |
|
12 | 9 | from metricflow_semantics.mf_logging.formatting import indent
|
13 | 10 | from metricflow_semantics.mf_logging.pretty_formattable import MetricFlowPrettyFormattable
|
@@ -35,11 +32,6 @@ def __init__(
|
35 | 32 | self._include_none_object_fields = include_none_object_fields
|
36 | 33 | self._include_empty_object_fields = include_empty_object_fields
|
37 | 34 |
|
38 |
| - @staticmethod |
39 |
| - def _is_pydantic_base_model(obj: Any): # type:ignore |
40 |
| - # Checking the attribute as the BaseModel check fails for newer version of Pydantic. |
41 |
| - return isinstance(obj, BaseModel) or hasattr(obj, "__config__") |
42 |
| - |
43 | 35 | def _handle_sequence_obj(
|
44 | 36 | self, list_like_obj: Union[list, tuple, set, frozenset], remaining_line_width: Optional[int]
|
45 | 37 | ) -> str:
|
@@ -353,16 +345,28 @@ def _handle_any_obj(self, obj: Any, remaining_line_width: Optional[int]) -> str:
|
353 | 345 | remaining_line_width=remaining_line_width,
|
354 | 346 | )
|
355 | 347 |
|
356 |
| - if MetricFlowPrettyFormatter._is_pydantic_base_model(obj): |
357 |
| - mapping = {key: getattr(obj, key) for key in obj.dict().keys()} |
358 |
| - return self._handle_mapping_like_obj( |
359 |
| - mapping, |
360 |
| - left_enclose_str=type(obj).__name__ + "(", |
361 |
| - key_value_seperator="=", |
362 |
| - right_enclose_str=")", |
363 |
| - is_dataclass_like_object=True, |
364 |
| - remaining_line_width=remaining_line_width, |
365 |
| - ) |
| 348 | + # For Pydantic-like objects with a `dict`-like method that returns field keys / values. |
| 349 | + # In Pydantic v1, it's `.dict()`, in v2 it's `.model_dump()`. |
| 350 | + # Going with this approach for now to check for a Pydantic model as using `isinstance()` requires more |
| 351 | + # consideration when dealing with Pydantic v1 and Pydantic v2 objects. |
| 352 | + pydantic_dict_method = getattr(obj, "model_dump", None) or getattr(obj, "dict", None) |
| 353 | + if pydantic_dict_method is not None and callable(pydantic_dict_method): |
| 354 | + try: |
| 355 | + # Calling `dict` on a Pydantic model does not recursively convert nested fields into dictionaries, |
| 356 | + # which is what we want. `.model_dump()` does the recursive conversion. |
| 357 | + mapping = dict(obj) |
| 358 | + return self._handle_mapping_like_obj( |
| 359 | + mapping, |
| 360 | + left_enclose_str=type(obj).__name__ + "(", |
| 361 | + key_value_seperator="=", |
| 362 | + right_enclose_str=")", |
| 363 | + is_dataclass_like_object=True, |
| 364 | + remaining_line_width=remaining_line_width, |
| 365 | + ) |
| 366 | + except Exception: |
| 367 | + # Fall back to built-in pretty-print in case the dict method can't be called. e.g. requires arguments. |
| 368 | + # Consider logging a warning. |
| 369 | + pass |
366 | 370 |
|
367 | 371 | # Any other object that's not handled.
|
368 | 372 | return pprint.pformat(obj, width=self._max_line_width, sort_dicts=False)
|
|
0 commit comments