Skip to content

Maintenance/code cleanup 2026-05 Phase I (general base)#6663

Open
matteius wants to merge 65 commits into
mainfrom
maintenance/code-cleanup-2026-05
Open

Maintenance/code cleanup 2026-05 Phase I (general base)#6663
matteius wants to merge 65 commits into
mainfrom
maintenance/code-cleanup-2026-05

Conversation

@matteius
Copy link
Copy Markdown
Member

@matteius matteius commented May 12, 2026

Summary

Maintenance-mode modernization pass on pipenv's owned code, executed as a planned multi-initiative refactor under docs/dev/modernization-prd.md (intent) and docs/dev/modernization-plan.md (1441-line dependency-aware task tree). Touches 0 vendored or patched files; pipenv/vendor/ and pipenv/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 in tests/, +5728 lines of design/inventory/plan docs under docs/dev/. 651 unit tests pass; +47 new unit tests added.

What changed

Initiative A — vendored-utility consolidation

  • Inventoried the URL/path/internet utility surface; consolidated is_valid_url to a single canonical home (pipenv.utils.internet).
  • Removed the dead duplicate path_to_url in pipenv/utils/shell.py.
  • Deleted the is_valid_url deprecation shim and its sibling pipenv.project.SourceNotFound re-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.iterutils subset → purpose-built dict-merge helper; prepare_pip_source_args, unpack_url, get_http_url, is_editable adopted from indexes.py / pip-internal). Net: requirementslib.py 740 → 274 lines.

Initiative C — RoutineContext (replaces wide threaded parameter lists)

Introduced pipenv/routines/context.py: a frozen @dataclass with four nested groups (TargetEnv, InstallPolicy, PackageSelection, ExecutionOptions) plus from_cli(...) constructor. Migrated every routine module to consume it:

Routine Before After
do_install 17 individual kwargs (project, ctx)
do_install_dependencies, handle_new_packages, handle_lockfile ~9–12 kwargs each (project, ctx, …)
do_update, do_sync, do_lock, do_uninstall wide threaded lists (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_property on Project:

  • 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 every project.settings.get(...) caller continues to work; project.use_pylock/update_settings retired.

pylock first-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 plus BAD_PACKAGES to pipenv/utils/dependencies.py (the canonical home). requirements.py now holds only the deliberate redact_* pip-internal forks with provenance docstrings.

Renamed module-level add_index_to_pipfileadd_index_to_pipfile_with_trust_check to disambiguate from the Sources method of the same name.

T_E.3T_E.7 (further consolidation; optional rename to redact.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 (typed ResolverRequest/ResolverResponse schema) 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.
  • Dead resolver argv (324a1927) — removed three flags the resolver script accepted but never read.
  • do_lock UnboundLocalError (4f5fb81c) — old_lock_data referenced before assignment on first-lock / new-category paths.
  • do_install_dependencies policy-collapse regression (c4668fcc) — the do_sync migration accidentally pinned skip_lock=True on both do_init AND do_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.
  • Sources API migration in test_project.py (827962a4) — three test sites still used the pre-T_D.2 project.sources[...] / project.pipfile_sources() API.

Other dead-code removals (incidental)

  • Project.get_outdated_packages wrapper — pointed at a non-existent attribute (c794cf3c)
  • Entire _read_pyproject / pyproject.toml build-system subsystem on Project — orphaned (fe38d27c)
  • pep423_name dead else branch + normalize_name consolidation (e874e9d0)
  • is_editable duplicate where dependencies.py:1503 copy was dead (842939a4)
  • Resolver get_pipenv_sitedir orphan + lone import (5cb928d6)
  • do_install_validations dead local pre = project.settings.get("allow_prereleases") (T_C.7)
  • do_install_dependencies mutating its own ignore_hashes parameter mid-loop (T_C.7)

What's NOT in this PR

Queued for future PRs to keep this one reviewable:

  • T_D.4 Extract VenvLocator subsystem
  • T_D.5 Extract Lockfile subsystem (Pipfile.lock-only; pylock-awareness deferred to 2027)
  • T_D.6 Extract Pipfile subsystem
  • T_E.3T_E.7 Further requirement-model consolidation
  • T_F.2 Typed resolver subprocess schema

Test plan

  • 651 unit tests pass locally (pytest tests/unit/)
  • +47 new targeted unit tests for the extracted subsystems (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)
  • Smoke/integration CI on Linux/macOS/Windows × Python 3.10–3.14 (this PR)
  • Manual pipenv install <X> smoke for: PyPI package, remote wheel with extras, file:/// URL, editable VCS by ref, custom --index (regression coverage for the c4668fcc fix)

🤖 Generated with Claude Code

matteius and others added 30 commits May 12, 2026 00:00
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>
matteius and others added 16 commits May 12, 2026 03:22
…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>
@matteius matteius marked this pull request as ready for review May 12, 2026 14:50
@matteius matteius requested review from Copilot and oz123 and removed request for oz123 May 12, 2026 14:50
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

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 RoutineContext and migrate install/lock/sync/update/uninstall routines + CLI wiring to pass a single context object.
  • Extract Sources and Settings subsystems from Project and 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.

Comment thread pipenv/utils/sources.py
Comment thread pipenv/utils/sources.py Outdated
Comment thread pipenv/utils/fileutils.py
Comment thread news/+initiative-a-is-valid-url.removal.rst Outdated
matteius and others added 2 commits May 12, 2026 11:09
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>
@matteius matteius changed the title Maintenance/code cleanup 2026 05 Maintenance/code cleanup 2026-05 May 12, 2026
@matteius matteius changed the title Maintenance/code cleanup 2026-05 Maintenance/code cleanup 2026-05 Phase I May 12, 2026
@matteius matteius changed the title Maintenance/code cleanup 2026-05 Phase I Maintenance/code cleanup 2026-05 Phase I (general base) May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants