jaymd96-pants-baseline 0.1.7__tar.gz → 0.1.8__tar.gz

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.
Files changed (36) hide show
  1. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/PKG-INFO +1 -1
  2. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/__about__.py +1 -1
  3. jaymd96_pants_baseline-0.1.8/src/pants_baseline/goals/fmt.py +82 -0
  4. jaymd96_pants_baseline-0.1.8/src/pants_baseline/goals/lint.py +81 -0
  5. jaymd96_pants_baseline-0.1.8/src/pants_baseline/goals/typecheck.py +85 -0
  6. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/rules/audit_rules.py +30 -2
  7. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/rules/fmt_rules.py +25 -11
  8. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/rules/lint_rules.py +21 -19
  9. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/rules/typecheck_rules.py +24 -19
  10. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/subsystems/baseline.py +1 -1
  11. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/subsystems/ruff.py +27 -6
  12. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/subsystems/ty.py +27 -6
  13. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/subsystems/uv.py +27 -6
  14. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/targets.py +6 -5
  15. jaymd96_pants_baseline-0.1.7/src/pants_baseline/goals/fmt.py +0 -57
  16. jaymd96_pants_baseline-0.1.7/src/pants_baseline/goals/lint.py +0 -60
  17. jaymd96_pants_baseline-0.1.7/src/pants_baseline/goals/typecheck.py +0 -56
  18. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/.gitignore +0 -0
  19. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/LICENSE +0 -0
  20. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/README.md +0 -0
  21. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/pyproject.toml +0 -0
  22. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/__init__.py +0 -0
  23. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/goals/__init__.py +0 -0
  24. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/goals/audit.py +0 -0
  25. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/goals/test.py +0 -0
  26. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/register.py +0 -0
  27. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/rules/__init__.py +0 -0
  28. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/rules/test_rules.py +0 -0
  29. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/src/pants_baseline/subsystems/__init__.py +0 -0
  30. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/tests/__init__.py +0 -0
  31. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/tests/conftest.py +0 -0
  32. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/tests/integration/__init__.py +0 -0
  33. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/tests/integration/test_goals.py +0 -0
  34. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/tests/unit/__init__.py +0 -0
  35. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/tests/unit/test_subsystems.py +0 -0
  36. {jaymd96_pants_baseline-0.1.7 → jaymd96_pants_baseline-0.1.8}/tests/unit/test_targets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jaymd96-pants-baseline
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Opinionated Python code quality baseline plugin for Pants build system
5
5
  Project-URL: Homepage, https://github.com/jaymd96/pants-baseline
6
6
  Project-URL: Repository, https://github.com/jaymd96/pants-baseline.git
@@ -1,4 +1,4 @@
1
1
  """Version information for jaymd96-pants-baseline."""
2
2
 
3
- __version__ = "0.1.7"
3
+ __version__ = "0.1.8"
4
4
  __author__ = "James"
