Skip to content

Commit 5b7f839

Browse files
committed
Dig into some performance optimizations.
1 parent 1dc28bd commit 5b7f839

File tree

7 files changed

+171
-214
lines changed

7 files changed

+171
-214
lines changed

pipenv/environment.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def __init__(
7878
self.prefix = Path(prefix if prefix else sys.prefix)
7979
self._base_paths = {}
8080
if self.is_venv:
81-
self._base_paths = self.get_paths()
81+
self._base_paths = self.get_paths
8282
self.sys_paths = get_paths()
8383

8484
def safe_import(self, name: str) -> ModuleType:
@@ -180,7 +180,7 @@ def base_paths(self) -> dict[str, str]:
180180
paths = self._base_paths.copy()
181181
else:
182182
try:
183-
paths = self.get_paths()
183+
paths = self.get_paths
184184
except Exception:
185185
paths = get_paths(
186186
self.install_scheme,
@@ -257,12 +257,6 @@ def python(self) -> str:
257257

258258
@cached_property
259259
def sys_path(self) -> list[str]:
260-
"""
261-
The system path inside the environment
262-
263-
:return: The :data:`sys.path` from the environment
264-
:rtype: list
265-
"""
266260
import json
267261

268262
current_executable = Path(sys.executable).as_posix()
@@ -328,6 +322,7 @@ def build_command(
328322
py_command = py_command % lines_as_str
329323
return py_command
330324

325+
@cached_property
331326
def get_paths(self) -> dict[str, str] | None:
332327
"""
333328
Get the paths for the environment by running a subcommand

pipenv/project.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -824,16 +824,6 @@ def dev_packages(self):
824824
"""Returns a list of dev-packages."""
825825
return self.get_pipfile_section("dev-packages")
826826

827-
@property
828-
def pipfile_is_empty(self):
829-
if not self.pipfile_exists:
830-
return True
831-
832-
if not self.read_pipfile():
833-
return True
834-
835-
return False
836-
837827
def create_pipfile(self, python=None):
838828
"""Creates the Pipfile, filled with juicy defaults."""
839829
# Inherit the pip's index configuration of install command.
@@ -983,7 +973,7 @@ def write_lockfile(self, content):
983973
f.write("\n")
984974

985975
def pipfile_sources(self, expand_vars=True):
986-
if self.pipfile_is_empty or "source" not in self.parsed_pipfile:
976+
if not self.pipfile_exists or "source" not in self.parsed_pipfile:
987977
sources = [self.default_source]
988978
if os.environ.get("PIPENV_PYPI_MIRROR"):
989979
sources[0]["url"] = os.environ["PIPENV_PYPI_MIRROR"]
@@ -1163,6 +1153,7 @@ def generate_package_pipfile_entry(
11631153
vcs_specifier = determine_vcs_specifier(package)
11641154
name = self.get_package_name_in_pipfile(req_name, category=category)
11651155
normalized_name = normalize_name(req_name)
1156+
markers = pip_line.split(";")[-1].strip() if ";" in pip_line else ""
11661157

11671158
extras = package.extras
11681159
specifier = "*"
@@ -1173,6 +1164,8 @@ def generate_package_pipfile_entry(
11731164
entry = {}
11741165
if extras:
11751166
entry["extras"] = list(extras)
1167+
if markers:
1168+
entry["markers"] = str(markers)
11761169
if path_specifier:
11771170
entry["file"] = unquote(str(path_specifier))
11781171
if pip_line.startswith("-e"):

pipenv/routines/update.py

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
import os
33
import sys
44
from collections import defaultdict
5-
from pathlib import Path
65
from typing import Dict, Set, Tuple
76

8-
from pipenv.exceptions import JSONParseError, PipenvCmdError
97
from pipenv.patched.pip._vendor.packaging.specifiers import SpecifierSet
108
from pipenv.patched.pip._vendor.packaging.version import InvalidVersion, Version
119
from pipenv.routines.outdated import do_outdated
@@ -17,11 +15,11 @@
1715
get_lockfile_section_using_pipfile_category,
1816
get_pipfile_category_using_lockfile_section,
1917
)
20-
from pipenv.utils.processes import run_command
2118
from pipenv.utils.project import ensure_project
2219
from pipenv.utils.requirements import add_index_to_pipfile
2320
from pipenv.utils.resolver import venv_resolve_deps
24-
from pipenv.vendor import pipdeptree
21+
from pipenv.vendor.pipdeptree._discovery import get_installed_distributions
22+
from pipenv.vendor.pipdeptree._models import PackageDAG
2523

2624

2725
def do_update(
@@ -106,44 +104,25 @@ def do_update(
106104

107105

108106
def get_reverse_dependencies(project) -> Dict[str, Set[Tuple[str, str]]]:
109-
"""Get reverse dependencies using pipdeptree."""
110-
pipdeptree_path = Path(pipdeptree.__file__).parent
111-
python_path = project.python()
112-
cmd_args = [python_path, str(pipdeptree_path), "-l", "--reverse", "--json-tree"]
113-
114-
c = run_command(cmd_args, is_verbose=project.s.is_verbose())
115-
if c.returncode != 0:
116-
raise PipenvCmdError(c.err, c.out, c.returncode)
117-
try:
118-
dep_tree = json.loads(c.stdout.strip())
119-
except json.JSONDecodeError:
120-
raise JSONParseError(c.stdout, c.stderr)
121-
122-
# Build reverse dependency map: package -> set of (dependent_package, required_version)
123-
reverse_deps = defaultdict(set)
107+
"""Get reverse dependencies without running pipdeptree as a subprocess."""
124108

125-
def process_tree_node(n, parents=None):
126-
if parents is None:
127-
parents = []
109+
# Use the project's specified Python interpreter
110+
python_interpreter = project.python()
128111

129-
package_name = n["package_name"]
130-
required_version = n.get("required_version", "Any")
112+
# Get installed packages for the specified interpreter
113+
pkgs = get_installed_distributions(interpreter=python_interpreter)
131114

132-
# Add the current node to its parents' reverse dependencies
133-
for parent in parents:
134-
reverse_deps[parent].add((package_name, required_version))
115+
# Create a package dependency tree (DAG) and reverse it
116+
dep_tree = PackageDAG.from_pkgs(pkgs).reverse()
135117

136-
# Process dependencies recursively, keeping track of parent path
137-
for dep in n.get("dependencies", []):
138-
process_tree_node(dep, parents + [package_name])
118+
# Initialize reverse dependency map
119+
reverse_deps = defaultdict(set)
139120

140-
# Start processing the tree from the root nodes
141-
for node in dep_tree:
142-
try:
143-
process_tree_node(node)
144-
except Exception as e: # noqa: PERF203
145-
err.print(
146-
f"[red bold]Warning[/red bold]: Unable to analyze dependencies: {str(e)}"
121+
# Populate the reverse dependency map
122+
for package, dependents in dep_tree.items():
123+
for dep in dependents:
124+
reverse_deps[dep.project_name].add(
125+
(package.project_name, getattr(package, "installed_version", "Any"))
147126
)
148127

149128
return reverse_deps
@@ -290,8 +269,13 @@ def upgrade(
290269
# Early conflict detection
291270
conflicts_found = False
292271
for package in package_args:
293-
if "==" in package:
294-
name, version = package.split("==")
272+
package_parts = [package]
273+
if ";" in package:
274+
package_parts = package.split(";")
275+
# Not using markers here for now
276+
# markers = ";".join(package_parts[1:]) if len(package_parts) > 1 else None
277+
if "==" in package_parts[0]:
278+
name, version = package_parts[0].split("==")
295279
conflicts = check_version_conflicts(name, version, reverse_deps, lockfile)
296280
if conflicts:
297281
conflicts_found = True

pipenv/utils/pipfile.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,23 +74,24 @@ def ensure_pipfile(
7474
if not (project.s.USING_DEFAULT_PYTHON or system)
7575
else None
7676
)
77-
if project.pipfile_is_empty:
77+
if not project.pipfile_exists:
7878
# Show an error message and exit if system is passed and no pipfile exists
7979
if system and not project.s.PIPENV_VIRTUALENV:
8080
raise exceptions.PipenvOptionsError(
8181
"--system",
8282
"--system is intended to be used for pre-existing Pipfile "
8383
"installation, not installation of specific packages. Aborting.",
8484
)
85+
err.print("Creating a Pipfile for this project...", style="bold")
86+
# Create the pipfile if it doesn't exist.
87+
project.create_pipfile(python=python)
8588
# If there's a requirements file, but no Pipfile...
8689
if project.requirements_exists and not skip_requirements:
8790
requirements_dir_path = os.path.dirname(project.requirements_location)
8891
console.print(
8992
f"[bold]requirements.txt[/bold] found in [bold yellow]{requirements_dir_path}"
9093
"[/bold yellow] instead of [bold]Pipfile[/bold]! Converting..."
9194
)
92-
# Create a Pipfile...
93-
project.create_pipfile(python=python)
9495
with console.status(
9596
"Importing requirements...", spinner=project.s.PIPENV_SPINNER
9697
) as st:
@@ -110,10 +111,6 @@ def ensure_pipfile(
110111
'We recommend updating your [bold]Pipfile[/bold] to specify the [bold]"*"'
111112
"[/bold] version, instead."
112113
)
113-
else:
114-
err.print("Creating a Pipfile for this project...", style="bold")
115-
# Create the pipfile if it doesn't exist.
116-
project.create_pipfile(python=python)
117114
# Validate the Pipfile's contents.
118115
if validate and project.virtualenv_exists and not project.s.PIPENV_SKIP_VALIDATION:
119116
# Ensure that Pipfile is using proper casing.

0 commit comments

Comments
 (0)