sqlmesh 0.205.1.dev7__py3-none-any.whl → 0.205.1.dev8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sqlmesh/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.205.1.dev7'
21
- __version_tuple__ = version_tuple = (0, 205, 1, 'dev7')
20
+ __version__ = version = '0.205.1.dev8'
21
+ __version_tuple__ = version_tuple = (0, 205, 1, 'dev8')
@@ -84,19 +84,8 @@ class TokenPositionDetails(PydanticModel):
84
84
  )
85
85
 
86
86
 
87
- def read_range_from_file(file: Path, text_range: Range) -> str:
88
- """
89
- Read the file and return the content within the specified range.
90
-
91
- Args:
92
- file: Path to the file to read
93
- text_range: The range of text to extract
94
-
95
- Returns:
96
- The content within the specified range
97
- """
98
- with file.open("r", encoding="utf-8") as f:
99
- lines = f.readlines()
87
+ def read_range_from_string(content: str, text_range: Range) -> str:
88
+ lines = content.splitlines(keepends=False)
100
89
 
101
90
  # Ensure the range is within bounds
102
91
  start_line = max(0, text_range.start.line)
@@ -116,6 +105,23 @@ def read_range_from_file(file: Path, text_range: Range) -> str:
116
105
  return "".join(result)
117
106
 
118
107
 
108
+ def read_range_from_file(file: Path, text_range: Range) -> str:
109
+ """
110
+ Read the file and return the content within the specified range.
111
+
112
+ Args:
113
+ file: Path to the file to read
114
+ text_range: The range of text to extract
115
+
116
+ Returns:
117
+ The content within the specified range
118
+ """
119
+ with file.open("r", encoding="utf-8") as f:
120
+ lines = f.readlines()
121
+
122
+ return read_range_from_string("".join(lines), text_range)
123
+
124
+
119
125
  def get_range_of_model_block(
120
126
  sql: str,
121
127
  dialect: str,
@@ -7,10 +7,16 @@ import typing as t
7
7
  from sqlglot.expressions import Star
8
8
  from sqlglot.helper import subclasses
9
9
 
10
- from sqlmesh.core.linter.helpers import TokenPositionDetails, get_range_of_model_block
10
+ from sqlmesh.core.dialect import normalize_model_name
11
+ from sqlmesh.core.linter.helpers import (
12
+ TokenPositionDetails,
13
+ get_range_of_model_block,
14
+ read_range_from_string,
15
+ )
11
16
  from sqlmesh.core.linter.rule import Rule, RuleViolation, Range, Fix, TextEdit
12
17
  from sqlmesh.core.linter.definition import RuleSet
13
18
  from sqlmesh.core.model import Model, SqlModel, ExternalModel
19
+ from sqlmesh.utils.lineage import extract_references_from_query, ExternalModelReference
14
20
 
15
21
 
16
22
  class NoSelectStar(Rule):
@@ -113,7 +119,9 @@ class NoMissingAudits(Rule):
113
119
  class NoMissingExternalModels(Rule):
114
120
  """All external models must be registered in the external_models.yaml file"""
115
121
 
116
- def check_model(self, model: Model) -> t.Optional[RuleViolation]:
122
+ def check_model(
123
+ self, model: Model
124
+ ) -> t.Optional[t.Union[RuleViolation, t.List[RuleViolation]]]:
117
125
  # Ignore external models themselves, because either they are registered,
118
126
  # and if they are not, they will be caught as referenced in another model.
119
127
  if isinstance(model, ExternalModel):
@@ -129,10 +137,74 @@ class NoMissingExternalModels(Rule):
129
137
  if not not_registered_external_models:
130
138
  return None
131
139
 
140
+ # If the model is anything other than a sql model that and has a path
141
+ # that ends with .sql, we cannot extract the references from the query.
142
+ path = model._path
143
+ if not isinstance(model, SqlModel) or not path or not str(path).endswith(".sql"):
144
+ return self._standard_error_message(
145
+ model_name=model.fqn,
146
+ external_models=not_registered_external_models,
147
+ )
148
+
149
+ with open(path, "r", encoding="utf-8") as file:
150
+ read_file = file.read()
151
+ split_read_file = read_file.splitlines()
152
+
153
+ # If there are any unregistered external models, return a violation find
154
+ # the ranges for them.
155
+ references = extract_references_from_query(
156
+ query=model.query,
157
+ context=self.context,
158
+ document_path=path,
159
+ read_file=split_read_file,
160
+ depends_on=model.depends_on,
161
+ dialect=model.dialect,
162
+ )
163
+ external_references = {
164
+ normalize_model_name(
165
+ table=read_range_from_string(read_file, ref.range),
166
+ default_catalog=model.default_catalog,
167
+ dialect=model.dialect,
168
+ ): ref
169
+ for ref in references
170
+ if isinstance(ref, ExternalModelReference) and ref.path is None
171
+ }
172
+
173
+ # Ensure that depends_on and external references match.
174
+ if not_registered_external_models != set(external_references.keys()):
175
+ return self._standard_error_message(
176
+ model_name=model.fqn,
177
+ external_models=not_registered_external_models,
178
+ )
179
+
180
+ # Return a violation for each unregistered external model with its range.
181
+ violations = []
182
+ for ref_name, ref in external_references.items():
183
+ if ref_name in not_registered_external_models:
184
+ violations.append(
185
+ RuleViolation(
186
+ rule=self,
187
+ violation_msg=f"Model '{model.fqn}' depends on unregistered external model '{ref_name}'. "
188
+ "Please register it in the external models file. This can be done by running 'sqlmesh create_external_models'.",
189
+ violation_range=ref.range,
190
+ )
191
+ )
192
+
193
+ if len(violations) < len(not_registered_external_models):
194
+ return self._standard_error_message(
195
+ model_name=model.fqn,
196
+ external_models=not_registered_external_models,
197
+ )
198
+
199
+ return violations
200
+
201
+ def _standard_error_message(
202
+ self, model_name: str, external_models: t.Set[str]
203
+ ) -> RuleViolation:
132
204
  return RuleViolation(
133
205
  rule=self,
134
- violation_msg=f"Model '{model.name}' depends on unregistered external models: "
135
- f"{', '.join(m for m in not_registered_external_models)}. "
206
+ violation_msg=f"Model '{model_name}' depends on unregistered external models: "
207
+ f"{', '.join(m for m in external_models)}. "
136
208
  "Please register them in the external models file. This can be done by running 'sqlmesh create_external_models'.",
137
209
  )
138
210
 
@@ -8,8 +8,8 @@ from sqlmesh.lsp.custom import (
8
8
  from sqlmesh import macro
9
9
  import typing as t
10
10
  from sqlmesh.lsp.context import AuditTarget, LSPContext, ModelTarget
11
- from sqlmesh.lsp.description import generate_markdown_description
12
11
  from sqlmesh.lsp.uri import URI
12
+ from sqlmesh.utils.lineage import generate_markdown_description
13
13
 
14
14
 
15
15
  def get_sql_completions(
sqlmesh/lsp/main.py CHANGED
@@ -23,6 +23,10 @@ from sqlmesh.lsp.api import (
23
23
  ApiResponseGetLineage,
24
24
  ApiResponseGetModels,
25
25
  )
26
+
27
+ # Define the command constant
28
+ EXTERNAL_MODEL_UPDATE_COLUMNS = "sqlmesh.external_model_update_columns"
29
+
26
30
  from sqlmesh.lsp.completions import get_sql_completions
27
31
  from sqlmesh.lsp.context import (
28
32
  LSPContext,
@@ -60,15 +64,15 @@ from sqlmesh.lsp.errors import ContextFailedError, context_error_to_diagnostic
60
64
  from sqlmesh.lsp.helpers import to_lsp_range, to_sqlmesh_position
61
65
  from sqlmesh.lsp.hints import get_hints
62
66
  from sqlmesh.lsp.reference import (
63
- LSPCteReference,
64
- LSPModelReference,
65
- LSPExternalModelReference,
67
+ CTEReference,
68
+ ModelReference,
66
69
  get_references,
67
70
  get_all_references,
68
71
  )
69
72
  from sqlmesh.lsp.rename import prepare_rename, rename_symbol, get_document_highlights
70
73
  from sqlmesh.lsp.uri import URI
71
74
  from sqlmesh.utils.errors import ConfigError
75
+ from sqlmesh.utils.lineage import ExternalModelReference
72
76
  from web.server.api.endpoints.lineage import column_lineage, model_lineage
73
77
  from web.server.api.endpoints.models import get_models
74
78
  from typing import Union
@@ -479,7 +483,7 @@ class SQLMeshLanguageServer:
479
483
  if not references:
480
484
  return None
481
485
  reference = references[0]
482
- if isinstance(reference, LSPCteReference) or not reference.markdown_description:
486
+ if isinstance(reference, CTEReference) or not reference.markdown_description:
483
487
  return None
484
488
  return types.Hover(
485
489
  contents=types.MarkupContent(
@@ -525,7 +529,7 @@ class SQLMeshLanguageServer:
525
529
  location_links = []
526
530
  for reference in references:
527
531
  # Use target_range if available (CTEs, Macros, and external models in YAML)
528
- if isinstance(reference, LSPModelReference):
532
+ if isinstance(reference, ModelReference):
529
533
  # Regular SQL models - default to start of file
530
534
  target_range = types.Range(
531
535
  start=types.Position(line=0, character=0),
@@ -535,7 +539,7 @@ class SQLMeshLanguageServer:
535
539
  start=types.Position(line=0, character=0),
536
540
  end=types.Position(line=0, character=0),
537
541
  )
538
- elif isinstance(reference, LSPExternalModelReference):
542
+ elif isinstance(reference, ExternalModelReference):
539
543
  # External models may have target_range set for YAML files
540
544
  target_range = types.Range(
541
545
  start=types.Position(line=0, character=0),