Skip to content

Commit 14da465

Browse files
committed
feat(vscode): showing config errors to users
[ci skip]
1 parent a837d35 commit 14da465

5 files changed

Lines changed: 98 additions & 6 deletions

File tree

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sqlmesh/core/config/loader.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def load_config_from_paths(
9191
"SQLMesh project config could not be found. Point the cli to the project path with `sqlmesh -p`. If you haven't set up the SQLMesh project, run `sqlmesh init`."
9292
)
9393

94+
yaml_config_path: t.Optional[Path] = None
9495
for path in [*project_paths, *personal_paths]:
9596
if not path.exists():
9697
continue
@@ -107,8 +108,9 @@ def load_config_from_paths(
107108
if extension in ("yml", "yaml"):
108109
if config_name != "config" and not python_config:
109110
raise ConfigError(
110-
"YAML configs do not support multiple configs. Use Python instead."
111+
"YAML configs do not support multiple configs. Use Python instead.",
111112
)
113+
yaml_config_path = path.resolve()
112114
non_python_configs.append(load_config_from_yaml(path))
113115
elif extension == "py":
114116
try:
@@ -118,11 +120,12 @@ def load_config_from_paths(
118120
except ValidationError as e:
119121
raise ConfigError(
120122
validation_error_message(e, f"Invalid project config '{config_name}':")
121-
+ "\n\nVerify your config.py."
123+
+ "\n\nVerify your config.py.",
124+
location=full_path,
122125
)
123126
else:
124127
raise ConfigError(
125-
f"Unsupported config file extension '{extension}' in config file '{path}'."
128+
f"Unsupported config file extension '{extension}' in config file '{path}'.",
126129
)
127130

128131
if load_from_env:
@@ -149,7 +152,8 @@ def load_config_from_paths(
149152
except ValidationError as e:
150153
raise ConfigError(
151154
validation_error_message(e, "Invalid project config:")
152-
+ "\n\nVerify your config.yaml and environment variables."
155+
+ "\n\nVerify your config.yaml and environment variables.",
156+
location=yaml_config_path,
153157
)
154158

155159
no_dialect_err_msg = "Default model SQL dialect is a required configuration parameter. Set it in the `model_defaults` `dialect` key in your config file."

sqlmesh/lsp/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@ def _get_diagnostics_for_uri(self, uri: URI) -> t.Tuple[t.List[types.Diagnostic]
779779
return LSPContext.diagnostics_to_lsp_diagnostics(diagnostics), 0
780780
except ConfigError as config_error:
781781
diagnostic, error = context_error_to_diagnostic(config_error, uri_filter=uri)
782-
if diagnostic:
782+
if diagnostic and diagnostic[0] == uri.value:
783783
return [diagnostic[1]], 0
784784
return [], 0
785785

vscode/extension/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
"fs-extra": "^11.3.0",
139139
"vscode-jsonrpc": "^8.2.1",
140140
"vscode-languageclient": "^9.0.1",
141+
"yaml": "^2.8.0",
141142
"zod": "^3.25.71"
142143
},
143144
"devDependencies": {

vscode/extension/tests/broken_project.spec.ts

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@ import { test, expect } from './fixtures'
22
import fs from 'fs-extra'
33
import os from 'os'
44
import path from 'path'
5-
import { openLineageView, saveFile, SUSHI_SOURCE_PATH } from './utils'
5+
import {
6+
openLineageView,
7+
runCommand,
8+
saveFile,
9+
SUSHI_SOURCE_PATH,
10+
} from './utils'
611
import { createPythonInterpreterSettingsSpecifier } from './utils_code_server'
12+
import { execAsync } from '../src/utilities/exec'
13+
import yaml from 'yaml'
714

815
test('bad project, double model', async ({ page, sharedCodeServer }) => {
916
const tempDir = await fs.mkdtemp(
@@ -253,3 +260,80 @@ test('bad project, double model, check lineage', async ({
253260

254261
await page.waitForTimeout(500)
255262
})
263+
264+
const setup = async (tempDir: string) => {
265+
// Run the sqlmesh CLI from the root of the repo using the local path
266+
const sqlmeshCliPath = path.resolve(__dirname, '../../../.venv/bin/sqlmesh')
267+
const result = await execAsync(sqlmeshCliPath, ['init', 'duckdb'], {
268+
cwd: tempDir,
269+
})
270+
expect(result.exitCode).toBe(0)
271+
}
272+
273+
test.describe('Bad config.py/config.yaml file issues', () => {
274+
test('sqlmesh init, then corrupted config.yaml, invalid yaml', async ({
275+
page,
276+
sharedCodeServer,
277+
}) => {
278+
const tempDir = await fs.mkdtemp(
279+
path.join(os.tmpdir(), 'vscode-test-tcloud-'),
280+
)
281+
await setup(tempDir)
282+
283+
const configYamlPath = path.join(tempDir, 'config.yaml')
284+
// Write an invalid YAML to config.yaml
285+
await fs.writeFile(configYamlPath, 'invalid_yaml:;asdfasudfy [1, 2, 3')
286+
await createPythonInterpreterSettingsSpecifier(tempDir)
287+
await page.goto(
288+
`http://127.0.0.1:${sharedCodeServer.codeServerPort}/?folder=${tempDir}`,
289+
)
290+
await page.waitForLoadState('networkidle')
291+
})
292+
// Load the page
293+
294+
test('sqlmesh init, then corrupted config.yaml, bad parameters', async ({
295+
page,
296+
sharedCodeServer,
297+
}) => {
298+
const tempDir = await fs.mkdtemp(
299+
path.join(os.tmpdir(), 'vscode-test-tcloud-'),
300+
)
301+
await setup(tempDir)
302+
await createPythonInterpreterSettingsSpecifier(tempDir)
303+
304+
const configYamlPath = path.join(tempDir, 'config.yaml')
305+
// Write an invalid YAML to config.yaml
306+
const config = {
307+
gateway: 'test',
308+
}
309+
// Write config to the yaml file
310+
await fs.writeFile(configYamlPath, yaml.stringify(config))
311+
312+
await page.goto(
313+
`http://127.0.0.1:${sharedCodeServer.codeServerPort}/?folder=${tempDir}`,
314+
)
315+
await page.waitForLoadState('networkidle')
316+
317+
// Open full_model.sql model
318+
await page
319+
.getByRole('treeitem', { name: 'models', exact: true })
320+
.locator('a')
321+
.click()
322+
await page
323+
.getByRole('treeitem', { name: 'full_model.sql', exact: true })
324+
.locator('a')
325+
.click()
326+
327+
// Wait for the error to appear
328+
await page.waitForSelector('text=Error creating context')
329+
330+
// Open the problems view
331+
await runCommand(page, 'View: Focus Problems')
332+
333+
// Asser that the error is present in the problems view
334+
await page
335+
.getByText('Invalid project config:', { exact: true })
336+
.isVisible({ timeout: 1_000 })
337+
await page.getByText('Invalid project config:', { exact: true }).click()
338+
})
339+
})

0 commit comments

Comments
 (0)