Skip to content

feat: implement multiple constraint files#1129

Open
tiran wants to merge 1 commit intopython-wheel-build:mainfrom
tiran:multiple-constraint-files
Open

feat: implement multiple constraint files#1129
tiran wants to merge 1 commit intopython-wheel-build:mainfrom
tiran:multiple-constraint-files

Conversation

@tiran
Copy link
Copy Markdown
Collaborator

@tiran tiran commented May 8, 2026

Pull Request Description

What

Fromager now supports multiple constraints files with -c / --constraints-file argument. Multiple constraints are merged and validated.

Example:

$ fromager \
    -c constraints.txt \
    -c local-constraints.txt \
    -c https://company.example/security-constraints.txt \
    bootstrap ...

Local and remote constraints are loaded in WorkContext.setup() and
dumped into a new file merged-constraints.txt in work-dir. Some
internals of WorkContext have changed in an API-incompatible way:

  • constraints_file argument is now
    constraints_files: tuple[str, ...] = ()
  • WorkContext now only accepts keyword arguments
  • input_constraints_uri is replaced by input_constraints_files

Why

Fixes: #1096

@tiran tiran requested a review from a team as a code owner May 8, 2026 15:55
@mergify mergify Bot added the ci label May 8, 2026
@tiran tiran added the enhancement New feature or request label May 8, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 66caded0-8f12-4ac4-a167-4e0d52ea147b

📥 Commits

Reviewing files that changed from the base of the PR and between f1e461d and 49d36b9.

📒 Files selected for processing (10)
  • docs/how-tos/bootstrap-constraints.rst
  • docs/spelling_wordlist.txt
  • src/fromager/__main__.py
  • src/fromager/constraints.py
  • src/fromager/context.py
  • tests/conftest.py
  • tests/test_cli.py
  • tests/test_constraints.py
  • tests/test_context.py
  • tests/test_cooldown.py
💤 Files with no reviewable changes (2)
  • tests/test_cooldown.py
  • tests/conftest.py
✅ Files skipped from review due to trivial changes (3)
  • docs/spelling_wordlist.txt
  • docs/how-tos/bootstrap-constraints.rst
  • tests/test_cli.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/fromager/constraints.py
  • src/fromager/main.py
  • tests/test_context.py
  • src/fromager/context.py

📝 Walkthrough

Walkthrough

This PR refactors the constraints system to support multiple constraint files with automatic merging. The Constraints class gains bool and dump_constraints to enable serialization of merged constraints. WorkContext now accepts a tuple of constraints_files, loads each input (file or URL), and writes work-dir/merged-constraints.txt during setup; pip_constraint_args points to that merged file when present. The CLI --constraints-file option collects multiple values and passes them through. Tests and documentation were updated to cover behavior and examples.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: implement multiple constraint files' accurately summarizes the main change: adding support for multiple constraint files via repeated -c arguments.
Description check ✅ Passed The description clearly explains what changed (multiple constraints files support), provides a usage example, documents API-breaking changes, and references the fixed issue #1096.
Linked Issues check ✅ Passed The PR fully implements both objectives from #1096: combining constraints with satisfiability validation [constraints.py] and extending --constraints-file to accept multiple files [main.py, context.py].
Out of Scope Changes check ✅ Passed All changes align with the scope of issue #1096: implementing multiple constraint files, merging logic, validation, and related documentation and test updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rd4398
Copy link
Copy Markdown
Contributor

rd4398 commented May 8, 2026

I think @smoparth has a similar PR #1128

I am afraid we need to close one?

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 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 `@docs/how-tos/bootstrap-constraints.rst`:
- Around line 47-49: The phrase "For examples" in the sentence starting with
"For examples ``egg>=1.0`` and ``egg!=1.1.2``..." is a typo; replace "For
examples" with "For example" so the sentence reads "For example ``egg>=1.0`` and
``egg!=1.1.2`` are combined into ``egg>=1.0,!=1.1.2``." Locate and edit that
exact string in the document.

