Skip to content

Commit 057c4eb

Browse files
committed
only sort root dict
1 parent 60ee5ee commit 057c4eb

File tree

6 files changed

+74
-182
lines changed

6 files changed

+74
-182
lines changed

sqlmesh/core/model/common.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,15 @@ def _add_variables_to_python_env(
153153

154154
variables = {k: v for k, v in (variables or {}).items() if k in used_variables}
155155
if variables:
156-
python_env[c.SQLMESH_VARS] = Executable.value(variables, use_deterministic_repr=True)
156+
python_env[c.SQLMESH_VARS] = Executable.value(variables, sort_root_dict=True)
157157

158158
if blueprint_variables:
159159
blueprint_variables = {
160160
k: SqlValue(sql=v.sql(dialect=dialect)) if isinstance(v, exp.Expression) else v
161161
for k, v in blueprint_variables.items()
162162
}
163163
python_env[c.SQLMESH_BLUEPRINT_VARS] = Executable.value(
164-
blueprint_variables, use_deterministic_repr=True
164+
blueprint_variables, sort_root_dict=True
165165
)
166166

167167
return python_env

sqlmesh/migrations/v0085_deterministic_repr.py

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,13 @@ class SqlValue:
2323
sql: str
2424

2525

26-
def _deterministic_repr(obj: t.Any) -> str:
27-
"""
28-
This is a copy of the function from utils.metaprogramming
29-
"""
30-
31-
def _normalize_for_repr(o: t.Any) -> t.Any:
32-
if isinstance(o, dict):
33-
sorted_items = sorted(o.items(), key=lambda x: str(x[0]))
34-
return {k: _normalize_for_repr(v) for k, v in sorted_items}
35-
if isinstance(o, (list, tuple)):
36-
# Recursively normalize nested structures
37-
normalized = [_normalize_for_repr(item) for item in o]
38-
return type(o)(normalized)
39-
return o
40-
26+
def non_recursive_dict_sort(obj: t.Any) -> str:
4127
try:
42-
return repr(_normalize_for_repr(obj))
28+
if isinstance(obj, dict):
29+
obj = dict(sorted(obj.items(), key=lambda x: str(x[0])))
4330
except Exception:
44-
return repr(obj)
31+
pass
32+
return repr(obj)
4533

4634

4735
def migrate(state_sync, **kwargs): # type: ignore
@@ -92,7 +80,7 @@ def migrate(state_sync, **kwargs): # type: ignore
9280
try:
9381
# Try to parse the old payload and re-serialize it deterministically
9482
parsed_value = eval(old_payload)
95-
new_payload = _deterministic_repr(parsed_value)
83+
new_payload = non_recursive_dict_sort(parsed_value)
9684

9785
# Only update if the representation changed
9886
if old_payload != new_payload:

sqlmesh/migrations/v0086_check_deterministic_bug.py

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import json
2-
import typing as t
32
from sqlglot import exp
43

54
from sqlmesh.core.console import get_console
@@ -8,36 +7,6 @@
87
KEYS_TO_MAKE_DETERMINISTIC = ["__sqlmesh__vars__", "__sqlmesh__blueprint__vars__"]
98

109

11-
def would_sorting_be_applied(obj: t.Any) -> bool:
12-
"""
13-
Detects if sorting would be applied to an object based on the
14-
deterministic_repr logic.
15-
16-
Returns True if the object is a dictionary or contains a dictionary
17-
at any nesting level (in lists or tuples).
18-
19-
Args:
20-
obj: The object to check
21-
22-
Returns:
23-
bool: True if sorting would be applied, False otherwise
24-
"""
25-
26-
def _check_for_dict(o: t.Any) -> bool:
27-
if isinstance(o, dict):
28-
return True
29-
if isinstance(o, (list, tuple)):
30-
return any(_check_for_dict(item) for item in o)
31-
32-
return False
33-
34-
try:
35-
return _check_for_dict(obj)
36-
except Exception:
37-
# If any error occurs during checking, assume no sorting
38-
return False
39-
40-
4110
def migrate(state_sync, **kwargs): # type: ignore
4211
engine_adapter = state_sync.engine_adapter
4312
schema = state_sync.schema
@@ -103,7 +72,7 @@ def migrate(state_sync, **kwargs): # type: ignore
10372
):
10473
try:
10574
parsed_value = eval(executable["payload"])
106-
if would_sorting_be_applied(parsed_value):
75+
if isinstance(parsed_value, dict):
10776
get_console().log_warning(warning)
10877
return
10978
except Exception:

sqlmesh/utils/metaprogramming.py

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -425,9 +425,9 @@ def is_value(self) -> bool:
425425

426426
@classmethod
427427
def value(
428-
cls, v: t.Any, is_metadata: t.Optional[bool] = None, use_deterministic_repr: bool = False
428+
cls, v: t.Any, is_metadata: t.Optional[bool] = None, sort_root_dict: bool = False
429429
) -> Executable:
430-
payload = _deterministic_repr(v) if use_deterministic_repr else repr(v)
430+
payload = non_recursive_dict_sort(v) if sort_root_dict else repr(v)
431431
return Executable(payload=payload, kind=ExecutableKind.VALUE, is_metadata=is_metadata)
432432

433433

@@ -636,36 +636,13 @@ def print_exception(
636636
out.write(tb)
637637

638638

639-
def _deterministic_repr(obj: t.Any) -> str:
640-
"""Create a deterministic representation by ensuring consistent ordering before repr().
641-
642-
For dictionaries, ensures consistent key ordering to prevent non-deterministic
643-
serialization that affects fingerprinting. Uses Python's native repr() logic
644-
for all formatting to handle edge cases properly.
645-
646-
Note that this function assumes list/tuple order is significant and therefore does not sort them.
647-
648-
Args:
649-
obj: The object to represent as a string.
650-
651-
Returns:
652-
A deterministic string representation of the object.
653-
"""
654-
655-
def _normalize_for_repr(o: t.Any) -> t.Any:
656-
if isinstance(o, dict):
657-
sorted_items = sorted(o.items(), key=lambda x: str(x[0]))
658-
return {k: _normalize_for_repr(v) for k, v in sorted_items}
659-
if isinstance(o, (list, tuple)):
660-
# Recursively normalize nested structures
661-
normalized = [_normalize_for_repr(item) for item in o]
662-
return type(o)(normalized)
663-
return o
664-
639+
def non_recursive_dict_sort(obj: t.Any) -> str:
665640
try:
666-
return repr(_normalize_for_repr(obj))
641+
if isinstance(obj, dict):
642+
obj = dict(sorted(obj.items(), key=lambda x: str(x[0])))
667643
except Exception:
668-
return repr(obj)
644+
pass
645+
return repr(obj)
669646

670647

671648
def import_python_file(path: Path, relative_base: Path = Path()) -> types.ModuleType:

tests/core/test_model.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6117,7 +6117,7 @@ def test_named_variable_macros() -> None:
61176117

61186118
assert model.python_env[c.SQLMESH_VARS] == Executable.value(
61196119
{c.GATEWAY: "in_memory", "test_var_a": "test_value", "overridden_var": "initial_value"},
6120-
use_deterministic_repr=True,
6120+
sort_root_dict=True,
61216121
)
61226122
assert (
61236123
model.render_query_or_raise().sql()
@@ -6144,7 +6144,7 @@ def test_variables_in_templates() -> None:
61446144

61456145
assert model.python_env[c.SQLMESH_VARS] == Executable.value(
61466146
{c.GATEWAY: "in_memory", "test_var_a": "test_value", "overridden_var": "initial_value"},
6147-
use_deterministic_repr=True,
6147+
sort_root_dict=True,
61486148
)
61496149
assert (
61506150
model.render_query_or_raise().sql()
@@ -6169,7 +6169,7 @@ def test_variables_in_templates() -> None:
61696169

61706170
assert model.python_env[c.SQLMESH_VARS] == Executable.value(
61716171
{c.GATEWAY: "in_memory", "test_var_a": "test_value", "overridden_var": "initial_value"},
6172-
use_deterministic_repr=True,
6172+
sort_root_dict=True,
61736173
)
61746174
assert (
61756175
model.render_query_or_raise().sql()
@@ -6309,7 +6309,7 @@ def test_variables_migrated_dbt_package_macro():
63096309
)
63106310
assert model.python_env[c.SQLMESH_VARS] == Executable.value(
63116311
{"test_var_a": "test_var_a_value", "__dbt_packages__.test.test_var_b": "test_var_b_value"},
6312-
use_deterministic_repr=True,
6312+
sort_root_dict=True,
63136313
)
63146314
assert (
63156315
model.render_query().sql(dialect="bigquery")
@@ -6535,7 +6535,7 @@ def test_unrendered_macros_sql_model(mocker: MockerFixture) -> None:
65356535
"virtual_var": "blb",
65366536
"session_var": "blc",
65376537
},
6538-
use_deterministic_repr=True,
6538+
sort_root_dict=True,
65396539
)
65406540

65416541
assert "location1" in model.physical_properties
@@ -6623,7 +6623,7 @@ def model_with_macros(evaluator, **kwargs):
66236623
"virtual_var": "blb",
66246624
"session_var": "blc",
66256625
},
6626-
use_deterministic_repr=True,
6626+
sort_root_dict=True,
66276627
)
66286628
assert python_sql_model.enabled
66296629

@@ -10583,10 +10583,10 @@ def unimportant_testing_macro(evaluator, *projections):
1058310583

1058410584
assert m.python_env.get(c.SQLMESH_VARS) == Executable.value(
1058510585
{"selector": "bla", "bla_variable": 1, "baz_variable": 2},
10586-
use_deterministic_repr=True,
10586+
sort_root_dict=True,
1058710587
)
1058810588
assert m.python_env.get(c.SQLMESH_BLUEPRINT_VARS) == Executable.value(
10589-
{"selector": "baz"}, use_deterministic_repr=True
10589+
{"selector": "baz"}, sort_root_dict=True
1059010590
)
1059110591

1059210592

0 commit comments

Comments
 (0)