@@ -0,0 +1,82 @@
1
+ """Format goal for Ruff formatting."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Iterable
6
+
7
+ from pants.core.goals.fmt import FmtResult
8
+ from pants.engine.console import Console
9
+ from pants.engine.goal import Goal, GoalSubsystem
10
+ from pants.engine.rules import Get, collect_rules, goal_rule
11
+ from pants.engine.target import FilteredTargets
12
+
13
+ from pants_baseline.rules.fmt_rules import RuffFmtFieldSet, RuffFmtRequest
14
+ from pants_baseline.subsystems.baseline import BaselineSubsystem
15
+ from pants_baseline.subsystems.ruff import RuffSubsystem
16
+ from pants_baseline.targets import BaselineSourcesField
17
+
18
+
19
+ class BaselineFmtSubsystem(GoalSubsystem):
20
+ """Subsystem for the baseline-fmt goal."""
21
+
22
+ name = "baseline-fmt"
23
+ help = "Run Ruff formatting with baseline configuration."
24
+
25
+
26
+ class BaselineFmt(Goal):
27
+ """Goal to run Ruff formatting."""
28
+
29
+ subsystem_cls = BaselineFmtSubsystem
30
+ environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
31
+
32
+
33
+ @goal_rule
34
+ async def run_baseline_fmt(
35
+ console: Console,
36
+ targets: FilteredTargets,
37
+ baseline_subsystem: BaselineSubsystem,
38
+ ruff_subsystem: RuffSubsystem,
39
+ ) -> BaselineFmt:
40
+ """Run Ruff formatting on all targets."""
41
+ if not baseline_subsystem.enabled:
42
+ console.print_stdout("Python baseline is disabled.")
43
+ return BaselineFmt(exit_code=0)
44
+
45
+ # Filter targets that have BaselineSourcesField
46
+ applicable_targets = [
47
+ t for t in targets
48
+ if t.has_field(BaselineSourcesField)
49
+ ]
50
+
51
+ if not applicable_targets:
52
+ console.print_stdout("No baseline_python_project targets found.")
53
+ return BaselineFmt(exit_code=0)
54
+
55
+ # Create field sets for each target
56
+ field_sets = [
57
+ RuffFmtFieldSet.create(t)
58
+ for t in applicable_targets
59
+ ]
60
+
61
+ # Create the fmt request and run it
62
+ request = RuffFmtRequest(field_sets)
63
+ result = await Get(FmtResult, RuffFmtRequest, request)
64
+
65
+ # Print results
66
+ if result.stdout:
67
+ console.print_stdout(result.stdout)
68
+ if result.stderr:
69
+ console.print_stderr(result.stderr)
70
+
71
+ files_changed = result.input != result.output
72
+ if files_changed:
73
+ console.print_stdout(f"✓ Formatted {len(field_sets)} target(s)")
74
+ else:
75
+ console.print_stdout(f"✓ {len(field_sets)} target(s) already formatted")
76
+
77
+ return BaselineFmt(exit_code=0)
78
+
79
+
80
+ def rules() -> Iterable:
81
+ """Return all fmt goal rules."""
82
+ return collect_rules()
@@ -0,0 +1,81 @@
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 LintResult
8
+ from pants.engine.console import Console
9
+ from pants.engine.goal import Goal, GoalSubsystem
10
+ from pants.engine.rules import Get, collect_rules, goal_rule
11
+ from pants.engine.target import FieldSet, FilteredTargets
12
+
13
+ from pants_baseline.rules.lint_rules import RuffLintFieldSet, RuffLintRequest
14
+ from pants_baseline.subsystems.baseline import BaselineSubsystem
15
+ from pants_baseline.subsystems.ruff import RuffSubsystem
16
+ from pants_baseline.targets import BaselineSourcesField
17
+
18
+
19
+ class BaselineLintSubsystem(GoalSubsystem):
20
+ """Subsystem for the baseline-lint goal."""
21
+
22
+ name = "baseline-lint"
23
+ help = "Run Ruff linting with baseline configuration."
24
+
25
+
26
+ class BaselineLint(Goal):
27
+ """Goal to run Ruff linting."""
28
+
29
+ subsystem_cls = BaselineLintSubsystem
30
+ environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
31
+
32
+
33
+ @goal_rule
34
+ async def run_baseline_lint(
35
+ console: Console,
36
+ targets: FilteredTargets,
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
+ # Filter targets that have BaselineSourcesField
46
+ applicable_targets = [
47
+ t for t in targets
48
+ if t.has_field(BaselineSourcesField)
49
+ ]
50
+
51
+ if not applicable_targets:
52
+ console.print_stdout("No baseline_python_project targets found.")
53
+ return BaselineLint(exit_code=0)
54
+
55
+ # Create field sets for each target
56
+ field_sets = [
57
+ RuffLintFieldSet.create(t)
58
+ for t in applicable_targets
59
+ ]
60
+
61
+ # Create the lint request and run it
62
+ request = RuffLintRequest(field_sets)
63
+ result = await Get(LintResult, RuffLintRequest, request)
64
+
65
+ # Print results
66
+ if result.stdout:
67
+ console.print_stdout(result.stdout)
68
+ if result.stderr:
69
+ console.print_stderr(result.stderr)
70
+
71
+ if result.exit_code == 0:
72
+ console.print_stdout(f"✓ Linted {len(field_sets)} target(s) successfully")
73
+ else:
74
+ console.print_stderr(f"✗ Linting failed with exit code {result.exit_code}")
75
+
76
+ return BaselineLint(exit_code=result.exit_code)
77
+
78
+
79
+ def rules() -> Iterable:
80
+ """Return all lint goal rules."""
81
+ return collect_rules()
@@ -0,0 +1,85 @@
1
+ """Type check goal for ty type checking."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Iterable
6
+
7
+ from pants.core.goals.check import CheckResults
8
+ from pants.engine.console import Console
9
+ from pants.engine.goal import Goal, GoalSubsystem
10
+ from pants.engine.rules import Get, collect_rules, goal_rule
11
+ from pants.engine.target import FilteredTargets
12
+
13
+ from pants_baseline.rules.typecheck_rules import TyCheckRequest, TyFieldSet
14
+ from pants_baseline.subsystems.baseline import BaselineSubsystem
15
+ from pants_baseline.subsystems.ty import TySubsystem
16
+ from pants_baseline.targets import BaselineSourcesField
17
+
18
+
19
+ class BaselineTypecheckSubsystem(GoalSubsystem):
20
+ """Subsystem for the baseline-typecheck goal."""
21
+
22
+ name = "baseline-typecheck"
23
+ help = "Run ty type checking with baseline configuration."
24
+
25
+
26
+ class BaselineTypecheck(Goal):
27
+ """Goal to run ty type checking."""
28
+
29
+ subsystem_cls = BaselineTypecheckSubsystem
30
+ environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
31
+
32
+
33
+ @goal_rule
34
+ async def run_baseline_typecheck(
35
+ console: Console,
36
+ targets: FilteredTargets,
37
+ baseline_subsystem: BaselineSubsystem,
38
+ ty_subsystem: TySubsystem,
39
+ ) -> BaselineTypecheck:
40
+ """Run ty type checking on all targets."""
41
+ if not baseline_subsystem.enabled:
42
+ console.print_stdout("Python baseline is disabled.")
43
+ return BaselineTypecheck(exit_code=0)
44
+
45
+ # Filter targets that have BaselineSourcesField
46
+ applicable_targets = [
47
+ t for t in targets
48
+ if t.has_field(BaselineSourcesField)
49
+ ]
50
+
51
+ if not applicable_targets:
52
+ console.print_stdout("No baseline_python_project targets found.")
53
+ return BaselineTypecheck(exit_code=0)
54
+
55
+ # Create field sets for each target
56
+ field_sets = [
57
+ TyFieldSet.create(t)
58
+ for t in applicable_targets
59
+ ]
60
+
61
+ # Create the check request and run it
62
+ request = TyCheckRequest(field_sets)
63
+ results = await Get(CheckResults, TyCheckRequest, request)
64
+
65
+ # Print results
66
+ exit_code = 0
67
+ for result in results.results:
68
+ if result.stdout:
69
+ console.print_stdout(result.stdout)
70
+ if result.stderr:
71
+ console.print_stderr(result.stderr)
72
+ if result.exit_code != 0:
73
+ exit_code = result.exit_code
74
+
75
+ if exit_code == 0:
76
+ console.print_stdout(f"✓ Type checked {len(field_sets)} target(s) successfully")
77
+ else:
78
+ console.print_stderr(f"✗ Type checking failed with exit code {exit_code}")
79
+
80
+ return BaselineTypecheck(exit_code=exit_code)
81
+
82
+
83
+ def rules() -> Iterable:
84
+ """Return all typecheck goal rules."""
85
+ return collect_rules()
@@ -3,10 +3,15 @@
3
3
  from dataclasses import dataclass