In `@src/fromager/constraints.py`:
- Around line 83-88: Add a proper docstring to the public method
dump_constraints describing its behavior and parameters (e.g., that it writes
sorted constraints without markers to the given TextIO), promoting the existing
inline comment into the docstring; update the function signature for
dump_constraints(self, output: typing.TextIO) to include the docstring at the
top of the function body, mentioning that constraints are sorted by normalized
name and that markers were evaluated in add_constraint(), and leave the
implementation (iterating over self._data and writing
f"{req.name}{req.specifier}\n") unchanged.

In `@tests/test_cli.py`:
- Around line 99-121: The test leaks state because it doesn't pass
-O/--output-dir to the CLI and relies on log output; modify
test_multiple_constraints_files to add "-O", str(tmp_path) (or "--output-dir",
str(tmp_path)) to the cli_runner.invoke args so WorkContext.setup() uses the
temp dir, then after invoking fromager read the merged constraints file that the
CLI writes into that output dir (the merged constraints artifact created by the
command) and assert its contents contain "foo==1.0" and the merged constraint
"bar!=2.1.1,>=2.0" instead of checking result.output; reference
WorkContext.setup(), the CLI option -O/--output-dir, and
test_multiple_constraints_files to locate where to change the test.
🪄 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: 502cbff3-998c-4443-83a5-48685ffcd411

📥 Commits

Reviewing files that changed from the base of the PR and between b30aae7 and f1e461d.

📒 Files selected for processing (10)
  • docs/how-tos/bootstrap-constraints.rst
  • docs/spelling_wordlist.txt
  • src/fromager/__main__.py
  • src/fromager/constraints.py
  • src/fromager/context.py
  • tests/conftest.py
  • tests/test_cli.py
  • tests/test_constraints.py
  • tests/test_context.py
  • tests/test_cooldown.py
💤 Files with no reviewable changes (2)
  • tests/conftest.py
  • tests/test_cooldown.py

Comment thread docs/how-tos/bootstrap-constraints.rst Outdated
Comment thread src/fromager/constraints.py
Comment thread tests/test_cli.py
Comment on lines +99 to +121
def test_multiple_constraints_files(
tmp_path: pathlib.Path, cli_runner: CliRunner
) -> None:
constraints1 = tmp_path / "constraints1.txt"
constraints1.write_text("foo==1.0\nbar!=2.1.1\n")
constraints2 = tmp_path / "constraints2.txt"
constraints2.write_text("bar>=2.0\n")

result = cli_runner.invoke(
fromager,
[
"--verbose",
"-c",
str(constraints1),
"--constraints-file",
str(constraints2),
"lint",
],
)
assert result.exit_code == 0, result.output
assert "foo==1.0" in result.output
assert "bar!=2.1.1,>=2.0" in result.output

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Two issues: missing --output-dir and log-coupled assertions.

  1. Test isolation: Without -O/--output-dir, WorkContext.setup() creates work-dir/ in the current working directory, leaking filesystem state across test runs.

  2. Brittle output assertions: "foo==1.0" in result.output and "bar!=2.1.1,>=2.0" in result.output work only because --verbose surfaces the logger.debug(...) that embeds the merged file content. Per project learnings, prefer asserting functional artifacts over log strings.

Fix both by passing -O str(tmp_path) and reading the merged constraints file directly:

🛠️ Proposed fix
 def test_multiple_constraints_files(
     tmp_path: pathlib.Path, cli_runner: CliRunner
 ) -> None:
     constraints1 = tmp_path / "constraints1.txt"
     constraints1.write_text("foo==1.0\nbar!=2.1.1\n")
     constraints2 = tmp_path / "constraints2.txt"
     constraints2.write_text("bar>=2.0\n")

     result = cli_runner.invoke(
         fromager,
         [
-            "--verbose",
+            "-O",
+            str(tmp_path / "output"),
             "-c",
             str(constraints1),
             "--constraints-file",
             str(constraints2),
             "lint",
         ],
     )
     assert result.exit_code == 0, result.output
-    assert "foo==1.0" in result.output
-    assert "bar!=2.1.1,>=2.0" in result.output
+    merged = tmp_path / "output" / "work-dir" / "merged-constraints.txt"
+    content = merged.read_text()
+    assert "foo==1.0" in content
+    assert "bar!=2.1.1,>=2.0" in content
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_multiple_constraints_files(
tmp_path: pathlib.Path, cli_runner: CliRunner
) -> None:
constraints1 = tmp_path / "constraints1.txt"
constraints1.write_text("foo==1.0\nbar!=2.1.1\n")
constraints2 = tmp_path / "constraints2.txt"
constraints2.write_text("bar>=2.0\n")
result = cli_runner.invoke(
fromager,
[
"--verbose",
"-c",
str(constraints1),
"--constraints-file",
str(constraints2),
"lint",
],
)
assert result.exit_code == 0, result.output
assert "foo==1.0" in result.output
assert "bar!=2.1.1,>=2.0" in result.output
def test_multiple_constraints_files(
tmp_path: pathlib.Path, cli_runner: CliRunner
) -> None:
constraints1 = tmp_path / "constraints1.txt"
constraints1.write_text("foo==1.0\nbar!=2.1.1\n")
constraints2 = tmp_path / "constraints2.txt"
constraints2.write_text("bar>=2.0\n")
result = cli_runner.invoke(
fromager,
[
"-O",
str(tmp_path / "output"),
"-c",
str(constraints1),
"--constraints-file",
str(constraints2),
"lint",
],
)
assert result.exit_code == 0, result.output
merged = tmp_path / "output" / "work-dir" / "merged-constraints.txt"
content = merged.read_text()
assert "foo==1.0" in content
assert "bar!=2.1.1,>=2.0" in content
🤖 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_cli.py` around lines 99 - 121, The test leaks state because it
doesn't pass -O/--output-dir to the CLI and relies on log output; modify
test_multiple_constraints_files to add "-O", str(tmp_path) (or "--output-dir",
str(tmp_path)) to the cli_runner.invoke args so WorkContext.setup() uses the
temp dir, then after invoking fromager read the merged constraints file that the
CLI writes into that output dir (the merged constraints artifact created by the
command) and assert its contents contain "foo==1.0" and the merged constraint
"bar!=2.1.1,>=2.0" instead of checking result.output; reference
WorkContext.setup(), the CLI option -O/--output-dir, and
test_multiple_constraints_files to locate where to change the test.

@tiran tiran force-pushed the multiple-constraint-files branch from f1e461d to 6c779c5 Compare May 8, 2026 16:31
Comment thread tests/test_constraints.py Outdated
text="remote>=1.0\n",
)
c.load_constraints_file(url)
assert c.get_constraint("remote") == Requirement("remote>1.0")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: assertion uses remote>1.0 but the mocked response is "remote>=1.0\n". Should be Requirement("remote>=1.0").

Fromager now supports multiple constraints files with `-c` /
``--constraints-file`` argument. Multiple constraints are merged and
validated.

Example:
```console
$ fromager \
    -c constraints.txt \
    -c local-constraints.txt \
    -c https://company.example/security-constraints.txt \
    bootstrap ...
```

Local and remote constraints are loaded in `WorkContext.setup()` and
dumped into a new file `merged-constraints.txt` in `work-dir`. Some
internals of `WorkContext` have changed in an API-incompatible way:

- `constraints_file` argument is now
  `constraints_files: tuple[str, ...] = ()`
- `WorkContext` now only accepts keyword arguments
- `input_constraints_uri` is replaced by `input_constraints_files`

Fixes: python-wheel-build#1096
Signed-off-by: Christian Heimes <[email protected]>
@tiran tiran force-pushed the multiple-constraint-files branch from 6c779c5 to 49d36b9 Compare May 9, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support multiple constraints and constraint files

3 participants