sqlmesh 0.187.6.dev5__py3-none-any.whl → 0.187.6.dev10__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.
Files changed (57) hide show
  1. sqlmesh/_version.py +2 -2
  2. sqlmesh/core/context.py +8 -5
  3. sqlmesh/core/selector.py +1 -1
  4. sqlmesh/lsp/main.py +178 -33
  5. sqlmesh/lsp/reference.py +194 -3
  6. {sqlmesh-0.187.6.dev5.dist-info → sqlmesh-0.187.6.dev10.dist-info}/METADATA +1 -1
  7. {sqlmesh-0.187.6.dev5.dist-info → sqlmesh-0.187.6.dev10.dist-info}/RECORD +56 -56
  8. web/client/dist/assets/{Audits-BPlxzqeG.js → Audits-C6gUfP0Y.js} +1 -1
  9. web/client/dist/assets/{Banner-BnIDM-p-.js → Banner-_C9c6nFf.js} +1 -1
  10. web/client/dist/assets/{ChevronDownIcon-v5uzV5KS.js → ChevronDownIcon-RfKS8PKd.js} +1 -1
  11. web/client/dist/assets/{ChevronRightIcon-lNBkq9fQ.js → ChevronRightIcon-CTOwDuap.js} +1 -1
  12. web/client/dist/assets/{Content-wP7OzMQ9.js → Content-Dxw_m-3F.js} +1 -1
  13. web/client/dist/assets/{Content-Dvky48vX.js → Content-Uj95lQQ-.js} +1 -1
  14. web/client/dist/assets/{Data-D2tj5W3z.js → Data-CH4v_k47.js} +1 -1
  15. web/client/dist/assets/{DataCatalog-H5ANpnSz.js → DataCatalog-CZgWBSbO.js} +1 -1
  16. web/client/dist/assets/{Editor-Dq1m6nff.js → Editor-CPGKdIv3.js} +1 -1
  17. web/client/dist/assets/{Editor-BpeAbLta.js → Editor-DDzPd4K5.js} +1 -1
  18. web/client/dist/assets/{Errors-BDlLsOz8.js → Errors-Bd_lCqWq.js} +1 -1
  19. web/client/dist/assets/{FileExplorer-BLOsvw4r.js → FileExplorer-FTqyN550.js} +1 -1
  20. web/client/dist/assets/{Footer-B4UOL2Zx.js → Footer-By8eV-Xw.js} +1 -1
  21. web/client/dist/assets/{Header-KC58-CQw.js → Header-CWgMNmyS.js} +1 -1
  22. web/client/dist/assets/{Input-DQ3H5Jf5.js → Input-tTLAeFk_.js} +1 -1
  23. web/client/dist/assets/Lineage-B24dFVy0.js +1 -0
  24. web/client/dist/assets/{ListboxShow-Bp0ARqZ0.js → ListboxShow-BnVk8Pao.js} +1 -1
  25. web/client/dist/assets/{ModelLineage-Du6fUL20.js → ModelLineage-CfoiOAVb.js} +1 -1
  26. web/client/dist/assets/{Models-BqQSh5xi.js → Models-C0WhiYIC.js} +1 -1
  27. web/client/dist/assets/{Page-C9v-mVEC.js → Page-LHbK1yD_.js} +1 -1
  28. web/client/dist/assets/{Plan-zagXv_gS.js → Plan-MwkSS6xa.js} +1 -1
  29. web/client/dist/assets/{PlusCircleIcon-DgugPkXr.js → PlusCircleIcon-CPdy9bpf.js} +1 -1
  30. web/client/dist/assets/{ReportErrors-ClY5ezEU.js → ReportErrors-9LAVRQKL.js} +1 -1
  31. web/client/dist/assets/{Root-BnlPjbrk.js → Root-CpkGXO_A.js} +1 -1
  32. web/client/dist/assets/{SearchList-B1ophTod.js → SearchList-DCuxxaPn.js} +1 -1
  33. web/client/dist/assets/{SelectEnvironment-CheqF0wg.js → SelectEnvironment-CltENlzE.js} +1 -1
  34. web/client/dist/assets/{SourceList-C_z4JbJU.js → SourceList-u_0soueL.js} +1 -1
  35. web/client/dist/assets/{SourceListItem-Be4vNQZL.js → SourceListItem-CHxTMFnQ.js} +1 -1
  36. web/client/dist/assets/{SplitPane-C8CPNQrK.js → SplitPane-DO1JEVxf.js} +1 -1
  37. web/client/dist/assets/{Tests-DuGqAE7Q.js → Tests-D_-8MKq9.js} +1 -1
  38. web/client/dist/assets/{Welcome-BovgrYsq.js → Welcome-DTulZMrl.js} +1 -1
  39. web/client/dist/assets/{context-BJbxiwu1.js → context-B7_ioojx.js} +1 -1
  40. web/client/dist/assets/{context-C7kvMRgQ.js → context-ByRTRbGV.js} +30 -30
  41. web/client/dist/assets/{editor-C3xrJUHo.js → editor-IxXTso9p.js} +1 -1
  42. web/client/dist/assets/{file-Cn8UMGdB.js → file-CtGXXxxH.js} +1 -1
  43. web/client/dist/assets/{floating-ui.react-dom-njuatPE_.js → floating-ui.react-dom-BqmCcnHz.js} +1 -1
  44. web/client/dist/assets/{help-B9pKFCi-.js → help-DZgsqPrY.js} +1 -1
  45. web/client/dist/assets/{index-D0B238GC.js → index-BOoFTMqX.js} +8 -8
  46. web/client/dist/assets/{index-0HTVGXbP.js → index-D-NV5rsk.js} +1 -1
  47. web/client/dist/assets/{plan-BT_YMvAu.js → plan-BITq8bOv.js} +1 -1
  48. web/client/dist/assets/{popover-zR5tE2ci.js → popover-CThXyzI8.js} +1 -1
  49. web/client/dist/assets/{project-8LRoNrpG.js → project-D9WffyNb.js} +1 -1
  50. web/client/dist/index.html +1 -1
  51. web/server/api/endpoints/files.py +1 -1
  52. web/server/utils.py +1 -2
  53. web/client/dist/assets/Lineage-COIwTlx7.js +0 -1
  54. {sqlmesh-0.187.6.dev5.dist-info → sqlmesh-0.187.6.dev10.dist-info}/WHEEL +0 -0
  55. {sqlmesh-0.187.6.dev5.dist-info → sqlmesh-0.187.6.dev10.dist-info}/entry_points.txt +0 -0
  56. {sqlmesh-0.187.6.dev5.dist-info → sqlmesh-0.187.6.dev10.dist-info}/licenses/LICENSE +0 -0
  57. {sqlmesh-0.187.6.dev5.dist-info → sqlmesh-0.187.6.dev10.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.187.6.dev5'
21
- __version_tuple__ = version_tuple = (0, 187, 6, 'dev5')
20
+ __version__ = version = '0.187.6.dev10'
21
+ __version_tuple__ = version_tuple = (0, 187, 6, 'dev10')
sqlmesh/core/context.py CHANGED
@@ -932,19 +932,22 @@ class GenericContext(BaseContext, t.Generic[C]):
932
932
 
933
933
  return snapshot
934
934
 
935
- def config_for_path(self, path: Path) -> Config:
935
+ def config_for_path(self, path: Path) -> t.Tuple[Config, Path]:
936
+ """Returns the config and path of the said project for a given file path."""
936
937
  for config_path, config in self.configs.items():
937
938
  try:
938
939
  path.relative_to(config_path)
939
- return config
940
+ return config, config_path
940
941
  except ValueError:
941
942
  pass
942
- return self.config
943
+ return self.config, self.path
943
944
 
944
945
  def config_for_node(self, node: str | Model | Audit) -> Config:
945
946
  if isinstance(node, str):
946
- return self.config_for_path(self.get_snapshot(node, raise_if_missing=True).node._path) # type: ignore
947
- return self.config_for_path(node._path) # type: ignore
947
+ return self.config_for_path(self.get_snapshot(node, raise_if_missing=True).node._path)[
948
+ 0
949
+ ] # type: ignore
950
+ return self.config_for_path(node._path)[0] # type: ignore
948
951
 
949
952
  @property
950
953
  def models(self) -> MappingProxyType[str, Model]:
sqlmesh/core/selector.py CHANGED
@@ -93,7 +93,7 @@ class Selector:
93
93
  }
