Skip to content

[Automated] Update pip CLI Options - #4554

Open
thomhurst wants to merge 1 commit into
mainfrom
automated/update-cli-options-pip
Open

[Automated] Update pip CLI Options#4554
thomhurst wants to merge 1 commit into
mainfrom
automated/update-cli-options-pip

Conversation

@thomhurst

@thomhurst thomhurst commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Summary

This PR contains automatically generated updates to pip CLI options classes.

The generator scraped the latest CLI help output from the installed tool.

Changes

  • Updated options classes to reflect latest CLI documentation
  • Added new commands if any were detected
  • Updated option types and descriptions

Assembly-wide public API impact

Affected API families: Assembly/common, Pip.

  • Added APIs: 33
  • Removed or changed APIs: 69
  • Members with matching names but changed signatures: 29

Breaking changes are present. Consumers may need to update method arguments, option property types or nullability, enum members, and references to removed APIs.

Representative removed or changed members:

  • ModularPipelines.Python.Options.PipDownloadOptions.Abi.get -> string?
  • ModularPipelines.Python.Options.PipDownloadOptions.AbiValues.get -> System.Collections.Generic.IEnumerable<string!>?
  • ModularPipelines.Python.Options.PipDownloadOptions.AbiValues.set -> void
  • ModularPipelines.Python.Options.PipDownloadOptions.Constraint.get -> string?
  • ModularPipelines.Python.Options.PipDownloadOptions.ConstraintValues.get -> System.Collections.Generic.IEnumerable<string!>?

Representative added members:

  • ModularPipelines.Python.Options.PipDownloadOptions.Abi.get -> System.Collections.Generic.IEnumerable<string!>?
  • ModularPipelines.Python.Options.PipDownloadOptions.Constraint.get -> System.Collections.Generic.IEnumerable<string!>?
  • ModularPipelines.Python.Options.PipDownloadOptions.Platform.get -> System.Collections.Generic.IEnumerable<string!>?
  • ModularPipelines.Python.Options.PipDownloadOptions.RequireHashes.get -> bool?
  • ModularPipelines.Python.Options.PipDownloadOptions.Requirement.get -> System.Collections.Generic.IEnumerable<string!>?

Command coverage

Command coverage report:

  • pip (pip 24.0 from /usr/lib/python3/dist-packages/pip (python 3.12)): 14 commands, tree a9b92993f96a5cee7b1131d5da44c679e8bd64ee6a736a1d92dc95e915bd18ac
    • Baseline comparison: 14 commands at pip 24.0 from /usr/lib/python3/dist-packages/pip (python 3.12) -> 14 commands at pip 24.0 from /usr/lib/python3/dist-packages/pip (python 3.12)

Verification

  • Solution builds successfully

🤖 Generated with ModularPipelines.OptionsGenerator

@thomhurst thomhurst added automated dependencies Pull requests that update a dependency file labels Sep 3, 2026
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • src/ModularPipelines.Python/Generated/Pip.Generation.json is excluded by !**/generated/**

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 46f0e0b7-97dd-47d3-9b58-4ac9537f795d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: d6b0162a-00ef-4a55-8b5d-b8a4d6d3e028

📥 Commits

Reviewing files that changed from the base of the PR and between 68dea97 and f78e951.

⛔ Files ignored due to path filters (3)
  • src/ModularPipelines.Python/Generated/Pip.Generation.json is excluded by !**/generated/**
  • src/ModularPipelines.Python/Options/PipUninstallOptions.Generated.cs is excluded by !**/*.generated.*
  • src/ModularPipelines.Python/Options/PipWheelOptions.Generated.cs is excluded by !**/*.generated.*
📒 Files selected for processing (1)
  • src/ModularPipelines.Python/PublicAPI.Unshipped.txt

Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

The change updates the public API declarations for pip option types. It removes obsolete getters, plural properties, constructors, validation, and an extension method. It also changes property types and requires non-nullable options for selected IPip methods.

Changes

Pip option API contracts

Layer / File(s) Summary
Shipped API cleanup
src/ModularPipelines.Python/PublicAPI.Shipped.txt
Removes selected pip option property getters and parameterless constructors while retaining setters and copy constructors where applicable.
Unshipped API normalization
src/ModularPipelines.Python/PublicAPI.Unshipped.txt
Changes properties to enumerable or boolean types, removes plural *Values properties, updates constructors and IPip method parameters, and removes obsolete API members.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to f78e9

The pip option API declarations have been updated with no remaining identified merge-blocking risk.

Poem

A rabbit checks each pip-shaped line
Getters hop out in neat design
New types gather, clear and bright
Constructors leave by moonlit night
The API rests, precise and light

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.54% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 26 functions across 5 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: an automated update to the pip CLI option classes. It is concise and related to the API and option updates described in the pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 11.54% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 26 functions across 5 files. (1 skipped: 1 unsupported.)

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch automated/update-cli-options-pip

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.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

Reviewed the auto-generated pip CLI options update. The bulk of the diff is mechanical regeneration (doc-comment tweaks, PublicAPI.Shipped.txt churn), but two of the regenerated files introduce a real, user-facing correctness bug via the "operand becomes a required primary-constructor argument" pattern.

Blocking

1. PipUninstallOptions: Validate() and Required = true on Package contradict each other
src/ModularPipelines.Python/Options/PipUninstallOptions.Generated.cs:57-79

Package is now a required primary-constructor argument (Required = true), but the new Validate() method explicitly allows Package to be empty as long as Requirement is set — matching pip's real behavior (pip uninstall -r requirements.txt is valid without any package names). Since Validate() doesn't cover the CLI-argument-building path, CommandArgumentBuilder.AddArguments (src/ModularPipelines/Helpers/Internal/CommandArgumentBuilder.cs:142-146) unconditionally throws ArgumentException for a Required argument with zero values, regardless of the validation outcome. A caller doing new PipUninstallOptions([]) { Requirement = ["requirements.txt"] } passes Validate() but still throws when the command is built — the two mechanisms disagree about what's valid.

2. PipWheelOptions: RequirementSpecifier made hard-required with no OR-alternative at all
src/ModularPipelines.Python/Options/PipWheelOptions.Generated.cs:18-93

Same pattern as above (RequirementSpecifier is now a required constructor arg), but unlike PipUninstallOptions, PipWheelOptions got no Validate() override to allow the -r/--requirement alternative. pip wheel -r requirements.txt (no positional package names) is a legitimate, common invocation that this options type can no longer represent — the constructor forces a non-empty RequirementSpecifier, and CommandArgumentBuilder throws on an empty required argument either way.

Suggested direction

Both stem from the same generator change: promoting the primary positional operand to a required constructor parameter doesn't account for pip subcommands where that operand is optional-if-another-option-is-set. Rather than patching each generated file by hand (they'll be overwritten on next regeneration per this repo's conventions), the fix belongs in the generator/scraper: either (a) don't mark an operand Required = true when the tool's own help text shows a mutually-exclusive alternative (-r/--requirement), keeping it as a settable optional property validated via IValidatableObject the way PipUninstallOptions.Package was clearly intended to work, or (b) if CommandArgumentBuilder is meant to be the single source of truth for required-argument enforcement, make it consult IValidatableObject.Validate() results (or a "required-unless" attribute) before throwing, so the two required-ness checks can't diverge. Right now correctness depends on both mechanisms agreeing, and this PR shows they can silently drift apart.

Everything else in the diff (doc-comment escaping, PublicAPI.Shipped.txt/Unshipped.txt updates, the new Pip.Generation.json baseline) looks like accurate, mechanical regeneration output and needs no changes.

@thomhurst

Copy link
Copy Markdown
Owner Author

Fixed both required-alternative findings at exact head a680d77. pip uninstall/wheel primary operands are now optional and paired with -r/--requirement as a required alternative. Focused tests: 31/31; full generator suite: 1,265/1,265. Authoritative pip regeneration is running: https://github.com/thomhurst/ModularPipelines/actions/runs/33746336829

@claude please re-review this exact head after generated output lands.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

Re-reviewed at the current head (a680d770388a33342622afdf353e586a550965af), following up on the prior blocking review at 801283b4f3ad48b44422565ecd18e1bda170509f and the author's comment that "both required-alternative findings" were fixed.

Blocking: the fix landed in the generator, but not in this PR's shipped generated code

The commit a680d770 (fix(pip): model requirement alternatives) only touches:

  • tools/ModularPipelines.OptionsGenerator/.../PipCliScraper.cs
  • tools/ModularPipelines.OptionsGenerator/.../PositionalOperandAdapterTests.cs

git diff 801283b4...a680d770 -- src/ModularPipelines.Python/ is empty — none of the generated artifacts changed. Concretely, at the current head:

  • src/ModularPipelines.Python/Options/PipUninstallOptions.Generated.cs:24 still declares Package as [CliArgument(0, ..., Required = true)], unchanged from the previously-reviewed bad state, still contradicted by its own Validate() (lines 173-179) which allows Package to be empty when Requirement is set.
  • src/ModularPipelines.Python/Options/PipWheelOptions.Generated.cs:22 still declares RequirementSpecifier as Required = true with no Validate()/IValidatableObject at all — pip wheel -r requirements.txt (no positional packages) still can't be represented.
  • src/ModularPipelines.Python/PublicAPI.Shipped.txt, PublicAPI.Unshipped.txt, and Generated/Pip.Generation.json are likewise unchanged since the last review.

The scraper fix itself is well targeted — it now marks the uninstall/wheel primary operand as optional and registers a RequiredAlternativeGroup with -r/--requirement (PipCliScraper.cs:185-257), and Generators/OptionsClassGenerator.cs does consume RequiredAlternativeGroups to emit the corresponding Validate() logic, so regenerating would produce different, correct output. But the PR as it stands doesn't regenerate: the referenced "authoritative pip regeneration" run (https://github.com/thomhurst/ModularPipelines/actions/runs/33746336829) is still queued against this exact head, so its output hasn't landed here.

Per this repo's own convention ("Current tool output is the sole source of truth for generated options" / "Do not edit this file manually" / "changes will be overwritten"), the generated .Generated.cs, PublicAPI.*.txt, and Pip.Generation.json files must reflect the fixed scraper before this can merge — right now they still reproduce the exact bug flagged previously (a caller can pass Validate() with only Requirement set, then hit ArgumentException from CommandArgumentBuilder.AddArguments when the command is actually built, because the required-primary-constructor-arg check and Validate() disagree).

Suggested next step: wait for/re-run the regeneration workflow against this branch (or run the generator locally) so the generated files, PublicAPI.*.txt, and Pip.Generation.json pick up the new RequiredAlternativeGroups behavior, then re-review the resulting diff.

Everything else in the diff (the new PositionalOperandAdapterTests cases, unrelated doc-comment churn in PipDownloadOptions.Generated.cs/PipInstallOptions.Generated.cs/IPip.Generated.cs/Pip.Generated.cs) looks correct and needs no changes.

@thomhurst

Copy link
Copy Markdown
Owner Author

@claude Please review the latest changes.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

Re-reviewed at the current head (755f1dcccfa9ea272638abd7cf80f701c11fbb23), following up on the prior blocking review at a680d770388a33342622afdf353e586a550965af, which noted that the scraper fix for the required-alternative pattern hadn't yet produced regenerated output.

Previously blocking issue is now resolved

The diff between a680d770 and 755f1dcc is exactly the regeneration that was pending:

  • src/ModularPipelines.Python/Options/PipUninstallOptions.Generated.cs: Package is no longer a required primary-constructor argument (Required = true) — it's now an optional settable property (IEnumerable<string>? Package { get; set; }), consistent with the existing Validate() override that requires Package or Requirement.
  • src/ModularPipelines.Python/Options/PipWheelOptions.Generated.cs: RequirementSpecifier is likewise now optional, and the class gained a matching Validate() implementing IValidatableObject, requiring RequirementSpecifier or Requirement — mirroring the PipUninstallOptions pattern.
  • PublicAPI.Unshipped.txt updated accordingly (constructors/Deconstruct removed for the now-optional properties, Validate entries added).

I checked CommandArgumentBuilder.AddArguments (src/ModularPipelines/Helpers/Internal/CommandArgumentBuilder.cs:141-147): since neither CliArgument attribute sets Required = true anymore, it no longer throws when the positional operand is empty and only -r/--requirement is supplied. The two mechanisms (constructor-required vs. Validate()) no longer disagree — new PipWheelOptions { Requirement = ["requirements.txt"] } and the equivalent for PipUninstallOptions now both pass validation and build a valid command line, matching real pip usage (pip wheel -r requirements.txt, pip uninstall -r requirements.txt).

No other files changed between the two commits (confirmed via git diff --stat), and the rest of the PR's diff (PipDownloadOptions.Generated.cs, PipInstallOptions.Generated.cs, IPip.Generated.cs, Pip.Generated.cs, Pip.Generation.json, the scraper, and the new PositionalOperandAdapterTests cases) was already reviewed as correct mechanical regeneration output in the prior pass and is unchanged here.

No further issues found.

@thomhurst

Copy link
Copy Markdown
Owner Author

Synced current main conflict-free at exact head $(01d77c5.Substring(0,10)) to register required CI contexts. The pip generated diff is unchanged; CI restarted. @claude please review this exact head.

@thomhurst
thomhurst force-pushed the automated/update-cli-options-pip branch from 01d77c5 to d7f04c3 Compare September 3, 2026 13:39

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review

Reviewed at head d7f04c347d3b655d1b79b2ae2a2b8bd9cd9841af. This is the auto-generated pip options regen, including the "required alternative" fix mentioned in the PR thread (making pip uninstall/pip wheel's positional operand pair with -r/--requirement).

Blocking: the required-alternative fix for PipUninstallOptions can't actually be used, and PipWheelOptions never got it

src/ModularPipelines.Python/Options/PipUninstallOptions.Generated.cs (lines 57–59, 73–79) adds IValidatableObject so that Package and Requirement are a required alternative pair — i.e. you should be able to call pip uninstall with only -r requirements.txt and no positional packages. But the positional argument keeps Required = true:

public record PipUninstallOptions(
    [property: CliArgument(0, Phase = CommandLinePhase.Passthrough, Required = true)] IEnumerable<string> Package
) : PipOptions, IValidatableObject

CommandLineBuilder runs the friendly IValidatableObject.Validate check first (CommandLineBuilder.cs:55), then separately builds the actual CLI arguments (CommandLineBuilder.cs:110CommandArgumentBuilder.AddArguments). That second step has its own, unconditional check (CommandArgumentBuilder.cs:142-148):

if (argumentPart.Attribute.Required && values.Count == 0)
{
    throw new ArgumentException(
        $"Required CLI argument '{optionsType.Name}.{argumentPart.PropertyName}' cannot be null or empty.",
        argumentPart.PropertyName);
}

So new PipUninstallOptions([]) { Requirement = ["requirements.txt"] } passes the friendly IValidatableObject check (since Requirement is present) but then always throws ArgumentException: Required CLI argument 'PipUninstallOptions.Package' cannot be null or empty when the command is actually built — the exact scenario the fix was meant to enable. The IValidatableObject validation is dead code for the "requirement-file-only" path.

PipWheelOptions (lines 91–93) didn't get the alternative-group treatment at all — RequirementSpecifier is still hard Required = true with no IValidatableObject, so pip wheel -r requirements.txt with no positional operand is impossible to express, which is a regression from before this PR (previously RequirementSpecifier was a plain optional IEnumerable<string>? property).

Root cause: the generic required-alternative resolution in CliScraperBase.cs (ResolveRequiredAlternativeGroups/TryResolveRequiredAlternativeGroup) never demotes a positional argument's required-ness when it becomes part of an alternative group — it only carries over IsRequired, and OptionsClassGenerator.GetPositionalAttributeString falls back to positional.IsValidationRequired ?? positional.IsRequired. KubectlCliScraper already has the correct pattern for this (explicitly setting IsValidationRequired = false on positionals that get alternative/omittable handling, e.g. KubectlCliScraper.cs:106,130,134,159), but that's kubectl-specific. Per this repo's guidance to fix generator problems generically rather than per-tool, ResolveRequiredAlternativeGroups/TryResolveRequiredAlternativeGroup should set IsValidationRequired = false on any positional argument it folds into a CliRequiredAlternativeGroup, so every tool with this pattern (not just pip) gets a working required-alternative instead of a Required = true CLI argument that silently defeats the IValidatableObject check. That also explains why wheel didn't pick up the group — worth checking why its usage synopsis wasn't recognized as containing an alternative, since as generated it's strictly worse than the pre-PR behavior.

This isn't something to hand-fix in the generated .Generated.cs files per CLAUDE.md ("fix generated-options problems in the scraper or generator, not in generated output") — the fix belongs in CliScraperBase.cs, and this PR should be regenerated after that fix lands.

Everything else in the diff (the <requirement specifier> doc-comment tweak, the PublicAPI.Shipped/Unshipped.txt bookkeeping, and dropping the options = null defaults on UninstallAsync/WheelAsync now that the option types require a positional value) is consistent with the code changes and the project's "generated output is the source of truth, breaking changes are expected" policy.

@thomhurst

Copy link
Copy Markdown
Owner Author

Restored the two previously reviewed Pip requirement-alternative commits after the automated branch was force-updated from stale main and dropped them. Command tree remains identical (pip 24.0, a9b92993…). Validation: PositionalOperandAdapterTests 31/31; ModularPipelines.Python.slnx Release build clean. Please re-review the new exact head.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review

Re-reviewed at head 21a8fd3b612309031821983812ace27ccf213082, following up on the prior blocking review at d7f04c347d3b655d1b79b2ae2a2b8bd9cd9841af, which flagged that the "required alternative" fix for pip uninstall/pip wheel didn't actually work because the positional operand kept Required = true alongside the new IValidatableObject check.

That specific bug is fixed. Both PipUninstallOptions.Package and PipWheelOptions.RequirementSpecifier are now generated as plain [CliArgument(0, Phase = CommandLinePhase.Passthrough)] (no Required = true) with nullable IEnumerable<string>? types, so CommandArgumentBuilder's unconditional required-argument check no longer defeats the IValidatableObject "at least one of Package/Requirement" validation. The fix lives in PipCliScraper.NormalizePipUsage (tools/ModularPipelines.OptionsGenerator/src/ModularPipelines.OptionsGenerator/Scrapers/Cli/PipCliScraper.cs:174-247), which now demotes the positional's IsRequired and injects a RequiredAlternativeGroups entry pairing it with --requirement.

Blocking: PipWheelOptions.Generated.cs doesn't match what this PR's own generator produces

OptionsClassGenerator.GenerateRequiredAlternativeValidation (tools/ModularPipelines.OptionsGenerator/src/ModularPipelines.OptionsGenerator/Generators/OptionsClassGenerator.cs:213-214) unconditionally emits the validation method as an explicit interface implementation:

sb.AppendLine("    IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)");

There is no branch anywhere in the generator that produces anything else — PipUninstallOptions.Generated.cs correctly reflects this (IEnumerable<ValidationResult> IValidatableObject.Validate(...), src/ModularPipelines.Python/Options/PipUninstallOptions.Generated.cs:181). But PipWheelOptions.Generated.cs:289 instead has:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)

a plain public method, not an explicit interface implementation. That's not something the generator code included in this very same PR can produce — meaning this file wasn't actually produced by running the regenerator, or was hand-adjusted afterward. This is corroborated by PublicAPI.Unshipped.txt, which gained a public-surface entry for PipWheelOptions.Validate(...) with no equivalent for PipUninstallOptions (explicit interface implementations aren't tracked the same way), so the two sibling types now have inconsistent public API shapes for what should be identical generated behavior.

Per this repo's CLAUDE.md: "Never hand-tweak auto-generated options in a PR - changes will be overwritten" and "Current tool output is the sole source of truth for generated options." Please regenerate PipWheelOptions.Generated.cs (and re-verify PublicAPI.Unshipped.txt) so it actually matches the generator's output — otherwise the next real regeneration will produce unexpected churn on this exact file, and in the meantime PipWheelOptions exposes Validate as public API while its sibling PipUninstallOptions doesn't.

Suggestion (non-blocking): centralize the required-alternative "demote positional" fix

The actual fix — demoting a positional's required-ness when it's folded into a required-alternative group — is implemented entirely inside PipCliScraper.NormalizePipUsage, hardcoded to the uninstall/wheel command names and the --requirement switch. The generic machinery in CliScraperBase.cs (ResolveRequiredAlternativeGroups / TryResolveRequiredAlternativeGroup, lines ~874-913) still never touches a member's required-ness — it only resolves switch/positional identities. That means any other tool with the same shape (a positional operand that becomes optional once an equivalent option switch is supplied) will hit the exact same silently-broken validation this PR just fixed for pip, and will need its own copy-pasted, tool-specific workaround rather than getting it for free.

Per CLAUDE.md's guidance to "prefer fixes in generic generator logic so every tool benefits; add tool-specific logic only when a generic fix is not possible," consider lifting the "if a positional is a member of a resolved CliRequiredAlternativeGroup, set its IsValidationRequired/IsRequired to false" step into TryResolveRequiredAlternativeGroup itself (similar in spirit to KubectlCliScraper's existing IsValidationRequired = false pattern, but centralized rather than per-tool). That would let PipCliScraper (and any future tool) focus only on detecting/declaring the alternative relationship, not on re-deriving the required-flag demotion by hand.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review

Re-reviewed at the current head (9010915673e596e57d21d4d96b486e6f5341dff0), following up on the prior blocking review at 21a8fd3b612309031821983812ace27ccf213082.

Generator fix: good, addresses the prior non-blocking suggestion

The new commit (fix(generator): demote alternative operands) moves the "demote a positional operand's required-ness when it's folded into a required-alternative group" logic out of PipCliScraper.NormalizePipUsage and into a generic DemoteRequiredAlternativeOperands step in CliScraperBase.ResolveAndApplyAsync (CliScraperBase.cs:565-574,919-944). It matches operands to alternative-group members by (Phase, PositionIndex), which is computed per-command from command.PositionalArguments/command.RequiredAlternativeGroups, so there's no cross-command collision risk. This is exactly what the previous review suggested — "consider lifting the... step into TryResolveRequiredAlternativeGroup... so every tool with this pattern... gets it for free" — and PipCliScraper.cs is now just 15 lines shorter, delegating entirely to the base class. The test changes are consistent: the demotion assertions were correctly moved out of PositionalOperandAdapterTests (which now only exercises PipCliScraper.Normalize in isolation, before the base-class demotion step runs) into a new integration-level CliScraperTraversalTests.Resolved_Alternatives_Demote_Required_Operands test that exercises the full pipeline.

Blocking: previously-flagged generated-file mismatch is still unresolved

The prior review at 21a8fd3b flagged that PipWheelOptions.Generated.cs couldn't have been produced by this repo's own generator, because OptionsClassGenerator.GenerateRequiredAlternativeValidation unconditionally emits the validation method as an explicit interface implementation:

sb.AppendLine("    IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)");

(OptionsClassGenerator.cs:214) — matching what PipUninstallOptions.Generated.cs:181 actually has.

git diff 21a8fd3b 9010915 -- src/ModularPipelines.Python/ is empty: none of the shipped generated files changed in this latest commit. So at the current head, PipWheelOptions.Generated.cs:288-294 still declares:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)

a plain public method rather than the explicit interface implementation the generator code (including in this very PR) unconditionally produces. This is corroborated by PublicAPI.Unshipped.txt, which still lists PipWheelOptions.Validate(...) as public API with no equivalent entry for PipUninstallOptions (explicit interface implementations aren't tracked there) — the two sibling types remain inconsistent.

Per CLAUDE.md: "Never hand-tweak auto-generated options in a PR - changes will be overwritten" and "Current tool output is the sole source of truth for generated options." The generator-side fix in this commit is sound, but PipWheelOptions.Generated.cs (and PublicAPI.Unshipped.txt) needs to actually be regenerated against it before this can merge — otherwise the next real regeneration run will produce unexpected churn on this exact file, and in the meantime it's shipping generated code that doesn't match what the generator in this repo produces.

Suggested next step: run the regenerator (or wait for the CI regeneration workflow) against this exact head so PipWheelOptions.Generated.cs and PublicAPI.Unshipped.txt pick up the explicit-interface-implementation form, then re-review the resulting diff.

Everything else (the CliScraperBase/PipCliScraper refactor, the new/relocated tests, PipDownloadOptions.Generated.cs/PipInstallOptions.Generated.cs/IPip.Generated.cs/Pip.Generated.cs/Pip.Generation.json, PipUninstallOptions.Generated.cs) is correct and unchanged from the prior clean pass.

@thomhurst

Copy link
Copy Markdown
Owner Author

Addressed the blocking validation API finding at current head 68dea97. The shared generator now demotes positional operands participating in resolved alternatives; Pip 24.0 regeneration emits explicit IValidatableObject.Validate for wheel. Pip version metadata is also normalized to the stable semantic version instead of embedding the installation path. Validation: generator tests 1267/1267, Python solution build, Python tests 1/1. @claude please review the current head.

@thomhurst
thomhurst force-pushed the automated/update-cli-options-pip branch from 68dea97 to 52bc6cf Compare September 3, 2026 16:19

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review

Re-reviewed at the current head (68dea97162ecf4bfbe33baedfb579d9fb1096668), following up on the prior blocking review at 9010915673e596e57d21d4d96b486e6f5341dff0.

Prior blocking issue: resolved

The last review flagged that PipWheelOptions.Generated.cs couldn't have come from this repo's own generator, because OptionsClassGenerator.GenerateRequiredAlternativeValidation unconditionally emits the validation method as an explicit interface implementation (IEnumerable<ValidationResult> IValidatableObject.Validate(...)), but the shipped file had a plain public IEnumerable<ValidationResult> Validate(...).

This commit fixes exactly that: PipWheelOptions.Generated.cs now declares IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext), matching PipUninstallOptions.Generated.cs and the generator's own unconditional output. PublicAPI.Unshipped.txt correctly drops the now-stale PipWheelOptions.Validate(...) entry (explicit interface implementations aren't tracked as public API surface), so the two sibling types are consistent again.

New change in this commit: pip version-string stabilization

PipCliScraper now overrides ParseVersionOutput with a PipVersionPattern (^pip\s+(?<version>\S+)) to extract just the numeric version (e.g. 24.0) instead of embedding the full pip --version output (pip 24.0 from /usr/lib/python3/dist-packages/pip (python 3.12)) into Pip.CommandCoverage.json's toolVersion field — the previous raw string bakes in an environment-specific interpreter path that would cause spurious coverage-file churn between machines. This follows an already-established pattern: CosignCliScraper, FlywayCliScraper, LiquibaseCliScraper, and SonarScannerCliScraper all already override ParseVersionOutput the same way for their own tool-specific version formats, so this isn't a one-off special case — it's the standard extension point working as intended. The new Pip_Uses_Stable_Version_Identity test exercises the fallback correctly (falls back to base.ParseVersionOutput when the pattern doesn't match).

I re-checked the full diff (this commit plus the accumulated pip options regeneration: CliScraperBase's generic DemoteRequiredAlternativeOperands step, PipCliScraper's NormalizePipUsage, the PipUninstallOptions/PipWheelOptions required-alternative validation, and the associated generated files) and everything is internally consistent and matches what the generator would actually produce.

One pre-existing, non-blocking gap (not introduced by this PR, and shared with several other generated options classes like CargoAddOptions/KubernetesApplyOptions): PipUninstallOptions/PipWheelOptions's new IValidatableObject.Validate() only inspects the strongly-typed Package/Requirement(RequirementSpecifier) properties, not the CommandLineToolOptions.AdditionalArguments escape hatch. A caller who supplies only AdditionalArguments (a documented way to pass extra values) with no typed Package/Requirement would get a CommandOptionsValidationException even though the resulting command line would be valid. Since this is an existing pattern-wide limitation rather than something this PR introduces, it doesn't need to block here, but would be worth a follow-up generic fix in OptionsClassGenerator.GenerateRequiredAlternativeValidation if the project wants required-alternative validation to be fully accurate.

No blocking issues remain.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review

Reviewed at head 52bc6cfdacdd986f6828f8c0844f6b64bbb3dd94 against base b8d7320f929864777172da7c614407bcba2fb160 (the actual gh pr diff content — 9 files under src/ModularPipelines.Python/).

Note on prior review comments: this thread already contains several claude reviews (referencing commits 21a8fd3b, 9010915, 68dea971) claiming these exact issues were found and then fixed via generic scraper/generator changes (CliScraperBase.DemoteRequiredAlternativeOperands, PipCliScraper.NormalizePipUsage, a PipVersionPattern version-stabilization commit, etc.). None of those commits are reachable from this PR's history (they're orphaned — fetchable by SHA directly from GitHub but not part of any branch), and none of that code exists in the current diff, in PipCliScraper.cs, or anywhere in the OptionsGenerator source at this head. The actual current diff is much smaller and does not contain any of the described fixes. I'd treat those comments as stale/unreliable for this head and would recommend the PR author double check how they got there — re-reviewing based on them would have produced a false "CLEAR" here.

Blocking: PipUninstallOptions.Package — validation and CLI-arg enforcement contradict each other

PipUninstallOptions.Generated.cs:21-23 promotes Package to a required primary-constructor parameter with [CliArgument(0, ..., Required = true)], while the new IValidatableObject.Validate() (line 181) explicitly treats Package/Requirement as an OR:

if (!(Package?.Any() == true || Requirement?.Any() == true))
{
    yield return new ValidationResult("At least one of Package or Requirement must be specified.", ...);
}

CommandLineBuilder.Build runs DataAnnotations validation first (CommandLineBuilder.cs:55), then later calls CommandArgumentBuilder.AddArguments (src/ModularPipelines/Helpers/Internal/CommandArgumentBuilder.cs:142-148), which unconditionally throws when a Required = true argument has zero values — with no awareness of the alternative-group validation that already passed:

if (argumentPart.Attribute.Required && values.Count == 0)
    throw new ArgumentException($"Required CLI argument '{optionsType.Name}.{argumentPart.PropertyName}' cannot be null or empty.", ...);

So new PipUninstallOptions(Package: []) { Requirement = ["requirements.txt"] } — a legitimate pip uninstall -r requirements.txt invocation — passes validation and then throws deeper in the pipeline with a message that contradicts what validation just said was fine.

The generator already has the right extension point for this: OptionsClassGenerator.cs:344 only emits Required = true when positional.IsValidationRequired ?? positional.IsRequired is true, and KubectlCliScraper already uses this (KubectlCliScraper.cs:106,130,134,159) to set IsValidationRequired = false on positionals that are members of a required-alternative group, while still leaving IsRequired = true so the property stays a required constructor parameter. PipCliScraper.cs has no equivalent override for Package. Per CLAUDE.md ("fix generated-options problems in the scraper or generator, not in generated output"), the fix belongs in PipCliScraper (a NormalizePositionalArguments/command-specific override setting IsValidationRequired = false for Package), not in the generated file.

Blocking: PipWheelOptions.RequirementSpecifier — no validation escape at all, worse than the above

PipWheelOptions.Generated.cs:20-23 does the same required-constructor-parameter + Required = true promotion for RequirementSpecifier, but this type doesn't implement IValidatableObject at all — no alternative-group validation was generated for it, even though PipWheelOptions also exposes Requirement (-r). pip wheel -r requirements.txt (a valid, requirement-file-only invocation) is now impossible to build through this API: any RequirementSpecifier: [] will throw at CommandArgumentBuilder.AddArguments with no earlier, clearer validation error to explain why.

This suggests the usage-synopsis scrape for wheel didn't produce a RequiredAlternativeGroup the way uninstall's did — worth checking why the same -r-vs-positional relationship pip exposes for both subcommands wasn't detected consistently for wheel, in addition to adding the same IsValidationRequired = false treatment once the group is captured.

Minor: dropped XML doc on promoted constructor parameters

Package (PipUninstallOptions.Generated.cs:21) and RequirementSpecifier (PipWheelOptions.Generated.cs:21) previously had a <summary> doc comment as settable properties (e.g. "The package operand."). After promotion to primary-constructor parameters they have no doc comment at all — a generator gap in the constructor-parameter emission path (OptionsClassGenerator.cs, near the required-constructor-parameter generation) that's dropping rather than reattaching the summary. Not blocking, but worth a follow-up so IntelliSense doesn't regress for every tool that gets this treatment.

Everything else in the diff (the <requirement specifier> doc-comment escaping fix in PipDownloadOptions/PipInstallOptions, the IPip/Pip.Generated.cs signature changes making options required instead of defaulting to new PipXxxOptions(), and the corresponding PublicAPI.Shipped/Unshipped.txt churn) looks consistent and correctly generated.

@thomhurst
thomhurst force-pushed the automated/update-cli-options-pip branch from 52bc6cf to f78e951 Compare September 3, 2026 16:54

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review

Re-reviewed at the current head (f78e951fcc9a49460cae39cc6eeab86b3710f693). This thread has cycled through this exact pair of findings several times already (blocking at 801283b4, d7f04c3, 21a8fd3, 9010915, 52bc6cf; briefly reported clear at 755f1dc and 68dea97 before the branch was force-pushed/rebased and the fix apparently dropped out again). I re-verified directly against the files at this head rather than trusting the prior thread, since an earlier reviewer in this same thread noted some of those "fixed" comments referenced commits that were orphaned and never actually reachable from the PR branch.

Blocking: PipUninstallOptions.PackageRequired = true still contradicts its own Validate()

src/ModularPipelines.Python/Options/PipUninstallOptions.Generated.cs:23 still declares:

public record PipUninstallOptions(
    [property: CliArgument(0, Phase = CommandLinePhase.Passthrough, Required = true)] IEnumerable<string> Package
) : PipOptions, IValidatableObject

while Validate() (lines 177-183) explicitly allows Package to be empty as long as Requirement is set:

if (!(Package?.Any() == true || Requirement?.Any() == true))
{
    yield return new ValidationResult("At least one of Package or Requirement must be specified.", ...);
}

CommandArgumentBuilder.AddArguments (src/ModularPipelines/Helpers/Internal/CommandArgumentBuilder.cs:142-146) has its own, unconditional check that ignores Validate() entirely:

if (argumentPart.Attribute.Required && values.Count == 0)
    throw new ArgumentException($"Required CLI argument '{optionsType.Name}.{argumentPart.PropertyName}' cannot be null or empty.", ...);

So new PipUninstallOptions(Package: []) { Requirement = ["requirements.txt"] } — a legitimate pip uninstall -r requirements.txt invocation — passes Validate() and then throws when the command is actually built. The two mechanisms disagree about what's valid, and the requirements-file-only path the Validate() method was clearly written to support is unreachable.

Blocking: PipWheelOptions.RequirementSpecifier — hard-required, no alternative path at all

src/ModularPipelines.Python/Options/PipWheelOptions.Generated.cs:22 has the same Required = true promotion for RequirementSpecifier, but this type doesn't implement IValidatableObject at all — no alternative-group validation exists here even though PipWheelOptions also exposes Requirement (-r/--requirement). pip wheel -r requirements.txt (no positional operand) is a legitimate invocation this API can no longer represent — any attempt throws the same ArgumentException from CommandArgumentBuilder, with no earlier, clearer validation error explaining why.

Suggested direction

Per CLAUDE.md, this isn't something to hand-patch in the .Generated.cs output — the fix belongs in tools/ModularPipelines.OptionsGenerator (scraper/generator), since it'll be overwritten on the next regeneration anyway. Both issues trace to the same root cause: when a pip subcommand's positional operand has a mutually-exclusive alternative (-r/--requirement), the generator needs to (a) recognize that alternative for every affected subcommand (currently wheel's usage synopsis isn't producing a required-alternative group the way uninstall's is), and (b) demote the positional's CLI-argument-level Required flag whenever it's folded into such a group, not just gate it in Validate(). Otherwise the IValidatableObject check and CommandArgumentBuilder's enforcement will keep silently disagreeing. Given how many times this exact pair of issues has resurfaced on this PR (5+ prior blocking reviews), it may be worth adding a regression test in the generator's test suite (e.g. asserting that any positional in a RequiredAlternativeGroup is emitted without Required = true) so future regenerations of pip or any other tool with this shape can't silently reintroduce it.

Everything else in the diff (the <requirement specifier> doc-comment escaping, the PublicAPI.Shipped.txt/PublicAPI.Unshipped.txt churn, and the new Pip.Generation.json baseline) is consistent, mechanical regeneration output and needs no changes. No in-repo callers construct PipUninstallOptions/PipWheelOptions or call UninstallAsync/WheelAsync, so this doesn't break anything else in-tree — but it does ship a public API that can't express two real, common pip invocations.

@thomhurst
thomhurst force-pushed the automated/update-cli-options-pip branch from f78e951 to 8afed4f Compare September 4, 2026 12:03

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review

Re-reviewed at the current head (8afed4f4307967d699ec1f02bded8e7aacc9a887). This branch was force-pushed down to a single fresh commit since the last review — the entire commit history from before (801283b4f78e951f, 9 rounds of review) is gone, including the generator fix (fix(pip): model requirement alternatives, adding a RequiredAlternativeGroup for -r/--requirement in PipCliScraper.cs) that finally got PipUninstallOptions/PipWheelOptions into a correct state at 755f1dcc/d7f04c34.

Blocking: the exact previously-fixed regression is back

That fix only ever lived on this PR's branch — it was never merged into main (main's last touch to tools/ModularPipelines.OptionsGenerator/.../PipCliScraper.cs is commit 65d7cad9bb from 2026-08-26, well before the RequiredAlternativeGroup work). Since this PR branch just got reset to a fresh commit off current main, regenerating from the unfixed scraper reproduced the identical bug that took 9 review rounds to resolve last time:

  • src/ModularPipelines.Python/Options/PipUninstallOptions.Generated.cs:22-23Package is a required primary-constructor argument (Required = true), but Validate() (lines 177-181) explicitly allows Package to be empty when Requirement is set. CommandArgumentBuilder.AddArguments (src/ModularPipelines/Helpers/Internal/CommandArgumentBuilder.cs:141-147) doesn't consult Validate() — it unconditionally throws ArgumentException for any Required argument with zero values. So new PipUninstallOptions([]) { Requirement = ["requirements.txt"] } — the real pip uninstall -r requirements.txt usage Validate() was written to allow — passes validation and then throws when the command is built.
  • src/ModularPipelines.Python/Options/PipWheelOptions.Generated.cs:21-22RequirementSpecifier is likewise Required = true, but this type has no IValidatableObject/Validate() at all, so pip wheel -r requirements.txt (no positional operand) can't be represented — any attempt throws the same ArgumentException.

Why this keeps recurring, and the actual fix

The root cause isn't in this PR's diff — it's that the fix for pip's required-alternative operands was built and validated entirely inside an auto-generated, disposable PR branch instead of being merged into the generator on main. Every time this pip-regeneration automation resets/re-runs, it starts from main's scraper, which never got the fix, so the bug returns. Per CLAUDE.md, generated-options bugs belong in the scraper/generator, not hand-patched here — but concretely, that means:

  1. Land the PipCliScraper.cs RequiredAlternativeGroup change (already written and reviewed correct at 755f1dcc/d7f04c34/etc. in this PR's now-discarded history) as its own PR against tools/ModularPipelines.OptionsGenerator on main, independent of any pip regeneration PR.
  2. Only after that merges, let this automated pip-options PR regenerate again — it will then pick up the optional operand + Validate()-based alternative-group check correctly, as it did before.
  3. Consider a generator-level regression test (e.g. in PositionalOperandAdapterTests) asserting that any positional folded into a RequiredAlternativeGroup is emitted without Required = true, so a future reset of this automation can't silently reintroduce the same class of bug again.

Everything else in the diff (doc-comment <requirement specifier> escaping in PipDownloadOptions/PipInstallOptions, PublicAPI.Shipped.txt/Unshipped.txt churn, the new Pip.Generation.json baseline, IPip.Generated.cs/Pip.Generated.cs) is accurate, mechanical regeneration output and needs no changes.

@thomhurst
thomhurst force-pushed the automated/update-cli-options-pip branch from 8afed4f to 96133b5 Compare September 4, 2026 20:32
@thomhurst
thomhurst deployed to Pull Requests September 4, 2026 20:36 — with GitHub Actions Active
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Review

This PR is a mechanical, automated regeneration of the pip CLI options classes (PipDownloadOptions, PipInstallOptions, PipUninstallOptions, PipWheelOptions, IPip/Pip.Generated.cs, and the two PublicAPI.*.txt tracking files) plus the underlying Pip.Generation.json scrape. Most of the diff — the shipped/unshipped API tracking file updates — is a straightforward catch-up to code shape that was already established, and I confirmed there are no other in-tree call sites referencing the changed PipUninstallOptions/PipWheelOptions constructors or the newly-required UninstallAsync/WheelAsync options parameters, so the acknowledged breaking API changes don't break anything else in the repo.

Blocking finding

PipWheelOptions is missing the required-alternative validation that PipUninstallOptions got in the same PR.

This PR makes both PipUninstallOptions.Package and PipWheelOptions.RequirementSpecifier mandatory positional constructor arguments (Required = true on CliArgument(0, ...)). For PipUninstallOptions, the generator also emits IValidatableObject.Validate (PipUninstallOptions.Generated.cs:176-183) so that supplying Requirement (-r/--requirement) instead of a positional Package is accepted, with a clear CommandOptionsValidationException if neither is set. PipWheelOptions gets no such validation (PipWheelOptions.Generated.cs:21-23), even though it has the exact same shape: a required positional RequirementSpecifier plus an alternate Requirement option (PipWheelOptions.Generated.cs:70-71).

Concretely: a caller who wants to run pip wheel -r requirements.txt (no positional requirement specifier) has no valid way to satisfy the required constructor parameter except passing an empty list, and if they truly have neither, the failure only shows up later as an opaque error from the external pip process rather than an early, actionable CommandOptionsValidationException — unlike the parallel PipUninstallOptions case that was just fixed to guard exactly this.

This is worth double-checking because a prior comment on this PR (at head 68dea97...) claimed: "The shared generator now demotes positional operands participating in resolved alternatives; Pip 24.0 regeneration emits explicit IValidatableObject.Validate for wheel." That does not match the current generated output at head 96133b5PipWheelOptions has no IValidatableObject implementation at all. Since this is generator-driven output, per repo convention (CLAUDE.md) the fix belongs in the shared generator/scraper logic (the same code path that resolves the Package/Requirement alternative for pip uninstall) so it applies uniformly to pip wheel too, rather than hand-editing the generated file.

Notes

  • PipDownloadOptions.RequirementSpecifier and PipInstallOptions.RequirementSpecifier remain optional (non-positional) properties in this PR, so they aren't affected by this gap — only uninstall and wheel moved to required positional operands.
  • The generated-options and public-API conventions elsewhere in the diff look consistent with existing patterns in the codebase (e.g. positional record constructors, [ExcludeFromCodeCoverage]), so I have no other concerns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant