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 +2 -2
- sqlmesh/core/linter/helpers.py +19 -13
- sqlmesh/core/linter/rules/builtin.py +76 -4
- sqlmesh/lsp/completions.py +1 -1
- sqlmesh/lsp/main.py +10 -6
- sqlmesh/lsp/reference.py +42 -376
- sqlmesh/lsp/rename.py +2 -2
- sqlmesh/utils/lineage.py +404 -0
- {sqlmesh-0.205.1.dev7.dist-info → sqlmesh-0.205.1.dev8.dist-info}/METADATA +1 -1
- {sqlmesh-0.205.1.dev7.dist-info → sqlmesh-0.205.1.dev8.dist-info}/RECORD +14 -14
- sqlmesh/lsp/description.py +0 -29
- {sqlmesh-0.205.1.dev7.dist-info → sqlmesh-0.205.1.dev8.dist-info}/WHEEL +0 -0
- {sqlmesh-0.205.1.dev7.dist-info → sqlmesh-0.205.1.dev8.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.205.1.dev7.dist-info → sqlmesh-0.205.1.dev8.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.205.1.dev7.dist-info → sqlmesh-0.205.1.dev8.dist-info}/top_level.txt +0 -0
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.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 205, 1, '
|
|
20
|
+
__version__ = version = '0.205.1.dev8'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 205, 1, 'dev8')
|
sqlmesh/core/linter/helpers.py
CHANGED
|
@@ -84,19 +84,8 @@ class TokenPositionDetails(PydanticModel):
|
|
|
84
84
|
)
|
|
85
85
|
|
|
86
86
|
|
|
87
|
-
def
|
|
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.
|
|
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(
|
|
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 '{
|
|
135
|
-
f"{', '.join(m for m in
|
|
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
|
|
sqlmesh/lsp/completions.py
CHANGED
|
@@ -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
|
-
|
|
64
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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),
|