Maintenance/code cleanup 2026-05 Phase I (general base)#6663
Open
matteius wants to merge 65 commits into
Open
Conversation
No behavioural change. Surgical, in the same spirit as 52de026: - pipfile.Pipfile.__contains__: collapse "if x: return True; return False" into a direct boolean return. - project.Project: replace "isinstance(c, list) and len(c) == 0" with "and not c"; drop the redundant `import urllib.parse` (the file already imports `from urllib import parse`) and use `parse.urlsplit` at the single call site. - markers.is_instance: collapse "if X: return True; return False" into "return X". - resolver._is_download_status_line: same collapse on the trailing return pattern. All 483 unit tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PRD captures six structural pain points in the codebase (Project god class; parallel requirement-modelling layer; resolver subprocess seam; wide threaded parameter lists in routines; inlined former-vendor modules; duplicate URL/path helpers) and decomposes constructive moves against each into four waves of small, behaviour-preserving PRs. The plan operationalizes the PRD as a dependency-aware task graph: Wave 1 + Wave 2 (Initiatives A, B, C) are PR-granular and swarm-executable today; Wave 3 + Wave 4 (D, E, F) are scaffolded only and get full plans regenerated after the foundation lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T0.1: Initiative C is reframed from "state dict" (which doesn't exist in the code) to "wide threaded parameter lists" (the actual pattern in pipenv/routines/install.py). Initiative B drops markers.py from its triage set — markers.py is project-owned glue, not inlined former vendor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T0.2: Operating manual for agents executing tasks from docs/dev/modernization-plan.md. Covers branch policy, pre-commit checklist, off-limits paths, commit conventions, news fragments, review-flagged TODOs, and the definition of "task complete." Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_B.4: markers.py is owned project glue over pipenv.patched.pip._vendor
{distlib,packaging}. No separate vendor lineage. Recorded so the
question stays closed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_B.3: Narrow audit of is_file_url, url_to_path, path_to_url. These are filesystem-flavored URL handlers; they belong in fileutils.py under the domain-boundary rule (URL/scheme -> internet; filesystem path -> fileutils). is_valid_url is deferred to Initiative A. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_B.2: Per-function audit of pipenv/utils/requirements.py with caller counts and adopt/vendor/delete recommendations. Flags the normalize_name <-> pep423_name overlap with dependencies.py as a candidate move for Initiative E. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_A.1: Module-level symbols of pipenv/utils/internet.py and pipenv/utils/fileutils.py with caller counts and canonical-home decisions. Notes adjacent duplicate path_to_url in shell.py surfaced by T_B.3. Drives T_A.2's is_valid_url consolidation. This commit recovers the T_A.1 deliverable after a filesystem freeze corrupted the original commit's object writes; the inventory file content was intact in the working tree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_B.1: Per-symbol audit of pipenv/utils/requirementslib.py with caller counts, upstream-provenance notes, and adopt/vendor/delete recommendations. Boltons-derived dict-walkers flagged as a group. This commit recovers the T_B.1 deliverable after a filesystem freeze corrupted the original commit's object writes; the triage file content was intact in the working tree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All five Wave 1a triage/inventory tasks completed. Status, log, and files-edited fields populated with each task's commit hash and a short summary of substantive findings (notably: latent path_to_url duplicate in shell.py surfaced by T_B.3/T_A.1; suspected pep423_name bug and add_index_to_pipfile name collision surfaced by T_B.2 — both flagged for Initiative E). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_A.2: is_valid_url was defined identically in both pipenv/utils/internet.py and pipenv/utils/fileutils.py. Keep the implementation in internet.py (the canonical URL module per Initiative A inventory) and replace the fileutils.py definition with a thin DeprecationWarning shim. Intra-module call in fileutils.open_file is rewired to the canonical import via a module-level alias so the shim isn't tripped by pipenv itself. External consumers importing pipenv.utils.fileutils.is_valid_url continue to get the correct return value but see a DeprecationWarning pointing them to the canonical import. The shim is held for one release per the modernization plan; T_A.4 removes it. T_A.3 fold-in: pipenv.utils.requirementslib was the sole real internal caller binding to the fileutils copy (via a multi-symbol import line). The import is retargeted to pipenv.utils.internet in this commit so the strict-mode validation (-W error::DeprecationWarning) passes; T_A.3 is therefore consumed by T_A.2 and the orchestrator can mark it complete alongside. Includes a Towncrier removal fragment and a unit test asserting the shim both returns the correct value and emits the DeprecationWarning. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_B.5: Synthesizes T_B.1-T_B.4 sub-files into docs/dev/initiative-b-triage.md with a summary table at the top, per-module sections preserving each sub-file's findings, and drafted GitHub issue text at the bottom for the adopt/vendor/delete execution groups. Sub-files are removed since their content is incorporated. Issues are NOT opened automatically — the issue text is drafted in code-fenced blocks for the maintainer to copy-paste after review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_A.2 sweep: pipenv/utils/shell.py defined a second path_to_url with a different implementation (Path.as_uri() vs. the quoting-aware fileutils version) and zero internal callers — confirmed by grep. The canonical implementation in pipenv/utils/fileutils.py stays. The normalize_drive import from pipenv.utils.fileutils is retained (with noqa: F401) because tests/integration/test_cli.py and tests/unit/test_utils.py import or access it via the shell module. Surfaced by T_A.1's inventory and T_B.3's triage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_A.2: is_valid_url consolidated to pipenv.utils.internet with a DeprecationWarning shim in fileutils.py. Sweep follow-up removed the dead duplicate path_to_url in shell.py (zero callers, different implementation than the canonical fileutils version). T_A.3: folded into T_A.2's primary commit by necessity — the strict DeprecationWarning validation revealed requirementslib.py's import needed retargeting to keep the test suite clean. T_B.5: four Wave-1a sub-files consolidated into docs/dev/initiative-b-triage.md with a summary table and seven drafted gh-issue-create blocks (issues not opened — left for maintainer review). Validation note added to T_A.2: the literal -W error::DeprecationWarning flag at pytest invocation tripped on pytest_asyncio's own PytestDeprecationWarning; the pytest-scoped -W form documented in the plan is what to use. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per maintainer review: no GitHub issues opened — Initiative B work is executed directly under Wave 1c rather than tracked as separate issues. Revisions: - Removed the 7 "gh issue create" drafts at the bottom of the doc. - Removed the "Suggested execution issue" column from the summary table (per-module sections retain full context). - Relabeled the boltons subset from "vendor" to "replace (purpose-built helper)" — replacing the six boltons.iterutils primitives with a small dict-merge helper drops ~180 lines without adding a new vendored package. - Relabeled redact_netloc / redact_auth_from_url from "vendor" to "adopt (with provenance docstring)" — these are already pipenv-owned forks; the change is a one-line docstring noting divergence from pip-internal, not a code move. Per-symbol audit and per-module sections preserved as the working record. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
W1's caller-verification grep surfaced that the triage doc's "0 callers" claim for prepare_pip_source_args was stale: pipenv/utils/dependencies.py lines 36-41 import the requirementslib copy and use it at line 524. Beyond the caller count, the two implementations are observably divergent on credential-stripping, missing-URL handling, and trusted-host port handling — so switching dependencies.py from the requirementslib copy to the indexes.py canonical copy is a behaviour change, not a refactor. Reclassified from "delete" to "investigate" — folded into W3's "can divergent stale pip-internal copies be safely replaced?" bucket alongside unpack_url/get_http_url. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
W1: Removes six zero-caller public symbols from pipenv/utils/requirementslib.py: - strip_ssh_from_git_uri (no importers) - is_star (canonical copy at dependencies.py:418) - convert_entry_to_path (cascade - only user was is_installable_file) - is_installable_file (no importers) - get_setup_paths (no importers) - get_package_finder (canonical copy at resolver.py:162) prepare_pip_source_args was originally on this list but a real caller at dependencies.py:36-41,524 was surfaced during verification; its two implementations also diverge in defensive coding, so it's routed to W3 for investigation rather than straight deletion. Behaviour-preserving: zero internal callers verified by grep (excl. patched/, vendor/, and the requirementslib.py definition site itself). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
W4: pep423_name had a latent bug in its scheme-token guard: `any(token not in name ...)` is True for any name missing at least one scheme token (i.e. every real package name), so the `else` branch (`return name` unchanged) was dead. Corrected the predicate to skip the _->- rewrite only when the input actually contains a scheme token, matching the function's apparent intent (protect URLs and VCS specifiers from underscore-mangling). Also: pipenv/utils/requirements.py:61 had a sibling helper normalize_name that did the same _->- + .lower() rewrite (without the scheme-token guard) and had 4 callers. All four call sites pass plain names, so migrating them to the now-correct pep423_name is observably equivalent. normalize_name removed; callers in locking.py, project.py (x2), and resolver.py retargeted to pep423_name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ose-built helper W2: pipenv/utils/requirementslib.py contained six inlined boltons.iterutils primitives (PathAccessError, get_path, default_visit, dict_path_enter, dict_path_exit, remap) used only by merge_items, which is itself called three times in pipenv (pipfile.py:318, locking.py:387,580). Replaced the boltons machinery with a small purpose-built dict-merge helper that does only what merge_items needs: recursive last-write-wins merge with tomlkit container awareness. merge_items's public signature is unchanged, so the three call sites are untouched. Net: ~180 lines removed from requirementslib.py, ~30 added. Pinned the three real call patterns in tests/unit/test_requirementslib.py first to make the refactor behaviour-preserving by construction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…helper W5: pipenv had two is_editable implementations: pipenv/utils/requirementslib.py (handles both dict mappings and "-e foo" strings) and pipenv/utils/dependencies.py:1503 (handles only dict mappings, treats strings as non-editable). The dependencies.py copy had zero in-tree callers; every active caller (pipenv/project.py:48, pipenv/utils/locking.py:29, pipenv/utils/pipfile.py:14) imported the requirementslib version. Real callers iterate Pipfile/lockfile package entries, which TOML parses as either Mapping (table) or str (version specifier); the requirementslib semantics are required to keep the dash-e string branch working. Per the Initiative B plan, the canonical home is pipenv/utils/dependencies.py (requirementslib.py is intended to shrink). The dependencies.py copy is replaced with the broader semantics (Mapping or str), the requirementslib copy is deleted, and locking.py / pipfile.py are repointed at dependencies. project.py already imported from dependencies and now resolves to the same single definition. Behaviour-preserving for every current caller; verified by pinning tests in tests/unit/test_dependencies.py (TestIsEditable, 12 cases covering dict/string/tomlkit-InlineTable/None inputs). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…forks
W3a: redact_netloc and redact_auth_from_url in pipenv/utils/requirements.py
are deliberate forks of pip._internal.utils.misc.{redact_netloc,
redact_auth_from_url}. Two intentional behavioural divergences from the
pip-internal version:
1. ${ENV_VAR} placeholders in user/password are preserved (pip's version
unconditionally rewrites both to ****).
2. Standard SSH usernames (currently "git") are preserved
(pip's version has no such allowlist).
Both divergences are user-visible -- they appear in CLI output and in
the generated Pipfile.lock -- so swapping in pip's version would be a
behaviour change, not a refactor. Future readers were likely to assume
these are blind copies and silently replace them; the docstring closes
that door and points at docs/dev/initiative-b-triage.md for full
rationale.
Comment-only; no behaviour change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nance docstrings
W3b: side-by-side comparison of pipenv/utils/requirementslib.py's
unpack_url/get_http_url against the still-extant
pipenv.patched.pip._internal.operations.prepare.{unpack_url, get_http_url}
surfaced two load-bearing divergences. Adopt path chosen.
unpack_url divergence (load-bearing):
- pipenv's fork returns File(location, content_type=None) for VCS links;
pip's version returns None.
- The sole caller is determine_package_name in pipenv/utils/dependencies.py
(~line 923). It guards with package.link.scheme in REMOTE_SCHEMES, and
REMOTE_SCHEMES in pipenv/utils/constants.py is REMOTE_FILE_SCHEMES +
VCS_SCHEMES -- VCS links DO reach unpack_url in practice.
- The caller then does unconditional local_file.path access, then
branches on .endswith(".whl"/".zip"/".tar.gz"/".tar.bz2") and falls
through to find_package_name_from_directory(local_file.path) for VCS
unpacks. Pip's None return would AttributeError this call site.
- VCS detection uses link.scheme in VCS_SCHEMES (local constant) rather
than link.is_vcs. Equivalent in practice but kept as-is.
get_http_url divergence (deliberate):
- TempDirectory(globally_managed=False) here vs globally_managed=True in
pip. Pip's mode hooks into the process-level TempDirectoryRegistry;
pipenv's caller already owns lifecycle via with TemporaryDirectory()
as td: ... so global registration is the wrong lifetime.
Switching to the patched-pip versions would either crash
determine_package_name on VCS deps or leak temp dirs to pip's
process-level registry. Provenance docstrings added; the divergence is
documented inline and cross-referenced to docs/dev/initiative-b-triage.md.
Comment-only; no behaviour change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ical indexes.py copy
W3c: side-by-side comparison of pipenv/utils/requirementslib.py:86 vs
pipenv/utils/indexes.py:18 surfaced three observable divergences. Replace
path chosen: dependencies.py now imports from indexes.py and the
requirementslib copy is deleted.
Divergences (indexes.py is the stricter / safer side of each):
1. --trusted-host argument formatting. indexes.py uses urllib3's
parse_url() and emits "host:port" when a port is present; the
requirementslib copy uses urlparse(...).hostname which DROPS the
port. Pip's documented --trusted-host format is HOST[:PORT];
dropping the port over-trusts every port on the host, which is a
subtle security weakening.
2. Missing-URL handling. indexes.py raises PipenvUsageError("[[source]]
section does not contain a URL."); the requirementslib copy uses
subscript access (sources[0]["url"]) and would KeyError with a
useless message.
3. Hostname None-guard. The requirementslib copy has an extra
`and hostname` short-circuit; indexes.py relies on parse_url
returning a usable host.
The single caller (dependencies.py:537 inside
dependency_as_pip_install_line) receives `indexes` from
project.pipfile_sources(), whose entries are Pipfile [[source]] tables
that by schema always carry a `url` key -- divergence #2 is therefore
not reachable for this caller. Divergence #1 is a strict improvement
for the caller. Every other importer in pipenv (pip.py, resolver.py,
environment.py) already uses the indexes.py copy; this commit makes the
last remaining importer match.
The stale copy is gone, plus the now-unused
`_strip_credentials_from_url` import from
`pipenv/utils/requirementslib.py`. News fragment added under
`news/+initiative-b-prepare-pip-source-args.trivial.rst`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…its) T_B.7's scope expanded from "delete decisions only" to the full Initiative B execution per maintainer direction. Wave 1c landed seven commits across five work units: W1 5a84f5a delete 6 dead requirementslib symbols W2 de6628b replace boltons subset with purpose-built helper W4 e874e9d fix pep423_name + consolidate normalize_name W5 842939a collapse is_editable duplicate W3a dc58f7a redact_* provenance docstrings W3b 2d897a0 unpack_url/get_http_url adopted with docstrings W3c 8a7d0a7 prepare_pip_source_args replaced with canonical copy Net: requirementslib.py 740 -> 274 lines (-63%); 516 unit tests pass (+32 over baseline); two correctness fixes; one latent tomlkit-array bug fixed as a side-effect; no new vendored surface; no GitHub issues opened. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_B.6 / closes out Initiative B: Pipenv code lives either under pipenv/utils/ and elsewhere in pipenv/ (owned; refactor freely) or under pipenv/patched/ + pipenv/vendor/ (managed by vendoring tooling, not hand-edited). There is no third state. Inlining upstream code without owning it -- the worst-of-both-worlds pattern Initiative B spent five work units cleaning up -- is explicitly out of policy going forward. Exception: deliberate forks of upstream code (e.g. requirements.py's redact_*, requirementslib.py's unpack_url) require a provenance docstring naming what they forked and what divergence is intentional. References docs/dev/initiative-b-triage.md for the audit that drove this policy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_C.2: Per-parameter table covering all routine entry points and internal helpers in pipenv/routines/ and pipenv/core.py taking more than 3 parameters besides `project`. Parameters bucketed into install_policy / target_env / package_selection / execution_options / state_flags / other. Drives T_C.3's RoutineContext dataclass shape proposal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_B.6 (aa459f6): owned-vs-vendored code policy documented in docs/dev/contributing.md with a pointer from top-level CONTRIBUTING.md. Closes out Initiative B. T_C.2 (0c60c58): 240-row parameter inventory across 23 routine functions in pipenv/routines/. Cross-cutting findings recorded for T_C.3's RoutineContext dataclass shape proposal — notably, state_flags and "other" parameter groups identified as NOT RoutineContext material. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_C.3: Design proposal for the typed routine-context dataclass that will replace wide threaded parameter lists in pipenv/routines/. Four nested frozen dataclasses (TargetEnv, InstallPolicy, PackageSelection, ExecutionOptions) under a top-level RoutineContext, constructed via from_cli at the command boundary and mutated via dataclasses.replace. Drawn from T_C.2's parameter inventory; explicitly excludes state_flags and "other" parameter groups per T_C.2's finding that those are per-routine workflow plumbing, not shared context. Awaits maintainer sign-off before T_C.4 introduces the dataclass to code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ome recoverable The resolver subprocess invocation at pipenv/utils/resolver.py wrapped c.wait() with no timeout — a hung mirror or stuck pip download caused pipenv install/lock/sync to hang forever from the user's perspective. Documented finding from T_F.1 (docs/dev/initiative-f-protocol.md §6). Adds PIPENV_RESOLVER_TIMEOUT_S environment variable (default 1800 seconds = 30 minutes). On timeout the subprocess is killed and a clear error is surfaced naming the env var so users with large resolutions can extend it. The default is intentionally generous: 30 minutes is long enough that normal resolutions complete with margin, short enough that "hung forever" becomes "hung for at most 30 minutes." Invalid or non-positive values fall back to the default rather than raising. User-visible surface touched: - new ResolutionFailure with message naming PIPENV_RESOLVER_TIMEOUT_S - err.print of the same message in red so it is not buried - spinner failure text emitted before the raise, matching the existing non-zero-returncode path Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…text T_C.7: do_init (~10 params), do_install_validations, do_install_dependencies, and any other wide-arity helper in pipenv/routines/install.py now consume the RoutineContext dataclass. T_C.6's inline-from_cli bridge at do_init's call site is collapsed. After this task, install.py is fully on RoutineContext. T_C.8 migrates update.py; T_C.9 migrates uninstall.py / lock.py / sync.py. Per T_C.3 sign-off section 9: no backwards-compat shim. sync.py is touched only to add a from_cli bridge at the do_init / do_install_dependencies call sites; sync.py itself is migrated by T_C.9. batch_install / batch_install_iteration keep their call-state args (deps, sources, procs, sequential_deps) per design doc section 3 (other group); a TODO(swarm) flags the future BatchInstall bundling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to the dead-flag removal at 324a192: with PIPENV_SITE_DIR no longer set (incidentally cleaned up by 577a12a's incorporation of the dead-flag agent's hunk), get_pipenv_sitedir had zero callers. Removed the function and the importlib.metadata import that only fed it; also dropped typing.Optional from the imports since the function's return type was the lone consumer (other 8 Optional matches in the file are in docstrings only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… fixes T_C.7 (969fa57): install.py fully on RoutineContext. Seven helpers migrated; T_C.6's bridge collapsed; sync.py got an inline bridge for T_C.9 to collapse. Two pre-existing bugs fixed as side-effects (dead local in do_install_validations; ignore_hashes mid-loop mutation in do_install_dependencies). Three standalone reliability/cruft commits also landed in this batch (not separate plan tasks; surfaced by T_F.1's protocol audit): 324a192 remove 3 dead argv flags from pipenv/resolver.py 577a12a add PIPENV_RESOLVER_TIMEOUT_S subprocess timeout 5cb928d remove orphan get_pipenv_sitedir cascade Net effect: hung-mirror reliability bug closed; ~50 lines of dead code gone from resolver.py + utils/resolver.py. Unit suite: 584 passed (575 baseline → +6 signature-pin tests from T_C.7 + 2 from timeout-handler + 1 net from test adjustments). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_E.1 sign-off: all 8 §6 questions answered (single canonical home in dependencies.py; requirementslib.py retired after E.4 finishes moves; add_index_to_pipfile_with_trust_check rename; new pipenv/utils/unpack.py for unpack_url/get_http_url; markers.py stays separate; redact_* stays in requirements.py; T_E.7 rename to redact.py if still applicable; per-wave test pinning). T_E.2 unblocked. T_D.3..T_D.6 regenerated per the T_D.2 precedent and the T_D.1 §8 sign-off (no holding-pattern wrappers, no DeprecationWarning, no two-phase rollout — extract + caller migration in the same PR). Sequenced strict-serial since each touches pipenv/project.py heavily and parallel agents would race. Recommended order: Settings (smallest, lowest risk) -> VenvLocator -> Lockfile (Pipfile.lock-only, pylock deferred per T_D.1 §8.1) -> Pipfile (largest, most coupled, sets up the helper-bucket disposition decision at the end). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_C.8: do_update (was N params) plus its wide-arity internal helpers now consume the RoutineContext dataclass introduced in T_C.4. cmd_update in pipenv/cli/command.py constructs the context via RoutineContext.from_cli(...) and passes it through. Calls to do_sync (T_C.9's sync.py) and overwrite_with_default (T_C.9's lock.py) remain on their old signatures and are passed args unpacked from ctx at the call site; T_C.9 will simplify those when it migrates the receivers. Per T_C.3 sign-off §9: no backwards-compat shim. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_C.8 (0add7df): do_update 17 params -> (project, ctx). Two internal helpers also migrated. Scope-creep (necessary): one call site in install.py:handle_new_packages updated since do_update's signature changed and there's no backwards-compat shim policy. Out-of-scope upgrade() function in update.py deferred with TODO(swarm); construct internal helper ctx to feed the migrated helpers. 598 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…neContext T_C.9: completes Initiative C's routine-layer migration. The remaining three routine entry points (do_lock, do_sync, do_uninstall + do_purge) now consume the RoutineContext dataclass instead of wide threaded parameter lists. (do_purge stays on its pre-migration signature: only 3 non-project params and never called from CLI, so it does not cross T_C.2's wide-arity threshold.) T_C.7's inline RoutineContext.from_cli(...) bridge inside pipenv/routines/sync.py collapses; do_sync now consumes its caller's ctx and pins ignore_pipfile=True / skip_lock=True via a single dataclasses.replace. T_C.8's inline bridge for the do_sync call from pipenv/routines/update.py also collapses. The Click-context passthrough kwarg on do_uninstall (distinct from RoutineContext; only used to render usage hints) is dropped: cmd_uninstall never supplied it, and design doc section 3 calls for click-ctx plumbing to live at the CLI layer. Internal callers migrated in the same commit: - install.py (handle_outdated_lockfile, handle_missing_lockfile) - uninstall.py (post-uninstall lock branch) - clean.py (ensure_lockfile) - outdated.py (do_outdated) - update.py (both do_sync call sites) - cli/command.py (cmd_lock, cmd_sync, cmd_uninstall) No backwards-compat shim per T_C.3 section 9. Test coverage: - New tests/unit/test_lock_sync_uninstall_context_routing.py adds 22 signature-pin + flag-routing tests modelled on the existing T_C.5 / T_C.8 pin tests. - tests/unit/test_locking_no_mutation.py and the T_C.8 do_sync call-arg assertions updated to construct/inspect a RoutineContext. After this commit, the five pipenv/routines/*.py modules in T_C.2's inventory are fully on RoutineContext. Initiative C's wave-2 chain is complete. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s don't crash pipenv/routines/lock.py:95 wrapped `old_lock_data = lockfile.pop(category)` in `contextlib.suppress(KeyError)` but then unconditionally referenced `old_lock_data` 20 lines below (passed into venv_resolve_deps). If the category was missing from the lockfile (typical for first lock of a project, or when a new category is added), the suppressed KeyError left old_lock_data unbound and the subsequent reference raised UnboundLocalError. Initialize old_lock_data = None ahead of the suppress block. venv_resolve_deps already handles old_lock_data=None correctly (pipenv/utils/resolver.py:1360 substitutes lockfile.get(...) when None). Surfaced by T_C.9's test fixturing — the agent had to ensure the lockfile category existed in the stub to work around this. Behaviour-preserving for happy-path locks; closes a latent crash in cold-lock paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_C.9 (58ca629): do_lock, do_sync, do_uninstall migrated to (project, ctx). do_purge stays at 3 params (below threshold). T_C.7's sync.py bridge collapsed; T_C.8's update.py do_sync bridge collapsed. Misnamed Click ctx= passthrough in do_uninstall dropped per design's CLI-layer error-rendering boundary. Companion bug fix (4f5fb81): pre-existing UnboundLocalError in do_lock on first-lock / new-category paths, surfaced by T_C.9's test fixturing. After this batch the five pipenv/routines/*.py modules in T_C.2's inventory are fully on RoutineContext. Initiative C's wave-2 chain is structurally complete. 620 tests pass (598 baseline + 22 new). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_D.3: Second extraction in Initiative D (after T_D.2 Sources). Per T_D.1 §6.5 inventory the Settings bucket is 3 methods on Project (settings, update_settings, use_pylock); they extract to pipenv/utils/settings.py with the @cached_property access pattern T_D.2 established. Naming-collision resolution: Option A (Mapping-shaped subsystem). The previous Project.settings property returned a tomlkit Table / dict that every external caller treated as a Mapping (project.settings.get(key, default)). Settings now subclasses collections.abc.MutableMapping, so every existing read call site continues to work unchanged — only the writer migrated (project.update_settings(d) → project.settings.update(d)) and the typed-read accessor (project.use_pylock → project.settings.use_pylock). Option B (rename the data accessor) was considered and rejected: the 12+ external .get(...) call sites would all have needed migration, against zero benefit; the Mapping ABC is the natural fit because the legacy shape already was a Mapping. Caller migration in the same PR per T_D.1 §8.4 sign-off — no holding-pattern wrappers, no DeprecationWarning. Sites migrated: update_settings → settings.update at pipenv/routines/install.py; use_pylock → settings.use_pylock at pipenv/routines/install.py and tests/integration/test_pylock.py (2 sites). Internal Project references (lockfile_content, write_lockfile) also migrated. pipenv/project.py shrinks by 5 net lines (the @cached_property docstring is longer than the original 4-line settings property, but the 12-line update_settings and 4-line use_pylock methods both move out). The Settings class lives in pipenv/utils/settings.py (130 lines). Tests added in tests/unit/test_settings.py (10 tests covering the constructor, Mapping protocol, get/contains/iteration, the use_pylock typed accessor, and the update writer including the no-op-when-keys-present mtime invariant). Behaviour-preserving: every Settings method's logic is a relocation. Same Pipfile-read semantics (Settings reads through project.parsed_pipfile on every call so the mtime-invalidated cache is honoured). Same write semantics (Settings.update calls project.write_toml, same as the old update_settings did). Same defaults (use_pylock default False). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s.py T_E.2: Per the T_E.1 design + sign-off, move 6 Pipfile/lockfile bridge functions plus BAD_PACKAGES from pipenv/utils/requirements.py to pipenv/utils/dependencies.py (the canonical home). Rename the module-level add_index_to_pipfile to add_index_to_pipfile_with_trust_check to disambiguate from Project.add_index_to_pipfile per T_E.1 §3 sign-off. Moved: import_requirements add_index_to_pipfile -> add_index_to_pipfile_with_trust_check requirement_from_lockfile requirements_from_lockfile requirement_from_pipfile requirements_from_pipfile BAD_PACKAGES Caller imports migrated in the same PR per T_C.3 §9 / T_E.1 sign-off — no backwards-compat shim. After this commit requirements.py contains only the redact_* fork pair (which stays per T_E.1 §6 sign-off); T_E.7 will optionally rename the file to redact.py. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T_D.3 (0b8b583): Settings subsystem extracted to pipenv/utils/settings.py (130 lines). Option A naming-collision resolution (Mapping-shaped subsystem subclassing MutableMapping) preserves every existing project.settings.get(...) caller without migration. Only the writer and use_pylock accessor needed caller-site updates. 10 new tests. T_E.2 (caa72db): 7 symbols (6 Pipfile/lockfile bridges + BAD_PACKAGES) moved from pipenv/utils/requirements.py to pipenv/utils/dependencies.py with the add_index_to_pipfile_with_trust_check rename. requirements.py shrunk 414 -> 72 lines, now holding only the redact_* fork pair. 21 new tests pinning import shape and behaviour. Parallel-collision note: T_D.3 committed first while T_E.2 was mid-edit on install.py; T_D.3 incidentally absorbed T_E.2's install.py caller-import hunks. T_E.2 detected, committed the remaining 14 files, no work lost. Final tree state is correct. 651 tests pass (620 baseline + 10 from T_D.3 + 21 from T_E.2). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ll_dependencies The T_C.9 RoutineContext migration of do_sync collapsed the two historically-distinct call-site policies into a single shared ctx that pinned ignore_pipfile=True + skip_lock=True for both do_init AND do_install_dependencies. Pre-cascade, only do_init received those flags; do_install_dependencies always saw skip_lock=False so it installed from the lockfile. With skip_lock=True, do_install_dependencies takes the Pipfile-read branch (install.py:687/710/753) and constructs pip lines via install_req_from_pipfile. That code path mangles complex specs: - Remote wheel + extras (fastapi[standard]@url) → URL%5Bstandard%5D - file:///… → ./file:/… (pip rejects as nonexistent filename) - -e git+url@ref → -e name @ git+url@ref (illegal mix) - --index source lookup misses (index_name from raw Pipfile, sources list filtered by lockfile entries) Because do_install → handle_new_packages → do_update → do_sync funnels every `pipenv install <X>` through this path post-resolve, the regression cascaded into 7+ integration test failures (test_install_remote_wheel_file_with_extras, test_file_urls_work, test_install_vcs_ref_by_commit_hash, test_get_vcs_refs, test_install_specifying_index_url, test_no_duplicate_source_on_install, test_uninstall_all_local_files) plus test_update_locks. Surgical fix: build init_ctx with the historical pins and call do_init with that; call do_install_dependencies with the unmodified caller ctx. Update the unit test that locked in the buggy behaviour to assert the corrected contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ion to Sources API T_D.2 extracted the Sources subsystem from Project: project.sources is now the Sources instance (not the data list), data lives on project.sources.all, and the read methods (pipfile_sources, get_source, find_source) moved onto Sources too. Three integration tests in test_project.py were left calling the old API and broke in CI on the T_D.2 commit with TypeError + AttributeError. Migrate the three sites: project.sources[0] → project.sources.all[0] project.pipfile_sources() → project.sources.pipfile_sources() project.get_source(name=…) → project.sources.get_source(name=…) project.find_source(…) → project.sources.find_source(…) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pipenv's stable API is the CLI, not the Python module surface (T_C.3 §9 sign-off). A DeprecationWarning shim for an internal-only re-export protects nothing — it just delays the cleanup. Delete both shims outright, update the two real internal callers, and remove the shim-only unit test. This resolves T_A.4 (previously "held for release"); the release-gate rationale is moot when there is no stable Python API to gate against. Folds in the parallel cleanup of the SourceNotFound re-export that T_D.2 left on Project for the same purported deprecation window. - pipenv/utils/fileutils.py: delete is_valid_url shim + the ``_is_valid_url`` alias import; intra-module caller already used the canonical name. - pipenv/project.py: drop SourceNotFound from the sources import. - pipenv/utils/indexes.py: import SourceNotFound directly from pipenv.utils.sources (the only non-test internal caller). - pipenv/utils/sources.py: trim the docstring reference to the removed re-export. - tests/unit/test_utils.py: delete the shim-only deprecation test. - tests/unit/test_sources.py: import SourceNotFound from pipenv.utils.sources. 650 unit tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Maintenance-focused modernization/refactor across pipenv-owned code: consolidates duplicated utilities, decomposes Project into subsystems (Sources, Settings), and introduces RoutineContext to replace wide threaded routine signatures; includes reliability fixes and extensive new unit coverage.
Changes:
- Introduce
RoutineContextand migrate install/lock/sync/update/uninstall routines + CLI wiring to pass a single context object. - Extract
SourcesandSettingssubsystems fromProjectand migrate callers/tests to the new access patterns (project.sources.*,project.settings.*). - Consolidate requirement/helpers into
pipenv.utils.dependencies, add resolver subprocess timeout support, and add targeted regression/unit tests.
Reviewed changes
Copilot reviewed 66 out of 66 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/test_utils.py | Update mocks to reflect Project.sources subsystem shape. |
| tests/unit/test_update.py | Update update-helper tests for RoutineContext threading. |
| tests/unit/test_sources.py | New unit coverage for extracted Sources subsystem. |
| tests/unit/test_settings.py | New unit coverage for extracted Settings subsystem. |
| tests/unit/test_routine_context.py | New unit coverage for RoutineContext immutability/defaults. |
| tests/unit/test_resolver_regressions.py | Adjust hash collection tests + add resolver timeout regression tests. |
| tests/unit/test_requirementslib.py | New unit coverage pinning merge_items behavior. |
| tests/unit/test_locking_no_mutation.py | Update do_lock call sites to use RoutineContext. |
| tests/unit/test_lock_sync_uninstall_context_routing.py | New routing/signature tests for lock/sync/uninstall migrations. |
| tests/unit/test_do_update_context_routing.py | New routing/signature tests for update migration. |
| tests/unit/test_do_install_context_routing.py | New routing/signature tests for install migration. |
| tests/unit/test_dependencies.py | Add tests for pep423_name and consolidated is_editable. |
| tests/unit/test_dependencies_bridges.py | New tests pinning moved dependency bridge functions/import paths. |
| tests/unit/test_core.py | Update comment reference to pep423_name. |
| tests/integration/test_update.py | Update integration tests for update routine signature change. |
| tests/integration/test_requirements.py | Move integration import to utils.dependencies. |
| tests/integration/test_pylock.py | Update integration tests to project.settings.use_pylock. |
| tests/integration/test_project.py | Update integration tests for Sources subsystem API. |
| tests/integration/test_lockfile.py | Move integration usage to utils.dependencies. |
| tests/integration/test_import_requirements.py | Move integration import to utils.dependencies. |
| pipenv/utils/virtualenv.py | Switch to project.sources.pipfile_sources(). |
| pipenv/utils/sources.py | New Sources subsystem extracted from Project. |
| pipenv/utils/shell.py | Remove dead path_to_url duplicate; annotate re-export import. |
| pipenv/utils/settings.py | New Settings subsystem extracted from Project. |
| pipenv/utils/resolver.py | Add resolver subprocess timeout + migrate to project.sources.* hashing/sources. |
| pipenv/utils/requirements.py | Reduce module to intentional redact_* forks with provenance docs. |
| pipenv/utils/project.py | Remove dead pyproject backend helpers. |
| pipenv/utils/pipfile.py | Move imports to consolidated dependency helpers; simplify __contains__. |
| pipenv/utils/markers.py | Simplify boolean-return helper. |
| pipenv/utils/locking.py | Use pep423_name and consolidated requirement_from_lockfile import. |
| pipenv/utils/indexes.py | Update to Sources subsystem + SourceNotFound relocation. |
| pipenv/utils/fileutils.py | Consolidate is_valid_url import to utils.internet. |
| pipenv/utils/dependencies.py | Consolidate requirement bridges + fix pep423_name scheme guard + canonical is_editable. |
| pipenv/routines/update.py | Migrate do_update + internal helpers to accept RoutineContext. |
| pipenv/routines/uninstall.py | Migrate do_uninstall to accept RoutineContext. |
| pipenv/routines/sync.py | Migrate do_sync to accept RoutineContext and pin init/install policies via replace(). |
| pipenv/routines/requirements.py | Switch imports to consolidated dependency bridges. |
| pipenv/routines/outdated.py | Construct RoutineContext for do_lock call. |
| pipenv/routines/lock.py | Migrate do_lock to accept RoutineContext; fix first-lock old_lock_data init. |
| pipenv/routines/graph.py | Import BAD_PACKAGES from consolidated location. |
| pipenv/routines/context.py | New RoutineContext dataclass scaffold + from_cli. |
| pipenv/routines/clean.py | Import BAD_PACKAGES from consolidated location; use RoutineContext for do_lock. |
| pipenv/routines/audit.py | Switch requirements_from_lockfile import to consolidated location. |
| pipenv/resolver.py | Remove unused/ignored argv; simplify parsed packages shaping; use project.sources.*. |
| pipenv/project.py | Extract Sources/Settings subsystems + migrate internal call sites. |
| pipenv/environments.py | Add PIPENV_RESOLVER_TIMEOUT_S setting with validation/defaulting. |
| pipenv/environment.py | Update Environment to use project.sources.all. |
| pipenv/cli/command.py | Build and pass RoutineContext at CLI boundary for migrated routines. |
| news/+resolver-subprocess-timeout.behavior.rst | Release note for resolver timeout behavior. |
| news/+pep423-name-scheme-guard.bugfix.rst | Release note for pep423_name scheme-guard fix. |
| news/+initiative-b-prepare-pip-source-args.trivial.rst | Release note for helper consolidation. |
| news/+initiative-a-is-valid-url.removal.rst | Release note for is_valid_url canonicalization. |
| docs/dev/swarm-ops.md | New dev ops doc for modernization swarm workflow. |
| docs/dev/modernization-prd.md | New PRD describing modernization initiatives/constraints. |
| docs/dev/initiative-a-inventory.md | New inventory doc driving URL/path utility consolidation. |
| docs/dev/contributing.md | Document “owned vs vendored” policy and provenance conventions. |
| CONTRIBUTING.md | Link to the new owned/vendored policy section. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
… and news fragment Three Copilot review findings on PR #6663: 1. **Sources.all could return None.** When a lockfile exists but ``_meta.sources`` is missing/empty, the if/else fell through with no explicit return — the implicit ``None`` would break callers (``default``, ``index_urls``, ``get_source``, ``find_source``) that expect a list. Inherited behaviour from the original ``Project.sources`` property, surfaced by Copilot now that the shape is documented as a List accessor. Always fall back to ``pipfile_sources()`` after the lockfile path declines to provide anything. 2. **Stale Python 3.7 reference in find_source.** Inline comment claimed the explicit iteration was needed "to stay compatible with Python 3.7" — pipenv has required ≥ 3.10 for some time. The short-circuit concern the comment described is also moot after #1 (``self.all`` no longer returns None), so the explicit-iteration pattern stands on its own without the misleading version note — dropped. 3. **News fragment mismatched reality.** The fragment described a deprecation-with-future-removal, but T_A.4 in this same PR removed the alias outright (commit 8554651). Rewrote the fragment to describe the actual change and the underlying "CLI is the stable API" rationale; also covers the parallel SourceNotFound re-export removal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
matteius
added a commit
that referenced
this pull request
May 12, 2026
… and news fragment Three Copilot review findings on PR #6663: 1. **Sources.all could return None.** When a lockfile exists but ``_meta.sources`` is missing/empty, the if/else fell through with no explicit return — the implicit ``None`` would break callers (``default``, ``index_urls``, ``get_source``, ``find_source``) that expect a list. Inherited behaviour from the original ``Project.sources`` property, surfaced by Copilot now that the shape is documented as a List accessor. Always fall back to ``pipfile_sources()`` after the lockfile path declines to provide anything. 2. **Stale Python 3.7 reference in find_source.** Inline comment claimed the explicit iteration was needed "to stay compatible with Python 3.7" — pipenv has required ≥ 3.10 for some time. The short-circuit concern the comment described is also moot after #1 (``self.all`` no longer returns None), so the explicit-iteration pattern stands on its own without the misleading version note — dropped. 3. **News fragment mismatched reality.** The fragment described a deprecation-with-future-removal, but T_A.4 in this same PR removed the alias outright (commit 8554651). Rewrote the fragment to describe the actual change and the underlying "CLI is the stable API" rationale; also covers the parallel SourceNotFound re-export removal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Maintenance-mode modernization pass on pipenv's owned code, executed as a planned multi-initiative refactor under
docs/dev/modernization-prd.md(intent) anddocs/dev/modernization-plan.md(1441-line dependency-aware task tree). Touches 0 vendored or patched files;pipenv/vendor/andpipenv/patched/are off limits. The CLI is treated as pipenv's only stable API — no Python-API deprecation windows or back-compat shims.63 commits, +2060/-1896 in
pipenv/, +2702/-28 intests/, +5728 lines of design/inventory/plan docs underdocs/dev/. 651 unit tests pass; +47 new unit tests added.What changed
Initiative A — vendored-utility consolidation
is_valid_urlto a single canonical home (pipenv.utils.internet).path_to_urlinpipenv/utils/shell.py.is_valid_urldeprecation shim and its siblingpipenv.project.SourceNotFoundre-export — CLI is stable API, internal shims add noise.Initiative B — inlined-vendor triage (
pipenv/utils/requirementslib.py)Audited the 740-line
requirementslib.py(legacy from when requirementslib was vendored). Deleted 6 dead symbols, replaced 5 inlined-from-elsewhere helpers with canonical copies (boltons.iterutilssubset → purpose-built dict-merge helper;prepare_pip_source_args,unpack_url,get_http_url,is_editableadopted fromindexes.py/ pip-internal). Net:requirementslib.py740 → 274 lines.Initiative C —
RoutineContext(replaces wide threaded parameter lists)Introduced
pipenv/routines/context.py: a frozen@dataclasswith four nested groups (TargetEnv,InstallPolicy,PackageSelection,ExecutionOptions) plusfrom_cli(...)constructor. Migrated every routine module to consume it:do_install(project, ctx)do_install_dependencies,handle_new_packages,handle_lockfile(project, ctx, …)do_update,do_sync,do_lock,do_uninstall(project, ctx)Per the design sign-off (
docs/dev/initiative-c-design.md): no back-compat shims, single CLI-defaults materialization point.Initiative D — Project god-class decomposition
pipenv/project.py: 1816 → 1526 lines (-290). Two subsystems extracted to dedicated modules, accessed via@cached_propertyonProject:Sources(pipenv/utils/sources.py, 412 lines, 16 methods) — owns[[source]]table read/write, name canonicalization, dedup.project.sources.all,project.sources.pipfile_sources(),project.sources.get_source(...), etc.Settings(pipenv/utils/settings.py, 130 lines) —MutableMapping-shaped so everyproject.settings.get(...)caller continues to work;project.use_pylock/update_settingsretired.pylockfirst-class promotion is deferred to 2027 per maintainer sign-off (docs/dev/initiative-d-inventory.md§8.1). The "collaborator" naming was rejected (collides with GitHub's term for repo-access humans) and renamed to "subsystem" throughout.T_D.4(VenvLocator),T_D.5(Lockfile),T_D.6(Pipfile) — not in this PR, queued for follow-up.Initiative E — requirement-model consolidation
pipenv/utils/requirements.py: 414 → 72 lines (-342). Moved 7 Pipfile/lockfile bridge functions plusBAD_PACKAGEStopipenv/utils/dependencies.py(the canonical home).requirements.pynow holds only the deliberateredact_*pip-internal forks with provenance docstrings.Renamed module-level
add_index_to_pipfile→add_index_to_pipfile_with_trust_checkto disambiguate from theSourcesmethod of the same name.T_E.3–T_E.7(further consolidation; optional rename toredact.py) — not in this PR.Initiative F — resolver subprocess protocol
Wrote
docs/dev/initiative-f-protocol.md(588 lines) — the first reference doc for pipenv's parent↔resolver subprocess contract. Surfaced four standalone findings; three landed as fixes in this PR (see "Reliability fixes" below).T_F.2(typedResolverRequest/ResolverResponseschema) deferred.Reliability fixes surfaced along the way
These were not part of the original plan; they were found while doing the structural work and landed as small standalone commits:
PIPENV_RESOLVER_TIMEOUT_S(577a12a8) — resolver subprocesses had no timeout. A hung resolver hung pipenv forever; now recoverable.324a1927) — removed three flags the resolver script accepted but never read.do_lockUnboundLocalError (4f5fb81c) —old_lock_datareferenced before assignment on first-lock / new-category paths.do_install_dependenciespolicy-collapse regression (c4668fcc) — thedo_syncmigration accidentally pinnedskip_lock=Trueon bothdo_initANDdo_install_dependencies, driving the dependency-install path through the Pipfile reader rather than the lockfile reader. Mangled file:// URLs, extras-on-wheel-URL syntax, and editable-VCS specs in 7 integration tests. Fixed by splitting the two contexts.827962a4) — three test sites still used the pre-T_D.2project.sources[...]/project.pipfile_sources()API.Other dead-code removals (incidental)
Project.get_outdated_packageswrapper — pointed at a non-existent attribute (c794cf3c)_read_pyproject/ pyproject.toml build-system subsystem onProject— orphaned (fe38d27c)pep423_namedeadelsebranch +normalize_nameconsolidation (e874e9d0)is_editableduplicate wheredependencies.py:1503copy was dead (842939a4)get_pipenv_sitedirorphan + lone import (5cb928d6)do_install_validationsdead localpre = project.settings.get("allow_prereleases")(T_C.7)do_install_dependenciesmutating its ownignore_hashesparameter mid-loop (T_C.7)What's NOT in this PR
Queued for future PRs to keep this one reviewable:
T_D.4ExtractVenvLocatorsubsystemT_D.5ExtractLockfilesubsystem (Pipfile.lock-only; pylock-awareness deferred to 2027)T_D.6ExtractPipfilesubsystemT_E.3–T_E.7Further requirement-model consolidationT_F.2Typed resolver subprocess schemaTest plan
pytest tests/unit/)test_sources.py,test_settings.py,test_dependencies_bridges.py,test_do_install_context_routing.py,test_lock_sync_uninstall_context_routing.py,test_update_context_routing.py)pipenv install <X>smoke for: PyPI package, remote wheel with extras,file:///URL, editable VCS by ref, custom--index(regression coverage for thec4668fccfix)🤖 Generated with Claude Code