From 62c52c3a62308aa0fd33eae81f87a5aec411d871 Mon Sep 17 00:00:00 2001 From: evieira Date: Wed, 1 Jul 2026 11:07:54 +0000 Subject: [PATCH 1/4] add lint --local option Signed-off-by: evieira --- docs/guides/linter.md | 10 +++++++++- docs/reference/cli.md | 3 +++ sqlmesh/cli/main.py | 8 ++++++++ tests/cli/test_cli.py | 23 +++++++++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) 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..5bbdf7f8eb 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. --help Show this message and exit. ``` + +`--local` skips loading state from the configured state connection. 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. diff --git a/sqlmesh/cli/main.py b/sqlmesh/cli/main.py index b3c7a7027b..e9f4267cf6 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,12 +1196,18 @@ 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, + help="Lint using only locally loaded project files without loading state.", +) @click.pass_obj @error_handler @cli_analytics def lint( obj: Context, models: t.Iterator[str], + local: bool, ) -> None: """Run the linter for the target model(s).""" obj.lint_models(models) diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index 7138cf4cc2..89fb1418e4 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -2305,6 +2305,29 @@ 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 From 75138c388dd3a3fff08a1b55945135586922554a Mon Sep 17 00:00:00 2001 From: evieira Date: Wed, 1 Jul 2026 11:21:50 +0000 Subject: [PATCH 2/4] format cli test Signed-off-by: evieira --- tests/cli/test_cli.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index 89fb1418e4..c29617ceaf 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -2305,14 +2305,10 @@ 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 -): +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"] - ) + monkeypatch.setattr("sys.argv", ["sqlmesh", "--paths", str(tmp_path), "lint", "--local"]) result = runner.invoke(cli, ["--paths", str(tmp_path), "lint", "--local"]) From adcac346fb8d3cf4fc153798dfdf11a1fc9d354d Mon Sep 17 00:00:00 2001 From: evieira Date: Wed, 1 Jul 2026 11:29:28 +0000 Subject: [PATCH 3/4] condense cli reference Signed-off-by: evieira --- docs/reference/cli.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 5bbdf7f8eb..9b6e28fe14 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -650,9 +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. + --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. -``` - -`--local` skips loading state from the configured state connection. 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. +``` \ No newline at end of file From 1b0f39107bd9f8f572756f8c66f726664a3ba268 Mon Sep 17 00:00:00 2001 From: evieira Date: Wed, 1 Jul 2026 11:36:36 +0000 Subject: [PATCH 4/4] no need to expose --local option Signed-off-by: evieira --- sqlmesh/cli/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlmesh/cli/main.py b/sqlmesh/cli/main.py index e9f4267cf6..3c4a1ce359 100644 --- a/sqlmesh/cli/main.py +++ b/sqlmesh/cli/main.py @@ -1199,6 +1199,7 @@ def environments(obj: Context) -> None: @click.option( "--local", is_flag=True, + expose_value=False, help="Lint using only locally loaded project files without loading state.", ) @click.pass_obj @@ -1207,7 +1208,6 @@ def environments(obj: Context) -> None: def lint( obj: Context, models: t.Iterator[str], - local: bool, ) -> None: """Run the linter for the target model(s).""" obj.lint_models(models)