sqlmesh 0.205.1.dev2__py3-none-any.whl → 0.205.1.dev4__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.dev2'
21
- __version_tuple__ = version_tuple = (0, 205, 1, 'dev2')
20
+ __version__ = version = '0.205.1.dev4'
21
+ __version_tuple__ = version_tuple = (0, 205, 1, 'dev4')
sqlmesh/lsp/context.py CHANGED
@@ -3,10 +3,12 @@ from pathlib import Path
3
3
  from sqlmesh.core.context import Context
4
4
  import typing as t
5
5
 
6
+ from sqlmesh.core.linter.rule import Range
6
7
  from sqlmesh.core.model.definition import SqlModel
7
8
  from sqlmesh.core.linter.definition import AnnotatedRuleViolation
8
- from sqlmesh.lsp.custom import ModelForRendering
9
+ from sqlmesh.lsp.custom import ModelForRendering, TestEntry, RunTestResponse
9
10
  from sqlmesh.lsp.custom import AllModelsResponse, RenderModelEntry
11
+ from sqlmesh.lsp.tests_ranges import get_test_ranges
10
12
  from sqlmesh.lsp.uri import URI
11
13
  from lsprotocol import types
12
14
 
@@ -63,6 +65,71 @@ class LSPContext:
63
65
  **audit_map,
64
66
  }
65
67
 
68
+ def list_workspace_tests(self) -> t.List[TestEntry]:
69
+ """List all tests in the workspace."""
70
+ tests = self.context.load_model_tests()
71
+
72
+ # Use a set to ensure unique URIs
73
+ unique_test_uris = {URI.from_path(test.path).value for test in tests}
74
+ test_uris: t.Dict[str, t.Dict[str, Range]] = {}
75
+ for uri in unique_test_uris:
76
+ test_ranges = get_test_ranges(URI(uri).to_path())
77
+ if uri not in test_uris:
78
+ test_uris[uri] = {}
79
+ test_uris[uri].update(test_ranges)
80
+ return [
81
+ TestEntry(
82
+ name=test.test_name,
83
+ uri=URI.from_path(test.path).value,
84
+ range=test_uris.get(URI.from_path(test.path).value, {}).get(test.test_name),
85
+ )
86
+ for test in tests
87
+ ]
88
+
89
+ def get_document_tests(self, uri: URI) -> t.List[TestEntry]:
90
+ """Get tests for a specific document.
91
+
92
+ Args:
93
+ uri: The URI of the file to get tests for.
94
+
95
+ Returns:
96
+ List of TestEntry objects for the specified document.
97
+ """
98
+ tests = self.context.load_model_tests(tests=[str(uri.to_path())])
99
+ test_ranges = get_test_ranges(uri.to_path())
100
+ return [
101
+ TestEntry(
102
+ name=test.test_name,
103
+ uri=URI.from_path(test.path).value,
104
+ range=test_ranges.get(test.test_name),
105
+ )
106
+ for test in tests
107
+ ]
108
+
109
+ def run_test(self, uri: URI, test_name: str) -> RunTestResponse:
110
+ """Run a specific test for a model.
111
+
112
+ Args:
113
+ uri: The URI of the file containing the test.
114
+ test_name: The name of the test to run.
115
+
116
+ Returns:
117
+ List of annotated rule violations from the test run.
118
+ """
119
+ path = uri.to_path()
120
+ results = self.context.test(
121
+ tests=[str(path)],
122
+ match_patterns=[test_name],
123
+ )
124
+ if results.testsRun != 1:
125
+ raise ValueError(f"Expected to run 1 test, but ran {results.testsRun} tests.")
126
+ if len(results.successes) == 1:
127
+ return RunTestResponse(success=True)
128
+ return RunTestResponse(
129
+ success=False,
130
+ error_message=str(results.failures[0][1]),
131
+ )
132
+
66
133
  def render_model(self, uri: URI) -> t.List[RenderModelEntry]:
