-
Notifications
You must be signed in to change notification settings - Fork 37
feat: add github annotations reporter #1059
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
d495627
2ca9816
9c66cb1
841c587
890c71e
239a7f2
fc9962b
6188552
a16f978
4ca19af
132d1f7
77bbe53
a33bae1
a7f6f89
8950f31
b483f72
36bca11
54c6f05
4b457a1
261aa89
c7fa470
a26e41c
4324cff
1c4bbbd
e6f76d8
7fe1324
23031df
9517654
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from deptry.reporters.github import GithubReporter | ||
| from deptry.reporters.json import JSONReporter | ||
| from deptry.reporters.text import TextReporter | ||
|
|
||
| __all__ = ("JSONReporter", "TextReporter") | ||
| __all__ = ("GithubReporter", "JSONReporter", "TextReporter") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass, field | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from deptry.reporters.base import Reporter | ||
|
|
||
| if TYPE_CHECKING: | ||
| from deptry.violations import Violation | ||
|
|
||
|
|
||
| @dataclass | ||
| class GithubReporter(Reporter): | ||
| warning_ids: list[str] = field(default_factory=list) # list of error codes to print as warnings | ||
|
|
||
| def report(self) -> None: | ||
| self._log_and_exit() | ||
|
|
||
| def _log_and_exit(self) -> None: | ||
| self._log_violations(self.violations) | ||
|
|
||
| def _log_violations(self, violations: list[Violation]) -> None: | ||
| for violation in violations: | ||
| self._print_github_annotation(violation) | ||
|
|
||
| def _print_github_annotation(self, violation: Violation) -> None: | ||
| annotation_severity = "warning" if violation.error_code in self.warning_ids else "error" | ||
| file_name = violation.location.file | ||
| if violation.location.line is not None and violation.location.column is not None: | ||
|
||
| ret = _build_workflow_command( | ||
| annotation_severity, | ||
| str(file_name), | ||
| violation.location.line, | ||
| column=violation.location.column, | ||
| title=violation.error_code, | ||
| message=violation.get_error_message(), | ||
| ) | ||
| print(ret) # noqa: T201 | ||
OrenMe marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| def _build_workflow_command( | ||
| command_name: str, | ||
| file: str, | ||
| line: int, | ||
| end_line: int | None = None, | ||
| column: int | None = None, | ||
| end_column: int | None = None, | ||
| title: str | None = None, | ||
| message: str | None = None, | ||
| ) -> str: | ||
| """Build a command to annotate a workflow.""" | ||
| result = f"::{command_name} " | ||
|
|
||
| entries = [ | ||
| ("file", file), | ||
| ("line", line), | ||
| ("endLine", end_line), | ||
| ("col", column), | ||
| ("endColumn", end_column), | ||
| ("title", title), | ||
| ] | ||
mkniewallner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| result = result + ",".join(f"{k}={v}" for k, v in entries if v is not None) | ||
|
|
||
| if message is not None: | ||
| result = result + "::" + _escape(message) | ||
|
|
||
| return result | ||
|
|
||
|
|
||
| def _escape(s: str) -> str: | ||
| return s.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import io | ||
| import sys | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| import pytest | ||
|
|
||
| from deptry.imports.location import Location | ||
| from deptry.module import Module | ||
| from deptry.reporters.github import GithubReporter, _build_workflow_command, _escape | ||
| from deptry.violations import DEP001MissingDependencyViolation | ||
|
|
||
| if TYPE_CHECKING: | ||
| from deptry.violations import Violation | ||
|
|
||
| # Extract violation instance as a parameter | ||
| violation_instance = DEP001MissingDependencyViolation( | ||
| Module("foo", package="foo-package"), Location(Path("foo.py"), 1, 2) | ||
| ) | ||
|
|
||
| expected_warning = _build_workflow_command( | ||
| "warning", | ||
| "foo.py", | ||
| 1, | ||
| column=2, | ||
| title="DEP001", | ||
| message="'foo' imported but missing from the dependency definitions", | ||
| ) | ||
|
|
||
| expected_error = _build_workflow_command( | ||
| "error", "foo.py", 1, column=2, title="DEP001", message="'foo' imported but missing from the dependency definitions" | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| ("violation", "warning_ids", "expected"), | ||
| [ | ||
| (violation_instance, ["DEP001"], expected_warning), | ||
| (violation_instance, [], expected_error), | ||
| ], | ||
| ) | ||
| def test_github_annotation( | ||
| monkeypatch: pytest.MonkeyPatch, violation: Violation, warning_ids: list[str], expected: str | ||
| ) -> None: | ||
| reporter = GithubReporter(violations=[violation], warning_ids=warning_ids) | ||
|
|
||
| captured_output = io.StringIO() | ||
| monkeypatch.setattr(sys, "stdout", captured_output) | ||
|
|
||
| reporter.report() | ||
| output = captured_output.getvalue().strip() | ||
| assert output == expected | ||
|
|
||
|
|
||
| def test_build_workflow_command_escaping() -> None: | ||
| # Directly test _build_workflow_command with characters needing escape. | ||
| message = "Error % occurred\r\nNew line" | ||
| escaped_message = _escape(message) | ||
| command = _build_workflow_command("warning", "file.py", 10, column=2, title="TEST", message=message) | ||
| assert "::warning file=file.py,line=10,col=2,title=TEST::" in command | ||
| assert escaped_message in command |
Uh oh!
There was an error while loading. Please reload this page.