Skip to content

feat: Install Poetry requires-plugins before running Poetry commands#14707

Merged
markhallen merged 10 commits intomainfrom
poetry-requires-poetry-constraint
Apr 14, 2026
Merged

feat: Install Poetry requires-plugins before running Poetry commands#14707
markhallen merged 10 commits intomainfrom
poetry-requires-poetry-constraint

Conversation

@markhallen
Copy link
Copy Markdown
Contributor

@markhallen markhallen commented Apr 13, 2026

What are you trying to accomplish?

Poetry v2 supports a requires-plugins field in pyproject.toml that declares which Poetry plugins a project depends on. When Dependabot runs Poetry commands (lock, update, resolve) on a project that requires plugins, those plugins must be installed first — otherwise Poetry may fail with confusing errors or produce incorrect results.

This builds on the requires-poetry constraint support merged in #14684 by adding automatic plugin installation.

Anything you want to highlight for special attention from reviewers?

  • New PoetryPluginInstaller class — reads [tool.poetry.requires-plugins] from pyproject.toml and installs each declared plugin via poetry self add. Input validation (regex allowlists for plugin names and version constraints) prevents command injection. Uses Shellwords.shellescape as an additional safety layer.
  • Integrated into all three Poetry code paths — the lockfile generator, file updater, and version resolver all call poetry_plugin_installer.install_required_plugins before executing Poetry commands, following the same pattern as language_version_manager.install_required_python.
  • Plugin installation failures are non-fatal — if a plugin can't be installed (unavailable on PyPI, version conflict, private registry), a warning is logged and processing continues. The Poetry command itself may still succeed, or fail with a clearer error.
  • PEP 621 updater fallbackPep621Updater now falls back to matching against the normalized requirement string when source_requirement metadata is absent (e.g. after DependencySet merge or deserialization), preventing silent update failures.
  • Explicit != operator handlingbump_single_requirement now handles != explicitly (keeps unchanged) instead of relying on a catch-all else branch.

How will you know you've accomplished your goal?

  • PoetryPluginInstaller has comprehensive unit tests covering: single plugin, multiple plugins, missing/empty section, invalid plugin names and constraints (command injection prevention), TOML parse errors, failure resilience (warns instead of raising), and idempotent installation.
  • Integration tests in lockfile generator, file updater, and version resolver specs verify that install_required_plugins is invoked during their respective workflows.
  • PEP 621 fallback test confirms dependencies without source_requirement metadata still get updated correctly.
  • Range requirement edge case tests cover strict > lower bound conversion and != exclusion alongside ranges for both BumpVersions and BumpVersionsIfNecessary strategies.
  • 3 fixture files (requires_plugins.toml, requires_plugins_multiple.toml, requires_plugins_none.toml) support the test scenarios.

Checklist

  • I have run the complete test suite to ensure all tests and linters pass.
  • I have thoroughly tested my code changes to ensure they work as expected, including adding additional tests for new functionality.
  • I have written clear and descriptive commit messages.
  • I have provided a detailed description of the changes in the pull request, including the problem it addresses, how it fixes the problem, and any relevant details about the implementation.
  • I have ensured that the code is well-documented and easy to understand.

Introduce a new PoetryPluginInstaller class that reads
[tool.poetry.requires-plugins] from pyproject.toml and installs
the declared Poetry plugins before running Poetry commands.

Includes input validation to prevent command injection and
comprehensive test coverage with fixture files.
Wire the PoetryPluginInstaller into the lockfile generator, file
updater, and version resolver so that required Poetry plugins are
installed before any Poetry commands are executed.

Adds integration tests verifying plugin installation is invoked
in each component.
Fix unnecessary safe navigation in file_parser.rb where pyproject
is already checked for nil. Remove redundant T.cast in
package_manager.rb and add clarifying comment about Poetry-specific
requires-poetry constraint support.
@markhallen markhallen requested a review from a team as a code owner April 13, 2026 13:31
Copilot AI review requested due to automatic review settings April 13, 2026 13:31
@markhallen markhallen force-pushed the poetry-requires-poetry-constraint branch from 155ba5b to 8303d33 Compare April 13, 2026 13:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support in the Python (Poetry) ecosystem for Poetry v2’s requires-plugins field by ensuring declared Poetry plugins are installed before Dependabot runs Poetry commands, reducing confusing failures when plugins are required.

Changes:

  • Introduces Dependabot::Python::PoetryPluginInstaller to parse tool.poetry.requires-plugins from pyproject.toml and install plugins via poetry self add with validation/escaping.
  • Integrates plugin installation into Poetry’s lockfile generation, file updating, and version resolution workflows.
  • Adds unit/integration specs plus fixtures covering single/multiple/no plugins and invalid/malicious inputs.