67
134
  """Get rendered models for a file, using cache when available.
68
135
 
sqlmesh/lsp/custom.py CHANGED
@@ -1,5 +1,7 @@
1
1
  from lsprotocol import types
2
2
  import typing as t
3
+
4
+ from sqlmesh.core.linter.rule import Range
3
5
  from sqlmesh.utils.pydantic import PydanticModel
4
6
 
5
7
 
@@ -143,3 +145,52 @@ class FormatProjectResponse(CustomMethodResponseBaseClass):
143
145
  """
144
146
 
145
147
  pass
148
+
149
+
150
+ LIST_WORKSPACE_TESTS_FEATURE = "sqlmesh/list_workspace_tests"
151
+
152
+
153
+ class ListWorkspaceTestsRequest(CustomMethodRequestBaseClass):
154
+ """
155
+ Request to list all tests in the current project.
156
+ """
157
+
158
+ pass
159
+
160
+
161
+ class TestEntry(PydanticModel):
162
+ """
163
+ An entry representing a test in the workspace.
164
+ """
165
+
166
+ name: str
167
+ uri: str
168
+ range: Range
169
+
170
+
171
+ class ListWorkspaceTestsResponse(CustomMethodResponseBaseClass):
172
+ tests: t.List[TestEntry]
173
+
174
+
175
+ LIST_DOCUMENT_TESTS_FEATURE = "sqlmesh/list_document_tests"
176
+
177
+
178
+ class ListDocumentTestsRequest(CustomMethodRequestBaseClass):
179
+ textDocument: types.TextDocumentIdentifier
180
+
181
+
182
+ class ListDocumentTestsResponse(CustomMethodResponseBaseClass):
183
+ tests: t.List[TestEntry]
184
+
185
+
186
+ RUN_TEST_FEATURE = "sqlmesh/run_test"
187
+
188
+
189
+ class RunTestRequest(CustomMethodRequestBaseClass):
190
+ textDocument: types.TextDocumentIdentifier
191
+ testName: str
192
+
193
+
194
+ class RunTestResponse(CustomMethodResponseBaseClass):
195
+ success: bool
196
+ error_message: t.Optional[str] = None
sqlmesh/lsp/main.py CHANGED
@@ -46,6 +46,15 @@ from sqlmesh.lsp.custom import (
46
46
  FormatProjectRequest,
47
47
  FormatProjectResponse,
48
48
  CustomMethod,
49
+ LIST_WORKSPACE_TESTS_FEATURE,
50
+ ListWorkspaceTestsRequest,
51
+ ListWorkspaceTestsResponse,
52
+ LIST_DOCUMENT_TESTS_FEATURE,
53
+ ListDocumentTestsRequest,
54
+ ListDocumentTestsResponse,
55
+ RUN_TEST_FEATURE,
56
+ RunTestRequest,
57
+ RunTestResponse,
49
58
  )
50
59
  from sqlmesh.lsp.errors import ContextFailedError, context_error_to_diagnostic
51
60
  from sqlmesh.lsp.helpers import to_lsp_range, to_sqlmesh_position
@@ -127,11 +136,58 @@ class SQLMeshLanguageServer:
127
136
  API_FEATURE: self._custom_api,
128
137
  SUPPORTED_METHODS_FEATURE: self._custom_supported_methods,
129
138
  FORMAT_PROJECT_FEATURE: self._custom_format_project,
139
+ LIST_WORKSPACE_TESTS_FEATURE: self._list_workspace_tests,
140
+ LIST_DOCUMENT_TESTS_FEATURE: self._list_document_tests,
141
+ RUN_TEST_FEATURE: self._run_test,
130
142
  }
131
143
 
132
144
  # Register LSP features (e.g., formatting, hover, etc.)
133
145
  self._register_features()
134
146
 
