Skip to content

Commit df3d295

Browse files
committed
Add typo suggestions to MODEL block field errors
1 parent 9e5bf54 commit df3d295

3 files changed

Lines changed: 31 additions & 5 deletions

File tree

sqlmesh/core/model/common.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pathlib import Path
66

77
from astor import to_source
8+
from difflib import get_close_matches
89
from sqlglot import exp
910
from sqlglot.helper import ensure_list
1011

@@ -267,13 +268,33 @@ def validate_extra_and_required_fields(
267268
) -> None:
268269
missing_required_fields = klass.missing_required_fields(provided_fields)
269270
if missing_required_fields:
271+
field_names = "'" + "', '".join(missing_required_fields) + "'"
270272
raise_config_error(
271-
f"Missing required fields {missing_required_fields} in the {entity_name}"
273+
f"Please add required field{'s' if len(missing_required_fields) > 1 else ''} {field_names} to the {entity_name}."
272274
)
273275

274276
extra_fields = klass.extra_fields(provided_fields)
275277
if extra_fields:
276-
raise_config_error(f"Invalid extra fields {extra_fields} in the {entity_name}")
278+
extra_field_names = "'" + "', '".join(extra_fields) + "'"
279+
280+
all_fields = klass.all_fields()
281+
close_matches = {}
282+
for field in extra_fields:
283+
matches = get_close_matches(field, all_fields, n=1)
284+
if matches:
285+
close_matches[field] = matches[0]
286+
287+
if len(close_matches) == 1:
288+
similar_msg = ". Did you mean " + "'" + "', '".join(close_matches.values()) + "'?"
289+
else:
290+
similar = [
291+
f"- {field}: Did you mean '{match}'?" for field, match in close_matches.items()
292+
]
293+
similar_msg = "\n\n " + "\n ".join(similar) if similar else ""
294+
295+
raise_config_error(
296+
f"Invalid field name{'s' if len(extra_fields) > 1 else ''} present in the {entity_name}: {extra_field_names}{similar_msg}"
297+
)
277298

278299

279300
def single_value_or_tuple(values: t.Sequence) -> exp.Identifier | exp.Tuple:

sqlmesh/core/model/definition.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2156,7 +2156,10 @@ def load_sql_based_model(
21562156
name = get_model_name(path)
21572157

21582158
if not name:
2159-
raise_config_error("Model must have a name", path)
2159+
raise_config_error(
2160+
"Please add the required 'name' field to the MODEL block at the top of the file.\n\n"
2161+
+ "Learn more at https://sqlmesh.readthedocs.io/en/stable/concepts/models/overview"
2162+
)
21602163
if "default_catalog" in meta_fields:
21612164
raise_config_error(
21622165
"`default_catalog` cannot be set on a per-model basis. It must be set at the connection level.",
@@ -2400,7 +2403,7 @@ def _create_model(
24002403
**kwargs: t.Any,
24012404
) -> Model:
24022405
validate_extra_and_required_fields(
2403-
klass, {"name", *kwargs} - {"grain", "table_properties"}, "model definition"
2406+
klass, {"name", *kwargs} - {"grain", "table_properties"}, "MODEL block"
24042407
)
24052408

24062409
for prop in PROPERTIES:

sqlmesh/core/model/kind.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,9 @@ def create_model_kind(v: t.Any, dialect: str, defaults: t.Dict[str, t.Any]) -> M
10151015
actual_kind_type, _ = custom_materialization
10161016
return actual_kind_type(**props)
10171017

1018-
validate_extra_and_required_fields(kind_type, set(props), f"model kind '{name}'")
1018+
validate_extra_and_required_fields(
1019+
kind_type, set(props), f"MODEL block 'kind {name}(' field"
1020+
)
10191021
return kind_type(**props)
10201022

10211023
name = (v.name if isinstance(v, exp.Expression) else str(v)).upper()

0 commit comments

Comments
 (0)