jaymd96-pants-baseline 0.1.2__py3-none-any.whl → 0.2.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jaymd96-pants-baseline
3
- Version: 0.1.2
3
+ Version: 0.2.0
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
@@ -21,6 +21,8 @@ Classifier: Programming Language :: Python :: 3.13
21
21
  Classifier: Topic :: Software Development :: Build Tools
22
22
  Classifier: Topic :: Software Development :: Quality Assurance
23
23
  Requires-Python: <4,>=3.11
24
+ Provides-Extra: claude
25
+ Requires-Dist: jaymd96-pants-claude-plugins>=0.2.0; extra == 'claude'
24
26
  Provides-Extra: dev
25
27
  Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
26
28
  Requires-Dist: pytest>=7.4.0; extra == 'dev'
@@ -54,7 +56,7 @@ backend_packages = [
54
56
  ]
55
57
 
56
58
  plugins = [
57
- "jaymd96-pants-baseline==0.1.0",
59
+ "jaymd96-pants-baseline==0.2.0",
58
60
  ]
59
61
  ```
60
62
 
@@ -308,7 +310,7 @@ backend_packages = [
308
310
  "pants.backend.python",
309
311
  "pants_baseline",
310
312
  ]
311
- plugins = ["jaymd96-pants-baseline==0.1.0"]
313
+ plugins = ["jaymd96-pants-baseline==0.2.0"]
312
314
 
313
315
  [python]
314
316
  interpreter_constraints = ["CPython>=3.13,<4"]
@@ -346,6 +348,37 @@ strict = true
346
348
  | MyPy/Pyright | ty |
347
349
  | pip-audit | uv audit |
348
350
 
351
+ ## Claude Code Integration
352
+
353
+ This plugin bundles recommended [Claude Code](https://claude.ai/code) plugins for enhanced AI-assisted development workflows.
354
+
355
+ ### Install Bundled Claude Plugins
356
+
357
+ When combined with [jaymd96-pants-claude-plugins](https://github.com/jaymd96/pants-claude-plugins):
358
+
359
+ ```toml
360
+ # pants.toml
361
+ [GLOBAL]
362
+ plugins = [
363
+ "jaymd96-pants-baseline==0.2.0",
364
+ "jaymd96-pants-claude-plugins>=0.2.0",
365
+ ]
366
+ backend_packages = [
367
+ "pants_baseline",
368
+ "pants_claude_plugins",
369
+ ]
370
+ ```
371
+
372
+ Then run:
373
+
374
+ ```bash
375
+ pants claude-install --include-bundled ::
376
+ ```
377
+
378
+ This automatically installs:
379
+ - **github** - GitHub integration for PR workflows and issue management
380
+ - **commit-commands** - Git workflow commands for commits, pushes, and PRs
381
+
349
382
  ## Development
350
383
 
351
384
  ### Setup
@@ -0,0 +1,26 @@
1
+ pants_baseline/__about__.py,sha256=wxKDEvde9_K-nhyadAqBTbIh_mR7ckFwDJRU14BkV8A,98
2
+ pants_baseline/__init__.py,sha256=uVRGi1D2gFjc7emmeewWdcvpO-NsUuKsMbX3rztOxWU,655
3
+ pants_baseline/bundled_claude_plugins.py,sha256=jfNzFrvZBvv8Iz1XMLomgIa993ZYH_DMNOk-apFs8-g,926
4
+ pants_baseline/register.py,sha256=gdODsaI7VIzU2tGONANsg9KbKZkHidmLs2AyvUNIypg,1675
5
+ pants_baseline/targets.py,sha256=Z9O09Aqd5inMqwIUFt4HSE9HtWjn8pGSU4FZHg22Tpo,3379
6
+ pants_baseline/goals/__init__.py,sha256=pf6KU2CIQuDkx8ER3IS0H-kuNbBtX-AH5B5SnSP9_yw,192
7
+ pants_baseline/goals/audit.py,sha256=i7zyyKssWw6EE2zN5putWP9sikkcjUKn7g3Jrapv994,2380
8
+ pants_baseline/goals/fmt.py,sha256=CyUPhy42rscUzJp4DhSRp3FGhGYcIKN42b46l1hJ9tY,2432
9
+ pants_baseline/goals/lint.py,sha256=vGyG-wvjgjE4dgglmTOiFNnng2bQbOQXkZ3Fd2HdiOU,2435
10
+ pants_baseline/goals/test.py,sha256=THW4kJAFbAzPCjLbq1dxg81T19QdywXYiwTNKSB4z8M,1653
11
+ pants_baseline/goals/typecheck.py,sha256=nqfwy1BZqhEzlDX-_hme9mKuDI2KqH-XpN4ygzWMQ6Y,2626
12
+ pants_baseline/rules/__init__.py,sha256=UpvDpGVImhRfp2_VeUNsRPGiWjBbMI6AV1-Yx3kS0Gg,252
13
+ pants_baseline/rules/audit_rules.py,sha256=gr-2u0u07z62Q42c9vXVGJng8UPIP5JzZ-SKrl9z7yM,2762
14
+ pants_baseline/rules/fmt_rules.py,sha256=O-JQVk565H8w6jlsG-cqndzNaig3alepqTV0zKhfyDw,4174
15
+ pants_baseline/rules/lint_rules.py,sha256=rYsbaPyYNDVDJn_dF91ews8iskcj2QoqnzZInLxkIzc,4219
16
+ pants_baseline/rules/test_rules.py,sha256=jj4lk3-mueOPujPFPK2TCdBFZZA_vovdDdHED9jGp7U,4225
17
+ pants_baseline/rules/typecheck_rules.py,sha256=XmL6uIXwjphB1YDVMrCM6A_Wa_1H-gHuLg8x59v_YQo,4499
18
+ pants_baseline/subsystems/__init__.py,sha256=LteH_qmUIgRAnXYmmi7f6o894QfpY3hMNH5dlvJbSoM,387
19
+ pants_baseline/subsystems/baseline.py,sha256=KWDRMdLOUN5cNHntY8f97bSsoITXwmX4ET6n2aW-cRk,2049
20
+ pants_baseline/subsystems/ruff.py,sha256=NW0AFv59-j6ANkZG8LjvuXPzWBD4yDsEr7Pb6eZbdck,3941
21
+ pants_baseline/subsystems/ty.py,sha256=OpaU8Z7Bk6kj5QAfhPom5L9v8sKNR8XF664_U5mUVJw,3419
22
+ pants_baseline/subsystems/uv.py,sha256=dXmVzg4ZxzHa4g_TowEZXMYuhKiTOHjqqadkNrZQ9jk,3066
23
+ jaymd96_pants_baseline-0.2.0.dist-info/METADATA,sha256=XsPPcHLtQ4TdFAAjbezNEDcPhcw_Hj7t68NiNwlfcIc,8764
24
+ jaymd96_pants_baseline-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
25
+ jaymd96_pants_baseline-0.2.0.dist-info/licenses/LICENSE,sha256=oLGLZv7XKM_oKCbdMW1bZB37SXsdexmhNSuh3Xg4m4I,10754
26
+ jaymd96_pants_baseline-0.2.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  """Version information for jaymd96-pants-baseline."""
2
2
 
3
- __version__ = "0.1.2"
3
+ __version__ = "0.2.0"
4
4
  __author__ = "James"
@@ -0,0 +1,29 @@
1
+ """Bundled Claude Code plugins for pants-baseline.
2
+
3
+ These plugins are automatically discovered and installed when users run:
4
+ pants claude-install --include-bundled ::
5
+
6
+ This requires the jaymd96-pants-claude-plugins package to be installed.
7
+ """
8
+
9
+ # Marketplaces to add before installing plugins
10
+ # The demo marketplace contains commit-commands and other workflow plugins
11
+ BUNDLED_MARKETPLACES = [
12
+ "anthropics/claude-code", # Demo plugins marketplace
13
+ ]
14
+
15
+ # Claude Code plugins to install
16
+ BUNDLED_CLAUDE_PLUGINS = [
17
+ {
18
+ "plugin": "github",
19
+ "marketplace": "claude-plugins-official",
20
+ "scope": "project",
21
+ "description": "GitHub integration for PR workflows and issue management",
22
+ },
23
+ {
24
+ "plugin": "commit-commands",
25
+ "marketplace": "anthropics-claude-code",
26
+ "scope": "project",
27
+ "description": "Git workflow commands for commits, pushes, and PRs",
28
+ },
29
+ ]
@@ -1,7 +1,5 @@
1
1
  """Audit goal for uv security scanning."""
2
2
 
3
- from __future__ import annotations
4
-
5
3
  from typing import Iterable
6
4
 
7
5
  from pants.engine.console import Console
@@ -25,6 +23,7 @@ class BaselineAudit(Goal):
25
23
  """Goal to run uv security audit."""
26
24
 
27
25
  subsystem_cls = BaselineAuditSubsystem
26
+ environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
28
27
 
29
28
 
30
29
  @goal_rule
@@ -49,12 +48,12 @@ async def run_baseline_audit(
49
48
  console.print_stdout(f" Ignoring: {', '.join(uv_subsystem.audit_ignore_vulns)}")
50
49
  console.print_stdout("")
51
50
 
52
- audit_request = UvAuditRequest(
51
+ audit_request: UvAuditRequest = UvAuditRequest(
53
52
  lock_file=uv_subsystem.lock_file,
54
53
  ignore_vulns=tuple(uv_subsystem.audit_ignore_vulns),
55
54
  output_format=uv_subsystem.output_format,
56
55
  )
57
- result = await Get(AuditResult, {UvAuditRequest: audit_request})
56
+ result = await Get(AuditResult, UvAuditRequest, audit_request)
58
57
 
59
58
  if result.stdout:
60
59
  console.print_stdout(result.stdout)
@@ -4,13 +4,16 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Iterable
6
6
 
7
+ from pants.core.goals.fmt import FmtResult
7
8
  from pants.engine.console import Console
8
9
  from pants.engine.goal import Goal, GoalSubsystem
9
- from pants.engine.rules import collect_rules, goal_rule
10
- from pants.engine.target import Targets
10
+ from pants.engine.rules import Get, collect_rules, goal_rule
11
+ from pants.engine.target import FilteredTargets
11
12
 
13
+ from pants_baseline.rules.fmt_rules import RuffFmtFieldSet, RuffFmtRequest
12
14
  from pants_baseline.subsystems.baseline import BaselineSubsystem
13
15
  from pants_baseline.subsystems.ruff import RuffSubsystem
16
+ from pants_baseline.targets import BaselineSourcesField
14
17
 
15
18
 
16
19
  class BaselineFmtSubsystem(GoalSubsystem):
@@ -24,12 +27,13 @@ class BaselineFmt(Goal):
24
27
  """Goal to run Ruff formatting."""
25
28
 
26
29
  subsystem_cls = BaselineFmtSubsystem
30
+ environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
27
31
 
28
32
 
29
33
  @goal_rule
30
34
  async def run_baseline_fmt(
31
35
  console: Console,
32
- targets: Targets,
36
+ targets: FilteredTargets,
33
37
  baseline_subsystem: BaselineSubsystem,
34
38
  ruff_subsystem: RuffSubsystem,
35
39
  ) -> BaselineFmt:
@@ -38,15 +42,37 @@ async def run_baseline_fmt(
38
42
  console.print_stdout("Python baseline is disabled.")
39
43
  return BaselineFmt(exit_code=0)
40
44
 
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("")
45
+ # Filter targets that have BaselineSourcesField
46
+ applicable_targets = [
47
+ t for t in targets
48
+ if t.has_field(BaselineSourcesField)
49
+ ]
47
50
 
48
- # The actual formatting is handled by the FmtTargetsRequest mechanism
49
- # This goal just provides a user-friendly entry point
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")
50
76
 
51
77
  return BaselineFmt(exit_code=0)
52
78
 
@@ -4,17 +4,16 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Iterable
6
6
 
7
- from pants.core.goals.lint import LintFilesRequest, LintResult
7
+ from pants.core.goals.lint import LintResult
8
8
  from pants.engine.console import Console
9
- from pants.engine.fs import PathGlobs, Paths
10
9
  from pants.engine.goal import Goal, GoalSubsystem
11
10
  from pants.engine.rules import Get, collect_rules, goal_rule
12
- from pants.engine.target import Targets
13
- from pants.util.logging import LogLevel
11
+ from pants.engine.target import FieldSet, FilteredTargets
14
12
 
15
- from pants_baseline.rules.lint_rules import RuffLintRequest
13
+ from pants_baseline.rules.lint_rules import RuffLintFieldSet, RuffLintRequest
16
14
  from pants_baseline.subsystems.baseline import BaselineSubsystem
17
15
  from pants_baseline.subsystems.ruff import RuffSubsystem
16
+ from pants_baseline.targets import BaselineSourcesField
18
17
 
19
18
 
20
19
  class BaselineLintSubsystem(GoalSubsystem):
@@ -28,12 +27,13 @@ class BaselineLint(Goal):
28
27
  """Goal to run Ruff linting."""
29
28
 
30
29
  subsystem_cls = BaselineLintSubsystem
30
+ environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
31
31
 
32
32
 
33
33
  @goal_rule
34
34
  async def run_baseline_lint(
35
35
  console: Console,
36
- targets: Targets,
36
+ targets: FilteredTargets,
37
37
  baseline_subsystem: BaselineSubsystem,
38
38
  ruff_subsystem: RuffSubsystem,
39
39
  ) -> BaselineLint:
@@ -42,16 +42,38 @@ async def run_baseline_lint(
42
42
  console.print_stdout("Python baseline is disabled.")
43
43
  return BaselineLint(exit_code=0)
44
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("")
45
+ # Filter targets that have BaselineSourcesField
46
+ applicable_targets = [
47
+ t for t in targets
48
+ if t.has_field(BaselineSourcesField)
49
+ ]
50
50
 
51
- # The actual linting is handled by the LintTargetsRequest mechanism
52
- # This goal just provides a user-friendly entry point
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}")
53
75
 
54
- return BaselineLint(exit_code=0)
76
+ return BaselineLint(exit_code=result.exit_code)
55
77
 
56
78
 
57
79
  def rules() -> Iterable:
@@ -23,6 +23,7 @@ class BaselineTest(Goal):
23
23
  """Goal to run pytest tests."""
24
24
 
25
25
  subsystem_cls = BaselineTestSubsystem
26
+ environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
26
27
 
27
28
 
28
29
  @goal_rule
@@ -4,13 +4,16 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Iterable
6
6
 
7
+ from pants.core.goals.check import CheckResults
7
8
  from pants.engine.console import Console
8
9
  from pants.engine.goal import Goal, GoalSubsystem
9
- from pants.engine.rules import collect_rules, goal_rule
10
- from pants.engine.target import Targets
10
+ from pants.engine.rules import Get, collect_rules, goal_rule
11
+ from pants.engine.target import FilteredTargets
11
12
 
13
+ from pants_baseline.rules.typecheck_rules import TyCheckRequest, TyFieldSet
12
14
  from pants_baseline.subsystems.baseline import BaselineSubsystem
13
15
  from pants_baseline.subsystems.ty import TySubsystem
16
+ from pants_baseline.targets import BaselineSourcesField
14
17
 
15
18
 
16
19
  class BaselineTypecheckSubsystem(GoalSubsystem):
@@ -24,12 +27,13 @@ class BaselineTypecheck(Goal):
24
27
  """Goal to run ty type checking."""
25
28
 
26
29
  subsystem_cls = BaselineTypecheckSubsystem
30
+ environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
27
31
 
28
32
 
29
33
  @goal_rule
30
34
  async def run_baseline_typecheck(
31
35
  console: Console,
32
- targets: Targets,
36
+ targets: FilteredTargets,
33
37
  baseline_subsystem: BaselineSubsystem,
34
38
  ty_subsystem: TySubsystem,
35
39
  ) -> BaselineTypecheck:
@@ -38,16 +42,42 @@ async def run_baseline_typecheck(
38
42
  console.print_stdout("Python baseline is disabled.")
39
43
  return BaselineTypecheck(exit_code=0)
40
44
 
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("")
45
+ # Filter targets that have BaselineSourcesField
46
+ applicable_targets = [
47
+ t for t in targets
48
+ if t.has_field(BaselineSourcesField)
49
+ ]
46
50
 
47
- # The actual type checking is handled by the CheckRequest mechanism
48
- # This goal just provides a user-friendly entry point
51
+ if not applicable_targets:
52
+ console.print_stdout("No baseline_python_project targets found.")
53
+ return BaselineTypecheck(exit_code=0)
49
54
 
50
- return BaselineTypecheck(exit_code=0)
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)
51
81
 
52
82
 
53
83
  def rules() -> Iterable:
@@ -6,7 +6,7 @@ It registers all rules, targets, and subsystems provided by this plugin.
6
6
 
7
7
  from typing import Iterable
8
8
 
9
- from pants.engine.rules import Rule, collect_rules
9
+ from pants.engine.rules import Rule
10
10
 
11
11
  from pants_baseline.goals import audit as audit_goal
12
12
  from pants_baseline.goals import fmt as fmt_goal
@@ -22,22 +22,23 @@ from pants_baseline.targets import BaselinePythonProject
22
22
 
23
23
 
24
24
  def rules() -> Iterable[Rule]:
25
- """Return all rules provided by this plugin."""
25
+ """Return all rules provided by this plugin.
26
+
27
+ Each module's rules() function returns both @rule decorated functions
28
+ AND UnionRule registrations. We must call the rules() functions directly
29
+ rather than using collect_rules() which only collects @rule functions.
30
+ """
26
31
  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(),
32
+ *lint_rules.rules(),
33
+ *fmt_rules.rules(),
34
+ *typecheck_rules.rules(),
35
+ *test_rules.rules(),
36
+ *audit_rules.rules(),
37
+ *lint_goal.rules(),
38
+ *fmt_goal.rules(),
39
+ *typecheck_goal.rules(),
40
+ *test_goal.rules(),
41
+ *audit_goal.rules(),
41
42
  ]
42
43
 
43
44
 
@@ -1,14 +1,17 @@
1
1
  """Rules for uv security auditing."""
2
2
 
3
- from __future__ import annotations
4
-
5
3
  from dataclasses import dataclass
6
4
  from typing import Iterable
7
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
8
9
  from pants.engine.process import FallibleProcessResult, Process
9
10
  from pants.engine.rules import Get, collect_rules, rule
10
11
  from pants.util.logging import LogLevel
11
12
 
13
+ from pants_baseline.subsystems.uv import UvSubsystem
14
+
12
15
 
13
16
  @dataclass(frozen=True)
14
17
  class AuditResult:
@@ -32,15 +35,37 @@ class UvAuditRequest:
32
35
  @rule(desc="Audit dependencies with uv", level=LogLevel.DEBUG)
33
36
  async def run_uv_audit(
34
37
  request: UvAuditRequest,
38
+ uv_subsystem: UvSubsystem,
39
+ platform: Platform,
35
40
  ) -> AuditResult:
36
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
+
37
61
  # Build ignore args
38
62
  ignore_args = []
39
63
  for vuln in request.ignore_vulns:
40
64
  ignore_args.extend(["--ignore", vuln])
41
65
 
42
66
  argv = [
43
- "uv",
67
+ downloaded_uv.exe,
68
+ "pip",
44
69
  "audit",
45
70
  f"--output-format={request.output_format}",
46
71
  *ignore_args,
@@ -48,11 +73,12 @@ async def run_uv_audit(
48
73
 
49
74
  process = Process(
50
75
  argv=argv,
76
+ input_digest=input_digest,
51
77
  description="Run uv security audit",
52
78
  level=LogLevel.DEBUG,
53
79
  )
54
80
 
55
- result = await Get(FallibleProcessResult, {Process: process})
81
+ result = await Get(FallibleProcessResult, Process, process)
56
82
 
57
83
  stdout = result.stdout.decode()
58
84
  stderr = result.stderr.decode()
@@ -1,17 +1,16 @@
1
1
  """Rules for Ruff formatting."""
2
2
 
3
- from __future__ import annotations
4
-
5
3
  from dataclasses import dataclass
6
4
  from typing import Iterable
7
5
 
8
- from pants.core.goals.fmt import FmtResult, FmtTargetsRequest
6
+ from pants.core.goals.fmt import FmtResult, FmtTargetsRequest, PartitionerType
7
+ from pants.core.util_rules.external_tool import DownloadedExternalTool, ExternalToolRequest
9
8
  from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest
10
- from pants.engine.fs import Digest, Snapshot
9
+ from pants.engine.fs import Digest, MergeDigests, Snapshot
10
+ from pants.engine.platform import Platform
11
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
12
+ from pants.engine.rules import Get, MultiGet, collect_rules, rule
13
+ from pants.engine.target import FieldSet, Target
15
14
  from pants.util.logging import LogLevel
16
15
 
17
16
  from pants_baseline.subsystems.baseline import BaselineSubsystem
@@ -28,63 +27,81 @@ class RuffFmtFieldSet(FieldSet):
28
27
  sources: BaselineSourcesField
29
28
  skip_fmt: SkipFormatField
30
29
 
30
+ @classmethod
31
+ def opt_out(cls, tgt: Target) -> bool:
32
+ """Allow targets to opt out of formatting."""
33
+ return tgt.get(SkipFormatField).value
34
+
31
35
 
32
36
  class RuffFmtRequest(FmtTargetsRequest):
33
37
  """Request to run Ruff formatting."""
34
38
 
35
39
  field_set_type = RuffFmtFieldSet
36
- tool_name = "ruff"
40
+ tool_subsystem = RuffSubsystem
41
+ partitioner_type = PartitionerType.DEFAULT_SINGLE_PARTITION
37
42
 
38
43
 
39
44
  @rule(desc="Format with Ruff", level=LogLevel.DEBUG)
40
45
  async def run_ruff_fmt(
41
- request: RuffFmtRequest,
46
+ request: RuffFmtRequest.Batch,
42
47
  ruff_subsystem: RuffSubsystem,
43
48
  baseline_subsystem: BaselineSubsystem,
49
+ platform: Platform,
44
50
  ) -> FmtResult:
45
51
  """Run Ruff formatter on Python files."""
52
+ field_sets = request.elements
53
+ snapshot = request.snapshot
54
+
46
55
  if not baseline_subsystem.enabled:
47
56
  return FmtResult(
48
- input=request.snapshot,
49
- output=request.snapshot,
57
+ input=snapshot,
58
+ output=snapshot,
50
59
  stdout="",
51
60
  stderr="",
52
61
  formatter_name="ruff",
53
62
  )
54
63
 
55
- # Filter out skipped targets
56
- field_sets = [fs for fs in request.field_sets if not fs.skip_fmt.value]
57
-
58
64
  if not field_sets:
59
65
  return FmtResult(
60
- input=request.snapshot,
61
- output=request.snapshot,
66
+ input=snapshot,
67
+ output=snapshot,
62
68
  stdout="No targets to format",
63
69
  stderr="",
64
70
  formatter_name="ruff",
65
71
  )
66
72
 
67
- # Get source files
68
- source_files_request = SourceFilesRequest(
69
- sources_fields=[fs.sources for fs in field_sets],
70
- for_sources_types=(BaselineSourcesField,),
73
+ # Download ruff and get source files in parallel
74
+ downloaded_ruff, sources = await MultiGet(
75
+ Get(DownloadedExternalTool, ExternalToolRequest, ruff_subsystem.get_request(platform)),
76
+ Get(
77
+ SourceFiles,
78
+ SourceFilesRequest(
79
+ sources_fields=[fs.sources for fs in field_sets],
80
+ for_sources_types=(BaselineSourcesField,),
81
+ ),
82
+ ),
71
83
  )
72
- sources = await Get(SourceFiles, {SourceFilesRequest: source_files_request})
73
84
 
74
85
  if not sources.files:
75
86
  return FmtResult(
76
- input=request.snapshot,
77
- output=request.snapshot,
87
+ input=snapshot,
88
+ output=snapshot,
78
89
  stdout="No files to format",
79
90
  stderr="",
80
91
  formatter_name="ruff",
81
92
  )
82
93
 
94
+ # Merge the ruff binary with the source files
95
+ input_digest = await Get(
96
+ Digest,
97
+ MergeDigests([downloaded_ruff.digest, sources.snapshot.digest]),
98
+ )
99
+
83
100
  # Build Ruff format command
84
101
  argv = [
85
- "ruff",
102
+ downloaded_ruff.exe,
86
103
  "format",
87
- f"--target-version={baseline_subsystem.get_python_target_version()}",
104
+ f"--target-version=py{baseline_subsystem.python_version.replace('.', '')}",
88
105
  f"--line-length={baseline_subsystem.line_length}",
89
106
  f"--quote-style={ruff_subsystem.quote_style}",
90
107
  f"--indent-style={ruff_subsystem.indent_style}",
@@ -93,15 +110,16 @@ async def run_ruff_fmt(
93
110
 
94
111
  process = Process(
95
112
  argv=argv,
96
- input_digest=sources.snapshot.digest,
113
+ input_digest=input_digest,
97
114
  output_files=sources.files,
98
115
  description=f"Run Ruff format on {len(sources.files)} files",
99
116
  level=LogLevel.DEBUG,
100
117
  )
101
118
 
102
- result = await Get(FallibleProcessResult, {Process: process})
119
+ result = await Get(FallibleProcessResult, Process, process)
103
120
 
104
- output_snapshot = await Get(Snapshot, {Digest: result.output_digest})
121
+ output_digest: Digest = result.output_digest
122
+ output_snapshot = await Get(Snapshot, Digest, output_digest)
105
123
 
106
124
  return FmtResult(
107
125
  input=sources.snapshot,
@@ -116,5 +134,5 @@ def rules() -> Iterable:
116
134
  """Return all format rules."""
117
135
  return [
118
136
  *collect_rules(),
119
- UnionRule(FmtTargetsRequest, RuffFmtRequest),
137
+ *RuffFmtRequest.rules(),
120
138
  ]
@@ -1,19 +1,17 @@
1
1
  """Rules for Ruff linting."""
2
2
 
3
- from __future__ import annotations
4
-
5
3
  from dataclasses import dataclass
6
4
  from typing import Iterable
7
5
 
8
- from pants.core.goals.lint import LintResult, LintTargetsRequest
6
+ from pants.core.goals.lint import LintResult, LintTargetsRequest, PartitionerType
9
7
  from pants.core.util_rules.external_tool import DownloadedExternalTool, ExternalToolRequest
8
+ from pants.core.util_rules.partitions import Partitions
10
9
  from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest
11
10
  from pants.engine.fs import Digest, MergeDigests
12
11
  from pants.engine.platform import Platform
13
12
  from pants.engine.process import FallibleProcessResult, Process
14
13
  from pants.engine.rules import Get, MultiGet, collect_rules, rule
15
14
  from pants.engine.target import FieldSet, Target
16
- from pants.engine.unions import UnionRule
17
15
  from pants.util.logging import LogLevel
18
16
 
19
17
  from pants_baseline.subsystems.baseline import BaselineSubsystem
@@ -30,26 +28,23 @@ class RuffLintFieldSet(FieldSet):
30
28
  sources: BaselineSourcesField
31
29
  skip_lint: SkipLintField
32
30
 
31
+ @classmethod
32
+ def opt_out(cls, tgt: Target) -> bool:
33
+ """Allow targets to opt out of linting."""
34
+ return tgt.get(SkipLintField).value
35
+
33
36
 
34
37
  class RuffLintRequest(LintTargetsRequest):
35
38
  """Request to run Ruff linting."""
36
39
 
37
40
  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
41
+ tool_subsystem = RuffSubsystem
42
+ partitioner_type = PartitionerType.DEFAULT_SINGLE_PARTITION
48
43
 
49
44
 
50
45
  @rule(desc="Lint with Ruff", level=LogLevel.DEBUG)
51
46
  async def run_ruff_lint(
52
- request: RuffLintRequest,
47
+ request: RuffLintRequest.Batch,
53
48
  ruff_subsystem: RuffSubsystem,
54
49
  baseline_subsystem: BaselineSubsystem,
55
50
  platform: Platform,
@@ -64,8 +59,7 @@ async def run_ruff_lint(
64
59
  partition_description=None,
65
60
  )
66
61
 
67
- # Filter out skipped targets
68
- field_sets = [fs for fs in request.field_sets if not fs.skip_lint.value]
62
+ field_sets = request.elements
69
63
 
70
64
  if not field_sets:
71
65
  return LintResult(
@@ -76,12 +70,17 @@ async def run_ruff_lint(
76
70
  partition_description=None,
77
71
  )
78
72
 
79
- # Get source files
80
- source_files_request = SourceFilesRequest(
81
- sources_fields=[fs.sources for fs in field_sets],
82
- for_sources_types=(BaselineSourcesField,),
73
+ # Download ruff and get source files in parallel
74
+ downloaded_ruff, sources = await MultiGet(
75
+ Get(DownloadedExternalTool, ExternalToolRequest, ruff_subsystem.get_request(platform)),
76
+ Get(
77
+ SourceFiles,
78
+ SourceFilesRequest(
79
+ sources_fields=[fs.sources for fs in field_sets],
80
+ for_sources_types=(BaselineSourcesField,),
81
+ ),
82
+ ),
83
83
  )
84
- sources = await Get(SourceFiles, {SourceFilesRequest: source_files_request})
85
84
 
86
85
  if not sources.files:
87
86
  return LintResult(
@@ -92,14 +91,20 @@ async def run_ruff_lint(
92
91
  partition_description=None,
93
92
  )
94
93
 
94
+ # Merge the ruff binary with the source files
95
+ input_digest = await Get(
96
+ Digest,
97
+ MergeDigests([downloaded_ruff.digest, sources.snapshot.digest]),
98
+ )
99
+
95
100
  # Build Ruff command
96
101
  select_args = [f"--select={','.join(ruff_subsystem.select)}"] if ruff_subsystem.select else []
97
102
  ignore_args = [f"--ignore={','.join(ruff_subsystem.ignore)}"] if ruff_subsystem.ignore else []
98
103
 
99
104
  argv = [
100
- "ruff",
105
+ downloaded_ruff.exe,
101
106
  "check",
102
- f"--target-version={baseline_subsystem.get_python_target_version()}",
107
+ f"--target-version=py{baseline_subsystem.python_version.replace('.', '')}",
103
108
  f"--line-length={baseline_subsystem.line_length}",
104
109
  *select_args,
105
110
  *ignore_args,
@@ -109,19 +114,19 @@ async def run_ruff_lint(
109
114
 
110
115
  process = Process(
111
116
  argv=argv,
112
- input_digest=sources.snapshot.digest,
117
+ input_digest=input_digest,
113
118
  description=f"Run Ruff lint on {len(sources.files)} files",
114
119
  level=LogLevel.DEBUG,
115
120
  )
116
121
 
117
- result = await Get(FallibleProcessResult, {Process: process})
122
+ result = await Get(FallibleProcessResult, Process, process)
118
123
 
119
124
  return LintResult(
120
125
  exit_code=result.exit_code,
121
126
  stdout=result.stdout.decode(),
122
127
  stderr=result.stderr.decode(),
123
128
  linter_name="ruff",
124
- partition_description=None,
129
+ partition_description=request.partition_metadata,
125
130
  )
126
131
 
127
132
 
@@ -129,5 +134,5 @@ def rules() -> Iterable:
129
134
  """Return all lint rules."""
130
135
  return [
131
136
  *collect_rules(),
132
- UnionRule(LintTargetsRequest, RuffLintRequest),
137
+ *RuffLintRequest.rules(),
133
138
  ]
@@ -1,7 +1,5 @@
1
1
  """Rules for pytest testing with coverage."""
2
2
 
3
- from __future__ import annotations
4
-
5
3
  from dataclasses import dataclass
6
4
  from typing import Iterable
7
5
 
@@ -73,18 +71,18 @@ async def run_pytest(
73
71
  )
74
72
 
75
73
  # Get test source files
76
- test_source_files_request = SourceFilesRequest(
74
+ test_source_files_request: SourceFilesRequest = SourceFilesRequest(
77
75
  sources_fields=[fs.test_sources for fs in field_sets],
78
76
  for_sources_types=(BaselineTestSourcesField,),
79
77
  )
80
- test_sources = await Get(SourceFiles, {SourceFilesRequest: test_source_files_request})
78
+ test_sources = await Get(SourceFiles, SourceFilesRequest, test_source_files_request)
81
79
 
82
80
  # Get source files for coverage
83
- source_files_request = SourceFilesRequest(
81
+ source_files_request: SourceFilesRequest = SourceFilesRequest(
84
82
  sources_fields=[fs.sources for fs in field_sets],
85
83
  for_sources_types=(BaselineSourcesField,),
86
84
  )
87
- sources = await Get(SourceFiles, {SourceFilesRequest: source_files_request})
85
+ sources = await Get(SourceFiles, SourceFilesRequest, source_files_request)
88
86
 
89
87
  if not test_sources.files:
90
88
  return TestResult(
@@ -121,14 +119,14 @@ async def run_pytest(
121
119
  *test_sources.files,
122
120
  ]
123
121
 
124
- process = Process(
122
+ process: Process = Process(
125
123
  argv=argv,
126
124
  input_digest=test_sources.snapshot.digest,
127
125
  description=f"Run pytest on {len(test_sources.files)} test files",
128
126
  level=LogLevel.DEBUG,
129
127
  )
130
128
 
131
- result = await Get(FallibleProcessResult, {Process: process})
129
+ result = await Get(FallibleProcessResult, Process, process)
132
130
 
133
131
  return TestResult(
134
132
  exit_code=result.exit_code,
@@ -1,15 +1,16 @@
1
1
  """Rules for ty type checking."""
2
2
 
3
- from __future__ import annotations
4
-
5
3
  from dataclasses import dataclass
6
4
  from typing import Iterable
7
5
 
8
6
  from pants.core.goals.check import CheckRequest, CheckResult, CheckResults
7
+ from pants.core.util_rules.external_tool import DownloadedExternalTool, ExternalToolRequest
9
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
10
11
  from pants.engine.process import FallibleProcessResult, Process
11
- from pants.engine.rules import Get, collect_rules, rule
12
- from pants.engine.target import FieldSet
12
+ from pants.engine.rules import Get, MultiGet, collect_rules, rule
13
+ from pants.engine.target import FieldSet, Target
13
14
  from pants.engine.unions import UnionRule
14
15
  from pants.util.logging import LogLevel
15
16
 
@@ -27,6 +28,11 @@ class TyFieldSet(FieldSet):
27
28
  sources: BaselineSourcesField
28
29
  skip_typecheck: SkipTypecheckField
29
30
 
31
+ @classmethod
32
+ def opt_out(cls, tgt: Target) -> bool:
33
+ """Allow targets to opt out of type checking."""
34
+ return tgt.get(SkipTypecheckField).value
35
+
30
36
 
31
37
  class TyCheckRequest(CheckRequest):
32
38
  """Request to run ty type checking."""
@@ -35,21 +41,12 @@ class TyCheckRequest(CheckRequest):
35
41
  tool_name = "ty"
36
42
 
37
43
 
38
- @dataclass(frozen=True)
39
- class TyCheckResult:
40
- """Result of running ty type check."""
41
-
42
- exit_code: int
43
- stdout: str
44
- stderr: str
45
- partition_description: str | None = None
46
-
47
-
48
44
  @rule(desc="Type check with ty", level=LogLevel.DEBUG)
49
45
  async def run_ty_check(
50
46
  request: TyCheckRequest,
51
47
  ty_subsystem: TySubsystem,
52
48
  baseline_subsystem: BaselineSubsystem,
49
+ platform: Platform,
53
50
  ) -> CheckResults:
54
51
  """Run ty type checker on Python files."""
55
52
  if not baseline_subsystem.enabled:
@@ -81,12 +78,17 @@ async def run_ty_check(
81
78
  checker_name="ty",
82
79
  )
83
80
 
84
- # Get source files
85
- source_files_request = SourceFilesRequest(
86
- sources_fields=[fs.sources for fs in field_sets],
87
- for_sources_types=(BaselineSourcesField,),
81
+ # Download ty and get source files in parallel
82
+ downloaded_ty, sources = await MultiGet(
83
+ Get(DownloadedExternalTool, ExternalToolRequest, ty_subsystem.get_request(platform)),
84
+ Get(
85
+ SourceFiles,
86
+ SourceFilesRequest(
87
+ sources_fields=[fs.sources for fs in field_sets],
88
+ for_sources_types=(BaselineSourcesField,),
89
+ ),
90
+ ),
88
91
  )
89
- sources = await Get(SourceFiles, {SourceFilesRequest: source_files_request})
90
92
 
91
93
  if not sources.files:
92
94
  return CheckResults(
@@ -101,12 +103,18 @@ async def run_ty_check(
101
103
  checker_name="ty",
102
104
  )
103
105
 
106
+ # Merge the ty binary with the source files
107
+ input_digest = await Get(
108
+ Digest,
109
+ MergeDigests([downloaded_ty.digest, sources.snapshot.digest]),
110
+ )
111
+
104
112
  # Build ty command
105
113
  strict_arg = ["--strict"] if ty_subsystem.strict else []
106
114
  output_format_arg = [f"--output-format={ty_subsystem.output_format}"]
107
115
 
108
116
  argv = [
109
- "ty",
117
+ downloaded_ty.exe,
110
118
  "check",
111
119
  f"--python-version={baseline_subsystem.python_version}",
112
120
  *strict_arg,
@@ -116,12 +124,12 @@ async def run_ty_check(
116
124
 
117
125
  process = Process(
118
126
  argv=argv,
119
- input_digest=sources.snapshot.digest,
127
+ input_digest=input_digest,
120
128
  description=f"Run ty type check on {len(sources.files)} files",
121
129
  level=LogLevel.DEBUG,
122
130
  )
123
131
 
124
- result = await Get(FallibleProcessResult, {Process: process})
132
+ result = await Get(FallibleProcessResult, Process, process)
125
133
 
126
134
  return CheckResults(
127
135
  results=[
@@ -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.option.option_types import BoolOption, StrListOption, StrOption
6
- from pants.option.subsystem import Subsystem
5
+ from pants.core.util_rules.external_tool import ExternalTool
6
+ from pants.engine.platform import Platform
7
+ from pants.option.option_types import BoolOption, SkipOption, StrListOption, StrOption
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,33 @@ 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"
44
+
45
+ # Skip option required by Pants for tool subsystems
46
+ skip = SkipOption("lint", "fmt")
23
47
 
24
48
  # Linting configuration
25
49
  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(
pants_baseline/targets.py CHANGED
@@ -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,25 +0,0 @@
1
- pants_baseline/__about__.py,sha256=emEUrAUfvUyL9_8_zLXhpGfpLneSXOrp3muo2ermC_k,98
2
- pants_baseline/__init__.py,sha256=uVRGi1D2gFjc7emmeewWdcvpO-NsUuKsMbX3rztOxWU,655
3
- pants_baseline/register.py,sha256=AnQrzGYcPY4f7jMvgDVvs9bo-oY1XgOAwEO3fo813yg,1656
4
- pants_baseline/targets.py,sha256=G8ejj3QYaGHtLHbxirTyTjnEAWYxlpHFHYKV-_B4lMo,3300
5
- pants_baseline/goals/__init__.py,sha256=pf6KU2CIQuDkx8ER3IS0H-kuNbBtX-AH5B5SnSP9_yw,192
6
- pants_baseline/goals/audit.py,sha256=pzw1iczFb2yZKUXzQ9oggXUikv3uoZ-Nc5tVFJeTRLA,2339
7
- pants_baseline/goals/fmt.py,sha256=QNeQHBqR-wdLKE0oTkI3jtE3sFOmOp4EP0VXGkmG6Gg,1731
8
- pants_baseline/goals/lint.py,sha256=hvIRPd-zfh5zC6Q_RvEWSbqJyszGEtvCpPdKwrTe0Vo,1867
9
- pants_baseline/goals/test.py,sha256=cUSE7NyuXaEtZnBe22MqPAu-Fqjtn5oCpttmeU1XIk4,1590
10
- pants_baseline/goals/typecheck.py,sha256=LuuOW3lvzOhgsNf5-W0SC1jvNnI5Nt4ZQAGwOPg6ZoA,1706
11
- pants_baseline/rules/__init__.py,sha256=UpvDpGVImhRfp2_VeUNsRPGiWjBbMI6AV1-Yx3kS0Gg,252
12
- pants_baseline/rules/audit_rules.py,sha256=aauxy-yjah19EVBjWDOcwsRvQAIhsAtLg5VOKCbsRaM,1918
13
- pants_baseline/rules/fmt_rules.py,sha256=dxUyDcRHPPk04_q2ITDKu8VEvHV-vDFDaY99YCvE_yk,3534
14
- pants_baseline/rules/lint_rules.py,sha256=BKoGyfO66hSnieMkZ8f0ghpoVQR0eSIZtZglmCPkPEA,3862
15
- pants_baseline/rules/test_rules.py,sha256=gZug806LGT2Vr_GFhUdjAaRrg9RbVda-_78XhXcr0f0,4218
16
- pants_baseline/rules/typecheck_rules.py,sha256=JxkvJ6lJn_UvDF4IMMmmBAPWiw6kms_hTrdUE_EAzs8,4006
17
- pants_baseline/subsystems/__init__.py,sha256=LteH_qmUIgRAnXYmmi7f6o894QfpY3hMNH5dlvJbSoM,387
18
- pants_baseline/subsystems/baseline.py,sha256=M20muLytoWZnA4ZY3lkbSsdCXt6OMPH6IMJfXULAQSo,2049
19
- pants_baseline/subsystems/ruff.py,sha256=v0yHmbdgsPMMgjxJrs-YaKPE_n7rE6h-D0HGhXheyyE,2601
20
- pants_baseline/subsystems/ty.py,sha256=6M205VZD5TpX6T4vWZ4UFs5Y3T2rGqYtzidmiRh_6kg,2126
21
- pants_baseline/subsystems/uv.py,sha256=K6G8KR3l-Jo18aMh183gfhEelJR57cb20yIqKUMw3K0,1851
22
- jaymd96_pants_baseline-0.1.2.dist-info/METADATA,sha256=iahXBTWMePOuc64POXU-cljsgB4nyDv2eT1ALiXmrIM,7934
23
- jaymd96_pants_baseline-0.1.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
24
- jaymd96_pants_baseline-0.1.2.dist-info/licenses/LICENSE,sha256=oLGLZv7XKM_oKCbdMW1bZB37SXsdexmhNSuh3Xg4m4I,10754
25
- jaymd96_pants_baseline-0.1.2.dist-info/RECORD,,