147
+ def _list_workspace_tests(
148
+ self,
149
+ ls: LanguageServer,
150
+ params: ListWorkspaceTestsRequest,
151
+ ) -> ListWorkspaceTestsResponse:
152
+ """List all tests in the current workspace."""
153
+ try:
154
+ context = self._context_get_or_load()
155
+ tests = context.list_workspace_tests()
156
+ return ListWorkspaceTestsResponse(tests=tests)
157
+ except Exception as e:
158
+ ls.log_trace(f"Error listing workspace tests: {e}")
159
+ return ListWorkspaceTestsResponse(tests=[])
160
+
161
+ def _list_document_tests(
162
+ self,
163
+ ls: LanguageServer,
164
+ params: ListDocumentTestsRequest,
165
+ ) -> ListDocumentTestsResponse:
166
+ """List tests for a specific document."""
167
+ try:
168
+ uri = URI(params.textDocument.uri)
169
+ context = self._context_get_or_load(uri)
170
+ tests = context.get_document_tests(uri)
171
+ return ListDocumentTestsResponse(tests=tests)
172
+ except Exception as e:
173
+ ls.log_trace(f"Error listing document tests: {e}")
174
+ return ListDocumentTestsResponse(tests=[])
175
+
176
+ def _run_test(
177
+ self,
178
+ ls: LanguageServer,
179
+ params: RunTestRequest,
180
+ ) -> RunTestResponse:
181
+ """Run a specific test."""
182
+ try:
183
+ uri = URI(params.textDocument.uri)
184
+ context = self._context_get_or_load(uri)
185
+ result = context.run_test(uri, params.testName)
186
+ return result
187
+ except Exception as e:
188
+ ls.log_trace(f"Error running test: {e}")
189
+ return RunTestResponse(success=False, response_error=str(e))
190
+
135
191
  # All the custom LSP methods are registered here and prefixed with _custom
136
192
  def _custom_all_models(self, ls: LanguageServer, params: AllModelsRequest) -> AllModelsResponse:
137
193
  uri = URI(params.textDocument.uri)
@@ -0,0 +1,65 @@
1
+ """
2
+ Provides helper functions to get ranges of tests in SQLMesh LSP.
3
+ """
4
+
5
+ from pathlib import Path
6
+
7
+ from sqlmesh.core.linter.rule import Range, Position
8
+ from ruamel import yaml
9
+ from ruamel.yaml.comments import CommentedMap
10
+ import typing as t
11
+
12
+
13
+ def get_test_ranges(
14
+ path: Path,
15
+ ) -> t.Dict[str, Range]:
16
+ """
17
+ Test files are yaml files with a stucture of dict to test information. This returns a dictionary
18
+ with the test name as the key and the range of the test in the file as the value.
19
+ """
20
+ test_ranges: t.Dict[str, Range] = {}
21
+
22
+ with open(path, "r", encoding="utf-8") as file:
23
+ content = file.read()
24
+
25
+ # Parse YAML to get line numbers
26
+ yaml_obj = yaml.YAML()
27
+ yaml_obj.preserve_quotes = True
28
+ data = yaml_obj.load(content)
29
+
30
+ if not isinstance(data, dict):
31
+ raise ValueError("Invalid test file format: expected a dictionary at the top level.")
32
+
33
+ # For each top-level key (test name), find its range
34
+ for test_name in data:
35
+ if isinstance(data, CommentedMap) and test_name in data.lc.data:
36
+ # Get line and column info from ruamel yaml
37
+ line_info = data.lc.data[test_name]
38
+ start_line = line_info[0] # 0-based line number
39
+ start_col = line_info[1] # 0-based column number
40
+
41
+ # Find the end of this test by looking for the next test or end of file
42
+ lines = content.splitlines()
43
+ end_line = start_line
44
+
45
+ # Find where this test ends by looking for the next top-level key
46
+ # or the end of the file
47
+ for i in range(start_line + 1, len(lines)):
48
+ line = lines[i]
49
+ # Check if this line starts a new top-level key (no leading spaces)
50
+ if line and not line[0].isspace() and ":" in line:
51
+ end_line = i - 1
52
+ break
53
+ else:
54
+ # This test goes to the end of the file
55
+ end_line = len(lines) - 1
56
+
57
+ # Create the range
58
+ test_ranges[test_name] = Range(
59
+ start=Position(line=start_line, character=start_col),
60
+ end=Position(
61
+ line=end_line, character=len(lines[end_line]) if end_line < len(lines) else 0
62
+ ),
63
+ )
64
+
65
+ return test_ranges
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlmesh
3
- Version: 0.205.1.dev2
3
+ Version: 0.205.1.dev4
4
4
  Summary: Next-generation data transformation framework
5
5
  Author-email: "TobikoData Inc." <engineering@tobikodata.com>
6
6
  License: Apache License
@@ -1,5 +1,5 @@
1
1
  sqlmesh/__init__.py,sha256=QjwaamJIdW7voXoS880yAx7tHm-Im4_mqGA-XJgafos,7631
2
- sqlmesh/_version.py,sha256=wvHRk-Wl7LeAzSpg3e0vfUHDMdJ5DH63UYCfl1wffow,528
2
+ sqlmesh/_version.py,sha256=rZRzp4g2FCgBGXHt1GXpJ4RK5Sh_jMQdy24whrSvmVA,528
3
3
  sqlmesh/magics.py,sha256=bEzYqXHcM0P3CjyUZ1O9arlmXAdLAhoglKbQ29fD9AE,41842
4
4
  sqlmesh/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  sqlmesh/cicd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -165,15 +165,16 @@ sqlmesh/integrations/github/cicd/config.py,sha256=n4KyNv65y1gpys82iLAQZUb6g2pnzy
165
165
  sqlmesh/integrations/github/cicd/controller.py,sha256=XZIh8y7527sBcTMCyXyVxulieFGa90Uz5Uq0wfLPjOY,54857
166
166
  sqlmesh/lsp/api.py,sha256=_lA_buSPZFIoLJuINbS53LbNOOaHp3orb0uFTE3aCqI,2328
167
167
  sqlmesh/lsp/completions.py,sha256=Nt1XpFL_-QAIRM8BBBC6Ia6YG0hMZriiNJra7nGfD4Q,6676
168
- sqlmesh/lsp/context.py,sha256=szHCrmKAjg4wDRnSbjRU158wSGx9HBRnNswSNw9N0Qc,12439
169
- sqlmesh/lsp/custom.py,sha256=IJ70li8d5RbOFDrjHfsI2UNOiOp-WB22lrgpdJ7lVSg,2942
168
+ sqlmesh/lsp/context.py,sha256=7zkDu1J4OyeFgWM_q96_zSqI057Ei_dExx-zcBVADNs,14850
169
+ sqlmesh/lsp/custom.py,sha256=3gfN_z9IXKOhTGkaZX7V5I8WUmI4z1GWe1sViOo92pw,3980
170
170
  sqlmesh/lsp/description.py,sha256=PjguwyVw5YeYSgT0JMhkf5U1Jry-sDPLvQ2caIR9JPQ,856
171
171
  sqlmesh/lsp/errors.py,sha256=3NMim_5J00Eypz7t8b7XbkBfy8gIsRkeq-VcjD4COtc,1489
172
172
  sqlmesh/lsp/helpers.py,sha256=EFc1u3-b7kSv5-tNwmKUDxId72RCLDBnN2lLTgRSzzQ,1020
173
173
  sqlmesh/lsp/hints.py,sha256=j-blccBUil94yRoDjWfkSM0HqMmsKD6hP2TaWdWWVk4,3734
174
- sqlmesh/lsp/main.py,sha256=UsnEsrX8wOmhgK_MVWOzZcQVcEkEQLFihloAZ-taBQY,40275
174
+ sqlmesh/lsp/main.py,sha256=72u3yprMyAabiYryNyDNMypcyz59OImP4ytYhor2j9g,42330
175
175
  sqlmesh/lsp/reference.py,sha256=ucyvi52bmmmD5XGC_On0JDvQefwPrgX41KNuamjyOPQ,32624
176
176
  sqlmesh/lsp/rename.py,sha256=DPnxrYXT5gSECbX3q9lzWYl-5xnQMw-jlMOkiKgxjxg,4674
177
+ sqlmesh/lsp/tests_ranges.py,sha256=YO5w534J5_y2NWxj9t637wHLbFDJdXBvM7i5F4C-P0I,2308
177
178
  sqlmesh/lsp/uri.py,sha256=mvSLbZGmMTHPpliThOtVWiFcHnfJzp1w39q9l-xD2SI,802
178
179
  sqlmesh/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
179
180
  sqlmesh/migrations/v0001_init.py,sha256=h8FwMYJj9_O8yGpk12guaSaGwX5A3we2GiKeHrXi15o,2048
@@ -285,7 +286,7 @@ sqlmesh/utils/pydantic.py,sha256=o_NsXbIpDqNpUA1Uc5xF0ZzoXQYYB0DfHwdRxBwXPNk,120
285
286
  sqlmesh/utils/rich.py,sha256=cwQ5nJ6sgz64xHtoh6_ec7ReV5YpsOGhMtUJnwoRfEI,3549
286
287
  sqlmesh/utils/windows.py,sha256=r7Yin4WEwbvH7sKA3hOJtRsclmscNuBeCklCfVPvtQs,611
287
288
  sqlmesh/utils/yaml.py,sha256=hvo5lmybYLcIx4p6CQwufS8X4MbwS60lEBnCmYJCXmU,2336
288
- sqlmesh-0.205.1.dev2.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
289
+ sqlmesh-0.205.1.dev4.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
289
290
  web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
290
291
  web/client/dist/index.html,sha256=SXykdSRNgrT1k6I2dPZPEfpxPjIsi8-yVFijt-naSA4,5472
291
292
  web/client/dist/assets/Audits-Jm353fhk.js,sha256=ry6b5IKT3hLAfD5C3T2BqxulfauEV0n0YZ2vsP4eQ_U,1041
@@ -403,8 +404,8 @@ web/server/api/endpoints/models.py,sha256=kwj0s7uve3iZSMfmjkoPVMFMeY1sD0peTeyrWf
403
404
  web/server/api/endpoints/modules.py,sha256=8hqqgonGay_mJmpCw0IdbjsPhWlQH2VLdKAqha-myac,468
404
405
  web/server/api/endpoints/plan.py,sha256=bbbY50W_2MsZSTxOHWMKz0tbIm75nsRSlPy8GI2fg9Q,9306
405
406
  web/server/api/endpoints/table_diff.py,sha256=SCy3-pr5x6bEEiOaZGCDbiHQhpgxwmnz3Acqa2Ie6jI,2090
406
- sqlmesh-0.205.1.dev2.dist-info/METADATA,sha256=7JQ7ZryNVEC5WdKjb65YNQXcEoQYNeamH_6ECqJ4X1M,26678
407
- sqlmesh-0.205.1.dev2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
408
- sqlmesh-0.205.1.dev2.dist-info/entry_points.txt,sha256=pIYKtg-u-uO7BK_Y0qrdmfyj0bYrcMc3Xiqr5WE3VFk,121
409
- sqlmesh-0.205.1.dev2.dist-info/top_level.txt,sha256=bspjo5vjELQkuN0qKuTFIbsrUt3Zeo8D2mlKeHG1LdE,12
410
- sqlmesh-0.205.1.dev2.dist-info/RECORD,,
407
+ sqlmesh-0.205.1.dev4.dist-info/METADATA,sha256=Imi2-9e7J6HTVPEV0FMRBRdWwS_7rEo1Ufhcuzu0_j0,26678
408
+ sqlmesh-0.205.1.dev4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
409
+ sqlmesh-0.205.1.dev4.dist-info/entry_points.txt,sha256=pIYKtg-u-uO7BK_Y0qrdmfyj0bYrcMc3Xiqr5WE3VFk,121
410
+ sqlmesh-0.205.1.dev4.dist-info/top_level.txt,sha256=bspjo5vjELQkuN0qKuTFIbsrUt3Zeo8D2mlKeHG1LdE,12
411
+ sqlmesh-0.205.1.dev4.dist-info/RECORD,,