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.
@@ -0,0 +1,149 @@
1
+ """Rules for pytest testing with coverage."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Iterable
7
+
8
+ from pants.core.goals.test import TestRequest, TestResult
9
+ from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest
10
+ from pants.engine.process import FallibleProcessResult, Process
11
+ from pants.engine.rules import Get, collect_rules, rule
12
+ from pants.engine.target import FieldSet
13
+ from pants.engine.unions import UnionRule
14
+ from pants.util.logging import LogLevel
15
+
16
+ from pants_baseline.subsystems.baseline import BaselineSubsystem
17
+ from pants_baseline.targets import (
18
+ BaselineSourcesField,
19
+ BaselineTestSourcesField,
20
+ CoverageThresholdField,
21
+ SkipTestField,
22
+ )
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class PytestFieldSet(FieldSet):
27
+ """Field set for pytest testing."""
28
+
29
+ required_fields = (BaselineTestSourcesField,)
30
+
31
+ sources: BaselineSourcesField
32
+ test_sources: BaselineTestSourcesField
33
+ coverage_threshold: CoverageThresholdField
34
+ skip_test: SkipTestField
35
+
36
+
37
+ class PytestTestRequest(TestRequest):
38
+ """Request to run pytest tests."""
39
+
40
+ field_set_type = PytestFieldSet
41
+ tool_name = "pytest"
42
+
43
+
44
+ @rule(desc="Test with pytest", level=LogLevel.DEBUG)
45
+ async def run_pytest(
46
+ request: PytestTestRequest,
47
+ baseline_subsystem: BaselineSubsystem,
48
+ ) -> TestResult:
49
+ """Run pytest on test files with coverage."""
50
+ if not baseline_subsystem.enabled:
51
+ return TestResult(
52
+ exit_code=0,
53
+ stdout="",
54
+ stderr="",
55
+ stdout_digest=None,
56
+ stderr_digest=None,
57
+ address=None,
58
+ output_setting=None,
59
+ )
60
+
61
+ # Filter out skipped targets
62
+ field_sets = [fs for fs in request.field_sets if not fs.skip_test.value]
63
+
64
+ if not field_sets:
65
+ return TestResult(
66
+ exit_code=0,
67
+ stdout="No targets to test",
68
+ stderr="",
69
+ stdout_digest=None,
70
+ stderr_digest=None,
71
+ address=None,
72
+ output_setting=None,
73
+ )
74
+
75
+ # Get test source files
76
+ test_source_files_request = SourceFilesRequest(
77
+ sources_fields=[fs.test_sources for fs in field_sets],
78
+ for_sources_types=(BaselineTestSourcesField,),
79
+ )
80
+ test_sources = await Get(SourceFiles, {SourceFilesRequest: test_source_files_request})
81
+
82
+ # Get source files for coverage
83
+ source_files_request = SourceFilesRequest(
84
+ sources_fields=[fs.sources for fs in field_sets],
85
+ for_sources_types=(BaselineSourcesField,),
86
+ )
87
+ sources = await Get(SourceFiles, {SourceFilesRequest: source_files_request})
88
+
89
+ if not test_sources.files:
90
+ return TestResult(
91
+ exit_code=0,
92
+ stdout="No test files found",
93
+ stderr="",
94
+ stdout_digest=None,
95
+ stderr_digest=None,
96
+ address=None,
97
+ output_setting=None,
98
+ )
99
+
100
+ # Get coverage threshold from first field set (or use default)
101
+ coverage_threshold = (
102
+ field_sets[0].coverage_threshold.value
103
+ if field_sets
104
+ else baseline_subsystem.coverage_threshold
105
+ )
106
+
107
+ # Build pytest command with coverage
108
+ src_root = ",".join(baseline_subsystem.src_roots)
109
+
110
+ argv = [
111
+ "pytest",
112
+ "-v",
113
+ "--strict-markers",
114
+ "--strict-config",
115
+ "-ra",
116
+ "--tb=short",
117
+ f"--cov={src_root}",
118
+ "--cov-report=term-missing",
119
+ f"--cov-fail-under={coverage_threshold}",
120
+ "--cov-branch",
121
+ *test_sources.files,
122
+ ]
123
+
124
+ process = Process(
125
+ argv=argv,
126
+ input_digest=test_sources.snapshot.digest,
127
+ description=f"Run pytest on {len(test_sources.files)} test files",
128
+ level=LogLevel.DEBUG,
129
+ )
130
+
131
+ result = await Get(FallibleProcessResult, {Process: process})
132
+
133
+ return TestResult(
134
+ exit_code=result.exit_code,
135
+ stdout=result.stdout.decode(),
136
+ stderr=result.stderr.decode(),
137
+ stdout_digest=None,
138
+ stderr_digest=None,
139
+ address=None,
140
+ output_setting=None,
141
+ )
142
+
143
+
144
+ def rules() -> Iterable:
145
+ """Return all test rules."""
146
+ return [
147
+ *collect_rules(),
148
+ UnionRule(TestRequest, PytestTestRequest),
149
+ ]
@@ -0,0 +1,144 @@
1
+ """Rules for ty type checking."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Iterable
7
+
8
+ from pants.core.goals.check import CheckRequest, CheckResult, CheckResults
9
+ from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest
10
+ from pants.engine.process import FallibleProcessResult, Process
11
+ from pants.engine.rules import Get, collect_rules, rule
12
+ from pants.engine.target import FieldSet
13
+ from pants.engine.unions import UnionRule
14
+ from pants.util.logging import LogLevel
15
+
16
+ from pants_baseline.subsystems.baseline import BaselineSubsystem
17
+ from pants_baseline.subsystems.ty import TySubsystem
18
+ from pants_baseline.targets import BaselineSourcesField, SkipTypecheckField
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class TyFieldSet(FieldSet):
23
+ """Field set for ty type checking."""
24
+
25
+ required_fields = (BaselineSourcesField,)
26
+
27
+ sources: BaselineSourcesField
28
+ skip_typecheck: SkipTypecheckField
29
+
30
+
31
+ class TyCheckRequest(CheckRequest):
32
+ """Request to run ty type checking."""
33
+
34
+ field_set_type = TyFieldSet
35
+ tool_name = "ty"
36
+
37
+
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
+ @rule(desc="Type check with ty", level=LogLevel.DEBUG)
49
+ async def run_ty_check(
50
+ request: TyCheckRequest,
51
+ ty_subsystem: TySubsystem,
52
+ baseline_subsystem: BaselineSubsystem,
53
+ ) -> CheckResults:
54
+ """Run ty type checker on Python files."""
55
+ if not baseline_subsystem.enabled:
56
+ return CheckResults(
57
+ results=[
58
+ CheckResult(
59
+ exit_code=0,
60
+ stdout="",
61
+ stderr="",
62
+ partition_description=None,
63
+ )
64
+ ],
65
+ checker_name="ty",
66
+ )
67
+
68
+ # Filter out skipped targets
69
+ field_sets = [fs for fs in request.field_sets if not fs.skip_typecheck.value]
70
+
71
+ if not field_sets:
72
+ return CheckResults(
73
+ results=[
74
+ CheckResult(
75
+ exit_code=0,
76
+ stdout="No targets to type check",
77
+ stderr="",
78
+ partition_description=None,
79
+ )
80
+ ],
81
+ checker_name="ty",
82
+ )
83
+
84
+ # Get source files
85
+ source_files_request = SourceFilesRequest(
86
+ sources_fields=[fs.sources for fs in field_sets],
87
+ for_sources_types=(BaselineSourcesField,),
88
+ )
89
+ sources = await Get(SourceFiles, {SourceFilesRequest: source_files_request})
90
+
91
+ if not sources.files:
92
+ return CheckResults(
93
+ results=[
94
+ CheckResult(
95
+ exit_code=0,
96
+ stdout="No files to type check",
97
+ stderr="",
98
+ partition_description=None,
99
+ )
100
+ ],
101
+ checker_name="ty",
102
+ )
103
+
104
+ # Build ty command
105
+ strict_arg = ["--strict"] if ty_subsystem.strict else []
106
+ output_format_arg = [f"--output-format={ty_subsystem.output_format}"]
107
+
108
+ argv = [
109
+ "ty",
110
+ "check",
111
+ f"--python-version={baseline_subsystem.python_version}",
112
+ *strict_arg,
113
+ *output_format_arg,
114
+ *sources.files,
115
+ ]
116
+
117
+ process = Process(
118
+ argv=argv,
119
+ input_digest=sources.snapshot.digest,
120
+ description=f"Run ty type check on {len(sources.files)} files",
121
+ level=LogLevel.DEBUG,
122
+ )
123
+
124
+ result = await Get(FallibleProcessResult, {Process: process})
125
+
126
+ return CheckResults(
127
+ results=[
128
+ CheckResult(
129
+ exit_code=result.exit_code,
130
+ stdout=result.stdout.decode(),
131
+ stderr=result.stderr.decode(),
132
+ partition_description=None,
133
+ )
134
+ ],
135
+ checker_name="ty",
136
+ )
137
+
138
+
139
+ def rules() -> Iterable:
140
+ """Return all type check rules."""
141
+ return [
142
+ *collect_rules(),
143
+ UnionRule(CheckRequest, TyCheckRequest),
144
+ ]
@@ -0,0 +1,13 @@
1
+ """Subsystems for Python Baseline plugin configuration."""
2
+
3
+ from pants_baseline.subsystems.baseline import BaselineSubsystem
4
+ from pants_baseline.subsystems.ruff import RuffSubsystem
5
+ from pants_baseline.subsystems.ty import TySubsystem
6
+ from pants_baseline.subsystems.uv import UvSubsystem
7
+
8
+ __all__ = [
9
+ "BaselineSubsystem",
10
+ "RuffSubsystem",
11
+ "TySubsystem",
12
+ "UvSubsystem",
13
+ ]
@@ -0,0 +1,74 @@
1
+ """Main baseline subsystem for coordinating all quality tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pants.option.option_types import BoolOption, IntOption, StrListOption, StrOption
6
+ from pants.option.subsystem import Subsystem
7
+
8
+
9
+ class BaselineSubsystem(Subsystem):
10
+ """Configuration for the Python Baseline quality framework.
11
+
12
+ This subsystem provides global configuration for all baseline tools
13
+ including Ruff, ty, pytest, and uv audit.
14
+ """
15
+
16
+ options_scope = "python-baseline"
17
+ help = "Opinionated Python code quality baseline configuration."
18
+
19
+ # Global settings
20
+ enabled = BoolOption(
21
+ default=True,
22
+ help="Whether to enable the Python baseline checks globally.",
23
+ )
24
+
25
+ python_version = StrOption(
26
+ default="3.11",
27
+ help="Target Python version for all tools (e.g., '3.11', '3.12').",
28
+ )
29
+
30
+ line_length = IntOption(
31
+ default=120,
32
+ help="Maximum line length for formatting and linting.",
33
+ )
34
+
35
+ src_roots = StrListOption(
36
+ default=["src"],
37
+ help="Source root directories for Python code.",
38
+ )
39
+
40
+ test_roots = StrListOption(
41
+ default=["tests"],
42
+ help="Test root directories.",
43
+ )
44
+
45
+ exclude_patterns = StrListOption(
46
+ default=[
47
+ ".venv",
48
+ ".git",
49
+ "__pycache__",
50
+ "dist",
51
+ "build",
52
+ ".pytest_cache",
53
+ ".ruff_cache",
54
+ "migrations",
55
+ ],
56
+ help="Patterns to exclude from all baseline checks.",
57
+ )
58
+
59
+ # Coverage threshold
60
+ coverage_threshold = IntOption(
61
+ default=80,
62
+ help="Minimum code coverage percentage required.",
63
+ )
64
+
65
+ # Strictness level
66
+ strict_mode = BoolOption(
67
+ default=True,
68
+ help="Enable strict mode for all tools (more rigorous checks).",
69
+ )
70
+
71
+ def get_python_target_version(self) -> str:
72
+ """Return Python version in format suitable for tools (e.g., 'py311')."""
73
+ version = self.python_version.replace(".", "")
74
+ return f"py{version}"
@@ -0,0 +1,90 @@
1
+ """Ruff subsystem for linting and formatting configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pants.option.option_types import BoolOption, StrListOption, StrOption
6
+ from pants.option.subsystem import Subsystem
7
+
8
+
9
+ class RuffSubsystem(Subsystem):
10
+ """Configuration for Ruff linting and formatting.
11
+
12
+ Ruff is an extremely fast Python linter and formatter, written in Rust.
13
+ It replaces Black, isort, Flake8, and many other tools in a single binary.
14
+ """
15
+
16
+ options_scope = "baseline-ruff"
17
+ help = "Ruff linting and formatting configuration for baseline plugin."
18
+
19
+ version = StrOption(
20
+ default="0.2.0",
21
+ help="Version of Ruff to use.",
22
+ )
23
+
24
+ # Linting configuration
25
+ select = StrListOption(
26
+ default=[
27
+ "E", # pycodestyle errors
28
+ "W", # pycodestyle warnings
29
+ "F", # pyflakes
30
+ "I", # isort
31
+ "N", # pep8-naming
32
+ "UP", # pyupgrade
33
+ "B", # flake8-bugbear
34
+ "C4", # flake8-comprehensions
35
+ "SIM", # flake8-simplify
36
+ "ASYNC", # flake8-async
37
+ "DTZ", # flake8-datetimez
38
+ "PIE", # flake8-pie
39
+ "RUF", # Ruff-specific rules
40
+ ],
41
+ help="Rule codes to enable for linting.",
42
+ )
43
+
44
+ ignore = StrListOption(
45
+ default=[
46
+ "E501", # line too long (handled by formatter)
47
+ "W292", # blank line at end of file
48
+ ],
49
+ help="Rule codes to ignore.",
50
+ )
51
+
52
+ # Formatting configuration
53
+ quote_style = StrOption(
54
+ default="double",
55
+ help="Quote style for formatting ('double' or 'single').",
56
+ )
57
+
58
+ indent_style = StrOption(
59
+ default="space",
60
+ help="Indentation style ('space' or 'tab').",
61
+ )
62
+
63
+ # Auto-fix
64
+ fix = BoolOption(
65
+ default=True,
66
+ help="Automatically fix auto-fixable issues.",
67
+ )
68
+
69
+ unsafe_fixes = BoolOption(
70
+ default=False,
71
+ help="Allow unsafe fixes that may change code behavior.",
72
+ )
73
+
74
+ # Per-file ignores (common patterns)
75
+ skip_tests_rules = StrListOption(
76
+ default=[
77
+ "F401", # unused imports OK in tests
78
+ "F811", # redefined function OK in tests
79
+ "S101", # assert OK in tests
80
+ ],
81
+ help="Rules to skip in test files.",
82
+ )
83
+
84
+ skip_init_rules = StrListOption(
85
+ default=[
86
+ "F401", # unused imports (exports)
87
+ "F403", # star imports OK for namespace
88
+ ],
89
+ help="Rules to skip in __init__.py files.",
90
+ )
@@ -0,0 +1,80 @@
1
+ """ty subsystem for type checking configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pants.option.option_types import BoolOption, StrListOption, StrOption
6
+ from pants.option.subsystem import Subsystem
7
+
8
+
9
+ class TySubsystem(Subsystem):
10
+ """Configuration for ty type checker.
11
+
12
+ ty is Astral's next-generation Python type checker, designed for
13
+ exceptional performance (10-60x faster than MyPy, 80x faster than
14
+ Pyright on incremental edits).
15
+
16
+ Features:
17
+ - LSP-first architecture for IDE integration
18
+ - Incremental checking optimized for real-time feedback
19
+ - First-class intersection types and reachability analysis
20
+ - Rust implementation with obsessive performance focus
21
+ """
22
+
23
+ options_scope = "baseline-ty"
24
+ help = "ty type checker configuration for baseline plugin (Astral's next-gen type checker)."
25
+
26
+ version = StrOption(
27
+ default="0.1.0",
28
+ help="Version of ty to use.",
29
+ )
30
+
31
+ # Type checking mode
32
+ strict = BoolOption(
33
+ default=True,
34
+ help="Enable strict type checking mode.",
35
+ )
36
+
37
+ # Error reporting
38
+ report_missing_imports = BoolOption(
39
+ default=True,
40
+ help="Report errors for missing imports.",
41
+ )
42
+
43
+ report_unused_imports = BoolOption(
44
+ default=True,
45
+ help="Report errors for unused imports.",
46
+ )
47
+
48
+ report_unused_variables = BoolOption(
49
+ default=True,
50
+ help="Report errors for unused variables.",
51
+ )
52
+
53
+ # Include/exclude paths
54
+ include = StrListOption(
55
+ default=["src", "tests"],
56
+ help="Directories to include in type checking.",
57
+ )
58
+
59
+ exclude = StrListOption(
60
+ default=[
61
+ ".venv",
62
+ ".git",
63
+ "__pycache__",
64
+ "dist",
65
+ "build",
66
+ ],
67
+ help="Patterns to exclude from type checking.",
68
+ )
69
+
70
+ # Type stub handling
71
+ stub_path = StrOption(
72
+ default="",
73
+ help="Path to custom type stubs directory.",
74
+ )
75
+
76
+ # Output format
77
+ output_format = StrOption(
78
+ default="text",
79
+ help="Output format for type errors ('text', 'json', 'github').",
80
+ )
@@ -0,0 +1,66 @@
1
+ """uv subsystem for dependency management and security auditing."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pants.option.option_types import BoolOption, StrListOption, StrOption
6
+ from pants.option.subsystem import Subsystem
7
+
8
+
9
+ class UvSubsystem(Subsystem):
10
+ """Configuration for uv dependency management and security auditing.
11
+
12
+ uv is Astral's ultra-fast Python package installer and resolver,
13
+ written in Rust. It's 10-100x faster than pip for dependency resolution.
14
+
15
+ This subsystem configures uv for:
16
+ - Dependency security auditing (vulnerability scanning)
17
+ - Lock file management
18
+ - Virtual environment management
19
+ """
20
+
21
+ options_scope = "baseline-uv"
22
+ help = "uv dependency management and security auditing configuration for baseline plugin."
23
+
24
+ version = StrOption(
25
+ default="0.5.0",
26
+ help="Version of uv to use.",
27
+ )
28
+
29
+ # Security auditing
30
+ audit_enabled = BoolOption(
31
+ default=True,
32
+ help="Enable dependency security auditing.",
33
+ )
34
+
35
+ audit_ignore_vulns = StrListOption(
36
+ default=[],
37
+ help="Vulnerability IDs to ignore (e.g., 'GHSA-xxxx-xxxx-xxxx').",
38
+ )
39
+
40
+ audit_fail_on_warning = BoolOption(
41
+ default=False,
42
+ help="Fail the audit if any warnings are found (not just errors).",
43
+ )
44
+
45
+ # Lock file settings
46
+ lock_file = StrOption(
47
+ default="uv.lock",
48
+ help="Path to the uv lock file.",
49
+ )
50
+
51
+ require_lock = BoolOption(
52
+ default=True,
53
+ help="Require a lock file to exist for auditing.",
54
+ )
55
+
56
+ # Output format
57
+ output_format = StrOption(
58
+ default="text",
59
+ help="Output format for audit results ('text', 'json', 'github').",
60
+ )
61
+
62
+ # Additional arguments
63
+ extra_args = StrListOption(
64
+ default=[],
65
+ help="Additional arguments to pass to uv commands.",
66
+ )