Skip to content

Builtin pip not idempotent for local install using pyproject.toml file #86908

@mrvanes

Description

@mrvanes

Summary

We recently migrated from a simple requirements.txt file to the by now ubiquitous pyproject.toml installation. The location of this project is on the local filesystem of the target machine after extraction of a remote tar.gz release.

Since this migration, we see an idempotency flag of 1 on the virtualenv pip that is caused by the last output of the pyproject.toml pip install /path/to/package, triggered by a 'Successfully installed' of the base package.

Builtin pip sees this as a change, because there is no out_freeze_before which is caused by the fact that we no longer use requirements and name does not look like a vcs url. Nevertheless, a comparisson of out_freeze_before and out_freeze_after would correctly reveal no changes in this case.

The name of the package looks like /path/to/package[test] so adding ^/ to _VCS_RE would already help a lot in our case, but I'm not sure whether any non-local package could also start with / and others might specify the location relative and still be hit with a change?

Issue Type

Bug Report

Component Name

ansible.builtin.pip

Ansible Version

$ ansible --version
ansible [core 2.20.1]
  config file = /***/ansible.cfg
  configured module search path = ['/***/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  ansible collection location = /home/martin/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.14.4 (main, Apr  8 2026, 04:02:31) [GCC 15.2.0] (/usr/bin/python3)
  jinja version = 3.1.6
  pyyaml version = 6.0.3 (with libyaml v0.2.5)

Configuration

# if using a version older than ansible-core 2.12 you should omit the '-t all'
$ ansible-config dump --only-changed -t all
ACHE_PLUGIN(/***/ansible.cfg) = yaml
CACHE_PLUGIN_CONNECTION(/***/ansible.cfg) = .ansible/facts
CALLBACKS_ENABLED(/***/ansible.cfg) = ['profile_tasks']
CONFIG_FILE() = /***/ansible.cfg
DEFAULT_BECOME(/***/ansible.cfg) = True
DEFAULT_FORKS(/***/ansible.cfg) = 25
DEFAULT_GATHERING(/***/ansible.cfg) = smart
DEFAULT_LOAD_CALLBACK_PLUGINS(/***/ansible.cfg) = True
DEFAULT_STDOUT_CALLBACK(/***/ansible.cfg) = yaml
DEFAULT_UNDEFINED_VAR_BEHAVIOR(/***/ansible.cfg) = True
DEPRECATION_WARNINGS(/***/ansible.cfg) = True
HOST_KEY_CHECKING(/***/ansible.cfg) = False
INTERPRETER_PYTHON(/***/ansible.cfg) = /usr/bin/python3
MAX_FILE_SIZE_FOR_DIFF(/***/ansible.cfg) = 1044480

GALAXY_SERVERS:


CACHE:
=====

jsonfile:
________
_uri(/***/ansible.cfg) = /***/.ansible/facts

CONNECTION:
==========

paramiko_ssh:
____________
host_key_checking(/***/ansible.cfg) = False

ssh:
___
host_key_checking(/***/ansible.cfg) = False
pipelining(/***/ansible.cfg) = True
ssh_args(/***/ansible.cfg) = -o ControlMaster=auto -o ControlPersist=3600s

OS / Environment

Host: Ubuntu 26.04
Target: Debian GNU/Linux 12

Steps to Reproduce

  1. Replace requirments.txt with pyproject.toml
  2. Remove requirements from ansible task
  3. Add name, pointing to project root on remote filesystem

Expected Results

No changes detected when switching from requirements.txt to pyproject.toml

Actual Results

Changes detected after use of pip module after migration to pyproject.toml:

Output of pip install /path/to/sbs[test]
...
Building wheels for collected packages: sbs
  Building wheel for sbs (pyproject.toml): started
  Building wheel for sbs (pyproject.toml): finished with status 'done'
  Created wheel for sbs: filename=sbs-0.0.0-py3-none-any.whl size=1373588 sha256=34b70435875d2317014f8b8f76cbb37e7de67c1eeec7d73c518e0fa9285726bb
  Stored in directory: /tmp/pip-ephem-wheel-cache-yl9xmalr/wheels/d0/ec/9b/163ad8d57bb4b7f0181499046424e21dd138f20e2c1b8484eb
Successfully built sbs
Installing collected packages: sbs
  Attempting uninstall: sbs
    Found existing installation: sbs 0.0.0
    Uninstalling sbs-0.0.0:
      Successfully uninstalled sbs-0.0.0
Successfully installed sbs-0.0.0

The final 'Succesully installed ...' triggers the changed state and spoils the idempotency.

Code of Conduct

  • I agree to follow the Ansible Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    affects_2.20bugThis issue/PR relates to a bug.has_prThis issue has an associated PR.moduleThis issue/PR relates to a module.verifiedThis issue has been verified/reproduced by maintainer

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions