The previous test computed the cutoff locally without ever calling resolver.pip_options,
so it would pass even if uploaded_prior_to was never set. Rewrite it to call
resolver.pip_options directly (stubbing pip_command, pip_args, and
check_release_control_exclusive) and assert that pip_options.uploaded_prior_to is set
to approximately now - timedelta(days=30). Also drop the unused SimpleNamespace import.
Signed-off-by: Oz Tiram <oz.tiram@gmail.com>
Passing --uploaded-prior-to via PIPENV_EXTRA_PIP_ARGS caused the subprocess
resolver to reject it as an unknown option. Instead, set pip_options.uploaded_prior_to
directly in Resolver.pip_options as a datetime object — the same pattern used for
other pip options like pre and cache_dir.
Rename _get_uploaded_prior_to_arg → _get_cool_down_timedelta to reflect that it now
returns a timedelta (or None) rather than a pip arg list. Update unit tests accordingly.
Switch the integration test from the private pypi fixture to pipenv_instance_pypi (real
PyPI), since pypiserver does not expose upload-time metadata and pip errors out rather
than silently ignoring the filter when --uploaded-prior-to is supplied.
Signed-off-by: Oz Tiram <oz.tiram@gmail.com>
Replace nested and sequential if-blocks with short-circuit or-expressions,
reducing branch count without any behavioural change.
Signed-off-by: Oz Tiram <oz.tiram@gmail.com>
Reads the new `cool-down-period = "<n>d"` Pipfile setting introduced in
plette 2.2.1 and forwards it to pip's `--uploaded-prior-to P<n>D` flag
during dependency resolution, restricting the resolver to package versions
uploaded at least N days ago.
Includes unit tests for the arg-conversion helper and the injection into
`extra_pip_args`, an integration test verifying the lock succeeds with the
setting present, and documentation in docs/pipfile.md.
Signed-off-by: Oz Tiram <oz.tiram@gmail.com>
Updates vendored plette from 2.1.0 to 2.2.1, which adds the
`cool-down-period` field to the `[pipenv]` section model with
validation and a `cool_down_period_timedelta` helper property.
Signed-off-by: Oz Tiram <oz.tiram@gmail.com>
* fix: don't mutate cached parsed_pipfile when locking deps
get_locked_dep popped ``version`` and ``ref`` directly off the entry it
received from ``pipfile_section``. Since #6649 made ``parsed_pipfile``
return a cached TOMLDocument by reference, those pops persisted across
the rest of the pipenv invocation — a subsequent ``write_toml`` (e.g.
``add_pipfile_entry_to_pipfile`` for the newly installed package) would
emit ``six = {}`` instead of ``six = {version = "*"}`` and strip the
version from any inline-table or outline-table siblings.
Copy the dict before scrubbing those keys. Add a unit regression test
that asserts get_locked_dep leaves the section untouched.
Fixes the integration regression hit by ``test_rewrite_outline_table``
and ``test_rewrite_outline_table_ooo`` on main since the pip 26.1
vendoring run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix recursive pipenv test bootstrap dependency
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix Pipfile script env expansion for project vars
* Harden script env expansion against ambient vars
* Isolate project-dir script test from outer pipenv
* Stabilize project-dir script regression under nested run
* Replace flaky project-dir run test with unit coverage
* Update tests/unit/test_utils.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Expand script env vars without mutating os.environ
Replace the temp_environ() clear+repopulate in safe_expandvars(env=...)
with a regex-based expander that reads from the provided mapping
directly. Avoids the per-arg environment copy and the global mutation,
which was not thread-safe and showed up in the script-args hot path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Cache resolver constraint work and harden benchmark runner
Resolver:
- Memoize prepare_constraint_file / default_constraint_file so the same
temporary constraint file isn't written twice per resolve.
- Cache parsed_constraints and parsed_default_constraints so pip's
parse_requirements doesn't re-open and re-parse them on each property
access.
- Cache the ignore_compatibility PackageFinder used by collect_hashes,
and reuse one finder() across the candidate lookups in
resolve_constraints.
- Drop the eager reverse_dependencies() load in process_resolver_results;
Entry never consumed it, and update-time routines build their own.
Benchmarks:
- Replace ad-hoc timing with a TimingRecord dataclass that captures
elapsed / user / system / cpu% / maxrss / block I/O via getrusage.
- Add --repeat (median across runs), --profile (cProfile output under
timings/, resolver kept in-process), --output-json, --no-json, and
--force-setup.
- Reuse an existing local requirements.txt unless --force-setup is
passed, and bump the update timeout to 900s.
Tests / docs / CI:
- New unit tests covering constraint caching, single-finder reuse, and
the reverse-dependencies skip.
- README documents the new flags; Makefile and .gitignore track the new
JSON output; CI uploads benchmark-results.json alongside stats.csv.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Cache parsed Pipfile document to avoid repeated TOML re-parses
Profiling pipenv lock against an 86-package Pipfile showed
parsed_pipfile being called 548 times, each time reading the file
and running tomlkit.parse — roughly 6.9s of the 39s profile was
spent re-parsing the same document.
Cache the parsed TOMLDocument on the Project instance and refresh it
only when the Pipfile's mtime changes. write_toml() explicitly
invalidates the cache as well so same-mtime writes (pipenv mutating
the Pipfile itself) always reparse on next access.
Measured on the benchmarks harness with an 86-package Pipfile:
pipenv lock before: 22.4s wall / 17.7s user CPU
pipenv lock after: 18.8s wall / 14.2s user CPU
→ ~16% wall / ~20% user-CPU reduction
_parse_pipfile call count in the profile drops from 548 → 2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add regression tests for Pipfile cache invalidation edge cases
Cover three scenarios that could theoretically produce stale reads:
- Rapid back-to-back writes within a single mtime tick (explicit
invalidation in write_toml must win over the mtime check).
- In-place mutation of the cached document followed by write_toml
(the ensure_proper_casing / add_package_to_pipfile pattern).
- write_toml to an unrelated path (snapshot/intermediate buffer),
which must NOT drop the Pipfile cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Parallelize network-bound resolver work and add small caches
Profiling post-parsed_pipfile-cache showed the remaining pipenv-side
cost in lock-warm was dominated by two tight sequential loops, each
issuing a network request per package:
* Resolver.resolve_hashes — 149 calls to collect_hashes, each
potentially hitting PyPI (get_hashes_from_pypi) or a simple-index
HTML page.
* Resolver.resolve_constraints — 149 calls to
PackageFinder.find_best_candidate just to read link.requires_python
and build python_requirements markers.
Both sets of calls are independent and keyed on distinct project
names. Dispatch them on a ThreadPoolExecutor (max_workers 8-16). pip
PackageFinder mutates its per-project candidate cache (self._all_candidates),
but different project names target different keys, and CPython's GIL
makes the dict writes atomic; requests.Session is safe for concurrent
GETs.
Two incidental caches:
* prepare_index_lookup() result — computed once per resolve (sources
and index_lookup are stable after Resolver.create()).
* canonicalize_name in get_constraints_from_deps — hoisted above
the branches so each dep is canonicalized once instead of twice.
Measured on the benchmarks harness (86-package Pipfile, post-parsed_pipfile
cache as baseline):
before: 18.8s wall / 14.2s user CPU
after: 15.1s wall / 12.3s user CPU
→ ~20% additional wall / ~13% additional user-CPU reduction
Cumulative from the pre-cache main-branch baseline (22.4s):
→ ~33% wall / ~30% user-CPU reduction across the branch.
Three new unit tests cover the parallelism and the index-lookup cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update tests/unit/test_resolver_regressions.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update benchmarks/benchmark.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update pipenv/utils/resolver.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update pipenv/utils/resolver.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix: replace timing-based concurrency check with deterministic Barrier in test_resolve_constraints_runs_candidate_lookup_in_parallel; stub _hash_finder in hash parallel test
Agent-Logs-Url: https://github.com/pypa/pipenv/sessions/cb9beef1-1f08-4e28-9a69-5f8a52a49bee
Co-authored-by: matteius <479892+matteius@users.noreply.github.com>
* refactor: compute is_pipfile boolean once using Path.resolve() in write_toml, reuse for newlines and cache invalidation
Agent-Logs-Url: https://github.com/pypa/pipenv/sessions/2556ac1e-ecc8-4f9f-b424-8405f02b1450
Co-authored-by: matteius <479892+matteius@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: matteius <479892+matteius@users.noreply.github.com>
* Fix pipenv shell breaking terminal input echo (#6633)
Move setecho(False) after the startup sentinel so the shell's readline
library has already initialised and saved its baseline terminal state
with echo ON. Previously, setecho(False) ran before readline init,
causing readline to save echo-off as its baseline and restore it on
every new prompt—permanently disabling input echo.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Scrub sentinel text from pexpect buffer before interact()
interact() flushes its internal buffer to stdout, so leaked sentinel
strings (__PIPENV_SHELL_READY__, __PIPENV_STARTUP_READY__) are printed
when setecho(False) was ineffective and expect() matched the echoed
command rather than the command output.
Fixes feedback on #6636.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Stop touching pty termios in fork_compat (fixes#6633 properly)
The previous sentinel + setecho(False/True) dance fought with the shell's
own readline termios management:
* setecho(True) at the end re-enables kernel pty ECHO *after* bash's
readline has already set stty -echo for its own line editing. Both the
kernel and readline then echo every keystroke, producing the "1234 →
11223344" and "^C → ^C^C" symptoms rlaager reported.
* setecho(False) early makes readline save echo-off as its baseline so
every new prompt permanently disables echo (the original #6572 bug).
Drop all setecho manipulation and rely on pexpect's expect() calls to
drain the buffer before interact() takes over. The final sentinel must
be consumed twice: once for the command echo, once for the command's
actual output — otherwise __PIPENV_SHELL_READY__ leaks to the user's
terminal when interact() flushes the buffer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update news/6633.bugfix.rst
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Validate tar link targets in data_filter fallback (GHSA-p4qx-p8p6-4gjf)
The ``LinkOutsideDestinationError`` fallback in ``untar_file`` routed
through ``tarfile.tar_filter``, which does not perform link-target
containment checks (those live inside the ``for_data`` branch of
CPython's ``_get_filtered_attrs``). On the three CPython patch versions
the workaround targets, attacker-controlled hardlink/symlink members
whose targets escaped the destination directory were silently allowed,
permitting writes outside the install root.
The fallback now only runs when an independent containment check
confirms the link target stays inside the destination — otherwise it
fails closed and the extraction is aborted. The check is also captured
in tasks/vendoring/patches/patched/ so it survives a re-vendor of pip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update tests/unit/test_tar_link_safety.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update tests/unit/test_tar_link_safety.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update tests/unit/test_tar_link_safety.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix: remove unused target_dir param from _build_malicious_tar, fix assertions
Agent-Logs-Url: https://github.com/pypa/pipenv/sessions/e5e508a6-9ae2-4384-8634-fc86125bbb84
Co-authored-by: matteius <479892+matteius@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: matteius <479892+matteius@users.noreply.github.com>
Drop the unnecessary `as _python_version_for_path` alias — `python_version`
doesn't collide with any local name (the only same-spelled tokens are dict
string keys and docstring references).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- #6641: pipenv run <cmd> -h <arg> passes -h through instead of showing
pipenv's help. Add a REMAINDER positional on the run subparser so flags
following the command name aren't reparsed as pipenv options.
- #6645: Pipfile python_version="<specifier>" (e.g. ">=3.9") no longer
crashes the resolver with "invalid literal for int()". Detect PEP 440
specifier expressions in _get_pipfile_python_override and return None
so the running interpreter's version is used.
- #6647: Install-time marker filtering evaluates markers against the
target venv's Python version, not the Python pipenv runs under. Removes
spurious "Ignoring …: markers … don't match your environment" warnings
when the system Python differs from the venv Python.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the outdated _PIPENV_COMPLETE instructions with the new
register-python-argcomplete workflow. Add Git Bash mention and
migration note for users upgrading from pipenv < 2026.5.0.
Refs #6631
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add documentation for users migrating from the old _PIPENV_COMPLETE
mechanism (pre-2026.5.0) to the new argcomplete-based workflow, and
add Git Bash (Windows) activation instructions.
Closes#6631
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix#6632: `pipenv audit --locked` failed because pip-audit's --locked
flag doesn't support Pipfile.lock format. Now generates a temporary
requirements file from Pipfile.lock and passes it via -r instead.
- Fix#6631: `_PIPENV_COMPLETE=bash_source pipenv` dumped the full help
text instead of a completion script after the Click-to-argparse migration.
Now detects the legacy env var and prints a migration message pointing
users to the new argcomplete-based workflow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix CLI regressions from argparse migration and docs update
- Fix#6628: --python flag ignored during sync. Shared CLI options now use
argparse.SUPPRESS so subparser defaults don't overwrite root parser values.
- Fix#6626: pipenv run no longer passes all arguments to target process.
Removed _add_common_options from run subparser so flags like --verbose, -v
are passed through to the user's command.
- Fix#6625: env var expansion broken in private source URLs. Fixed
expand_url_credentials to expand env vars before splitting user:password,
so a single ${TOKEN} containing 'user:pass' works correctly.
- Fix#6627: pipenv shell adds unexpected lines to command history. Prefixed
internal sendline commands with space to leverage HISTCONTROL=ignorespace.
- Fix#6167: Removed Fedora dnf install instructions from docs (package dropped
in Fedora 40).
* Add regression tests for GH-6628, GH-6626, GH-6625, GH-6627
- Test --python flag preserved when placed before/after subcommand
- Test pipenv run passes --verbose, -v, -q through to user command
- Test pipenv run --system still works
- Test fork_compat sendline commands have leading space for history
- Test expand_url_credentials with single token containing user:pass
- Test expand_url_credentials with separate user/pass env vars
- Test expand_url_credentials with no auth, literals, and unset vars
* Address PR feedback: preserve unexpanded env var tokens, improve comments
- expand_url_credentials: skip percent-encoding when no env vars were
actually expanded, so ${VAR} placeholders survive intact for later
expansion instead of becoming %24%7BVAR%7D.
- run subparser: add detailed comment explaining why -h/--help and
--system are intentionally kept while _add_common_options is removed.
- Update test_expand_url_credentials_unset_var_left_unchanged to verify
that unexpanded tokens are preserved without percent-encoding.