diff --git a/sqlmesh/utils/pydantic.py b/sqlmesh/utils/pydantic.py index 43f389ef62..4e5cfc3dc1 100644 --- a/sqlmesh/utils/pydantic.py +++ b/sqlmesh/utils/pydantic.py @@ -242,7 +242,7 @@ def _formatted_validation_errors(error: pydantic.ValidationError) -> t.List[str] for e in error.errors(): msg = e["msg"] loc: t.Optional[t.Tuple] = e.get("loc") - loc_str = ".".join(loc) if loc else None + loc_str = ".".join(str(p) for p in loc) if loc else None result.append(f"Invalid field '{loc_str}':\n {msg}" if loc_str else msg) return result diff --git a/tests/core/test_config.py b/tests/core/test_config.py index 7af556d6a3..becdc92df2 100644 --- a/tests/core/test_config.py +++ b/tests/core/test_config.py @@ -700,7 +700,7 @@ def test_load_model_defaults_validation_statements(tmp_path): """ ) - with pytest.raises(TypeError, match=r"expected str instance, int found"): + with pytest.raises(ConfigError, match=r"Invalid field 'model_defaults\.pre_statements\.0"): config = load_config_from_paths( Config, project_paths=[config_path], diff --git a/tests/utils/test_pydantic.py b/tests/utils/test_pydantic.py index b07d45acb1..9234589218 100644 --- a/tests/utils/test_pydantic.py +++ b/tests/utils/test_pydantic.py @@ -2,8 +2,14 @@ import pytest from functools import cached_property +import pydantic + from sqlmesh.utils.date import TimeLike, to_date, to_datetime -from sqlmesh.utils.pydantic import PydanticModel, get_concrete_types_from_typehint +from sqlmesh.utils.pydantic import ( + PydanticModel, + get_concrete_types_from_typehint, + validation_error_message, +) def test_datetime_date_serialization() -> None: @@ -87,3 +93,19 @@ class TestModel(PydanticModel): ) def test_get_concrete_types_from_typehint(input: t.Any, output: set[type]) -> None: assert get_concrete_types_from_typehint(input) == output + + +def test_validation_error_message_with_list_index_location() -> None: + class Inner(PydanticModel): + x: int + + class Outer(PydanticModel): + items: t.List[Inner] + + with pytest.raises(pydantic.ValidationError) as ex: + Outer(items=[{"x": "not_an_int"}]) + + # The error is located at a list index, so pydantic's loc mixes str field + # names with an int sequence index (e.g. ("items", 0, "x")). + message = validation_error_message(ex.value, "Invalid config:") + assert "Invalid field 'items.0.x'" in message