jaymd96-pants-baseline 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jaymd96_pants_baseline-0.1.2.dist-info/METADATA +385 -0
- jaymd96_pants_baseline-0.1.2.dist-info/RECORD +25 -0
- jaymd96_pants_baseline-0.1.2.dist-info/WHEEL +4 -0
- jaymd96_pants_baseline-0.1.2.dist-info/licenses/LICENSE +190 -0
- pants_baseline/__about__.py +4 -0
- pants_baseline/__init__.py +17 -0
- pants_baseline/goals/__init__.py +11 -0
- pants_baseline/goals/audit.py +76 -0
- pants_baseline/goals/fmt.py +56 -0
- pants_baseline/goals/lint.py +59 -0
- pants_baseline/goals/test.py +53 -0
- pants_baseline/goals/typecheck.py +55 -0
- pants_baseline/register.py +46 -0
- pants_baseline/rules/__init__.py +11 -0
- pants_baseline/rules/audit_rules.py +78 -0
- pants_baseline/rules/fmt_rules.py +120 -0
- pants_baseline/rules/lint_rules.py +133 -0
- pants_baseline/rules/test_rules.py +149 -0
- pants_baseline/rules/typecheck_rules.py +144 -0
- pants_baseline/subsystems/__init__.py +13 -0
- pants_baseline/subsystems/baseline.py +74 -0
- pants_baseline/subsystems/ruff.py +90 -0
- pants_baseline/subsystems/ty.py +80 -0
- pants_baseline/subsystems/uv.py +66 -0
- pants_baseline/targets.py +138 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Audit goal for uv security scanning."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Iterable
|
|
6
|
+
|
|
7
|
+
from pants.engine.console import Console
|
|
8
|
+
from pants.engine.goal import Goal, GoalSubsystem
|
|
9
|
+
from pants.engine.rules import Get, collect_rules, goal_rule
|
|
10
|
+
from pants.engine.target import Targets
|
|
11
|
+
|
|
12
|
+
from pants_baseline.rules.audit_rules import AuditResult, UvAuditRequest
|
|
13
|
+
from pants_baseline.subsystems.baseline import BaselineSubsystem
|
|
14
|
+
from pants_baseline.subsystems.uv import UvSubsystem
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaselineAuditSubsystem(GoalSubsystem):
|
|
18
|
+
"""Subsystem for the baseline-audit goal."""
|
|
19
|
+
|
|
20
|
+
name = "baseline-audit"
|
|
21
|
+
help = "Run uv security audit on dependencies."
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BaselineAudit(Goal):
|
|
25
|
+
"""Goal to run uv security audit."""
|
|
26
|
+
|
|
27
|
+
subsystem_cls = BaselineAuditSubsystem
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@goal_rule
|
|
31
|
+
async def run_baseline_audit(
|
|
32
|
+
console: Console,
|
|
33
|
+
targets: Targets,
|
|
34
|
+
baseline_subsystem: BaselineSubsystem,
|
|
35
|
+
uv_subsystem: UvSubsystem,
|
|
36
|
+
) -> BaselineAudit:
|
|
37
|
+
"""Run uv security audit on dependencies."""
|
|
38
|
+
if not baseline_subsystem.enabled:
|
|
39
|
+
console.print_stdout("Python baseline is disabled.")
|
|
40
|
+
return BaselineAudit(exit_code=0)
|
|
41
|
+
|
|
42
|
+
if not uv_subsystem.audit_enabled:
|
|
43
|
+
console.print_stdout("Security audit is disabled.")
|
|
44
|
+
return BaselineAudit(exit_code=0)
|
|
45
|
+
|
|
46
|
+
console.print_stdout("Running uv security audit...")
|
|
47
|
+
console.print_stdout(f" Lock file: {uv_subsystem.lock_file}")
|
|
48
|
+
if uv_subsystem.audit_ignore_vulns:
|
|
49
|
+
console.print_stdout(f" Ignoring: {', '.join(uv_subsystem.audit_ignore_vulns)}")
|
|
50
|
+
console.print_stdout("")
|
|
51
|
+
|
|
52
|
+
audit_request = UvAuditRequest(
|
|
53
|
+
lock_file=uv_subsystem.lock_file,
|
|
54
|
+
ignore_vulns=tuple(uv_subsystem.audit_ignore_vulns),
|
|
55
|
+
output_format=uv_subsystem.output_format,
|
|
56
|
+
)
|
|
57
|
+
result = await Get(AuditResult, {UvAuditRequest: audit_request})
|
|
58
|
+
|
|
59
|
+
if result.stdout:
|
|
60
|
+
console.print_stdout(result.stdout)
|
|
61
|
+
if result.stderr:
|
|
62
|
+
console.print_stderr(result.stderr)
|
|
63
|
+
|
|
64
|
+
if result.exit_code == 0:
|
|
65
|
+
console.print_stdout("\nNo vulnerabilities found.")
|
|
66
|
+
else:
|
|
67
|
+
console.print_stderr(
|
|
68
|
+
f"\nFound {result.vulnerabilities_found} vulnerabilities!"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return BaselineAudit(exit_code=result.exit_code)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def rules() -> Iterable:
|
|
75
|
+
"""Return all audit goal rules."""
|
|
76
|
+
return collect_rules()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Format goal for Ruff formatting."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Iterable
|
|
6
|
+
|
|
7
|
+
from pants.engine.console import Console
|
|
8
|
+
from pants.engine.goal import Goal, GoalSubsystem
|
|
9
|
+
from pants.engine.rules import collect_rules, goal_rule
|
|
10
|
+
from pants.engine.target import Targets
|
|
11
|
+
|
|
12
|
+
from pants_baseline.subsystems.baseline import BaselineSubsystem
|
|
13
|
+
from pants_baseline.subsystems.ruff import RuffSubsystem
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaselineFmtSubsystem(GoalSubsystem):
|
|
17
|
+
"""Subsystem for the baseline-fmt goal."""
|
|
18
|
+
|
|
19
|
+
name = "baseline-fmt"
|
|
20
|
+
help = "Run Ruff formatting with baseline configuration."
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BaselineFmt(Goal):
|
|
24
|
+
"""Goal to run Ruff formatting."""
|
|
25
|
+
|
|
26
|
+
subsystem_cls = BaselineFmtSubsystem
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@goal_rule
|
|
30
|
+
async def run_baseline_fmt(
|
|
31
|
+
console: Console,
|
|
32
|
+
targets: Targets,
|
|
33
|
+
baseline_subsystem: BaselineSubsystem,
|
|
34
|
+
ruff_subsystem: RuffSubsystem,
|
|
35
|
+
) -> BaselineFmt:
|
|
36
|
+
"""Run Ruff formatting on all targets."""
|
|
37
|
+
if not baseline_subsystem.enabled:
|
|
38
|
+
console.print_stdout("Python baseline is disabled.")
|
|
39
|
+
return BaselineFmt(exit_code=0)
|
|
40
|
+
|
|
41
|
+
console.print_stdout("Running Ruff formatter...")
|
|
42
|
+
console.print_stdout(f" Target Python version: {baseline_subsystem.python_version}")
|
|
43
|
+
console.print_stdout(f" Line length: {baseline_subsystem.line_length}")
|
|
44
|
+
console.print_stdout(f" Quote style: {ruff_subsystem.quote_style}")
|
|
45
|
+
console.print_stdout(f" Indent style: {ruff_subsystem.indent_style}")
|
|
46
|
+
console.print_stdout("")
|
|
47
|
+
|
|
48
|
+
# The actual formatting is handled by the FmtTargetsRequest mechanism
|
|
49
|
+
# This goal just provides a user-friendly entry point
|
|
50
|
+
|
|
51
|
+
return BaselineFmt(exit_code=0)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def rules() -> Iterable:
|
|
55
|
+
"""Return all fmt goal rules."""
|
|
56
|
+
return collect_rules()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Lint goal for Ruff linting."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Iterable
|
|
6
|
+
|
|
7
|
+
from pants.core.goals.lint import LintFilesRequest, LintResult
|
|
8
|
+
from pants.engine.console import Console
|
|
9
|
+
from pants.engine.fs import PathGlobs, Paths
|
|
10
|
+
from pants.engine.goal import Goal, GoalSubsystem
|
|
11
|
+
from pants.engine.rules import Get, collect_rules, goal_rule
|
|
12
|
+
from pants.engine.target import Targets
|
|
13
|
+
from pants.util.logging import LogLevel
|
|
14
|
+
|
|
15
|
+
from pants_baseline.rules.lint_rules import RuffLintRequest
|
|
16
|
+
from pants_baseline.subsystems.baseline import BaselineSubsystem
|
|
17
|
+
from pants_baseline.subsystems.ruff import RuffSubsystem
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BaselineLintSubsystem(GoalSubsystem):
|
|
21
|
+
"""Subsystem for the baseline-lint goal."""
|
|
22
|
+
|
|
23
|
+
name = "baseline-lint"
|
|
24
|
+
help = "Run Ruff linting with baseline configuration."
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BaselineLint(Goal):
|
|
28
|
+
"""Goal to run Ruff linting."""
|
|
29
|
+
|
|
30
|
+
subsystem_cls = BaselineLintSubsystem
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@goal_rule
|
|
34
|
+
async def run_baseline_lint(
|
|
35
|
+
console: Console,
|
|
36
|
+
targets: Targets,
|
|
37
|
+
baseline_subsystem: BaselineSubsystem,
|
|
38
|
+
ruff_subsystem: RuffSubsystem,
|
|
39
|
+
) -> BaselineLint:
|
|
40
|
+
"""Run Ruff linting on all targets."""
|
|
41
|
+
if not baseline_subsystem.enabled:
|
|
42
|
+
console.print_stdout("Python baseline is disabled.")
|
|
43
|
+
return BaselineLint(exit_code=0)
|
|
44
|
+
|
|
45
|
+
console.print_stdout("Running Ruff linter...")
|
|
46
|
+
console.print_stdout(f" Target Python version: {baseline_subsystem.python_version}")
|
|
47
|
+
console.print_stdout(f" Line length: {baseline_subsystem.line_length}")
|
|
48
|
+
console.print_stdout(f" Rules: {', '.join(ruff_subsystem.select[:5])}...")
|
|
49
|
+
console.print_stdout("")
|
|
50
|
+
|
|
51
|
+
# The actual linting is handled by the LintTargetsRequest mechanism
|
|
52
|
+
# This goal just provides a user-friendly entry point
|
|
53
|
+
|
|
54
|
+
return BaselineLint(exit_code=0)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def rules() -> Iterable:
|
|
58
|
+
"""Return all lint goal rules."""
|
|
59
|
+
return collect_rules()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Test goal for pytest testing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Iterable
|
|
6
|
+
|
|
7
|
+
from pants.engine.console import Console
|
|
8
|
+
from pants.engine.goal import Goal, GoalSubsystem
|
|
9
|
+
from pants.engine.rules import collect_rules, goal_rule
|
|
10
|
+
from pants.engine.target import Targets
|
|
11
|
+
|
|
12
|
+
from pants_baseline.subsystems.baseline import BaselineSubsystem
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaselineTestSubsystem(GoalSubsystem):
|
|
16
|
+
"""Subsystem for the baseline-test goal."""
|
|
17
|
+
|
|
18
|
+
name = "baseline-test"
|
|
19
|
+
help = "Run pytest with coverage and baseline configuration."
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BaselineTest(Goal):
|
|
23
|
+
"""Goal to run pytest tests."""
|
|
24
|
+
|
|
25
|
+
subsystem_cls = BaselineTestSubsystem
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@goal_rule
|
|
29
|
+
async def run_baseline_test(
|
|
30
|
+
console: Console,
|
|
31
|
+
targets: Targets,
|
|
32
|
+
baseline_subsystem: BaselineSubsystem,
|
|
33
|
+
) -> BaselineTest:
|
|
34
|
+
"""Run pytest on all test targets."""
|
|
35
|
+
if not baseline_subsystem.enabled:
|
|
36
|
+
console.print_stdout("Python baseline is disabled.")
|
|
37
|
+
return BaselineTest(exit_code=0)
|
|
38
|
+
|
|
39
|
+
console.print_stdout("Running pytest with coverage...")
|
|
40
|
+
console.print_stdout(f" Source roots: {', '.join(baseline_subsystem.src_roots)}")
|
|
41
|
+
console.print_stdout(f" Test roots: {', '.join(baseline_subsystem.test_roots)}")
|
|
42
|
+
console.print_stdout(f" Coverage threshold: {baseline_subsystem.coverage_threshold}%")
|
|
43
|
+
console.print_stdout("")
|
|
44
|
+
|
|
45
|
+
# The actual testing is handled by the TestRequest mechanism
|
|
46
|
+
# This goal just provides a user-friendly entry point
|
|
47
|
+
|
|
48
|
+
return BaselineTest(exit_code=0)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def rules() -> Iterable:
|
|
52
|
+
"""Return all test goal rules."""
|
|
53
|
+
return collect_rules()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Type check goal for ty type checking."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Iterable
|
|
6
|
+
|
|
7
|
+
from pants.engine.console import Console
|
|
8
|
+
from pants.engine.goal import Goal, GoalSubsystem
|
|
9
|
+
from pants.engine.rules import collect_rules, goal_rule
|
|
10
|
+
from pants.engine.target import Targets
|
|
11
|
+
|
|
12
|
+
from pants_baseline.subsystems.baseline import BaselineSubsystem
|
|
13
|
+
from pants_baseline.subsystems.ty import TySubsystem
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaselineTypecheckSubsystem(GoalSubsystem):
|
|
17
|
+
"""Subsystem for the baseline-typecheck goal."""
|
|
18
|
+
|
|
19
|
+
name = "baseline-typecheck"
|
|
20
|
+
help = "Run ty type checking with baseline configuration."
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BaselineTypecheck(Goal):
|
|
24
|
+
"""Goal to run ty type checking."""
|
|
25
|
+
|
|
26
|
+
subsystem_cls = BaselineTypecheckSubsystem
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@goal_rule
|
|
30
|
+
async def run_baseline_typecheck(
|
|
31
|
+
console: Console,
|
|
32
|
+
targets: Targets,
|
|
33
|
+
baseline_subsystem: BaselineSubsystem,
|
|
34
|
+
ty_subsystem: TySubsystem,
|
|
35
|
+
) -> BaselineTypecheck:
|
|
36
|
+
"""Run ty type checking on all targets."""
|
|
37
|
+
if not baseline_subsystem.enabled:
|
|
38
|
+
console.print_stdout("Python baseline is disabled.")
|
|
39
|
+
return BaselineTypecheck(exit_code=0)
|
|
40
|
+
|
|
41
|
+
console.print_stdout("Running ty type checker...")
|
|
42
|
+
console.print_stdout(f" Target Python version: {baseline_subsystem.python_version}")
|
|
43
|
+
console.print_stdout(f" Strict mode: {ty_subsystem.strict}")
|
|
44
|
+
console.print_stdout(f" Output format: {ty_subsystem.output_format}")
|
|
45
|
+
console.print_stdout("")
|
|
46
|
+
|
|
47
|
+
# The actual type checking is handled by the CheckRequest mechanism
|
|
48
|
+
# This goal just provides a user-friendly entry point
|
|
49
|
+
|
|
50
|
+
return BaselineTypecheck(exit_code=0)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def rules() -> Iterable:
|
|
54
|
+
"""Return all typecheck goal rules."""
|
|
55
|
+
return collect_rules()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Register the Python Baseline plugin with Pants.
|
|
2
|
+
|
|
3
|
+
This module is the entry point for the Pants plugin system.
|
|
4
|
+
It registers all rules, targets, and subsystems provided by this plugin.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Iterable
|
|
8
|
+
|
|
9
|
+
from pants.engine.rules import Rule, collect_rules
|
|
10
|
+
|
|
11
|
+
from pants_baseline.goals import audit as audit_goal
|
|
12
|
+
from pants_baseline.goals import fmt as fmt_goal
|
|
13
|
+
from pants_baseline.goals import lint as lint_goal
|
|
14
|
+
from pants_baseline.goals import test as test_goal
|
|
15
|
+
from pants_baseline.goals import typecheck as typecheck_goal
|
|
16
|
+
from pants_baseline.rules import audit_rules, fmt_rules, lint_rules, test_rules, typecheck_rules
|
|
17
|
+
from pants_baseline.subsystems.baseline import BaselineSubsystem
|
|
18
|
+
from pants_baseline.subsystems.ruff import RuffSubsystem
|
|
19
|
+
from pants_baseline.subsystems.ty import TySubsystem
|
|
20
|
+
from pants_baseline.subsystems.uv import UvSubsystem
|
|
21
|
+
from pants_baseline.targets import BaselinePythonProject
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def rules() -> Iterable[Rule]:
|
|
25
|
+
"""Return all rules provided by this plugin."""
|
|
26
|
+
return [
|
|
27
|
+
*collect_rules(lint_rules),
|
|
28
|
+
*collect_rules(fmt_rules),
|
|
29
|
+
*collect_rules(typecheck_rules),
|
|
30
|
+
*collect_rules(test_rules),
|
|
31
|
+
*collect_rules(audit_rules),
|
|
32
|
+
*collect_rules(lint_goal),
|
|
33
|
+
*collect_rules(fmt_goal),
|
|
34
|
+
*collect_rules(typecheck_goal),
|
|
35
|
+
*collect_rules(test_goal),
|
|
36
|
+
*collect_rules(audit_goal),
|
|
37
|
+
*BaselineSubsystem.rules(),
|
|
38
|
+
*RuffSubsystem.rules(),
|
|
39
|
+
*TySubsystem.rules(),
|
|
40
|
+
*UvSubsystem.rules(),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def target_types() -> Iterable[type]:
|
|
45
|
+
"""Return all custom target types provided by this plugin."""
|
|
46
|
+
return [BaselinePythonProject]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Rules for uv security auditing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Iterable
|
|
7
|
+
|
|
8
|
+
from pants.engine.process import FallibleProcessResult, Process
|
|
9
|
+
from pants.engine.rules import Get, collect_rules, rule
|
|
10
|
+
from pants.util.logging import LogLevel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class AuditResult:
|
|
15
|
+
"""Result of running uv audit."""
|
|
16
|
+
|
|
17
|
+
exit_code: int
|
|
18
|
+
stdout: str
|
|
19
|
+
stderr: str
|
|
20
|
+
vulnerabilities_found: int
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class UvAuditRequest:
|
|
25
|
+
"""Request to run uv audit."""
|
|
26
|
+
|
|
27
|
+
lock_file: str
|
|
28
|
+
ignore_vulns: tuple[str, ...]
|
|
29
|
+
output_format: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@rule(desc="Audit dependencies with uv", level=LogLevel.DEBUG)
|
|
33
|
+
async def run_uv_audit(
|
|
34
|
+
request: UvAuditRequest,
|
|
35
|
+
) -> AuditResult:
|
|
36
|
+
"""Run uv audit on dependencies."""
|
|
37
|
+
# Build ignore args
|
|
38
|
+
ignore_args = []
|
|
39
|
+
for vuln in request.ignore_vulns:
|
|
40
|
+
ignore_args.extend(["--ignore", vuln])
|
|
41
|
+
|
|
42
|
+
argv = [
|
|
43
|
+
"uv",
|
|
44
|
+
"audit",
|
|
45
|
+
f"--output-format={request.output_format}",
|
|
46
|
+
*ignore_args,
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
process = Process(
|
|
50
|
+
argv=argv,
|
|
51
|
+
description="Run uv security audit",
|
|
52
|
+
level=LogLevel.DEBUG,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
result = await Get(FallibleProcessResult, {Process: process})
|
|
56
|
+
|
|
57
|
+
stdout = result.stdout.decode()
|
|
58
|
+
stderr = result.stderr.decode()
|
|
59
|
+
|
|
60
|
+
# Parse vulnerability count from output (simplified)
|
|
61
|
+
vulnerabilities_found = 0
|
|
62
|
+
if result.exit_code != 0:
|
|
63
|
+
# Count lines that look like vulnerability reports
|
|
64
|
+
for line in stdout.split("\n"):
|
|
65
|
+
if "vulnerability" in line.lower() or "CVE-" in line or "GHSA-" in line:
|
|
66
|
+
vulnerabilities_found += 1
|
|
67
|
+
|
|
68
|
+
return AuditResult(
|
|
69
|
+
exit_code=result.exit_code,
|
|
70
|
+
stdout=stdout,
|
|
71
|
+
stderr=stderr,
|
|
72
|
+
vulnerabilities_found=vulnerabilities_found,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def rules() -> Iterable:
|
|
77
|
+
"""Return all audit rules."""
|
|
78
|
+
return collect_rules()
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Rules for Ruff formatting."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Iterable
|
|
7
|
+
|
|
8
|
+
from pants.core.goals.fmt import FmtResult, FmtTargetsRequest
|
|
9
|
+
from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest
|
|
10
|
+
from pants.engine.fs import Digest, Snapshot
|
|
11
|
+
from pants.engine.process import FallibleProcessResult, Process
|
|
12
|
+
from pants.engine.rules import Get, collect_rules, rule
|
|
13
|
+
from pants.engine.target import FieldSet
|
|
14
|
+
from pants.engine.unions import UnionRule
|
|
15
|
+
from pants.util.logging import LogLevel
|
|
16
|
+
|
|
17
|
+
from pants_baseline.subsystems.baseline import BaselineSubsystem
|
|
18
|
+
from pants_baseline.subsystems.ruff import RuffSubsystem
|
|
19
|
+
from pants_baseline.targets import BaselineSourcesField, SkipFormatField
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class RuffFmtFieldSet(FieldSet):
|
|
24
|
+
"""Field set for Ruff formatting."""
|
|
25
|
+
|
|
26
|
+
required_fields = (BaselineSourcesField,)
|
|
27
|
+
|
|
28
|
+
sources: BaselineSourcesField
|
|
29
|
+
skip_fmt: SkipFormatField
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class RuffFmtRequest(FmtTargetsRequest):
|
|
33
|
+
"""Request to run Ruff formatting."""
|
|
34
|
+
|
|
35
|
+
field_set_type = RuffFmtFieldSet
|
|
36
|
+
tool_name = "ruff"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@rule(desc="Format with Ruff", level=LogLevel.DEBUG)
|
|
40
|
+
async def run_ruff_fmt(
|
|
41
|
+
request: RuffFmtRequest,
|
|
42
|
+
ruff_subsystem: RuffSubsystem,
|
|
43
|
+
baseline_subsystem: BaselineSubsystem,
|
|
44
|
+
) -> FmtResult:
|
|
45
|
+
"""Run Ruff formatter on Python files."""
|
|
46
|
+
if not baseline_subsystem.enabled:
|
|
47
|
+
return FmtResult(
|
|
48
|
+
input=request.snapshot,
|
|
49
|
+
output=request.snapshot,
|
|
50
|
+
stdout="",
|
|
51
|
+
stderr="",
|
|
52
|
+
formatter_name="ruff",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Filter out skipped targets
|
|
56
|
+
field_sets = [fs for fs in request.field_sets if not fs.skip_fmt.value]
|
|
57
|
+
|
|
58
|
+
if not field_sets:
|
|
59
|
+
return FmtResult(
|
|
60
|
+
input=request.snapshot,
|
|
61
|
+
output=request.snapshot,
|
|
62
|
+
stdout="No targets to format",
|
|
63
|
+
stderr="",
|
|
64
|
+
formatter_name="ruff",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Get source files
|
|
68
|
+
source_files_request = SourceFilesRequest(
|
|
69
|
+
sources_fields=[fs.sources for fs in field_sets],
|
|
70
|
+
for_sources_types=(BaselineSourcesField,),
|
|
71
|
+
)
|
|
72
|
+
sources = await Get(SourceFiles, {SourceFilesRequest: source_files_request})
|
|
73
|
+
|
|
74
|
+
if not sources.files:
|
|
75
|
+
return FmtResult(
|
|
76
|
+
input=request.snapshot,
|
|
77
|
+
output=request.snapshot,
|
|
78
|
+
stdout="No files to format",
|
|
79
|
+
stderr="",
|
|
80
|
+
formatter_name="ruff",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Build Ruff format command
|
|
84
|
+
argv = [
|
|
85
|
+
"ruff",
|
|
86
|
+
"format",
|
|
87
|
+
f"--target-version={baseline_subsystem.get_python_target_version()}",
|
|
88
|
+
f"--line-length={baseline_subsystem.line_length}",
|
|
89
|
+
f"--quote-style={ruff_subsystem.quote_style}",
|
|
90
|
+
f"--indent-style={ruff_subsystem.indent_style}",
|
|
91
|
+
*sources.files,
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
process = Process(
|
|
95
|
+
argv=argv,
|
|
96
|
+
input_digest=sources.snapshot.digest,
|
|
97
|
+
output_files=sources.files,
|
|
98
|
+
description=f"Run Ruff format on {len(sources.files)} files",
|
|
99
|
+
level=LogLevel.DEBUG,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
result = await Get(FallibleProcessResult, {Process: process})
|
|
103
|
+
|
|
104
|
+
output_snapshot = await Get(Snapshot, {Digest: result.output_digest})
|
|
105
|
+
|
|
106
|
+
return FmtResult(
|
|
107
|
+
input=sources.snapshot,
|
|
108
|
+
output=output_snapshot,
|
|
109
|
+
stdout=result.stdout.decode(),
|
|
110
|
+
stderr=result.stderr.decode(),
|
|
111
|
+
formatter_name="ruff",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def rules() -> Iterable:
|
|
116
|
+
"""Return all format rules."""
|
|
117
|
+
return [
|
|
118
|
+
*collect_rules(),
|
|
119
|
+
UnionRule(FmtTargetsRequest, RuffFmtRequest),
|
|
120
|
+
]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Rules for Ruff linting."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Iterable
|
|
7
|
+
|
|
8
|
+
from pants.core.goals.lint import LintResult, LintTargetsRequest
|
|
9
|
+
from pants.core.util_rules.external_tool import DownloadedExternalTool, ExternalToolRequest
|
|
10
|
+
from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest
|
|
11
|
+
from pants.engine.fs import Digest, MergeDigests
|
|
12
|
+
from pants.engine.platform import Platform
|
|
13
|
+
from pants.engine.process import FallibleProcessResult, Process
|
|
14
|
+
from pants.engine.rules import Get, MultiGet, collect_rules, rule
|
|
15
|
+
from pants.engine.target import FieldSet, Target
|
|
16
|
+
from pants.engine.unions import UnionRule
|
|
17
|
+
from pants.util.logging import LogLevel
|
|
18
|
+
|
|
19
|
+
from pants_baseline.subsystems.baseline import BaselineSubsystem
|
|
20
|
+
from pants_baseline.subsystems.ruff import RuffSubsystem
|
|
21
|
+
from pants_baseline.targets import BaselineSourcesField, SkipLintField
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class RuffLintFieldSet(FieldSet):
|
|
26
|
+
"""Field set for Ruff linting."""
|
|
27
|
+
|
|
28
|
+
required_fields = (BaselineSourcesField,)
|
|
29
|
+
|
|
30
|
+
sources: BaselineSourcesField
|
|
31
|
+
skip_lint: SkipLintField
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RuffLintRequest(LintTargetsRequest):
|
|
35
|
+
"""Request to run Ruff linting."""
|
|
36
|
+
|
|
37
|
+
field_set_type = RuffLintFieldSet
|
|
38
|
+
tool_name = "ruff"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class RuffLintResult:
|
|
43
|
+
"""Result of running Ruff lint."""
|
|
44
|
+
|
|
45
|
+
exit_code: int
|
|
46
|
+
stdout: str
|
|
47
|
+
stderr: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@rule(desc="Lint with Ruff", level=LogLevel.DEBUG)
|
|
51
|
+
async def run_ruff_lint(
|
|
52
|
+
request: RuffLintRequest,
|
|
53
|
+
ruff_subsystem: RuffSubsystem,
|
|
54
|
+
baseline_subsystem: BaselineSubsystem,
|
|
55
|
+
platform: Platform,
|
|
56
|
+
) -> LintResult:
|
|
57
|
+
"""Run Ruff linter on Python files."""
|
|
58
|
+
if not baseline_subsystem.enabled:
|
|
59
|
+
return LintResult(
|
|
60
|
+
exit_code=0,
|
|
61
|
+
stdout="",
|
|
62
|
+
stderr="",
|
|
63
|
+
linter_name="ruff",
|
|
64
|
+
partition_description=None,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Filter out skipped targets
|
|
68
|
+
field_sets = [fs for fs in request.field_sets if not fs.skip_lint.value]
|
|
69
|
+
|
|
70
|
+
if not field_sets:
|
|
71
|
+
return LintResult(
|
|
72
|
+
exit_code=0,
|
|
73
|
+
stdout="No targets to lint",
|
|
74
|
+
stderr="",
|
|
75
|
+
linter_name="ruff",
|
|
76
|
+
partition_description=None,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Get source files
|
|
80
|
+
source_files_request = SourceFilesRequest(
|
|
81
|
+
sources_fields=[fs.sources for fs in field_sets],
|
|
82
|
+
for_sources_types=(BaselineSourcesField,),
|
|
83
|
+
)
|
|
84
|
+
sources = await Get(SourceFiles, {SourceFilesRequest: source_files_request})
|
|
85
|
+
|
|
86
|
+
if not sources.files:
|
|
87
|
+
return LintResult(
|
|
88
|
+
exit_code=0,
|
|
89
|
+
stdout="No files to lint",
|
|
90
|
+
stderr="",
|
|
91
|
+
linter_name="ruff",
|
|
92
|
+
partition_description=None,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Build Ruff command
|
|
96
|
+
select_args = [f"--select={','.join(ruff_subsystem.select)}"] if ruff_subsystem.select else []
|
|
97
|
+
ignore_args = [f"--ignore={','.join(ruff_subsystem.ignore)}"] if ruff_subsystem.ignore else []
|
|
98
|
+
|
|
99
|
+
argv = [
|
|
100
|
+
"ruff",
|
|
101
|
+
"check",
|
|
102
|
+
f"--target-version={baseline_subsystem.get_python_target_version()}",
|
|
103
|
+
f"--line-length={baseline_subsystem.line_length}",
|
|
104
|
+
*select_args,
|
|
105
|
+
*ignore_args,
|
|
106
|
+
"--output-format=text",
|
|
107
|
+
*sources.files,
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
process = Process(
|
|
111
|
+
argv=argv,
|
|
112
|
+
input_digest=sources.snapshot.digest,
|
|
113
|
+
description=f"Run Ruff lint on {len(sources.files)} files",
|
|
114
|
+
level=LogLevel.DEBUG,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
result = await Get(FallibleProcessResult, {Process: process})
|
|
118
|
+
|
|
119
|
+
return LintResult(
|
|
120
|
+
exit_code=result.exit_code,
|
|
121
|
+
stdout=result.stdout.decode(),
|
|
122
|
+
stderr=result.stderr.decode(),
|
|
123
|
+
linter_name="ruff",
|
|
124
|
+
partition_description=None,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def rules() -> Iterable:
|
|
129
|
+
"""Return all lint rules."""
|
|
130
|
+
return [
|
|
131
|
+
*collect_rules(),
|
|
132
|
+
UnionRule(LintTargetsRequest, RuffLintRequest),
|
|
133
|
+
]
|