Skip to content

Prevent installation failure with custom install_package using pip #210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ lint.ignore = [
"D301", # Use `r"""` if any backslashes in a docstring
"D401", # First line of docstring should be in imperative mood
"DOC201", # no support for sphinx
"DOC501", # no support for sphinx https://github.com/astral-sh/ruff/issues/12520
"ISC001", # Conflict with formatter
"S104", # Possible binding to all interface
]
Expand Down
20 changes: 18 additions & 2 deletions src/tox_uv/_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class UvInstaller(Pip):

def __init__(self, tox_env: Python, with_list_deps: bool = True) -> None: # noqa: FBT001, FBT002
self._with_list_deps = with_list_deps
self._pkg_manager = "uv"
super().__init__(tox_env)

def freeze_cmd(self) -> list[str]:
Expand Down Expand Up @@ -67,14 +68,29 @@ def default_install_command(self, conf: Config, env_name: str | None) -> Command
return Command(cmd)

def post_process_install_command(self, cmd: Command) -> Command:
"""Returns uv or pip based on current config.

Returns:
A string, uv or pip currently.
Raises:
RuntimeError: if unexpected a data is found in config.
"""
install_cmd = cmd.args
# uv pip command does not have same option named for reinstall and we want to allow users that use original
# 'pip' in their install_command to still be able to make use of it.
if len(install_cmd) < 1 or not isinstance(install_cmd[0], str): # pragma: no cover
msg = f"Unable to determine install command. {install_cmd}"
raise RuntimeError(msg)
self._pkg_manager = "uv" if install_cmd[0].endswith("uv") else "pip"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say custom install commands are not supported with tox-uv 🤔 I'd rather raise an error than support it..

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree: people may want a different installer for some of the envs but not the others. Is there an alternative workaround? Can I force a different installer for a specific env?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why, what's the use case?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have own lock files integrated like this, for example. And when tox-uv is installed in the env, it interferes with everything and breaks stuff in the end.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please expand.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ssbarnea can you also explain the use case for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did have few isolated cases where I wanted to use pip instead of uv installer. In these cases it was because uv installer was buggy (or it could also be because the problematic package happened to install with pip and fail with uv for their own internal assumptions). I think that we should allow users to have custom installers to allow them to implement workarounds.

On the other hand, if you want I can change the rewrite logic to do nothing if the case is unable to detect either pip or uv as being inside the install_cmd.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did have few isolated cases where I wanted to use pip instead of uv installer. In these cases it was because uv installer was buggy (or it could also be because the problematic package happened to install with pip and fail with uv for their own internal assumptions).

In those cases you can just set the runner to be not the uv one, no?


install_command = cmd.args
pip_pre: bool = self._env.conf["pip_pre"]
uv_resolution: str = self._env.conf["uv_resolution"]
try:
opts_at = install_command.index("{opts}")
except ValueError:
if pip_pre:
install_command.extend(("--prerelease", "allow"))
install_command.extend(("--prerelease", "allow") if self._pkg_manager == "uv" else ("--pre",))
if uv_resolution:
install_command.extend(("--resolution", uv_resolution))
else:
Expand Down Expand Up @@ -136,7 +152,7 @@ def _install_list_of_deps( # noqa: C901, PLR0912
new_deps = sorted(set(groups["req"]) - set(old or []))
if new_deps: # pragma: no branch
self._execute_installer(new_deps, req_of_type)
install_args = ["--reinstall"]
install_args = ["--reinstall"] if self._pkg_manager == "uv" else ["--force-reinstall"]
if groups["uv"]:
self._execute_installer(install_args + groups["uv"], of_type)
if groups["uv_editable"]:
Expand Down
23 changes: 23 additions & 0 deletions tests/test_tox_uv_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,29 @@ def test_uv_install_with_pre(tox_project: ToxProjectCreator) -> None:
result.assert_success()


def test_uv_install_with_pre_custom_install_cmd_using_original_pip(tox_project: ToxProjectCreator) -> None:
project = tox_project({
"tox.ini": """
[testenv]
deps = tomli
pip_pre = true
package = skip
uv_seed = true
commands = python -c 'import tomli'

[testenv:a]

[testenv:b]
install_command = python3 -m pip install {packages}

[testenv:c]
install_command = uv pip install {packages}
"""
})
result = project.run("r", "-v", "-e", "a,b,c")
result.assert_success()


def test_uv_install_with_pre_custom_install_cmd(tox_project: ToxProjectCreator) -> None:
project = tox_project({
"tox.ini": """
Expand Down