Repository navigation
feat(graph): add graph check command for pre-rebuild conflict detection - #999
stmcgovern wants to merge 4 commits into
Conversation
|
Warning Review limit reached
Next review available in: 45 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis change adds a new Estimated code review effort: 4 (Complex) | ~45 minutes 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/fromager/commands/graph.py (2)
1075-1100: Consider making--jsonand--constraintsmutually exclusive.If both flags are specified,
--constraintssilently takes precedence. This could surprise users.Option: Add a click mutex constraint
`@click.option`( "--json", "as_json", is_flag=True, default=False, help="Output results as JSON.", ) `@click.option`( "--constraints", "as_constraints", is_flag=True, default=False, help="Output collapsible pins in constraints.txt format.", )You could add validation at the start of
check():if as_json and as_constraints: raise click.UsageError("--json and --constraints are mutually exclusive")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/fromager/commands/graph.py` around lines 1075 - 1100, The check() command accepts both --json (as_json) and --constraints (as_constraints) but currently lets --constraints silently win; add an explicit mutual-exclusion check at the start of the check() function to detect if both as_json and as_constraints are True and raise a click.UsageError (with a clear message like "--json and --constraints are mutually exclusive"). This change should be implemented inside the check() function body that receives wkctx, graph_file, as_json, as_constraints so any callers get an immediate, user-facing error instead of surprising behavior.
1113-1129: Avoid reading the graph file twice.The graph JSON is loaded at line 1115, then re-read at line 1127 via
from_file. Usefrom_dict(graph_dict)instead.Proposed fix
if not wf_issues: - graph = DependencyGraph.from_file(graph_file) + graph = DependencyGraph.from_dict(graph_dict) entries = _classify_conflicts(graph) n_nodes = len(graph) - 1 # exclude root🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/fromager/commands/graph.py` around lines 1113 - 1129, The code currently loads graph_file into graph_dict and later re-reads the file via DependencyGraph.from_file; replace that second read with DependencyGraph.from_dict(graph_dict) to avoid double I/O: after verifying wf_issues and acyclicity (using _check_well_formed and _check_acyclicity), call DependencyGraph.from_dict(graph_dict) (not from_file) to build the graph used by _classify_conflicts and to compute n_nodes, keeping the existing logic for entries = _classify_conflicts(graph) and n_nodes = len(graph) - 1.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/fromager/commands/graph.py`:
- Around line 1075-1100: The check() command accepts both --json (as_json) and
--constraints (as_constraints) but currently lets --constraints silently win;
add an explicit mutual-exclusion check at the start of the check() function to
detect if both as_json and as_constraints are True and raise a click.UsageError
(with a clear message like "--json and --constraints are mutually exclusive").
This change should be implemented inside the check() function body that receives
wkctx, graph_file, as_json, as_constraints so any callers get an immediate,
user-facing error instead of surprising behavior.
- Around line 1113-1129: The code currently loads graph_file into graph_dict and
later re-reads the file via DependencyGraph.from_file; replace that second read
with DependencyGraph.from_dict(graph_dict) to avoid double I/O: after verifying
wf_issues and acyclicity (using _check_well_formed and _check_acyclicity), call
DependencyGraph.from_dict(graph_dict) (not from_file) to build the graph used by
_classify_conflicts and to compute n_nodes, keeping the existing logic for
entries = _classify_conflicts(graph) and n_nodes = len(graph) - 1.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 59054cbc-aa40-420f-b03f-7f71ab591faa
📒 Files selected for processing (2)
src/fromager/commands/graph.pytests/test_graph_validate.py
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/test_graph_check.py (1)
347-360: Consider adding assertion forbuild_efficiencyfield.The JSON output includes
build_efficiencywithtotal_wheelsandextra_buildsper the implementation, but no test verifies this field. Minor gap.assert "build_efficiency" in data assert "total_wheels" in data["build_efficiency"]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/test_graph_check.py` around lines 347 - 360, The test_clean_graph_json test is missing assertions for the build_efficiency section of the output; update the test (in test_clean_graph_json) to assert that "build_efficiency" exists in the parsed data dict and that it contains the expected keys (e.g., "total_wheels" and "extra_builds") and optionally that those values are present/not-None (use the local variable data to check keys and simple truthiness or type). Ensure assertions reference data["build_efficiency"]["total_wheels"] and data["build_efficiency"]["extra_builds"] so the test will fail if those fields are absent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@tests/test_graph_check.py`:
- Around line 347-360: The test_clean_graph_json test is missing assertions for
the build_efficiency section of the output; update the test (in
test_clean_graph_json) to assert that "build_efficiency" exists in the parsed
data dict and that it contains the expected keys (e.g., "total_wheels" and
"extra_builds") and optionally that those values are present/not-None (use the
local variable data to check keys and simple truthiness or type). Ensure
assertions reference data["build_efficiency"]["total_wheels"] and
data["build_efficiency"]["extra_builds"] so the test will fail if those fields
are absent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 923cb1b7-9918-481e-aa54-06d541398075
📒 Files selected for processing (1)
tests/test_graph_check.py
…tion Add a pre-rebuild check that answers two questions in seconds: 1. Is this graph structurally sound? (well-formed, acyclic) 2. How many wheels are extra? (collapsible vs required) The conflict classification uses the same specifier.filter logic as show_explain_duplicates and write_constraints_file. It is conservative: collapsible here guarantees write_constraints_file succeeds. Self-loops (e.g. safetensors[numpy]) are reported as warnings, not failures — they are common in extras and harmless for install deps. Real cycles (e.g. haystack-ai ↔ haystack-experimental) cause failure. Supports --json for CI integration and --constraints for pin output. Closes: python-wheel-build#998
…e file read Address review feedback: raise UsageError when both output flags are passed, and use from_dict(graph_dict) instead of re-reading the file.
Align test filename with the command name per CONTRIBUTING.md convention.
8679050 to
59d2482
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/fromager/commands/graph.py (1)
1086-1114: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winRecursive DFS may hit Python's recursion limit on deep dependency chains.
dfs()recurses per edge traversed; depth equals the longest dependency chain. Fromager targets deep AI/ML dependency trees, so a sufficiently long chain could raiseRecursionErroron what's meant to be a fast, reliable pre-rebuild gate. Consider converting to an explicit stack-based DFS.♻️ Iterative alternative (sketch)
def dfs(start: str) -> None: stack = [(start, iter(graph_dict.get(start, {}).get("edges", [])))] color[start] = in_stack path.append(start) while stack: key, edges = stack[-1] advanced = False for edge in edges: tgt = edge["key"] if tgt == key: continue if tgt not in color: continue if color[tgt] == in_stack: cycle_start = path.index(tgt) cycles.append(f"CYCLE: {' -> '.join(path[cycle_start:] + [tgt])}") elif color[tgt] == unvisited: color[tgt] = in_stack path.append(tgt) stack.append((tgt, iter(graph_dict.get(tgt, {}).get("edges", [])))) advanced = True break if not advanced: path.pop() color[key] = done stack.pop()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/fromager/commands/graph.py` around lines 1086 - 1114, The cycle-detection DFS in the graph-building logic uses recursion and can overflow on long dependency chains. Replace the recursive dfs() used in the cycle detection block with an explicit stack-based traversal, preserving the existing color/path/cycles behavior while avoiding RecursionError; update the implementation around dfs, path, and the graph_dict edge walk accordingly.tests/test_graph_check.py (1)
332-575: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winNo test for missing/invalid graph file.
The CLI test class covers structural and conflict scenarios well, but nothing exercises
graph check <nonexistent-file>or a malformed-JSON file — the path whereopen()/json.load()currently produce a raw traceback (see companion comment insrc/fromager/commands/graph.py). Once that's fixed withClickPath(exists=True)/JSON error handling, a test asserting a cleanclick.UsageError/ClickExceptionwould lock in the behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_graph_check.py` around lines 332 - 575, Add coverage in TestCheckCLI for invalid input paths: exercise graph check with a nonexistent file and with a malformed JSON file. The current tests cover only valid graphs, so add cases that invoke fromager with a bad path or bad JSON and assert a clean ClickException/UsageError instead of a traceback. Use the existing helpers _write_graph, cli_runner.invoke, and the graph check command invocation to keep the new tests aligned with the other TestCheckCLI scenarios.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/fromager/commands/graph.py`:
- Around line 1344-1372: The check command’s graph-file argument currently
bypasses the same missing-file validation used elsewhere, so a bad path falls
through to open(graph_file) and json.load with a traceback. Update the check CLI
in graph.py to use the existing Click path validation pattern used by
find_best_fit, ideally clickext.ClickPath(exists=True), so missing files produce
a clean Click error before parsing. Keep the change localized to the check
function and its graph-file argument definition.
---
Nitpick comments:
In `@src/fromager/commands/graph.py`:
- Around line 1086-1114: The cycle-detection DFS in the graph-building logic
uses recursion and can overflow on long dependency chains. Replace the recursive
dfs() used in the cycle detection block with an explicit stack-based traversal,
preserving the existing color/path/cycles behavior while avoiding
RecursionError; update the implementation around dfs, path, and the graph_dict
edge walk accordingly.
In `@tests/test_graph_check.py`:
- Around line 332-575: Add coverage in TestCheckCLI for invalid input paths:
exercise graph check with a nonexistent file and with a malformed JSON file. The
current tests cover only valid graphs, so add cases that invoke fromager with a
bad path or bad JSON and assert a clean ClickException/UsageError instead of a
traceback. Use the existing helpers _write_graph, cli_runner.invoke, and the
graph check command invocation to keep the new tests aligned with the other
TestCheckCLI scenarios.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d5ac1689-4344-48eb-b50a-0ae6316b5a07
📒 Files selected for processing (2)
src/fromager/commands/graph.pytests/test_graph_check.py
Use `clickext.ClickPath(exists=True)` for the graph-file argument, matching the pattern used by `find-best-fit` in the same file. Wrap `json.load` with a `JSONDecodeError` handler for malformed input. Addresses CodeRabbit review feedback on PR python-wheel-build#999. Signed-off-by: Sean McGovern <smcgover@redhat.com>
Pull Request Description
What
Add a pre-rebuild check that answers two questions in seconds:
The conflict classification uses the same specifier.filter logic as show_explain_duplicates and write_constraints_file. It is conservative: collapsible here guarantees write_constraints_file succeeds.
Self-loops (e.g. safetensors[numpy]) are reported as warnings, not failures — they are common in extras and harmless for install deps. Real cycles (e.g. haystack-ai ↔ haystack-experimental) cause failure.
Supports --json for CI integration and --constraints for pin output.
Why
Closes: #998