|
5 | 5 | import importlib |
6 | 6 | import inspect |
7 | 7 | import linecache |
| 8 | +import logging |
8 | 9 | import os |
9 | 10 | import re |
10 | 11 | import sys |
|
23 | 24 | from sqlmesh.utils.errors import SQLMeshError |
24 | 25 | from sqlmesh.utils.pydantic import PydanticModel |
25 | 26 |
|
| 27 | +logger = logging.getLogger(__name__) |
| 28 | + |
| 29 | + |
26 | 30 | IGNORE_DECORATORS = {"macro", "model", "signal"} |
27 | 31 | SERIALIZABLE_CALLABLES = (type, types.FunctionType) |
28 | 32 | LITERALS = (Number, str, bytes, tuple, list, dict, set, bool) |
@@ -424,10 +428,11 @@ def is_value(self) -> bool: |
424 | 428 | return self.kind == ExecutableKind.VALUE |
425 | 429 |
|
426 | 430 | @classmethod |
427 | | - def value(cls, v: t.Any, is_metadata: t.Optional[bool] = None) -> Executable: |
428 | | - return Executable( |
429 | | - payload=_deterministic_repr(v), kind=ExecutableKind.VALUE, is_metadata=is_metadata |
430 | | - ) |
| 431 | + def value( |
| 432 | + cls, v: t.Any, is_metadata: t.Optional[bool] = None, sort_root_dict: bool = False |
| 433 | + ) -> Executable: |
| 434 | + payload = _dict_sort(v) if sort_root_dict else repr(v) |
| 435 | + return Executable(payload=payload, kind=ExecutableKind.VALUE, is_metadata=is_metadata) |
431 | 436 |
|
432 | 437 |
|
433 | 438 | def serialize_env(env: t.Dict[str, t.Any], path: Path) -> t.Dict[str, Executable]: |
@@ -635,36 +640,13 @@ def print_exception( |
635 | 640 | out.write(tb) |
636 | 641 |
|
637 | 642 |
|
638 | | -def _deterministic_repr(obj: t.Any) -> str: |
639 | | - """Create a deterministic representation by ensuring consistent ordering before repr(). |
640 | | -
|
641 | | - For dictionaries, ensures consistent key ordering to prevent non-deterministic |
642 | | - serialization that affects fingerprinting. Uses Python's native repr() logic |
643 | | - for all formatting to handle edge cases properly. |
644 | | -
|
645 | | - Note that this function assumes list/tuple order is significant and therefore does not sort them. |
646 | | -
|
647 | | - Args: |
648 | | - obj: The object to represent as a string. |
649 | | -
|
650 | | - Returns: |
651 | | - A deterministic string representation of the object. |
652 | | - """ |
653 | | - |
654 | | - def _normalize_for_repr(o: t.Any) -> t.Any: |
655 | | - if isinstance(o, dict): |
656 | | - sorted_items = sorted(o.items(), key=lambda x: str(x[0])) |
657 | | - return {k: _normalize_for_repr(v) for k, v in sorted_items} |
658 | | - if isinstance(o, (list, tuple)): |
659 | | - # Recursively normalize nested structures |
660 | | - normalized = [_normalize_for_repr(item) for item in o] |
661 | | - return type(o)(normalized) |
662 | | - return o |
663 | | - |
| 643 | +def _dict_sort(obj: t.Any) -> str: |
664 | 644 | try: |
665 | | - return repr(_normalize_for_repr(obj)) |
| 645 | + if isinstance(obj, dict): |
| 646 | + obj = dict(sorted(obj.items(), key=lambda x: str(x[0]))) |
666 | 647 | except Exception: |
667 | | - return repr(obj) |
| 648 | + logger.warning("Failed to sort non-recursive dict", exc_info=True) |
| 649 | + return repr(obj) |
668 | 650 |
|
669 | 651 |
|
670 | 652 | def import_python_file(path: Path, relative_base: Path = Path()) -> types.ModuleType: |
|
0 commit comments