Skip to content

Commit 2cf6f51

Browse files
committed
Feat: Add CICD bot linter logging
1 parent 35216a2 commit 2cf6f51

File tree

8 files changed

+226
-23
lines changed

8 files changed

+226
-23
lines changed

sqlmesh/core/console.py

-1
Original file line numberDiff line numberDiff line change
@@ -2164,7 +2164,6 @@ def _show_categorized_snapshots(self, plan: Plan, default_catalog: t.Optional[st
21642164
def log_test_results(
21652165
self, result: unittest.result.TestResult, output: t.Optional[str], target_dialect: str
21662166
) -> None:
2167-
# import ipywidgets as widgets
21682167
if result.wasSuccessful():
21692168
self._print(
21702169
f"**Successfully Ran `{str(result.testsRun)}` Tests Against `{target_dialect}`**\n\n"

sqlmesh/core/context.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -2394,7 +2394,10 @@ def _get_models_for_interval_end(
23942394
)
23952395
return models_for_interval_end
23962396

2397-
def lint_models(self, models: t.Optional[t.Iterable[t.Union[str, Model]]] = None) -> None:
2397+
def lint_models(
2398+
self,
2399+
models: t.Optional[t.Iterable[t.Union[str, Model]]] = None,
2400+
) -> None:
23982401
found_error = False
23992402

24002403
model_list = (
@@ -2403,7 +2406,7 @@ def lint_models(self, models: t.Optional[t.Iterable[t.Union[str, Model]]] = None
24032406
for model in model_list:
24042407
# Linter may be `None` if the context is not loaded yet
24052408
if linter := self._linters.get(model.project):
2406-
found_error = linter.lint_model(model) or found_error
2409+
found_error = linter.lint_model(model, console=self.console) or found_error
24072410

24082411
if found_error:
24092412
raise LinterError(

sqlmesh/core/linter/definition.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from sqlmesh.core.model import Model
88

99
from sqlmesh.utils.errors import raise_config_error
10-
from sqlmesh.core.console import get_console
10+
from sqlmesh.core.console import get_console, Console
1111
from sqlmesh.core.linter.rule import RuleSet
1212

1313

@@ -50,7 +50,7 @@ def from_rules(cls, all_rules: RuleSet, config: LinterConfig) -> Linter:
5050

5151
return Linter(config.enabled, all_rules, rules, warn_rules)
5252

53-
def lint_model(self, model: Model) -> bool:
53+
def lint_model(self, model: Model, console: Console = get_console()) -> bool:
5454
if not self.enabled:
5555
return False
5656

@@ -63,10 +63,10 @@ def lint_model(self, model: Model) -> bool:
6363
warn_violations = warn_rules.check_model(model)
6464

6565
if warn_violations:
66-
get_console().show_linter_violations(warn_violations, model)
66+
console.show_linter_violations(warn_violations, model)
6767

6868
if error_violations:
69-
get_console().show_linter_violations(error_violations, model, is_error=True)
69+
console.show_linter_violations(error_violations, model, is_error=True)
7070
return True
7171

7272
return False

sqlmesh/integrations/github/cicd/command.py

+29-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
GithubController,
1414
TestFailure,
1515
)
16-
from sqlmesh.utils.errors import CICDBotError, ConflictingPlanError, PlanError
16+
from sqlmesh.utils.errors import CICDBotError, ConflictingPlanError, PlanError, LinterError
1717

1818
logger = logging.getLogger(__name__)
1919

@@ -80,6 +80,25 @@ def _run_tests(controller: GithubController) -> bool:
8080
return False
8181

8282

83+
def _run_linter(controller: GithubController) -> bool:
84+
controller.update_linter_check(status=GithubCheckStatus.IN_PROGRESS)
85+
try:
86+
controller.run_linter()
87+
except LinterError:
88+
controller.update_linter_check(
89+
status=GithubCheckStatus.COMPLETED,
90+
conclusion=GithubCheckConclusion.FAILURE,
91+
)
92+
return False
93+
94+
controller.update_linter_check(
95+
status=GithubCheckStatus.COMPLETED,
96+
conclusion=GithubCheckConclusion.SUCCESS,
97+
)
98+
99+
return True
100+
101+
83102
@github.command()
84103
@click.pass_context
85104
@cli_analytics
@@ -204,11 +223,13 @@ def _run_all(controller: GithubController) -> None:
204223
has_required_approval = True
205224
else:
206225
raise CICDBotError(f"Unsupported command: {command}")
226+
controller.update_linter_check(status=GithubCheckStatus.QUEUED)
207227
controller.update_pr_environment_check(status=GithubCheckStatus.QUEUED)
208228
controller.update_prod_plan_preview_check(status=GithubCheckStatus.QUEUED)
209229
controller.update_test_check(status=GithubCheckStatus.QUEUED)
210230
if is_auto_deploying_prod:
211231
controller.update_prod_environment_check(status=GithubCheckStatus.QUEUED)
232+
linter_passed = _run_linter(controller)
212233
tests_passed = _run_tests(controller)
213234
if controller.do_required_approval_check:
214235
if has_required_approval:
@@ -218,23 +239,25 @@ def _run_all(controller: GithubController) -> None:
218239
else:
219240
controller.update_required_approval_check(status=GithubCheckStatus.QUEUED)
220241
has_required_approval = _check_required_approvers(controller)
221-
if not tests_passed:
242+
if not tests_passed or not linter_passed:
222243
controller.update_pr_environment_check(
223244
status=GithubCheckStatus.COMPLETED,
224-
exception=TestFailure(),
245+
exception=LinterError("") if not linter_passed else TestFailure(),
225246
)
226247
controller.update_prod_plan_preview_check(
227248
status=GithubCheckStatus.COMPLETED,
228249
conclusion=GithubCheckConclusion.SKIPPED,
229-
summary="Unit Test(s) Failed so skipping creating prod plan",
250+
summary="Linter or Unit Test(s) failed so skipping creating prod plan",
230251
)
231252
if is_auto_deploying_prod:
232253
controller.update_prod_environment_check(
233254
status=GithubCheckStatus.COMPLETED,
234255
conclusion=GithubCheckConclusion.SKIPPED,
235-
skip_reason="Unit Test(s) Failed so skipping deploying to production",
256+
skip_reason="Linter or Unit Test(s) failed so skipping deploying to production",
236257
)
237-
raise CICDBotError("Failed to run tests. See check status for more information.")
258+
259+
raise CICDBotError("Linter or Unit Test(s) failed. See check status for more information.")
260+
238261
pr_environment_updated = _update_pr_environment(controller)
239262
prod_plan_generated = False
240263
if pr_environment_updated:

sqlmesh/integrations/github/cicd/controller.py

+45-7
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
format_intervals,
3030
)
3131
from sqlmesh.core.user import User
32+
from sqlmesh.core.config import Config
3233
from sqlmesh.integrations.github.cicd.config import GithubCICDBotConfig
3334
from sqlmesh.utils import word_characters_only, Verbosity
3435
from sqlmesh.utils.concurrency import NodeExecutionFailedError
@@ -38,6 +39,7 @@
3839
NoChangesPlanError,
3940
PlanError,
4041
UncategorizedPlanError,
42+
LinterError,
4143
)
4244
from sqlmesh.utils.pydantic import PydanticModel
4345

@@ -50,8 +52,6 @@
5052
from github.PullRequestReview import PullRequestReview
5153
from github.Repository import Repository
5254

53-
from sqlmesh.core.config import Config
54-
5555
logger = logging.getLogger(__name__)
5656

5757

@@ -326,10 +326,7 @@ def __init__(
326326
if review.state.lower() == "approved"
327327
}
328328
logger.debug(f"Approvers: {', '.join(self._approvers)}")
329-
self._context: Context = Context(
330-
paths=self._paths,
331-
config=self.config,
332-
)
329+
self._context: Context = Context(paths=self._paths, config=self.config)
333330

334331
@property
335332
def deploy_command_enabled(self) -> bool:
@@ -394,6 +391,7 @@ def pr_plan(self) -> Plan:
394391
self._pr_plan_builder = self._context.plan_builder(
395392
environment=self.pr_environment_name,
396393
skip_tests=True,
394+
skip_linter=True,
397395
categorizer_config=self.bot_config.auto_categorize_changes,
398396
start=self.bot_config.default_pr_start,
399397
skip_backfill=self.bot_config.skip_pr_backfill,
@@ -409,6 +407,7 @@ def prod_plan(self) -> Plan:
409407
c.PROD,
410408
no_gaps=True,
411409
skip_tests=True,
410+
skip_linter=True,
412411
categorizer_config=self.bot_config.auto_categorize_changes,
413412
run=self.bot_config.run_on_deploy_to_prod,
414413
)
@@ -423,6 +422,7 @@ def prod_plan_with_gaps(self) -> Plan:
423422
no_gaps=False,
424423
no_auto_categorization=True,
425424
skip_tests=True,
425+
skip_linter=True,
426426
run=self.bot_config.run_on_deploy_to_prod,
427427
)
428428
assert self._prod_plan_with_gaps_builder
@@ -478,6 +478,13 @@ def run_tests(self) -> t.Tuple[unittest.result.TestResult, str]:
478478
"""
479479
return self._context._run_tests(verbosity=Verbosity.VERBOSE)
480480

481+
def run_linter(self) -> None:
482+
"""
483+
Run linter for the PR
484+
"""
485+
self._console.clear_captured_outputs()
486+
self._context.lint_models()
487+
481488
def _get_or_create_comment(self, header: str = BOT_HEADER_MSG) -> IssueComment:
482489
comment = seq_get(
483490
[comment for comment in self._issue.get_comments() if header in comment.body],
@@ -654,6 +661,37 @@ def _update_check_handler(
654661
full_summary=summary,
655662
)
656663

664+
def update_linter_check(
665+
self,
666+
status: GithubCheckStatus,
667+
conclusion: t.Optional[GithubCheckConclusion] = None,
668+
) -> None:
669+
if not self._context.config.linter.enabled:
670+
return
671+
672+
def conclusion_handler(
673+
conclusion: GithubCheckConclusion,
674+
) -> t.Tuple[GithubCheckConclusion, str, t.Optional[str]]:
675+
linter_summary = self._console.consume_captured_output() or "Linter Success"
676+
677+
title = "Linter results"
678+
679+
return conclusion, title, linter_summary
680+
681+
self._update_check_handler(
682+
check_name="SQLMesh - Linter",
683+
status=status,
684+
conclusion=conclusion,
685+
status_handler=lambda status: (
686+
{
687+
GithubCheckStatus.IN_PROGRESS: "Running linter",
688+
GithubCheckStatus.QUEUED: "Waiting to Run linter",
689+
}[status],
690+
None,
691+
),
692+
conclusion_handler=conclusion_handler,
693+
)
694+
657695
def update_test_check(
658696
self,
659697
status: GithubCheckStatus,
@@ -751,7 +789,7 @@ def update_pr_environment_check(
751789
Updates the status of the merge commit for the PR environment.
752790
"""
753791
conclusion: t.Optional[GithubCheckConclusion] = None
754-
if isinstance(exception, (NoChangesPlanError, TestFailure)):
792+
if isinstance(exception, (NoChangesPlanError, TestFailure, LinterError)):
755793
conclusion = GithubCheckConclusion.SKIPPED
756794
elif isinstance(exception, UncategorizedPlanError):
757795
conclusion = GithubCheckConclusion.ACTION_REQUIRED

tests/integrations/github/cicd/fixtures.py

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pytest
44
from pytest_mock.plugin import MockerFixture
55

6+
from sqlmesh.core.config import Config
67
from sqlmesh.core.console import set_console, get_console, MarkdownConsole
78
from sqlmesh.integrations.github.cicd.config import GithubCICDBotConfig
89
from sqlmesh.integrations.github.cicd.controller import (
@@ -67,6 +68,7 @@ def _make_function(
6768
merge_state_status: MergeStateStatus = MergeStateStatus.CLEAN,
6869
bot_config: t.Optional[GithubCICDBotConfig] = None,
6970
mock_out_context: bool = True,
71+
config: t.Optional[t.Union[Config, str]] = None,
7072
) -> GithubController:
7173
if mock_out_context:
7274
mocker.patch("sqlmesh.core.context.Context.apply", mocker.MagicMock())
@@ -97,6 +99,7 @@ def _make_function(
9799
else GithubEvent.from_obj(event_path)
98100
),
99101
client=client,
102+
config=config,
100103
)
101104
finally:
102105
set_console(orig_console)

tests/integrations/github/cicd/test_github_commands.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ def test_run_all_test_failed(
497497
)
498498
assert (
499499
prod_plan_preview_checks_runs[1]["output"]["summary"]
500-
== "Unit Test(s) Failed so skipping creating prod plan"
500+
== "Linter or Unit Test(s) failed so skipping creating prod plan"
501501
)
502502

503503
assert "SQLMesh - PR Environment Synced" in controller._check_run_mapping
@@ -625,7 +625,7 @@ def test_run_all_test_exception(
625625
)
626626
assert (
627627
prod_plan_preview_checks_runs[1]["output"]["summary"]
628-
== "Unit Test(s) Failed so skipping creating prod plan"
628+
== "Linter or Unit Test(s) failed so skipping creating prod plan"
629629
)
630630

631631
assert "SQLMesh - PR Environment Synced" in controller._check_run_mapping

0 commit comments

Comments
 (0)