Show a summary per file
File Description
python/lib/dependabot/python/poetry_plugin_installer.rb New installer that reads requires-plugins and runs poetry self add safely.
python/lib/dependabot/python/update_checker/poetry_version_resolver.rb Calls plugin installer before running Poetry update/resolve commands.
python/lib/dependabot/python/file_updater/poetry_file_updater.rb Calls plugin installer before running Poetry update during lockfile regeneration.
python/lib/dependabot/python/dependency_grapher/lockfile_generator.rb Calls plugin installer before running poetry lock for dependency graphing.
python/lib/dependabot/python/file_parser.rb Minor nil-guard cleanup for requires-poetry parsing.
python/lib/dependabot/python/package_manager.rb Removes redundant T.cast in raise_if_unsupported!.
python/spec/dependabot/python/poetry_plugin_installer_spec.rb New unit tests for parsing/validation/idempotency and failure handling.
python/spec/dependabot/python/update_checker/poetry_version_resolver_spec.rb Adds integration coverage asserting plugin installation is invoked during resolution.
python/spec/dependabot/python/file_updater/poetry_file_updater_spec.rb Adds integration coverage asserting plugin installation is invoked during updates.
python/spec/dependabot/python/dependency_grapher/lockfile_generator_spec.rb Adds integration coverage asserting plugin installation is invoked during lock generation.
python/spec/fixtures/pyproject_files/requires_plugins.toml Fixture with a single required Poetry plugin.
python/spec/fixtures/pyproject_files/requires_plugins_multiple.toml Fixture with multiple required Poetry plugins.
python/spec/fixtures/pyproject_files/requires_plugins_none.toml Fixture with no requires-plugins section.

Copilot's findings

Comments suppressed due to low confidence (2)

python/spec/dependabot/python/file_updater/poetry_file_updater_spec.rb:988

  • Rescuing StandardError here can mask unexpected failures and allow the example to pass even if the code under test raises before reaching the expectation. Prefer stubbing the remaining collaborators (e.g., file reads / poetry command runners) so updated_dependency_files can run to completion, and assert that no unexpected error is raised.
      allow(File).to receive(:read).with("poetry.lock").and_return("lock content")

      begin
        updater.updated_dependency_files
      rescue StandardError

python/spec/dependabot/python/update_checker/poetry_version_resolver_spec.rb:691

  • Catching StandardError in the test body can hide failures unrelated to plugin installation (e.g., file I/O issues) and still let the expectation run. Prefer making the resolver call succeed by stubbing required file reads/writes and helpers, and assert that no error is raised while verifying the installer is invoked.
      begin
        resolver.latest_resolvable_version(requirement: "*")
      rescue StandardError
        nil
      end
  • Files reviewed: 13/13 changed files
  • Comments generated: 2

Comment thread python/spec/dependabot/python/file_updater/poetry_file_updater_spec.rb Outdated
Comment thread python/spec/dependabot/python/update_checker/poetry_version_resolver_spec.rb Outdated
The tests were stubbing SharedHelpers.in_a_temporary_repo_directory,
but the production code uses SharedHelpers.in_a_temporary_directory
with SharedHelpers.with_git_configured. Updated both spec files to
stub the correct helpers, and added python_version to the
LanguageVersionManager double for the file updater spec.
robaiken
robaiken previously approved these changes Apr 13, 2026
- Add fallback regex in Pep621Updater when source_requirement metadata
  is absent (e.g. after DependencySet merge or deserialization)
- Make Poetry plugin installation failures non-fatal (log warning instead
  of raising DependabotError)
- Handle != operator explicitly in bump_single_requirement
- Clarify > to >= conversion comment in requirements updater
- Add tests for all edge cases
robaiken
robaiken previously approved these changes Apr 13, 2026
When BumpVersions bumps a lower bound (e.g. >=0.2.0,<1.0.0 to
>=0.3.5,<1.0.0) but the lockfile already has the target version locked,
the pyproject changes but poetry update produces the same lockfile.

Previously this raised 'Expected lockfile to change!' — now we simply
return the updated pyproject without the lockfile, which is a valid
constraint-only update.

Fixes DELTAFORCE-1B39
@markhallen
Copy link
Copy Markdown
Contributor Author

Post-deploy observation: Expected lockfile to change! error

After deploying this branch, we observed a new error in production:

RuntimeError: Expected lockfile to change!

Root cause: The RequirementsUpdater changes removed the new_version_satisfies? guard from update_pyproject_version for range requirements with the BumpVersions strategy. The new bump_requirements_range method now bumps lower bounds (e.g., >=0.2.0,<1.0.0>=0.3.5,<1.0.0) even when the locked version already satisfies the original range.

This creates a scenario where:

  1. The pyproject.toml is updated (constraint is bumped) → file_changed? returns true
  2. poetry update --lock runs, but the dependency is already locked at the target version
  3. The lockfile content is identical → "Expected lockfile to change!" is raised

Fix (commit 8c068156a): fetch_updated_dependency_files no longer raises when the lockfile is unchanged. A constraint-only update (bumping a lower bound without affecting resolution) is a valid change — we return just the updated pyproject.toml without the lockfile. Added a test covering this scenario.

@markhallen markhallen enabled auto-merge April 14, 2026 15:59
@markhallen markhallen merged commit ffa0682 into main Apr 14, 2026
87 of 91 checks passed
@markhallen markhallen deleted the poetry-requires-poetry-constraint branch April 14, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants