Skip to content

Commit

Permalink
Add experimental support for the uv isntaller
Browse files Browse the repository at this point in the history
  • Loading branch information
sbidoul committed Feb 18, 2024
1 parent a2ced36 commit 9fff054
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 12 deletions.
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,12 @@ pip-df sync
dependencies of the project. If not
specified, ask confirmation.
--installer [default|env-pip|uv]
The installer to use. For now the default is
to use 'pip' that is installed in the target
environment. To use 'uv', pip-deepfreeze must be
installed with the 'uv' extra.
--help Show this message and exit.
pip-df tree
Expand Down
4 changes: 4 additions & 0 deletions news/135.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add experimental support for `uv <https://github.com/astral-sh/uv>`_ as the installation
command. For now we still need `pip` to be installed in the target environment, to
inspect its content. A new ``--installer`` option is available to select the installer
to use.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dependencies=[
dynamic = ["version"]

[project.optional-dependencies]
"uv" = ["uv"]
"test" = ["pytest", "pytest-cov", "pytest-xdist", "virtualenv", "setuptools", "wheel"]
"mypy" = ["mypy", "types-toml", "types-setuptools"]

Expand Down
5 changes: 5 additions & 0 deletions src/pip_deepfreeze/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import typer
from packaging.utils import canonicalize_name

from .pip import Installer

Check warning on line 9 in src/pip_deepfreeze/__main__.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/__main__.py#L9

Added line #L9 was not covered by tests
from .pyproject_toml import load_pyproject_toml
from .sanity import check_env
from .sync import sync as sync_operation
Expand Down Expand Up @@ -68,6 +69,9 @@ def sync(
"Can be specified multiple times."
),
),
installer: Installer = typer.Option(
"default",
),
) -> None:
"""Install/update the environment to match the project requirements.
Expand All @@ -85,6 +89,7 @@ def sync(
uninstall_unneeded=uninstall_unneeded,
project_root=ctx.obj.project_root,
post_sync_commands=post_sync_commands,
installer=installer,
)


Expand Down
68 changes: 58 additions & 10 deletions src/pip_deepfreeze/pip.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import importlib.util

Check warning on line 1 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L1

Added line #L1 was not covered by tests
import json
import os

Check warning on line 3 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L3

Added line #L3 was not covered by tests
import shlex
import sys
from enum import Enum
from functools import lru_cache

Check warning on line 7 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L5-L7

Added lines #L5 - L7 were not covered by tests
from importlib.resources import path as resource_path
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, TypedDict, cast

import typer

Check warning on line 12 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L12

Added line #L12 was not covered by tests
from packaging.utils import NormalizedName
from packaging.version import Version

Expand All @@ -29,6 +35,7 @@
check_output,
get_temp_path_in_dir,
log_debug,
log_error,
log_info,
log_warning,
normalize_req_line,
Expand All @@ -41,11 +48,53 @@ class PipInspectReport(TypedDict, total=False):
environment: Dict[str, str]


class Installer(str, Enum):
default = "default"
envpip = "env-pip"
uv = "uv"

Check warning on line 54 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L51-L54

Added lines #L51 - L54 were not covered by tests


@lru_cache

Check warning on line 57 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L57

Added line #L57 was not covered by tests
def _has_uv() -> bool:
return bool(importlib.util.find_spec("uv"))

Check warning on line 59 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L59

Added line #L59 was not covered by tests


def _env_pip_install_cmd_and_env(python: str) -> Tuple[List[str], Dict[str, str]]:
return [python, "-m", "pip", "install"], {}

Check warning on line 63 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L62-L63

Added lines #L62 - L63 were not covered by tests


def _uv_install_cmd_and_env(python: str) -> Tuple[List[str], Dict[str, str]]:

Check warning on line 66 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L66

Added line #L66 was not covered by tests
# TODO when https://github.com/astral-sh/uv/issues/1396 is implemented,
# we will not need to return the VIRTUAL_ENV environment variable.
return [sys.executable, "-m", "uv", "pip", "install"], {

Check warning on line 69 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L69

Added line #L69 was not covered by tests
"VIRTUAL_ENV": str(Path(python).parent.parent)
}


def _install_cmd_and_env(

Check warning on line 74 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L74

Added line #L74 was not covered by tests
installer: Installer, python: str
) -> Tuple[List[str], Dict[str, str]]:
if installer == Installer.default:
installer = Installer.envpip

Check warning on line 78 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L78

Added line #L78 was not covered by tests
if installer == Installer.envpip:
return _env_pip_install_cmd_and_env(python)

Check warning on line 80 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L80

Added line #L80 was not covered by tests
elif installer == Installer.uv:
if not _has_uv():
log_error(

Check warning on line 83 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L83

Added line #L83 was not covered by tests
"The 'uv' installer was requested but it is not available. "
"Please install pip-deepfreeze with the 'uv' extra to use it."
)
raise typer.Exit(1)
return _uv_install_cmd_and_env(python)
raise NotImplementedError(f"Installer {installer} is not implemented.")

Check warning on line 89 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L87-L89

Added lines #L87 - L89 were not covered by tests


def pip_upgrade_project(
python: str,
constraints_filename: Path,
project_root: Path,
extras: Optional[Sequence[NormalizedName]] = None,
installer: Installer = Installer.envpip,
) -> None:
"""Upgrade a project.
Expand Down Expand Up @@ -117,15 +166,14 @@ def pip_upgrade_project(
# 4. install project with constraints
project_name = get_project_name(python, project_root)
log_info(f"Installing/updating {project_name}")
cmd = [
python,
"-m",
"pip",
"install",
"-c",
f"{constraints_without_editables_filename}",
*editable_constraints,
]
cmd, env = _install_cmd_and_env(installer, python)
cmd.extend(

Check warning on line 170 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L169-L170

Added lines #L169 - L170 were not covered by tests
[
"-c",
f"{constraints_without_editables_filename}",
*editable_constraints,
]
)
cmd.append("-e")
if extras:
extras_str = ",".join(extras)
Expand All @@ -141,7 +189,7 @@ def pip_upgrade_project(
log_debug(constraints)
else:
log_debug(f"with empty {constraints_without_editables_filename}.")
check_call(cmd)
check_call(cmd, os.environ.copy().update(env))

Check warning on line 192 in src/pip_deepfreeze/pip.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/pip.py#L192

Added line #L192 was not covered by tests


def _pip_list__env_info_json(python: str) -> InstalledDistributions:
Expand Down
3 changes: 3 additions & 0 deletions src/pip_deepfreeze/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from packaging.utils import NormalizedName

from .pip import (
Installer,
pip_fixup_vcs_direct_urls,
pip_freeze_dependencies_by_extra,
pip_uninstall,
Expand Down Expand Up @@ -43,6 +44,7 @@ def sync(
uninstall_unneeded: Optional[bool],
project_root: Path,
post_sync_commands: Sequence[str] = (),
installer: Installer = Installer.envpip,
) -> None:
project_name = get_project_name(python, project_root)
project_name_with_extras = make_project_name_with_extras(project_name, extras)
Expand All @@ -64,6 +66,7 @@ def sync(
constraints_path,
project_root,
extras=extras,
installer=installer,
)
# freeze dependencies
frozen_reqs_by_extra, unneeded_reqs = pip_freeze_dependencies_by_extra(
Expand Down
8 changes: 6 additions & 2 deletions src/pip_deepfreeze/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,13 @@ def log_error(msg: str) -> None:
typer.secho(msg, fg=typer.colors.RED, err=True)


def check_call(cmd: Sequence[Union[str, Path]], cwd: Optional[Path] = None) -> int:
def check_call(

Check warning on line 84 in src/pip_deepfreeze/utils.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/utils.py#L84

Added line #L84 was not covered by tests
cmd: Sequence[Union[str, Path]],
cwd: Optional[Path] = None,
env: Optional[Dict[str, str]] = None,
) -> int:
try:
return subprocess.check_call(cmd, cwd=cwd)
return subprocess.check_call(cmd, cwd=cwd, env=env)

Check warning on line 90 in src/pip_deepfreeze/utils.py

View check run for this annotation

Codecov / codecov/patch

src/pip_deepfreeze/utils.py#L90

Added line #L90 was not covered by tests
except CalledProcessError as e:
cmd_str = shlex.join(str(item) for item in cmd)
log_error(f"Error running: {cmd_str}.")
Expand Down

0 comments on commit 9fff054

Please sign in to comment.