Skip to content

fix(pip): normalize names in list_freeze_parse and detect modern pip …#68784

Open
cdalvaro wants to merge 3 commits into
saltstack:masterfrom
cdalvaro:bugfix/install_pip_packages_with_modern_pip
Open

fix(pip): normalize names in list_freeze_parse and detect modern pip …#68784
cdalvaro wants to merge 3 commits into
saltstack:masterfrom
cdalvaro:bugfix/install_pip_packages_with_modern_pip

Conversation

@cdalvaro

@cdalvaro cdalvaro commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Two related bugs in the pip state and module cause a Python package to be "installed" (and reported as changed) on every Salt state run, even when the package is already at the correct version.

Bug 1 — list_freeze_parse does not normalize package names

Commit 68728e6 updated list_() to return normalized package names (lowercase, hyphens) via normalize(). However, list_freeze_parse, which is the fallback used for pip < 9.0, was not updated. It still returns names exactly as they appear in pip freeze output (e.g. "requests_oauthlib" with an underscore).

Because _check_if_installed now receives a normalized prefix (e.g. "requests-oauthlib") and compares it against a CaseInsensitiveDict that only handles case — not underscore vs. hyphen — the lookup always fails. The package is added to target_pkgs and pip is called on every run.

Bug 2 — Post-install detection uses an outdated pip message

After calling pip.install, the state checks pip's stdout to determine whether a package was already present, to avoid reporting it as a change.

The check was:

if line.startswith("Requirement already up-to-date: "):

Modern pip (≥ 10.0) outputs "Requirement already satisfied: ..." instead.

Because the message never matched, already_installed_packages stayed empty and every pip call resulted in the package being reported as "Installed" in the state changes dict.

Fix

  • salt/modules/pip.pylist_freeze_parse now computes a normal_prefix and stores each package under its normalized name (normalize(name)), consistent with list_().
  • salt/states/pip_state.py — The post-install check now also matches "Requirement already satisfied: ". The package name is extracted by stripping any version specifier with re.split, then normalized with pip.normalize before being stored in already_installed_packages. The comparison with prefix (already normalized) no longer calls .lower() unnecessarily.

Merge requirements satisfied?

Commits signed with GPG?

Yes

@cdalvaro cdalvaro requested a review from a team as a code owner March 5, 2026 13:09
@cdalvaro cdalvaro changed the title fix(pip): normalize names in list_freeze_parse and detect modern pip … Draft: fix(pip): normalize names in list_freeze_parse and detect modern pip … Mar 5, 2026
@cdalvaro cdalvaro changed the base branch from 3006.x to master March 5, 2026 14:42
@cdalvaro cdalvaro force-pushed the bugfix/install_pip_packages_with_modern_pip branch from 25fdad8 to 201df65 Compare March 5, 2026 14:49
@cdalvaro cdalvaro changed the title Draft: fix(pip): normalize names in list_freeze_parse and detect modern pip … fix(pip): normalize names in list_freeze_parse and detect modern pip … Mar 5, 2026
Comment thread salt/states/pip_state.py Outdated
Comment on lines +852 to +853
'Package will be installed in editable mode (i.e. setuptools "develop mode") from {}.'.format(
editable

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.

Change to f str here too?

@cdalvaro cdalvaro Mar 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in commit 94d3354

Comment thread salt/modules/pip.py Outdated
if prefix is None or "pip".startswith(prefix):
normal_prefix = normalize(prefix) if prefix else None

if prefix is None or "pip".startswith(normal_prefix):

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.

Once we have normal_prefix I don't think we need to check prefix anymore. Maybe change to if normal_prefix is None or "pip".startswith(normal_prefix):

@cdalvaro cdalvaro Apr 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 2b5c39b

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for your suggestions!

Comment thread salt/modules/pip.py Outdated
continue

normal_name = normalize(name)
if prefix:

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.

Maybe if normal_prefix:

@cdalvaro cdalvaro Apr 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 2b5c39b

@cdalvaro

Copy link
Copy Markdown
Contributor Author

Finally all checks have passed! 🎉

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

Labels

test:full Run the full test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants