Skip to content

Commit ef04cd2

Browse files
committed
Replace the old Entry class with something simpler based on data-classes + pip session requests performance issue patch
1 parent 870226d commit ef04cd2

File tree

8 files changed

+392
-576
lines changed

8 files changed

+392
-576
lines changed

pipenv/resolver.py

Lines changed: 266 additions & 485 deletions
Large diffs are not rendered by default.

pipenv/routines/install.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,6 @@ def do_init(
701701
pypi_mirror=pypi_mirror,
702702
categories=categories,
703703
)
704-
err.print(packages_updated)
705704

706705
if not allow_global and not deploy and "PIPENV_ACTIVE" not in os.environ:
707706
console.print(

pipenv/utils/dependencies.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,22 +87,18 @@ def clean_pkg_version(version):
8787

8888
def get_lockfile_section_using_pipfile_category(category):
8989
if category == "dev-packages":
90-
lockfile_section = "develop"
90+
return "develop"
9191
elif category == "packages":
92-
lockfile_section = "default"
93-
else:
94-
lockfile_section = category
95-
return lockfile_section
92+
return "default"
93+
return category
9694

9795

9896
def get_pipfile_category_using_lockfile_section(category):
9997
if category == "develop":
100-
lockfile_section = "dev-packages"
98+
return "dev-packages"
10199
elif category == "default":
102-
lockfile_section = "packages"
103-
else:
104-
lockfile_section = category
105-
return lockfile_section
100+
return "packages"
101+
return category
106102

107103

108104
class HackedPythonVersion:
@@ -485,7 +481,7 @@ def dependency_as_pip_install_line(
485481
else:
486482
if "#egg=" in vcs_url:
487483
vcs_url = vcs_url.split("#egg=")[0]
488-
git_req = f"{dep_name}{extras}@ {include_vcs}{vcs_url}{ref}"
484+
git_req = f"{dep_name}{extras} @ {include_vcs}{vcs_url}{ref}"
489485
if "subdirectory" in dep:
490486
git_req += f"#subdirectory={dep['subdirectory']}"
491487

@@ -780,7 +776,7 @@ def determine_package_name(package: InstallRequirement):
780776
elif "#egg=" in str(package):
781777
req_name = str(package).split("#egg=")[1]
782778
req_name = req_name.split("[")[0]
783-
elif "@ " in str(package):
779+
elif " @ " in str(package):
784780
req_name = str(package).split("@ ")[0]
785781
req_name = req_name.split("[")[0]
786782
elif package.link and package.link.scheme in REMOTE_SCHEMES:

pipenv/utils/locking.py

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from json import JSONDecodeError
88
from pathlib import Path
99
from tempfile import NamedTemporaryFile
10-
from typing import Any, Dict, Iterator, List, Optional
10+
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple
1111

1212
from pipenv.patched.pip._internal.req.req_install import InstallRequirement
1313
from pipenv.utils.dependencies import (
@@ -43,77 +43,96 @@ def merge_markers(entry, markers):
4343

4444
def format_requirement_for_lockfile(
4545
req: InstallRequirement,
46-
markers_lookup,
47-
index_lookup,
48-
original_deps,
49-
pipfile_entries,
50-
hashes=None,
51-
):
52-
if req.specifier:
53-
version = str(req.specifier)
54-
else:
55-
version = None
46+
markers_lookup: Dict[str, str],
47+
index_lookup: Dict[str, str],
48+
original_deps: Dict[str, Any],
49+
pipfile_entries: Dict[str, Any],
50+
hashes: Optional[Set[str]] = None,
51+
) -> Tuple[str, Dict[str, Any]]:
52+
"""Format a requirement for the lockfile with improved VCS handling."""
5653
name = normalize_name(req.name)
57-
index = index_lookup.get(name)
58-
markers = req.markers
59-
req.index = index
60-
pipfile_entry = pipfile_entries[name] if name in pipfile_entries else {}
61-
entry = {}
54+
entry: Dict[str, Any] = {"name": name}
55+
pipfile_entry = pipfile_entries.get(name, {})
56+
57+
# Handle VCS requirements
6258
if req.link and req.link.is_vcs:
6359
vcs = req.link.scheme.split("+", 1)[0]
64-
entry["ref"] = determine_vcs_revision_hash(req, vcs, pipfile_entry.get("ref"))
6560

61+
# Get VCS URL from original deps or normalize the link URL
6662
if name in original_deps:
6763
entry[vcs] = original_deps[name]
6864
else:
6965
vcs_url, _ = normalize_vcs_url(req.link.url)
7066
entry[vcs] = vcs_url
71-
if pipfile_entry.get("subdirectory"):
67+
68+
# Handle reference information - try multiple sources
69+
ref = determine_vcs_revision_hash(req, vcs, pipfile_entry.get("ref"))
70+
if ref:
71+
entry["ref"] = ref
72+
73+
# Handle subdirectory information
74+
if isinstance(pipfile_entry, dict) and pipfile_entry.get("subdirectory"):
7275
entry["subdirectory"] = pipfile_entry["subdirectory"]
7376
elif req.link.subdirectory_fragment:
7477
entry["subdirectory"] = req.link.subdirectory_fragment
75-
if req.req:
76-
entry["version"] = str(req.specifier)
77-
elif version:
78-
entry["version"] = version
79-
elif req.link and req.link.is_file:
80-
entry["file"] = req.link.url
81-
if hashes:
82-
entry["hashes"] = sorted(set(hashes))
83-
entry["name"] = name
84-
if index:
85-
entry.update({"index": index})
78+
79+
# Handle non-VCS requirements
80+
else:
81+
if req.req and req.req.specifier:
82+
entry["version"] = str(req.req.specifier)
83+
elif req.specifier:
84+
entry["version"] = str(req.specifier)
85+
elif req.link and req.link.is_file:
86+
entry["file"] = req.link.url
87+
88+
# Add index information
89+
if name in index_lookup:
90+
entry["index"] = index_lookup[name]
91+
92+
# Handle markers
93+
markers = req.markers
8694
if markers:
87-
entry.update({"markers": str(markers)})
95+
entry["markers"] = str(markers)
8896
if name in markers_lookup:
8997
merge_markers(entry, markers_lookup[name])
90-
if isinstance(pipfile_entry, dict) and "markers" in pipfile_entry:
91-
merge_markers(entry, pipfile_entry["markers"])
92-
if isinstance(pipfile_entry, dict) and "os_name" in pipfile_entry:
93-
merge_markers(entry, f"os_name {pipfile_entry['os_name']}")
94-
entry = translate_markers(entry)
98+
if isinstance(pipfile_entry, dict):
99+
if "markers" in pipfile_entry:
100+
merge_markers(entry, pipfile_entry["markers"])
101+
if "os_name" in pipfile_entry:
102+
merge_markers(entry, f"os_name {pipfile_entry['os_name']}")
103+
104+
# Handle extras
95105
if req.extras:
96106
entry["extras"] = sorted(req.extras)
97-
if isinstance(pipfile_entry, dict) and pipfile_entry.get("file"):
98-
entry["file"] = pipfile_entry["file"]
99-
if pipfile_entry.get("editable"):
100-
entry["editable"] = pipfile_entry.get("editable")
101-
entry.pop("version", None)
102-
entry.pop("index", None)
103-
elif isinstance(pipfile_entry, dict) and pipfile_entry.get("path"):
104-
entry["path"] = pipfile_entry["path"]
105-
if pipfile_entry.get("editable"):
106-
entry["editable"] = pipfile_entry.get("editable")
107-
entry.pop("version", None)
108-
entry.pop("index", None)
107+
108+
# Handle hashes
109+
if hashes:
110+
entry["hashes"] = sorted(set(hashes))
111+
112+
# Handle file/path entries from Pipfile
113+
if isinstance(pipfile_entry, dict):
114+
if pipfile_entry.get("file"):
115+
entry["file"] = pipfile_entry["file"]
116+
if pipfile_entry.get("editable"):
117+
entry["editable"] = pipfile_entry["editable"]
118+
entry.pop("version", None)
119+
entry.pop("index", None)
120+
elif pipfile_entry.get("path"):
121+
entry["path"] = pipfile_entry["path"]
122+
if pipfile_entry.get("editable"):
123+
entry["editable"] = pipfile_entry["editable"]
124+
entry.pop("version", None)
125+
entry.pop("index", None)
126+
127+
entry = translate_markers(entry)
109128
return name, entry
110129

111130

112131
def get_locked_dep(project, dep, pipfile_section, current_entry=None):
113132
# initialize default values
114133
is_top_level = False
115134

116-
# if the dependency has a name, find corresponding entry in pipfile
135+
# # if the dependency has a name, find corresponding entry in pipfile
117136
if isinstance(dep, dict) and dep.get("name"):
118137
dep_name = pep423_name(dep["name"])
119138
for pipfile_key, pipfile_entry in pipfile_section.items():

pipenv/utils/project.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os
22
import sys
3-
from functools import lru_cache
43
from typing import Optional
54

65
from pipenv import exceptions
@@ -97,7 +96,6 @@ def ensure_project(
9796
os.environ["PIP_PYTHON_PATH"] = project.python(system=system)
9897

9998

100-
@lru_cache
10199
def get_setuptools_version() -> Optional["STRING_TYPE"]:
102100
try:
103101
setuptools_dist = importlib_metadata.distribution("setuptools")

pipenv/utils/requirements.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def requirement_from_lockfile(
193193
pip_line = f"-e {include_vcs}{vcs_url}{ref_str}{egg_fragment}{extras}"
194194
pip_line += f"&subdirectory={subdirectory}" if subdirectory else ""
195195
else:
196-
pip_line = f"{package_name}{extras}@ {include_vcs}{vcs_url}{ref_str}"
196+
pip_line = f"{package_name}{extras} @ {include_vcs}{vcs_url}{ref_str}"
197197
pip_line += f"#subdirectory={subdirectory}" if subdirectory else ""
198198
return pip_line
199199
# Handling file-sourced packages

pipenv/utils/resolver.py

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
import sys
66
import tempfile
77
import warnings
8-
from functools import lru_cache
8+
from functools import cached_property, lru_cache
99
from pathlib import Path
10-
from typing import Dict, List, Optional
10+
from typing import Any, Dict, List, Optional, Tuple, Union
1111

1212
from pipenv import environments, resolver
1313
from pipenv.exceptions import ResolutionFailure
@@ -27,6 +27,7 @@
2727
from pipenv.patched.pip._vendor.packaging.utils import canonicalize_name
2828
from pipenv.project import Project
2929
from pipenv.utils import console, err
30+
from pipenv.utils.dependencies import determine_vcs_revision_hash, normalize_vcs_url
3031
from pipenv.utils.fileutils import create_tracked_tempdir
3132
from pipenv.utils.requirements import normalize_name
3233

@@ -38,7 +39,6 @@
3839
get_lockfile_section_using_pipfile_category,
3940
is_pinned_requirement,
4041
prepare_constraint_file,
41-
translate_markers,
4242
)
4343
from .indexes import parse_indexes, prepare_pip_source_args
4444
from .internet import is_pypi_url
@@ -308,7 +308,7 @@ def pip_options(self):
308308
)
309309
return pip_options
310310

311-
@property
311+
@cached_property
312312
def session(self):
313313
return self.pip_command._build_session(self.pip_options)
314314

@@ -589,36 +589,57 @@ def resolve_hashes(self):
589589
return self.hashes
590590

591591
def clean_skipped_result(
592-
self, req_name: str, ireq: InstallRequirement, pipfile_entry
593-
):
594-
ref = None
592+
self,
593+
req_name: str,
594+
ireq: InstallRequirement,
595+
pipfile_entry: Union[str, Dict[str, Any]],
596+
) -> Tuple[str, Dict[str, Any]]:
597+
"""Clean up skipped requirements with better VCS handling."""
598+
# Start with pipfile entry if it's a dict, otherwise create new dict
599+
entry = pipfile_entry.copy() if isinstance(pipfile_entry, dict) else {}
600+
entry["name"] = req_name
601+
602+
# Handle VCS references
595603
if ireq.link and ireq.link.is_vcs:
596-
ref = ireq.link.egg_fragment
604+
vcs = ireq.link.scheme.split("+", 1)[0]
597605

598-
if isinstance(pipfile_entry, dict):
599-
entry = pipfile_entry.copy()
600-
else:
601-
entry = {}
602-
entry["name"] = req_name
606+
# Try to get reference from multiple sources
607+
ref = determine_vcs_revision_hash(ireq, vcs, ireq.link)
608+
609+
if ref:
610+
entry["ref"] = ref
611+
elif ireq.link.hash:
612+
entry["ref"] = ireq.link.hash
613+
614+
# Ensure VCS URL is present
615+
if vcs not in entry:
616+
vcs_url, _ = normalize_vcs_url(ireq.link.url)
617+
entry[vcs] = vcs_url
618+
619+
# Remove version if editable
603620
if entry.get("editable", False) and entry.get("version"):
604621
del entry["version"]
605-
ref = ref if ref is not None else entry.get("ref")
606-
if ref:
607-
entry["ref"] = ref
622+
623+
# Add hashes
608624
collected_hashes = self.collect_hashes(ireq)
609625
if collected_hashes:
610626
entry["hashes"] = sorted(set(collected_hashes))
627+
611628
return req_name, entry
612629

613-
def clean_results(self):
614-
reqs = [(ireq,) for ireq in self.resolved_tree]
630+
def clean_results(self) -> List[Dict[str, Any]]:
631+
"""Clean all results including both resolved and skipped packages."""
615632
results = {}
616-
for (ireq,) in reqs:
633+
634+
# Handle resolved packages
635+
for ireq in self.resolved_tree:
617636
if normalize_name(ireq.name) in self.skipped:
618637
continue
638+
619639
collected_hashes = self.hashes.get(ireq, set())
620640
if collected_hashes:
621641
collected_hashes = sorted(collected_hashes)
642+
622643
name, entry = format_requirement_for_lockfile(
623644
ireq,
624645
self.markers_lookup,
@@ -627,23 +648,25 @@ def clean_results(self):
627648
self.pipfile_entries,
628649
collected_hashes,
629650
)
630-
entry = translate_markers(entry)
651+
631652
if name in results:
632653
results[name].update(entry)
633654
else:
634655
results[name] = entry
656+
657+
# Handle skipped packages
635658
for req_name in self.skipped:
636659
install_req = self.install_reqs[req_name]
637-
name, entry = self.clean_skipped_result(
638-
req_name, install_req, self.pipfile_entries[req_name]
639-
)
640-
entry = translate_markers(entry)
660+
pipfile_entry = self.pipfile_entries.get(req_name, {})
661+
662+
name, entry = self.clean_skipped_result(req_name, install_req, pipfile_entry)
663+
641664
if name in results:
642665
results[name].update(entry)
643666
else:
644667
results[name] = entry
645-
results = list(results.values())
646-
return results
668+
669+
return list(results.values())
647670

648671

649672
def _show_warning(message, category, filename, lineno, line):

tests/integration/test_lock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ def test_lock_package_with_compatible_release_specifier(pipenv_instance_private_
588588
@pytest.mark.install
589589
def test_default_lock_overwrite_dev_lock(pipenv_instance_pypi):
590590
with pipenv_instance_pypi() as p:
591-
c = p.pipenv("install 'click==6.7'")
591+
c = p.pipenv("install click==6.7")
592592
assert c.returncode == 0
593593
c = p.pipenv("install -d flask")
594594
assert c.returncode == 0

0 commit comments

Comments
 (0)