94
94
 
95
95
  all_selected_models = self.expand_model_selections(
96
- model_selections, models={**self._models, **env_models}
96
+ model_selections, models={**env_models, **self._models}
97
97
  )
98
98
 
99
99
  dag: DAG[str] = DAG()
sqlmesh/lsp/main.py CHANGED
@@ -57,8 +57,12 @@ class SQLMeshLanguageServer:
57
57
  self.server = LanguageServer(server_name, version)
58
58
  self.context_class = context_class
59
59
  self.lsp_context: t.Optional[LSPContext] = None
60
- self.lint_cache: t.Dict[URI, t.List[AnnotatedRuleViolation]] = {}
61
60
 
61
+ # Cache stores tuples of (diagnostics, diagnostic_version)
62
+ self.lint_cache: t.Dict[URI, t.Tuple[t.List[AnnotatedRuleViolation], int]] = {}
63
+ self._diagnostic_version_counter: int = 0
64
+
65
+ self.client_supports_pull_diagnostics = False
62
66
  # Register LSP features (e.g., formatting, hover, etc.)
63
67
  self._register_features()
64
68
 
@@ -69,6 +73,18 @@ class SQLMeshLanguageServer:
69
73
  def initialize(ls: LanguageServer, params: types.InitializeParams) -> None:
70
74
  """Initialize the server when the client connects."""
71
75
  try:
76
+ # Check if client supports pull diagnostics
77
+ if params.capabilities and params.capabilities.text_document:
78
+ diagnostics = getattr(params.capabilities.text_document, "diagnostic", None)
79
+ if diagnostics:
80
+ self.client_supports_pull_diagnostics = True
81
+ ls.log_trace("Client supports pull diagnostics")
82
+ else:
83
+ self.client_supports_pull_diagnostics = False
84
+ ls.log_trace("Client does not support pull diagnostics")
85
+ else:
86
+ self.client_supports_pull_diagnostics = False
87
+
72
88
  if params.workspace_folders:
73
89
  # Try to find a SQLMesh config file in any workspace folder (only at the root level)
74
90
  for folder in params.workspace_folders:
@@ -153,61 +169,71 @@ class SQLMeshLanguageServer:
153
169
  def did_open(ls: LanguageServer, params: types.DidOpenTextDocumentParams) -> None:
154
170
  uri = URI(params.text_document.uri)
155
171
  context = self._context_get_or_load(uri)
156
- if self.lint_cache.get(uri) is not None:
172
+ models = context.map[uri.to_path()]
173
+ if models is None or not isinstance(models, ModelTarget):
174
+ return
175
+
176
+ if self.lint_cache.get(uri) is None:
177
+ diagnostics = context.context.lint_models(
178
+ models.names,
179
+ raise_on_error=False,
180
+ )
181
+ self._diagnostic_version_counter += 1
182
+ self.lint_cache[uri] = (diagnostics, self._diagnostic_version_counter)
183
+
184
+ # Only publish diagnostics if client doesn't support pull diagnostics
185
+ if not self.client_supports_pull_diagnostics:
186
+ diagnostics, _ = self.lint_cache[uri]
157
187
  ls.publish_diagnostics(
158
188
  params.text_document.uri,
159
- SQLMeshLanguageServer._diagnostics_to_lsp_diagnostics(self.lint_cache[uri]),
189
+ SQLMeshLanguageServer._diagnostics_to_lsp_diagnostics(diagnostics),
160
190
  )
161
- return
162
- models = context.map[uri.to_path()]
163
- if models is None:
164
- return
165
- if not isinstance(models, ModelTarget):
166
- return
167
- self.lint_cache[uri] = context.context.lint_models(
168
- models.names,
169
- raise_on_error=False,
170
- )
171
- ls.publish_diagnostics(
172
- params.text_document.uri,
173
- SQLMeshLanguageServer._diagnostics_to_lsp_diagnostics(self.lint_cache[uri]),
174
- )
175
191
 
176
192
  @self.server.feature(types.TEXT_DOCUMENT_DID_CHANGE)
177
193
  def did_change(ls: LanguageServer, params: types.DidChangeTextDocumentParams) -> None:
178
194
  uri = URI(params.text_document.uri)
179
195
  context = self._context_get_or_load(uri)
180
196
  models = context.map[uri.to_path()]
181
- if models is None:
197
+ if models is None or not isinstance(models, ModelTarget):
182
198
  return
183
- if not isinstance(models, ModelTarget):
184
- return
185
- self.lint_cache[uri] = context.context.lint_models(
199
+
200
+ # Always update the cache
201
+ diagnostics = context.context.lint_models(
186
202
  models.names,
187
203
  raise_on_error=False,
188
204
  )
189
- ls.publish_diagnostics(
190
- params.text_document.uri,
191
- SQLMeshLanguageServer._diagnostics_to_lsp_diagnostics(self.lint_cache[uri]),
192
- )
205
+ self._diagnostic_version_counter += 1
206
+ self.lint_cache[uri] = (diagnostics, self._diagnostic_version_counter)
207
+
208
+ # Only publish diagnostics if client doesn't support pull diagnostics
209
+ if not self.client_supports_pull_diagnostics:
210
+ ls.publish_diagnostics(
211
+ params.text_document.uri,
212
+ SQLMeshLanguageServer._diagnostics_to_lsp_diagnostics(diagnostics),
213
+ )
193
214
 
194
215
  @self.server.feature(types.TEXT_DOCUMENT_DID_SAVE)
195
216
  def did_save(ls: LanguageServer, params: types.DidSaveTextDocumentParams) -> None:
196
217
  uri = URI(params.text_document.uri)
197
218
  context = self._context_get_or_load(uri)
198
219
  models = context.map[uri.to_path()]
199
- if models is None:
200
- return
201
- if not isinstance(models, ModelTarget):
220
+ if models is None or not isinstance(models, ModelTarget):
202
221
  return
203
- self.lint_cache[uri] = context.context.lint_models(
222
+
223
+ # Always update the cache
224
+ diagnostics = context.context.lint_models(
204
225
  models.names,
205
226
  raise_on_error=False,
206
227
  )
207
- ls.publish_diagnostics(
208
- params.text_document.uri,
209
- SQLMeshLanguageServer._diagnostics_to_lsp_diagnostics(self.lint_cache[uri]),
210
- )
228
+ self._diagnostic_version_counter += 1
229
+ self.lint_cache[uri] = (diagnostics, self._diagnostic_version_counter)
230
+
231
+ # Only publish diagnostics if client doesn't support pull diagnostics
232
+ if not self.client_supports_pull_diagnostics:
233
+ ls.publish_diagnostics(
234
+ params.text_document.uri,
235
+ SQLMeshLanguageServer._diagnostics_to_lsp_diagnostics(diagnostics),
236
+ )
211
237
 
212
238
  @self.server.feature(types.TEXT_DOCUMENT_FORMATTING)
213
239
  def formatting(
@@ -327,6 +353,125 @@ class SQLMeshLanguageServer:
327
353
  ls.show_message(f"Error getting references: {e}", types.MessageType.Error)
328
354
  return []
329
355
 
356
+ @self.server.feature(types.TEXT_DOCUMENT_DIAGNOSTIC)
357
+ def diagnostic(
358
+ ls: LanguageServer, params: types.DocumentDiagnosticParams
359
+ ) -> types.DocumentDiagnosticReport:
360
+ """Handle diagnostic pull requests from the client."""
361
+ try:
362
+ uri = URI(params.text_document.uri)
363
+ diagnostics, result_id = self._get_diagnostics_for_uri(uri)
364
+
365
+ # Check if client provided a previous result ID
366
+ if hasattr(params, "previous_result_id") and params.previous_result_id == result_id:
367
+ # Return unchanged report if diagnostics haven't changed
368
+ return types.RelatedUnchangedDocumentDiagnosticReport(
369
+ kind=types.DocumentDiagnosticReportKind.Unchanged,
370
+ result_id=str(result_id),
371
+ )
372
+
373
+ return types.RelatedFullDocumentDiagnosticReport(
374
+ kind=types.DocumentDiagnosticReportKind.Full,
375
+ items=diagnostics,
376
+ result_id=str(result_id),
377
+ )
378
+ except Exception as e:
379
+ ls.show_message(f"Error getting diagnostics: {e}", types.MessageType.Error)
380
+ return types.RelatedFullDocumentDiagnosticReport(
381
+ kind=types.DocumentDiagnosticReportKind.Full,
382
+ items=[],
383
+ )
384
+
385
+ @self.server.feature(types.WORKSPACE_DIAGNOSTIC)
386
+ def workspace_diagnostic(
387
+ ls: LanguageServer, params: types.WorkspaceDiagnosticParams
388
+ ) -> types.WorkspaceDiagnosticReport:
389
+ """Handle workspace-wide diagnostic pull requests from the client."""
390
+ try:
391
+ if self.lsp_context is None:
392
+ current_path = Path.cwd()
393
+ self._ensure_context_in_folder(current_path)
394
+
395
+ if self.lsp_context is None:
396
+ return types.WorkspaceDiagnosticReport(items=[])
397
+
398
+ items: t.List[
399
+ t.Union[
400
+ types.WorkspaceFullDocumentDiagnosticReport,
401
+ types.WorkspaceUnchangedDocumentDiagnosticReport,
402
+ ]
403
+ ] = []
404
+
405
+ # Get all SQL and Python model files from the context
406
+ for path, target in self.lsp_context.map.items():
407
+ if isinstance(target, ModelTarget):
408
+ uri = URI.from_path(path)
409
+ diagnostics, result_id = self._get_diagnostics_for_uri(uri)
410
+
411
+ # Check if we have a previous result ID for this file
412
+ previous_result_id = None
413
+ if hasattr(params, "previous_result_ids") and params.previous_result_ids:
414
+ for prev in params.previous_result_ids:
415
+ if prev.uri == uri.value:
416
+ previous_result_id = prev.value
417
+ break
418
+
419
+ if previous_result_id and previous_result_id == result_id:
420
+ # File hasn't changed
421
+ items.append(
422
+ types.WorkspaceUnchangedDocumentDiagnosticReport(
423
+ kind=types.DocumentDiagnosticReportKind.Unchanged,
424
+ result_id=str(result_id),
425
+ uri=uri.value,
426
+ )
427
+ )
428
+ else:
429
+ # File has changed or is new
430
+ items.append(
431
+ types.WorkspaceFullDocumentDiagnosticReport(
432
+ kind=types.DocumentDiagnosticReportKind.Full,
433
+ result_id=str(result_id),
434
+ uri=uri.value,
435
+ items=diagnostics,
436
+ )
437
+ )
438
+
439
+ return types.WorkspaceDiagnosticReport(items=items)
440
+
441
+ except Exception as e:
442
+ ls.show_message(
443
+ f"Error getting workspace diagnostics: {e}", types.MessageType.Error
444
+ )
445
+ return types.WorkspaceDiagnosticReport(items=[])
446
+
447
+ def _get_diagnostics_for_uri(self, uri: URI) -> t.Tuple[t.List[types.Diagnostic], int]:
448
+ """Get diagnostics for a specific URI, returning (diagnostics, result_id)."""
449
+ # Check if we have cached diagnostics
450
+ if uri in self.lint_cache:
451
+ diagnostics, result_id = self.lint_cache[uri]
452
+ return SQLMeshLanguageServer._diagnostics_to_lsp_diagnostics(diagnostics), result_id
453
+
454
+ # Try to get diagnostics by loading context and linting
455
+ try:
456
+ context = self._context_get_or_load(uri)
457
+ models = context.map[uri.to_path()]
458
+ if models is None or not isinstance(models, ModelTarget):
459
+ return [], 0
460
+
461
+ # Lint the models and cache the results
462
+ diagnostics = context.context.lint_models(
463
+ models.names,
464
+ raise_on_error=False,
465
+ )
466
+ self._diagnostic_version_counter += 1
467
+ self.lint_cache[uri] = (diagnostics, self._diagnostic_version_counter)
468
+ return SQLMeshLanguageServer._diagnostics_to_lsp_diagnostics(
469
+ diagnostics
470
+ ), self._diagnostic_version_counter
471
+ except Exception:
472
+ # If we can't get diagnostics, return empty list with no result ID
473
+ return [], 0
474
+
330
475
  def _context_get_or_load(self, document_uri: URI) -> LSPContext:
331
476
  if self.lsp_context is None:
332
477
  self._ensure_context_for_document(document_uri)
sqlmesh/lsp/reference.py CHANGED
@@ -1,6 +1,8 @@
1
1
  from lsprotocol.types import Range, Position
2
2
  import typing as t
3
+ from pathlib import Path
3
4
 
5
+ from sqlmesh.core.audit import StandaloneAudit
4
6
  from sqlmesh.core.dialect import normalize_model_name
5
7
  from sqlmesh.core.model.definition import SqlModel
6
8
  from sqlmesh.lsp.context import LSPContext, ModelTarget, AuditTarget
@@ -10,6 +12,10 @@ from sqlglot.optimizer.scope import build_scope
10
12
  from sqlmesh.lsp.uri import URI
11
13
  from sqlmesh.utils.pydantic import PydanticModel
12
14
  from sqlglot.optimizer.normalize_identifiers import normalize_identifiers
15
+ import ast
16
+ from sqlmesh.core.model import Model
17
+ from sqlmesh import macro
18
+ import inspect
13
19
 
14
20
 
15
21
  class Reference(PydanticModel):
@@ -72,6 +78,11 @@ def get_references(
72
78
  A list of references at the given position
73
79
  """
74
80
  references = get_model_definitions_for_a_path(lint_context, document_uri)
81
+
82
+ # Get macro references before filtering by position
83
+ macro_references = get_macro_definitions_for_a_path(lint_context, document_uri)
84
+ references.extend(macro_references)
85
+
75
86
  filtered_references = list(filter(by_position(position), references))
76
87
  return filtered_references
77
88
 
@@ -134,9 +145,12 @@ def get_model_definitions_for_a_path(
134
145
  with open(file_path, "r", encoding="utf-8") as file:
135
146
  read_file = file.readlines()
136
147
 
137
- # Build scope tree to properly handle nested CTEs
138
- query = normalize_identifiers(query.copy(), dialect=dialect)
139
- root_scope = build_scope(query)
148
+ # Build a scope tree to properly handle nested CTEs
149
+ try:
150
+ query = normalize_identifiers(query.copy(), dialect=dialect)
151
+ root_scope = build_scope(query)
152
+ except Exception:
153
+ root_scope = None
140
154
 
141
155
  if root_scope:
142
156
  # Traverse all scopes to find CTE definitions and table references
@@ -287,3 +301,180 @@ def _range_from_token_position_details(
287
301
  start=Position(line=start_line_0, character=start_col_0),
288
302
  end=Position(line=end_line_0, character=end_col_0),
289
303
  )
304
+
305
+
306
+ def get_macro_definitions_for_a_path(
307
+ lsp_context: LSPContext, document_uri: URI
308
+ ) -> t.List[Reference]:
309
+ """
310
+ Get macro references for a given path.
311
+
312
+ This function finds all macro invocations (e.g., @ADD_ONE, @MULTIPLY) in a SQL file
313
+ and creates references to their definitions in the Python macro files.
314
+
315
+ Args:
316
+ lsp_context: The LSP context containing macro definitions
317
+ document_uri: The URI of the document to search for macro invocations
318
+
319
+ Returns:
320
+ A list of Reference objects for each macro invocation found
321
+ """
322
+ path = document_uri.to_path()
323
+ if path.suffix != ".sql":
324
+ return []
325
+
326
+ # Get the file info from the context map
327
+ if path not in lsp_context.map:
328
+ return []
329
+
330
+ file_info = lsp_context.map[path]
331
+ # Process based on whether it's a model or standalone audit
332
+ if isinstance(file_info, ModelTarget):
333
+ # It's a model
334
+ target: t.Optional[t.Union[Model, StandaloneAudit]] = lsp_context.context.get_model(
335
+ model_or_snapshot=file_info.names[0], raise_if_missing=False
336
+ )
337
+ if target is None or not isinstance(target, SqlModel):
338
+ return []
339
+ query = target.query
340
+ file_path = target._path
341
+ elif isinstance(file_info, AuditTarget):
342
+ # It's a standalone audit
343
+ target = lsp_context.context.standalone_audits.get(file_info.name)
344
+ if target is None:
345
+ return []
346
+ query = target.query
347
+ file_path = target._path
348
+ else:
349
+ return []
350
+
351
+ references = []
352
+ config_for_model, config_path = lsp_context.context.config_for_path(
353
+ file_path,
354
+ )
355
+
356
+ with open(file_path, "r", encoding="utf-8") as file:
357
+ read_file = file.readlines()
358
+
359
+ for node in query.find_all(exp.Anonymous):
360
+ macro_name = node.name.lower()
361
+ reference = get_macro_reference(
362
+ node=node,
363
+ target=target,
364
+ read_file=read_file,
365
+ config_path=config_path,
366
+ macro_name=macro_name,
367
+ )
368
+ if reference is not None:
369
+ references.append(reference)
370
+
371
+ return references
372
+
373
+
374
+ def get_macro_reference(
375
+ target: t.Union[Model, StandaloneAudit],
376
+ read_file: t.List[str],
377
+ config_path: t.Optional[Path],
378
+ node: exp.Expression,
379
+ macro_name: str,
380
+ ) -> t.Optional[Reference]:
381
+ # Get the file path where the macro is defined
382
+ try:
383
+ # Get the position of the macro invocation in the source file first
384
+ if hasattr(node, "meta") and node.meta:
385
+ token_details = TokenPositionDetails.from_meta(node.meta)
386
+ macro_range = _range_from_token_position_details(token_details, read_file)
387
+
388
+ # Check if it's a built-in method
389
+ if builtin := get_built_in_macro_reference(macro_name, macro_range):
390
+ return builtin
391
+ else:
392
+ # Skip if we can't get the position
393
+ return None
394
+
395
+ # Find the macro definition information
396
+ macro_def = target.python_env.get(macro_name)
397
+ if macro_def is None:
398
+ return None
399
+
400
+ function_name = macro_def.name
401
+ if not function_name:
402
+ return None
403
+ if not macro_def.path:
404
+ return None
405
+ if not config_path:
406
+ return None
407
+ path = Path(config_path).joinpath(macro_def.path)
408
+
409
+ # Parse the Python file to find the function definition
410
+ with open(path, "r") as f:
411
+ tree = ast.parse(f.read())
412
+ with open(path, "r") as f:
413
+ output_read_line = f.readlines()
414
+
415
+ # Find the function definition by name
416
+ start_line = None
417
+ end_line = None
418
+ get_length_of_end_line = None
419
+ docstring = None
420
+ for ast_node in ast.walk(tree):
421
+ if isinstance(ast_node, ast.FunctionDef) and ast_node.name == function_name:
422
+ start_line = ast_node.lineno
423
+ end_line = ast_node.end_lineno
424
+ get_length_of_end_line = (
425
+ len(output_read_line[end_line - 1])
426
+ if end_line is not None and end_line - 1 < len(read_file)
427
+ else 0
428
+ )
429
+ # Extract docstring if present
430
+ docstring = ast.get_docstring(ast_node)
431
+ break
432
+
433
+ if start_line is None or end_line is None or get_length_of_end_line is None:
434
+ return None
435
+
436
+ # Create a reference to the macro definition
437
+ macro_uri = URI.from_path(path)
438
+
439
+ return Reference(
440
+ uri=macro_uri.value,
441
+ range=macro_range,
442
+ target_range=Range(
443
+ start=Position(line=start_line - 1, character=0),
444
+ end=Position(line=end_line - 1, character=get_length_of_end_line),
445
+ ),
446
+ markdown_description=docstring,
447
+ )
448
+ except Exception:
449
+ return None
450
+
451
+
452
+ def get_built_in_macro_reference(macro_name: str, macro_range: Range) -> t.Optional[Reference]:
453
+ """
454
+ Get a reference to a built-in macro by its name.
455
+
456
+ Args:
457
+ macro_name: The name of the built-in macro (e.g., 'each', 'sql_literal')
458
+ macro_range: The range of the macro invocation in the source file
459
+ """
460
+ built_in_macros = macro.get_registry()
461
+ built_in_macro = built_in_macros.get(macro_name)
462
+ if built_in_macro is None:
463
+ return None
464
+
465
+ func = built_in_macro.func
466
+ filename = inspect.getfile(func)
467
+ source_lines, line_number = inspect.getsourcelines(func)
468
+
469
+ # Calculate the end line number by counting the number of source lines
470
+ end_line_number = line_number + len(source_lines) - 1
471
+
472
+ return Reference(
473
+ uri=URI.from_path(Path(filename)).value,
474
+ range=macro_range,
475
+ target_range=Range(
476
+ start=Position(line=line_number - 1, character=0),
477
+ end=Position(line=end_line_number - 1, character=0),
478
+ ),
479
+ markdown_description=func.__doc__ if func.__doc__ else None,
480
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlmesh
3
- Version: 0.187.6.dev5
3
+ Version: 0.187.6.dev10
4
4
  Summary: Next-generation data transformation framework
5
5
  Author-email: "TobikoData Inc." <engineering@tobikodata.com>
6
6
  License: Apache License