Skip to content

Commit

Permalink
Dig into some performance optimizations.
Browse files Browse the repository at this point in the history
  • Loading branch information
matteius committed Nov 7, 2024
1 parent 1dc28bd commit 5b7f839
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 214 deletions.
11 changes: 3 additions & 8 deletions pipenv/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def __init__(
self.prefix = Path(prefix if prefix else sys.prefix)
self._base_paths = {}
if self.is_venv:
self._base_paths = self.get_paths()
self._base_paths = self.get_paths
self.sys_paths = get_paths()

def safe_import(self, name: str) -> ModuleType:
Expand Down Expand Up @@ -180,7 +180,7 @@ def base_paths(self) -> dict[str, str]:
paths = self._base_paths.copy()
else:
try:
paths = self.get_paths()
paths = self.get_paths
except Exception:
paths = get_paths(
self.install_scheme,
Expand Down Expand Up @@ -257,12 +257,6 @@ def python(self) -> str:

@cached_property
def sys_path(self) -> list[str]:
"""
The system path inside the environment
:return: The :data:`sys.path` from the environment
:rtype: list
"""
import json

current_executable = Path(sys.executable).as_posix()
Expand Down Expand Up @@ -328,6 +322,7 @@ def build_command(
py_command = py_command % lines_as_str
return py_command

@cached_property
def get_paths(self) -> dict[str, str] | None:
"""
Get the paths for the environment by running a subcommand
Expand Down
15 changes: 4 additions & 11 deletions pipenv/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -824,16 +824,6 @@ def dev_packages(self):
"""Returns a list of dev-packages."""
return self.get_pipfile_section("dev-packages")

@property
def pipfile_is_empty(self):
if not self.pipfile_exists:
return True

if not self.read_pipfile():
return True

return False

def create_pipfile(self, python=None):
"""Creates the Pipfile, filled with juicy defaults."""
# Inherit the pip's index configuration of install command.
Expand Down Expand Up @@ -983,7 +973,7 @@ def write_lockfile(self, content):
f.write("\n")

def pipfile_sources(self, expand_vars=True):
if self.pipfile_is_empty or "source" not in self.parsed_pipfile:
if not self.pipfile_exists or "source" not in self.parsed_pipfile:
sources = [self.default_source]
if os.environ.get("PIPENV_PYPI_MIRROR"):
sources[0]["url"] = os.environ["PIPENV_PYPI_MIRROR"]
Expand Down Expand Up @@ -1163,6 +1153,7 @@ def generate_package_pipfile_entry(
vcs_specifier = determine_vcs_specifier(package)
name = self.get_package_name_in_pipfile(req_name, category=category)
normalized_name = normalize_name(req_name)
markers = pip_line.split(";")[-1].strip() if ";" in pip_line else ""

extras = package.extras
specifier = "*"
Expand All @@ -1173,6 +1164,8 @@ def generate_package_pipfile_entry(
entry = {}
if extras:
entry["extras"] = list(extras)
if markers:
entry["markers"] = str(markers)
if path_specifier:
entry["file"] = unquote(str(path_specifier))
if pip_line.startswith("-e"):
Expand Down
62 changes: 23 additions & 39 deletions pipenv/routines/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
import os
import sys
from collections import defaultdict
from pathlib import Path
from typing import Dict, Set, Tuple

from pipenv.exceptions import JSONParseError, PipenvCmdError
from pipenv.patched.pip._vendor.packaging.specifiers import SpecifierSet
from pipenv.patched.pip._vendor.packaging.version import InvalidVersion, Version
from pipenv.routines.outdated import do_outdated
Expand All @@ -17,11 +15,11 @@
get_lockfile_section_using_pipfile_category,
get_pipfile_category_using_lockfile_section,
)
from pipenv.utils.processes import run_command
from pipenv.utils.project import ensure_project
from pipenv.utils.requirements import add_index_to_pipfile
from pipenv.utils.resolver import venv_resolve_deps
from pipenv.vendor import pipdeptree
from pipenv.vendor.pipdeptree._discovery import get_installed_distributions
from pipenv.vendor.pipdeptree._models import PackageDAG


def do_update(
Expand Down Expand Up @@ -106,44 +104,25 @@ def do_update(


def get_reverse_dependencies(project) -> Dict[str, Set[Tuple[str, str]]]:
"""Get reverse dependencies using pipdeptree."""
pipdeptree_path = Path(pipdeptree.__file__).parent
python_path = project.python()
cmd_args = [python_path, str(pipdeptree_path), "-l", "--reverse", "--json-tree"]

c = run_command(cmd_args, is_verbose=project.s.is_verbose())
if c.returncode != 0:
raise PipenvCmdError(c.err, c.out, c.returncode)
try:
dep_tree = json.loads(c.stdout.strip())
except json.JSONDecodeError:
raise JSONParseError(c.stdout, c.stderr)

# Build reverse dependency map: package -> set of (dependent_package, required_version)
reverse_deps = defaultdict(set)
"""Get reverse dependencies without running pipdeptree as a subprocess."""

def process_tree_node(n, parents=None):
if parents is None:
parents = []
# Use the project's specified Python interpreter
python_interpreter = project.python()

package_name = n["package_name"]
required_version = n.get("required_version", "Any")
# Get installed packages for the specified interpreter
pkgs = get_installed_distributions(interpreter=python_interpreter)

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

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

# Start processing the tree from the root nodes
for node in dep_tree:
try:
process_tree_node(node)
except Exception as e: # noqa: PERF203
err.print(
f"[red bold]Warning[/red bold]: Unable to analyze dependencies: {str(e)}"
# Populate the reverse dependency map
for package, dependents in dep_tree.items():
for dep in dependents:
reverse_deps[dep.project_name].add(
(package.project_name, getattr(package, "installed_version", "Any"))
)

return reverse_deps
Expand Down Expand Up @@ -290,8 +269,13 @@ def upgrade(
# Early conflict detection
conflicts_found = False
for package in package_args:
if "==" in package:
name, version = package.split("==")
package_parts = [package]
if ";" in package:
package_parts = package.split(";")
# Not using markers here for now
# markers = ";".join(package_parts[1:]) if len(package_parts) > 1 else None
if "==" in package_parts[0]:
name, version = package_parts[0].split("==")
conflicts = check_version_conflicts(name, version, reverse_deps, lockfile)
if conflicts:
conflicts_found = True
Expand Down
11 changes: 4 additions & 7 deletions pipenv/utils/pipfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,24 @@ def ensure_pipfile(
if not (project.s.USING_DEFAULT_PYTHON or system)
else None
)
if project.pipfile_is_empty:
if not project.pipfile_exists:
# Show an error message and exit if system is passed and no pipfile exists
if system and not project.s.PIPENV_VIRTUALENV:
raise exceptions.PipenvOptionsError(
"--system",
"--system is intended to be used for pre-existing Pipfile "
"installation, not installation of specific packages. Aborting.",
)
err.print("Creating a Pipfile for this project...", style="bold")
# Create the pipfile if it doesn't exist.
project.create_pipfile(python=python)
# If there's a requirements file, but no Pipfile...
if project.requirements_exists and not skip_requirements:
requirements_dir_path = os.path.dirname(project.requirements_location)
console.print(
f"[bold]requirements.txt[/bold] found in [bold yellow]{requirements_dir_path}"
"[/bold yellow] instead of [bold]Pipfile[/bold]! Converting..."
)
# Create a Pipfile...
project.create_pipfile(python=python)
with console.status(
"Importing requirements...", spinner=project.s.PIPENV_SPINNER
) as st:
Expand All @@ -110,10 +111,6 @@ def ensure_pipfile(
'We recommend updating your [bold]Pipfile[/bold] to specify the [bold]"*"'
"[/bold] version, instead."
)
else:
err.print("Creating a Pipfile for this project...", style="bold")
# Create the pipfile if it doesn't exist.
project.create_pipfile(python=python)
# Validate the Pipfile's contents.
if validate and project.virtualenv_exists and not project.s.PIPENV_SKIP_VALIDATION:
# Ensure that Pipfile is using proper casing.
Expand Down
Loading

0 comments on commit 5b7f839

Please sign in to comment.