diff --git a/docs/guides/linter.md b/docs/guides/linter.md index 6cdac167ec..c9fc3407fe 100644 --- a/docs/guides/linter.md +++ b/docs/guides/linter.md @@ -126,6 +126,14 @@ Error: Linter detected errors in the code. Please fix them before proceeding. Use `sqlmesh lint --help` for more information. +You can pass `--local` to run lint without loading state from the configured state connection: + +``` bash +$ sqlmesh lint --local +``` + +This can make linting faster in repositories where all referenced models are loaded from local files. In multi-repository setups, or when linting only a subset of projects, `--local` may cause additional linting errors because SQLMesh will not resolve references or schemas from models that exist only in remote state. + ## Applying linting rules @@ -258,4 +266,4 @@ You may specify that a rule's violation should not error and only log a warning ) ``` -SQLMesh will raise an error if the same rule is included in more than one of the `rules`, `warn_rules`, and `ignored_rules` keys since they should be mutually exclusive. \ No newline at end of file +SQLMesh will raise an error if the same rule is included in more than one of the `rules`, `warn_rules`, and `ignored_rules` keys since they should be mutually exclusive. diff --git a/docs/reference/cli.md b/docs/reference/cli.md index b65f8256ac..9b6e28fe14 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -650,6 +650,9 @@ Usage: sqlmesh lint [OPTIONS] Options: --model TEXT A model to lint. Multiple models can be linted. If no models are specified, every model will be linted. + --local Lint using only locally loaded project files without loading state. In multi-repository setups, or when + linting only a subset of projects, this may cause additional linting errors because SQLMesh will not resolve + references or schemas from models that exist only in remote state. --help Show this message and exit. -``` +``` \ No newline at end of file diff --git a/sqlmesh/cli/main.py b/sqlmesh/cli/main.py index b3c7a7027b..3c4a1ce359 100644 --- a/sqlmesh/cli/main.py +++ b/sqlmesh/cli/main.py @@ -118,6 +118,8 @@ def cli( load = True # Local-only gating must hold for any number of --paths, so it stays outside the block below. load_state = ctx.invoked_subcommand not in LOCAL_ONLY_COMMANDS + if ctx.invoked_subcommand == "lint" and "--local" in sys.argv: + load_state = False if len(paths) == 1: path = os.path.abspath(paths[0]) @@ -1194,6 +1196,12 @@ def environments(obj: Context) -> None: multiple=True, help="A model to lint. Multiple models can be linted. If no models are specified, every model will be linted.", ) +@click.option( + "--local", + is_flag=True, + expose_value=False, + help="Lint using only locally loaded project files without loading state.", +) @click.pass_obj @error_handler @cli_analytics diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index 7138cf4cc2..c29617ceaf 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -2305,6 +2305,25 @@ def test_lint_still_loads_state(runner: CliRunner, tmp_path: Path, mocker): assert mock.called, "state-sync was never accessed during `lint`" +def test_lint_local_runs_without_state(runner: CliRunner, tmp_path: Path, mocker, monkeypatch): + mock = _setup_local_only_project(tmp_path, mocker) + init_spy = mocker.spy(Context, "__init__") + monkeypatch.setattr("sys.argv", ["sqlmesh", "--paths", str(tmp_path), "lint", "--local"]) + + result = runner.invoke(cli, ["--paths", str(tmp_path), "lint", "--local"]) + + assert result.exit_code == 0, f"Lint failed: {result.output}\nException: {result.exception}" + assert init_spy.called, "Context was never constructed" + for call in init_spy.call_args_list: + assert "load_state" in call.kwargs, ( + "CLI didn't pass load_state= explicitly; missing kwarg defaults to True silently" + ) + assert call.kwargs["load_state"] is False, ( + f"Context was constructed with load_state={call.kwargs['load_state']} for `lint --local`" + ) + mock.assert_not_called() + + @pytest.mark.parametrize("command", ["format"]) def test_local_only_commands_skip_state_multiple_paths( runner: CliRunner, tmp_path: Path, mocker, command: str