Skip to content

Commit 6d3388d

Browse files
committed
temp [ci skip]
1 parent 116a35b commit 6d3388d

6 files changed

Lines changed: 85 additions & 31 deletions

File tree

examples/sushi/models/marketing.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ MODEL (
22
name sushi.marketing,
33
kind SCD_TYPE_2(unique_key customer_id),
44
owner jen,
5+
owners test,
56
cron '@daily',
67
grain customer_id,
78
description 'Sushi marketing data'

sqlmesh/core/loader.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ def _load(path: Path) -> t.List[Model]:
364364
self._failed_to_load_model_error(
365365
path, f"Duplicate external model name: '{model.name}'."
366366
),
367-
path
367+
path,
368368
)
369369
models[model.fqn] = model
370370

@@ -404,13 +404,15 @@ def _load_requirements(self) -> t.Tuple[t.Dict[str, str], t.Set[str]]:
404404
args = [k.strip() for k in line.split("==")]
405405
if len(args) != 2:
406406
raise ConfigError(
407-
f"Invalid lock file entry '{line.strip()}'. Only 'dep==ver' is supported", requirements_path
407+
f"Invalid lock file entry '{line.strip()}'. Only 'dep==ver' is supported",
408+
requirements_path,
408409
)
409410
dep, ver = args
410411
other_ver = requirements.get(dep, ver)
411412
if ver != other_ver:
412413
raise ConfigError(
413-
f"Conflicting requirement {dep}: {ver} != {other_ver}. Fix your {c.REQUIREMENTS} file.", requirements_path
414+
f"Conflicting requirement {dep}: {ver} != {other_ver}. Fix your {c.REQUIREMENTS} file.",
415+
requirements_path,
414416
)
415417
requirements[dep] = ver
416418

@@ -622,7 +624,7 @@ def _load_sql_models(
622624
self._failed_to_load_model_error(
623625
path, f"Duplicate SQL model name: '{model.name}'."
624626
),
625-
path
627+
path,
626628
)
627629
elif model.enabled:
628630
model._path = path
@@ -785,7 +787,9 @@ def _load_metrics(self) -> UniqueKeyDict[str, MetricMeta]:
785787
metric = load_metric_ddl(expression, path=path, dialect=dialect)
786788
metrics[metric.name] = metric
787789
except SqlglotError as ex:
788-
raise ConfigError(f"Failed to parse metric definitions at '{path}': {ex}.", path)
790+
raise ConfigError(
791+
f"Failed to parse metric definitions at '{path}': {ex}.", path
792+
)
789793

790794
return metrics
791795

sqlmesh/core/model/common.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@
1313
from sqlmesh.core import dialect as d
1414
from sqlmesh.core.macros import MacroRegistry, MacroStrTemplate
1515
from sqlmesh.utils import str_to_bool
16-
from sqlmesh.utils.errors import ConfigError, SQLMeshError, raise_config_error
16+
from sqlmesh.utils.errors import (
17+
ConfigError,
18+
SQLMeshError,
19+
raise_config_error,
20+
ModelBlockFieldValidationMissingFieldsError,
21+
ModeBlockExtraFields,
22+
)
1723
from sqlmesh.utils.metaprogramming import (
1824
Executable,
1925
SqlValue,
@@ -269,34 +275,28 @@ def validate_extra_and_required_fields(
269275
) -> None:
270276
missing_required_fields = klass.missing_required_fields(provided_fields)
271277
if missing_required_fields:
272-
field_names = "'" + "', '".join(missing_required_fields) + "'"
273-
raise_config_error(
274-
f"Please add required field{'s' if len(missing_required_fields) > 1 else ''} {field_names} to the {entity_name}."
275-
)
278+
if path is None:
279+
raise_config_error(
280+
raise ModelBlockFieldValidationMissingFieldsError(path, missing_required_fields)
276281

277282
extra_fields = klass.extra_fields(provided_fields)
278283
if extra_fields:
279284
extra_field_names = "'" + "', '".join(extra_fields) + "'"
280285

281286
all_fields = klass.all_fields()
282-
close_matches = {}
287+
extra_with_close_match: t.Dict[str, t.Optional[str]] = {}
283288
for field in extra_fields:
284289
matches = get_close_matches(field, all_fields, n=1)
285290
if matches:
286-
close_matches[field] = matches[0]
287-
288-
if len(close_matches) == 1:
289-
similar_msg = ". Did you mean " + "'" + "', '".join(close_matches.values()) + "'?"
290-
else:
291-
similar = [
292-
f"- {field}: Did you mean '{match}'?" for field, match in close_matches.items()
293-
]
294-
similar_msg = "\n\n " + "\n ".join(similar) if similar else ""
295-
296-
raise_config_error(
297-
f"Invalid field name{'s' if len(extra_fields) > 1 else ''} present in the {entity_name}: {extra_field_names}{similar_msg}",
298-
path,
299-
)
291+
extra_with_close_match[field] = matches[0]
292+
else:
293+
extra_with_close_match[field] = None
294+
295+
if extra_with_close_match:
296+
raise ModeBlockExtraFields(
297+
path,
298+
extra_fields=extra_with_close_match,
299+
)
300300

301301

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

sqlmesh/core/model/kind.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,8 +1015,8 @@ def create_model_kind(v: t.Any, dialect: str, defaults: t.Dict[str, t.Any]) -> M
10151015
"The 'materialization' property is required for models of the CUSTOM kind"
10161016
)
10171017

1018-
# The below call will print a warning if a materialization with the given name doesn't exist
1019-
# we dont want to throw an error here because we still want Models with a CustomKind to be able
1018+
# The below call prints a warning if no materialization with the given name doesn't exist.
1019+
# We don't throw an error here because we still want Models with a CustomKind to be able
10201020
# to be serialized / deserialized in contexts where the custom materialization class may not be available,
10211021
# such as in HTTP request handlers
10221022
custom_materialization = get_custom_materialization_type(

sqlmesh/lsp/main.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,9 @@ def initialize(ls: LanguageServer, params: types.InitializeParams) -> None:
324324
ls.show_message("Client supports pull diagnostics", types.MessageType.Info)
325325
else:
326326
self.client_supports_pull_diagnostics = False
327-
ls.show_message("Client does not support pull diagnostics", types.MessageType.Info)
327+
ls.show_message(
328+
"Client does not support pull diagnostics", types.MessageType.Info
329+
)
328330
else:
329331
self.client_supports_pull_diagnostics = False
330332
ls.show_message("Client capabilities not available", types.MessageType.Info)
@@ -357,7 +359,7 @@ def did_open(ls: LanguageServer, params: types.DidOpenTextDocumentParams) -> Non
357359
uri = URI(params.text_document.uri)
358360
try:
359361
context = self._context_get_or_load(ls, uri)
360-
362+
361363
# Only publish diagnostics if client doesn't support pull diagnostics
362364
if not self.client_supports_pull_diagnostics:
363365
diagnostics = context.lint_model(uri)
@@ -895,7 +897,9 @@ def _create_lsp_context(
895897
except Exception as e:
896898
# Only show the error message once
897899
if not self.has_raised_loading_error:
898-
location_info = f" at {e.location}" if isinstance(e, ConfigError) and e.location else ""
900+
location_info = (
901+
f" at {e.location}" if isinstance(e, ConfigError) and e.location else ""
902+
)
899903
self.server.show_message(
900904
f"Error creating context error type {type(e)}: {e}{location_info}",
901905
types.MessageType.Error,
@@ -904,7 +908,9 @@ def _create_lsp_context(
904908
error_message = e if isinstance(e, ConfigError) else str(e)
905909
if isinstance(e, ConfigError) and e.location is not None:
906910
uri = URI.from_path(e.location)
907-
ls.show_message(f"Publishing diagnostic to URI: {uri.value}", types.MessageType.Info)
911+
ls.show_message(
912+
f"Publishing diagnostic to URI: {uri.value}", types.MessageType.Info
913+
)
908914
ls.publish_diagnostics(
909915
uri.value,
910916
[

sqlmesh/utils/errors.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,49 @@ def __init__(self, message: str | Exception, location: t.Optional[Path] = None)
3232
self.location = Path(location) if isinstance(location, str) else location
3333

3434

35+
class ModelBlockFieldValidationMissingFieldsError(ConfigError):
36+
"""Raised when required fields are missing from a model block."""
37+
38+
missing_fields: t.Set[str]
39+
40+
def __init__(self, path: Path, missing_fields: t.Set[str]) -> None:
41+
super().__init__(
42+
self.message(missing_fields),
43+
path,
44+
)
45+
self.missing_fields = missing_fields
46+
47+
@staticmethod
48+
def message(missing_fields: t.Set[str]) -> str:
49+
field_names = "'" + "', '".join(missing_fields) + "'"
50+
return f"Please add required field{'s' if len(missing_fields) > 1 else ''} {field_names} to the model block."
51+
52+
53+
class ModeBlockExtraFields(ConfigError):
54+
"""Raised when there are extra fields in a model block that are not defined in the model schema. If there are close
55+
matches, this tries to recommend them"""
56+
57+
def __init__(self, path: Path, extra_fields: t.Dict[str, t.Optional[None]]) -> None:
58+
super().__init__(
59+
self.message(extra_fields),
60+
path,
61+
)
62+
self.extra_fields = extra_fields
63+
64+
@staticmethod
65+
def message(extra_fields: t.Dict[str, t.Optional[None]]) -> str:
66+
if len(extra_with_close_match) == 1:
67+
similar_msg = (
68+
". Did you mean " + "'" + "', '".join(extra_with_close_match.values()) + "'?"
69+
)
70+
else:
71+
similar = [
72+
f"- {field}: Did you mean '{match}'?" for field, match in close_matches.items()
73+
]
74+
similar_msg = "\n\n " + "\n ".join(similar) if similar else ""
75+
return f"Invalid field name{'s' if len(extra_fields) > 1 else ''} present in the {entity_name}: {extra_field_names}{similar_msg}"
76+
77+
3578
class MissingDependencyError(SQLMeshError):
3679
"""Local environment is missing a required dependency for the given operation"""
3780

0 commit comments

Comments
 (0)