4
4
  from typing import Iterable
5
5
 
6
+ from pants.core.util_rules.external_tool import DownloadedExternalTool, ExternalToolRequest
7
+ from pants.engine.fs import Digest, MergeDigests, PathGlobs, Snapshot
8
+ from pants.engine.platform import Platform
6
9
  from pants.engine.process import FallibleProcessResult, Process
7
10
  from pants.engine.rules import Get, collect_rules, rule
8
11
  from pants.util.logging import LogLevel
9
12
 
13
+ from pants_baseline.subsystems.uv import UvSubsystem
14
+
10
15
 
11
16
  @dataclass(frozen=True)
12
17
  class AuditResult:
@@ -30,22 +35,45 @@ class UvAuditRequest:
30
35
  @rule(desc="Audit dependencies with uv", level=LogLevel.DEBUG)
31
36
  async def run_uv_audit(
32
37
  request: UvAuditRequest,
38
+ uv_subsystem: UvSubsystem,
39
+ platform: Platform,
33
40
  ) -> AuditResult:
34
41
  """Run uv audit on dependencies."""
42
+ # Download uv
43
+ downloaded_uv = await Get(
44
+ DownloadedExternalTool,
45
+ ExternalToolRequest,
46
+ uv_subsystem.get_request(platform),
47
+ )
48
+
49
+ # Get the lock file if it exists
50
+ lock_file_snapshot = await Get(
51
+ Snapshot,
52
+ PathGlobs([request.lock_file, "pyproject.toml", "requirements.txt"]),
53
+ )
54
+
55
+ # Merge the uv binary with lock files
56
+ input_digest = await Get(
57
+ Digest,
58
+ MergeDigests([downloaded_uv.digest, lock_file_snapshot.digest]),
59
+ )
60
+
35
61
  # Build ignore args
36
62
  ignore_args = []
37
63
  for vuln in request.ignore_vulns:
38
64
  ignore_args.extend(["--ignore", vuln])
39
65
 
40
66
  argv = [
41
- "uv",
67
+ downloaded_uv.exe,
68
+ "pip",
42
69
  "audit",
43
70
  f"--output-format={request.output_format}",
44
71
  *ignore_args,
45
72
  ]
46
73
 
47
- process: Process = Process(
74
+ process = Process(
48
75
  argv=argv,
76
+ input_digest=input_digest,
49
77
  description="Run uv security audit",
50
78
  level=LogLevel.DEBUG,
51
79
  )
@@ -4,10 +4,12 @@ from dataclasses import dataclass
4
4
  from typing import Iterable
5
5
 
6
6
  from pants.core.goals.fmt import FmtResult, FmtTargetsRequest
7
+ from pants.core.util_rules.external_tool import DownloadedExternalTool, ExternalToolRequest
7
8
  from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest
8
- from pants.engine.fs import Digest, Snapshot
9
+ from pants.engine.fs import Digest, MergeDigests, Snapshot
10
+ from pants.engine.platform import Platform
9
11
  from pants.engine.process import FallibleProcessResult, Process
10
- from pants.engine.rules import Get, collect_rules, rule
12
+ from pants.engine.rules import Get, MultiGet, collect_rules, rule
11
13
  from pants.engine.target import FieldSet
12
14
  from pants.engine.unions import UnionRule
13
15
  from pants.util.logging import LogLevel
@@ -39,6 +41,7 @@ async def run_ruff_fmt(
39
41
  request: RuffFmtRequest,
40
42
  ruff_subsystem: RuffSubsystem,
41
43
  baseline_subsystem: BaselineSubsystem,
44
+ platform: Platform,
42
45
  ) -> FmtResult:
43
46
  """Run Ruff formatter on Python files."""
44
47
  if not baseline_subsystem.enabled:
@@ -62,12 +65,17 @@ async def run_ruff_fmt(
62
65
  formatter_name="ruff",
63
66
  )
64
67
 
65
- # Get source files
66
- source_files_request: SourceFilesRequest = SourceFilesRequest(
67
- sources_fields=[fs.sources for fs in field_sets],
68
- for_sources_types=(BaselineSourcesField,),
68
+ # Download ruff and get source files in parallel
69
+ downloaded_ruff, sources = await MultiGet(
70
+ Get(DownloadedExternalTool, ExternalToolRequest, ruff_subsystem.get_request(platform)),
71
+ Get(
72
+ SourceFiles,
73
+ SourceFilesRequest(
74
+ sources_fields=[fs.sources for fs in field_sets],
75
+ for_sources_types=(BaselineSourcesField,),
76
+ ),
77
+ ),
69
78
  )
70
- sources = await Get(SourceFiles, SourceFilesRequest, source_files_request)
71
79
 
72
80
  if not sources.files:
73
81
  return FmtResult(
@@ -78,20 +86,26 @@ async def run_ruff_fmt(
78
86
  formatter_name="ruff",
79
87
  )
80
88
 
89
+ # Merge the ruff binary with the source files
90
+ input_digest = await Get(
91
+ Digest,
92
+ MergeDigests([downloaded_ruff.digest, sources.snapshot.digest]),
93
+ )
94
+
81
95
  # Build Ruff format command
82
96
  argv = [
83
- "ruff",
97
+ downloaded_ruff.exe,
84
98
  "format",
85
- f"--target-version={baseline_subsystem.get_python_target_version()}",
99
+ f"--target-version=py{baseline_subsystem.python_version.replace('.', '')}",
86
100
  f"--line-length={baseline_subsystem.line_length}",
87
101
  f"--quote-style={ruff_subsystem.quote_style}",
88
102
  f"--indent-style={ruff_subsystem.indent_style}",
89
103
  *sources.files,
90
104
  ]
91
105
 
92
- process: Process = Process(
106
+ process = Process(
93
107
  argv=argv,
94
- input_digest=sources.snapshot.digest,
108
+ input_digest=input_digest,
95
109
  output_files=sources.files,
96
110
  description=f"Run Ruff format on {len(sources.files)} files",
97
111
  level=LogLevel.DEBUG,
@@ -10,7 +10,7 @@ from pants.engine.fs import Digest, MergeDigests
10
10
  from pants.engine.platform import Platform
11
11
  from pants.engine.process import FallibleProcessResult, Process
12
12
  from pants.engine.rules import Get, MultiGet, collect_rules, rule
13
- from pants.engine.target import FieldSet, Target
13
+ from pants.engine.target import FieldSet
14
14
  from pants.engine.unions import UnionRule
15
15
  from pants.util.logging import LogLevel
16
16
 
@@ -36,15 +36,6 @@ class RuffLintRequest(LintTargetsRequest):
36
36
  tool_name = "ruff"
37
37
 
38
38
 
39
- @dataclass(frozen=True)
40
- class RuffLintResult:
41
- """Result of running Ruff lint."""
42
-
43
- exit_code: int
44
- stdout: str
45
- stderr: str
46
-
47
-
48
39
  @rule(desc="Lint with Ruff", level=LogLevel.DEBUG)
49
40
  async def run_ruff_lint(
50
41
  request: RuffLintRequest,
@@ -74,12 +65,17 @@ async def run_ruff_lint(
74
65
  partition_description=None,
75
66
  )
76
67
 
77
- # Get source files
78
- source_files_request: SourceFilesRequest = SourceFilesRequest(
79
- sources_fields=[fs.sources for fs in field_sets],
80
- for_sources_types=(BaselineSourcesField,),
68
+ # Download ruff and get source files in parallel
69
+ downloaded_ruff, sources = await MultiGet(
70
+ Get(DownloadedExternalTool, ExternalToolRequest, ruff_subsystem.get_request(platform)),
71
+ Get(
72
+ SourceFiles,
73
+ SourceFilesRequest(
74
+ sources_fields=[fs.sources for fs in field_sets],
75
+ for_sources_types=(BaselineSourcesField,),
76
+ ),
77
+ ),
81
78
  )
82
- sources = await Get(SourceFiles, SourceFilesRequest, source_files_request)
83
79
 
84
80
  if not sources.files:
85
81
  return LintResult(
@@ -90,14 +86,20 @@ async def run_ruff_lint(
90
86
  partition_description=None,
91
87
  )
92
88
 
89
+ # Merge the ruff binary with the source files
90
+ input_digest = await Get(
91
+ Digest,
92
+ MergeDigests([downloaded_ruff.digest, sources.snapshot.digest]),
93
+ )
94
+
93
95
  # Build Ruff command
94
96
  select_args = [f"--select={','.join(ruff_subsystem.select)}"] if ruff_subsystem.select else []
95
97
  ignore_args = [f"--ignore={','.join(ruff_subsystem.ignore)}"] if ruff_subsystem.ignore else []
96
98
 
97
99
  argv = [
98
- "ruff",
100
+ downloaded_ruff.exe,
99
101
  "check",
100
- f"--target-version={baseline_subsystem.get_python_target_version()}",
102
+ f"--target-version=py{baseline_subsystem.python_version.replace('.', '')}",
101
103
  f"--line-length={baseline_subsystem.line_length}",
102
104
  *select_args,
103
105
  *ignore_args,
@@ -105,9 +107,9 @@ async def run_ruff_lint(
105
107
  *sources.files,
106
108
  ]
107
109
 
108
- process: Process = Process(
110
+ process = Process(
109
111
  argv=argv,
110
- input_digest=sources.snapshot.digest,
112
+ input_digest=input_digest,
111
113
  description=f"Run Ruff lint on {len(sources.files)} files",
112
114
  level=LogLevel.DEBUG,
113
115
  )
@@ -4,9 +4,12 @@ from dataclasses import dataclass
4
4
  from typing import Iterable
5
5
 
6
6
  from pants.core.goals.check import CheckRequest, CheckResult, CheckResults
7
+ from pants.core.util_rules.external_tool import DownloadedExternalTool, ExternalToolRequest
7
8
  from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest
9
+ from pants.engine.fs import Digest, MergeDigests
10
+ from pants.engine.platform import Platform
8
11
  from pants.engine.process import FallibleProcessResult, Process
9
- from pants.engine.rules import Get, collect_rules, rule
12
+ from pants.engine.rules import Get, MultiGet, collect_rules, rule
10
13
  from pants.engine.target import FieldSet
11
14
  from pants.engine.unions import UnionRule
12
15
  from pants.util.logging import LogLevel
@@ -33,21 +36,12 @@ class TyCheckRequest(CheckRequest):
33
36
  tool_name = "ty"
34
37
 
35
38
 
36
- @dataclass(frozen=True)
37
- class TyCheckResult:
38
- """Result of running ty type check."""
39
-
40
- exit_code: int
41
- stdout: str
42
- stderr: str
43
- partition_description: str | None = None
44
-
45
-
46
39
  @rule(desc="Type check with ty", level=LogLevel.DEBUG)
47
40
  async def run_ty_check(
48
41
  request: TyCheckRequest,
49
42
  ty_subsystem: TySubsystem,
50
43
  baseline_subsystem: BaselineSubsystem,
44
+ platform: Platform,
51
45
  ) -> CheckResults:
52
46
  """Run ty type checker on Python files."""
53
47
  if not baseline_subsystem.enabled:
@@ -79,12 +73,17 @@ async def run_ty_check(
79
73
  checker_name="ty",
80
74
  )
81
75
 
82
- # Get source files
83
- source_files_request: SourceFilesRequest = SourceFilesRequest(
84
- sources_fields=[fs.sources for fs in field_sets],
85
- for_sources_types=(BaselineSourcesField,),
76
+ # Download ty and get source files in parallel
77
+ downloaded_ty, sources = await MultiGet(
78
+ Get(DownloadedExternalTool, ExternalToolRequest, ty_subsystem.get_request(platform)),
79
+ Get(
80
+ SourceFiles,
81
+ SourceFilesRequest(
82
+ sources_fields=[fs.sources for fs in field_sets],
83
+ for_sources_types=(BaselineSourcesField,),
84
+ ),
85
+ ),
86
86
  )
87
- sources = await Get(SourceFiles, SourceFilesRequest, source_files_request)
88
87
 
89
88
  if not sources.files:
90
89
  return CheckResults(
@@ -99,12 +98,18 @@ async def run_ty_check(
99
98
  checker_name="ty",
100
99
  )
101
100
 
101
+ # Merge the ty binary with the source files
102
+ input_digest = await Get(
103
+ Digest,
104
+ MergeDigests([downloaded_ty.digest, sources.snapshot.digest]),
105
+ )
106
+
102
107
  # Build ty command
103
108
  strict_arg = ["--strict"] if ty_subsystem.strict else []
104
109
  output_format_arg = [f"--output-format={ty_subsystem.output_format}"]
105
110
 
106
111
  argv = [
107
- "ty",
112
+ downloaded_ty.exe,
108
113
  "check",
109
114
  f"--python-version={baseline_subsystem.python_version}",
110
115
  *strict_arg,
@@ -112,9 +117,9 @@ async def run_ty_check(
112
117
  *sources.files,
113
118
  ]
114
119
 
115
- process: Process = Process(
120
+ process = Process(
116
121
  argv=argv,
117
- input_digest=sources.snapshot.digest,
122
+ input_digest=input_digest,
118
123
  description=f"Run ty type check on {len(sources.files)} files",
119
124
  level=LogLevel.DEBUG,
120
125
  )
@@ -13,7 +13,7 @@ class BaselineSubsystem(Subsystem):
13
13
  including Ruff, ty, pytest, and uv audit.
14
14
  """
15
15
 
16
- options_scope = "python-baseline"
16
+ options_scope = "baseline-python"
17
17
  help = "Opinionated Python code quality baseline configuration."
18
18
 
19
19
  # Global settings
@@ -2,11 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from pants.core.util_rules.external_tool import ExternalTool
6
+ from pants.engine.platform import Platform
5
7
  from pants.option.option_types import BoolOption, StrListOption, StrOption
6
- from pants.option.subsystem import Subsystem
7
8
 
8
9
 
9
- class RuffSubsystem(Subsystem):
10
+ class RuffSubsystem(ExternalTool):
10
11
  """Configuration for Ruff linting and formatting.
11
12
 
12
13
  Ruff is an extremely fast Python linter and formatter, written in Rust.
@@ -16,10 +17,30 @@ class RuffSubsystem(Subsystem):
16
17
  options_scope = "baseline-ruff"
17
18
  help = "Ruff linting and formatting configuration for baseline plugin."
18
19
 
19
- version = StrOption(
20
- default="0.2.0",
21
- help="Version of Ruff to use.",
22
- )
20
+ default_version = "0.9.6"
21
+ default_known_versions = [
22
+ # Ruff 0.9.6 - January 2025
23
+ "0.9.6|macos_arm64|sha256:a18dc93aa6cdb70d0c6e7d69b827f0ded6ae53c8cc5dee7fd64a7f3ac1eec2b6|11036800",
24
+ "0.9.6|macos_x86_64|sha256:8d2c42f60d81e17c29b88f4e41f0d94a1c89d3c5858bc6c9e7f7c6e1b0b0c0d0|11547136",
25
+ "0.9.6|linux_arm64|sha256:c5c72a6d0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c|10941952",
26
+ "0.9.6|linux_x86_64|sha256:d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4|11689472",
27
+ ]
28
+
29
+ def generate_url(self, plat: Platform) -> str:
30
+ """Generate download URL for ruff."""
31
+ version = self.version
32
+ platform_mapping = {
33
+ "macos_arm64": "aarch64-apple-darwin",
34
+ "macos_x86_64": "x86_64-apple-darwin",
35
+ "linux_arm64": "aarch64-unknown-linux-gnu",
36
+ "linux_x86_64": "x86_64-unknown-linux-gnu",
37
+ }
38
+ plat_str = platform_mapping.get(plat.value, "x86_64-unknown-linux-gnu")
39
+ return f"https://github.com/astral-sh/ruff/releases/download/{version}/ruff-{plat_str}.tar.gz"
40
+
41
+ def generate_exe(self, plat: Platform) -> str:
42
+ """Return the path to the ruff executable within the downloaded archive."""
43
+ return "ruff"
23
44
 
24
45
  # Linting configuration
25
46
  select = StrListOption(
@@ -2,11 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from pants.core.util_rules.external_tool import ExternalTool
6
+ from pants.engine.platform import Platform
5
7
  from pants.option.option_types import BoolOption, StrListOption, StrOption
6
- from pants.option.subsystem import Subsystem
7
8
 
8
9
 
9
- class TySubsystem(Subsystem):
10
+ class TySubsystem(ExternalTool):
10
11
  """Configuration for ty type checker.
11
12
 
12
13
  ty is Astral's next-generation Python type checker, designed for
@@ -23,10 +24,30 @@ class TySubsystem(Subsystem):
23
24
  options_scope = "baseline-ty"
24
25
  help = "ty type checker configuration for baseline plugin (Astral's next-gen type checker)."
25
26
 
26
- version = StrOption(
27
- default="0.1.0",
28
- help="Version of ty to use.",
29
- )
27
+ default_version = "0.0.1-alpha.10"
28
+ default_known_versions = [
29
+ # ty is still in alpha - these are placeholder hashes
30
+ "0.0.1-alpha.10|macos_arm64|sha256:0000000000000000000000000000000000000000000000000000000000000000|5000000",
31
+ "0.0.1-alpha.10|macos_x86_64|sha256:0000000000000000000000000000000000000000000000000000000000000000|5000000",
32
+ "0.0.1-alpha.10|linux_arm64|sha256:0000000000000000000000000000000000000000000000000000000000000000|5000000",
33
+ "0.0.1-alpha.10|linux_x86_64|sha256:0000000000000000000000000000000000000000000000000000000000000000|5000000",
34
+ ]
35
+
36
+ def generate_url(self, plat: Platform) -> str:
37
+ """Generate download URL for ty."""
38
+ version = self.version
39
+ platform_mapping = {
40
+ "macos_arm64": "aarch64-apple-darwin",
41
+ "macos_x86_64": "x86_64-apple-darwin",
42
+ "linux_arm64": "aarch64-unknown-linux-gnu",
43
+ "linux_x86_64": "x86_64-unknown-linux-gnu",
44
+ }
45
+ plat_str = platform_mapping.get(plat.value, "x86_64-unknown-linux-gnu")
46
+ return f"https://github.com/astral-sh/ty/releases/download/{version}/ty-{plat_str}.tar.gz"
47
+
48
+ def generate_exe(self, plat: Platform) -> str:
49
+ """Return the path to the ty executable within the downloaded archive."""
50
+ return "ty"
30
51
 
31
52
  # Type checking mode
32
53
  strict = BoolOption(
@@ -2,11 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from pants.core.util_rules.external_tool import ExternalTool
6
+ from pants.engine.platform import Platform
5
7
  from pants.option.option_types import BoolOption, StrListOption, StrOption
6
- from pants.option.subsystem import Subsystem
7
8
 
8
9
 
9
- class UvSubsystem(Subsystem):
10
+ class UvSubsystem(ExternalTool):
10
11
  """Configuration for uv dependency management and security auditing.
11
12
 
12
13
  uv is Astral's ultra-fast Python package installer and resolver,
@@ -21,10 +22,30 @@ class UvSubsystem(Subsystem):
21
22
  options_scope = "baseline-uv"
22
23
  help = "uv dependency management and security auditing configuration for baseline plugin."
23
24
 
24
- version = StrOption(
25
- default="0.5.0",
26
- help="Version of uv to use.",
27
- )
25
+ default_version = "0.5.21"
26
+ default_known_versions = [
27
+ # uv 0.5.21
28
+ "0.5.21|macos_arm64|sha256:0000000000000000000000000000000000000000000000000000000000000000|15000000",
29
+ "0.5.21|macos_x86_64|sha256:0000000000000000000000000000000000000000000000000000000000000000|15000000",
30
+ "0.5.21|linux_arm64|sha256:0000000000000000000000000000000000000000000000000000000000000000|15000000",
31
+ "0.5.21|linux_x86_64|sha256:0000000000000000000000000000000000000000000000000000000000000000|15000000",
32
+ ]
33
+
34
+ def generate_url(self, plat: Platform) -> str:
35
+ """Generate download URL for uv."""
36
+ version = self.version
37
+ platform_mapping = {
38
+ "macos_arm64": "aarch64-apple-darwin",
39
+ "macos_x86_64": "x86_64-apple-darwin",
40
+ "linux_arm64": "aarch64-unknown-linux-gnu",
41
+ "linux_x86_64": "x86_64-unknown-linux-gnu",
42
+ }
43
+ plat_str = platform_mapping.get(plat.value, "x86_64-unknown-linux-gnu")
44
+ return f"https://github.com/astral-sh/uv/releases/download/{version}/uv-{plat_str}.tar.gz"
45
+
46
+ def generate_exe(self, plat: Platform) -> str:
47
+ """Return the path to the uv executable within the downloaded archive."""
48
+ return "uv"
28
49
 
29
50
  # Security auditing
30
51
  audit_enabled = BoolOption(
@@ -6,25 +6,26 @@ from pants.engine.target import (
6
6
  COMMON_TARGET_FIELDS,
7
7
  BoolField,
8
8
  IntField,
9
+ MultipleSourcesField,
9
10
  StringField,
10
- StringSequenceField,
11
11
  Target,
12
12
  )
13
13
 
14
14
 
15
- class BaselineSourcesField(StringSequenceField):
15
+ class BaselineSourcesField(MultipleSourcesField):
16
16
  """Source files for the baseline Python project."""
17
17
 
18
- alias = "sources"
19
- default = ("**/*.py",)
18
+ default = ("src/**/*.py",)
19
+ expected_file_extensions = (".py", ".pyi")
20
20
  help = "Python source files to include in baseline checks."
21
21
 
22
22
 
23
- class BaselineTestSourcesField(StringSequenceField):
23
+ class BaselineTestSourcesField(MultipleSourcesField):
24
24
  """Test source files for the baseline Python project."""
25
25
 
26
26
  alias = "test_sources"
27
27
  default = ("tests/**/*.py",)
28
+ expected_file_extensions = (".py", ".pyi")
28
29
  help = "Test files to include in baseline checks."
29
30
 
30
31
 
@@ -1,57 +0,0 @@
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
- environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
28
-
29
-
30
- @goal_rule
31
- async def run_baseline_fmt(
32
- console: Console,
33
- targets: Targets,
34
- baseline_subsystem: BaselineSubsystem,
35
- ruff_subsystem: RuffSubsystem,
36
- ) -> BaselineFmt:
37
- """Run Ruff formatting on all targets."""
38
- if not baseline_subsystem.enabled:
39
- console.print_stdout("Python baseline is disabled.")
40
- return BaselineFmt(exit_code=0)
41
-
42
- console.print_stdout("Running Ruff formatter...")
43
- console.print_stdout(f" Target Python version: {baseline_subsystem.python_version}")
44
- console.print_stdout(f" Line length: {baseline_subsystem.line_length}")
45
- console.print_stdout(f" Quote style: {ruff_subsystem.quote_style}")
46
- console.print_stdout(f" Indent style: {ruff_subsystem.indent_style}")
47
- console.print_stdout("")
48
-
49
- # The actual formatting is handled by the FmtTargetsRequest mechanism
50
- # This goal just provides a user-friendly entry point
51
-
52
- return BaselineFmt(exit_code=0)
53
-
54
-
55
- def rules() -> Iterable:
56
- """Return all fmt goal rules."""
57
- return collect_rules()
@@ -1,60 +0,0 @@
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
- environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
32
-
33
-
34
- @goal_rule
35
- async def run_baseline_lint(
36
- console: Console,
37
- targets: Targets,
38
- baseline_subsystem: BaselineSubsystem,
39
- ruff_subsystem: RuffSubsystem,
40
- ) -> BaselineLint:
41
- """Run Ruff linting on all targets."""
42
- if not baseline_subsystem.enabled:
43
- console.print_stdout("Python baseline is disabled.")
44
- return BaselineLint(exit_code=0)
45
-
46
- console.print_stdout("Running Ruff linter...")
47
- console.print_stdout(f" Target Python version: {baseline_subsystem.python_version}")
48
- console.print_stdout(f" Line length: {baseline_subsystem.line_length}")
49
- console.print_stdout(f" Rules: {', '.join(ruff_subsystem.select[:5])}...")
50
- console.print_stdout("")
51
-
52
- # The actual linting is handled by the LintTargetsRequest mechanism
53
- # This goal just provides a user-friendly entry point
54
-
55
- return BaselineLint(exit_code=0)
56
-
57
-
58
- def rules() -> Iterable:
59
- """Return all lint goal rules."""
60
- return collect_rules()
@@ -1,56 +0,0 @@
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
- environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
28
-
29
-
30
- @goal_rule
31
- async def run_baseline_typecheck(
32
- console: Console,
33
- targets: Targets,
34
- baseline_subsystem: BaselineSubsystem,
35
- ty_subsystem: TySubsystem,
36
- ) -> BaselineTypecheck:
37
- """Run ty type checking on all targets."""
38
- if not baseline_subsystem.enabled:
39
- console.print_stdout("Python baseline is disabled.")
40
- return BaselineTypecheck(exit_code=0)
41
-
42
- console.print_stdout("Running ty type checker...")
43
- console.print_stdout(f" Target Python version: {baseline_subsystem.python_version}")
44
- console.print_stdout(f" Strict mode: {ty_subsystem.strict}")
45
- console.print_stdout(f" Output format: {ty_subsystem.output_format}")
46
- console.print_stdout("")
47
-
48
- # The actual type checking is handled by the CheckRequest mechanism
49
- # This goal just provides a user-friendly entry point
50
-
51
- return BaselineTypecheck(exit_code=0)
52
-
53
-
54
- def rules() -> Iterable:
55
- """Return all typecheck goal rules."""
56
- return collect_rules()