Use uv at runtime too (#125110)

This commit is contained in:
Robert Resch 2024-09-11 09:43:26 +02:00 committed by GitHub
parent b3377fe5fb
commit 7555f209b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 56 additions and 38 deletions

View File

@ -126,7 +126,7 @@ jobs:
env:
UV_PRERELEASE: allow
run: |
python3 -m pip install "$(grep '^uv' < requirements_test.txt)"
python3 -m pip install "$(grep '^uv' < requirements.txt)"
uv pip install packaging tomli
uv pip install .
python3 script/version_bump.py nightly --set-nightly-version "${{ needs.init.outputs.version }}"

View File

@ -252,7 +252,7 @@ jobs:
python -m venv venv
. venv/bin/activate
python --version
pip install "$(grep '^uv' < requirements_test.txt)"
pip install "$(grep '^uv' < requirements.txt)"
uv pip install "$(cat requirements_test.txt | grep pre-commit)"
- name: Restore pre-commit environment from cache
id: cache-precommit
@ -476,7 +476,7 @@ jobs:
- name: Generate partial uv restore key
id: generate-uv-key
run: |
uv_version=$(cat requirements_test.txt | grep uv | cut -d '=' -f 3)
uv_version=$(cat requirements.txt | grep uv | cut -d '=' -f 3)
echo "version=${uv_version}" >> $GITHUB_OUTPUT
echo "key=uv-${{ env.UV_CACHE_VERSION }}-${uv_version}-${{
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
@ -525,7 +525,7 @@ jobs:
python -m venv venv
. venv/bin/activate
python --version
pip install "$(grep '^uv' < requirements_test.txt)"
pip install "$(grep '^uv' < requirements.txt)"
uv pip install -U "pip>=21.3.1" setuptools wheel
uv pip install -r requirements.txt
python -m script.gen_requirements_all ci

View File

@ -46,7 +46,7 @@ jobs:
python -m venv venv
. venv/bin/activate
python --version
pip install "$(grep '^uv' < requirements_test.txt)"
pip install "$(grep '^uv' < requirements.txt)"
uv pip install -r requirements.txt
- name: Get information

View File

@ -83,7 +83,7 @@ repos:
pass_filenames: false
language: script
types: [text]
files: ^(homeassistant/.+/(icons|manifest|strings)\.json|homeassistant/brands/.*\.json|homeassistant/.+/services\.yaml|script/hassfest/(?!metadata|mypy_config).+\.py|requirements_test.txt)$
files: ^(homeassistant/.+/(icons|manifest|strings)\.json|homeassistant/brands/.*\.json|homeassistant/.+/services\.yaml|script/hassfest/(?!metadata|mypy_config).+\.py|requirements.+\.txt)$
- id: hassfest-metadata
name: hassfest-metadata
entry: script/run-in-env.sh python3 -m script.hassfest -p metadata

View File

@ -42,7 +42,6 @@ orjson==3.10.7
packaging>=23.1
paho-mqtt==1.6.1
Pillow==10.4.0
pip>=21.3.1
psutil-home-assistant==0.0.1
PyJWT==2.9.0
pymicro-vad==1.0.1
@ -59,6 +58,7 @@ SQLAlchemy==2.0.31
typing-extensions>=4.12.2,<5.0
ulid-transform==1.0.2
urllib3>=1.26.5,<2
uv==0.4.8
voluptuous-openapi==0.0.5
voluptuous-serialize==2.6.0
voluptuous==0.15.2

View File

@ -94,12 +94,11 @@ def install_package(
Return boolean if install successful.
"""
# Not using 'import pip; pip.main([])' because it breaks the logger
_LOGGER.info("Attempting install of %s", package)
env = os.environ.copy()
args = [sys.executable, "-m", "pip", "install", "--quiet", package]
args = ["uv", "pip", "install", "--quiet", package]
if timeout:
args += ["--timeout", str(timeout)]
env["HTTP_TIMEOUT"] = str(timeout)
if upgrade:
args.append("--upgrade")
if constraints is not None:
@ -109,7 +108,7 @@ def install_package(
# This only works if not running in venv
args += ["--user"]
env["PYTHONUSERBASE"] = os.path.abspath(target)
_LOGGER.debug("Running pip command: args=%s", args)
_LOGGER.debug("Running uv pip command: args=%s", args)
with Popen(
args,
stdin=PIPE,

View File

@ -54,7 +54,6 @@ dependencies = [
"pyOpenSSL==24.2.1",
"orjson==3.10.7",
"packaging>=23.1",
"pip>=21.3.1",
"psutil-home-assistant==0.0.1",
"python-slugify==8.0.4",
"PyYAML==6.0.2",
@ -66,6 +65,7 @@ dependencies = [
# Temporary setting an upper bound, to prevent compat issues with urllib3>=2
# https://github.com/home-assistant/core/issues/97248
"urllib3>=1.26.5,<2",
"uv==0.4.8",
"voluptuous==0.15.2",
"voluptuous-serialize==2.6.0",
"voluptuous-openapi==0.0.5",

View File

@ -29,7 +29,6 @@ Pillow==10.4.0
pyOpenSSL==24.2.1
orjson==3.10.7
packaging>=23.1
pip>=21.3.1
psutil-home-assistant==0.0.1
python-slugify==8.0.4
PyYAML==6.0.2
@ -38,6 +37,7 @@ SQLAlchemy==2.0.31
typing-extensions>=4.12.2,<5.0
ulid-transform==1.0.2
urllib3>=1.26.5,<2
uv==0.4.8
voluptuous==0.15.2
voluptuous-serialize==2.6.0
voluptuous-openapi==0.0.5

View File

@ -51,4 +51,3 @@ types-pytz==2024.1.0.20240417
types-PyYAML==6.0.12.20240311
types-requests==2.31.0.3
types-xmltodict==0.13.0.3
uv==0.4.8

View File

@ -172,8 +172,9 @@ def _generate_files(config: Config) -> list[File]:
+ 10
) * 1000
package_versions = _get_package_versions(
Path("requirements_test.txt"), {"pipdeptree", "tqdm", "uv"}
package_versions = _get_package_versions(Path("requirements.txt"), {"uv"})
package_versions |= _get_package_versions(
Path("requirements_test.txt"), {"pipdeptree", "tqdm"}
)
package_versions |= _get_package_versions(
Path("requirements_test_pre_commit.txt"), {"ruff"}

View File

@ -1,12 +1,13 @@
"""Test Home Assistant package util methods."""
import asyncio
from collections.abc import Generator
from importlib.metadata import metadata
import logging
import os
from subprocess import PIPE
import sys
from unittest.mock import MagicMock, call, patch
from unittest.mock import MagicMock, Mock, call, patch
import pytest
@ -24,7 +25,7 @@ TEST_ZIP_REQ = "file://{}#{}".format(
@pytest.fixture
def mock_sys():
def mock_sys() -> Generator[MagicMock]:
"""Mock sys."""
with patch("homeassistant.util.package.sys", spec=object) as sys_mock:
sys_mock.executable = "python3"
@ -32,19 +33,19 @@ def mock_sys():
@pytest.fixture
def deps_dir():
def deps_dir() -> str:
"""Return path to deps directory."""
return os.path.abspath("/deps_dir")
@pytest.fixture
def lib_dir(deps_dir):
def lib_dir(deps_dir) -> str:
"""Return path to lib directory."""
return os.path.join(deps_dir, "lib_dir")
@pytest.fixture
def mock_popen(lib_dir):
def mock_popen(lib_dir) -> Generator[MagicMock]:
"""Return a Popen mock."""
with patch("homeassistant.util.package.Popen") as popen_mock:
popen_mock.return_value.__enter__ = popen_mock
@ -57,7 +58,7 @@ def mock_popen(lib_dir):
@pytest.fixture
def mock_env_copy():
def mock_env_copy() -> Generator[Mock]:
"""Mock os.environ.copy."""
with patch("homeassistant.util.package.os.environ.copy") as env_copy:
env_copy.return_value = {}
@ -65,14 +66,14 @@ def mock_env_copy():
@pytest.fixture
def mock_venv():
def mock_venv() -> Generator[MagicMock]:
"""Mock homeassistant.util.package.is_virtual_env."""
with patch("homeassistant.util.package.is_virtual_env") as mock:
mock.return_value = True
yield mock
def mock_async_subprocess():
def mock_async_subprocess() -> Generator[MagicMock]:
"""Return an async Popen mock."""
async_popen = MagicMock()
@ -85,13 +86,14 @@ def mock_async_subprocess():
return async_popen
def test_install(mock_sys, mock_popen, mock_env_copy, mock_venv) -> None:
@pytest.mark.usefixtures("mock_sys", "mock_venv")
def test_install(mock_popen: MagicMock, mock_env_copy: MagicMock) -> None:
"""Test an install attempt on a package that doesn't exist."""
env = mock_env_copy()
assert package.install_package(TEST_NEW_REQ, False)
assert mock_popen.call_count == 2
assert mock_popen.mock_calls[0] == call(
[mock_sys.executable, "-m", "pip", "install", "--quiet", TEST_NEW_REQ],
["uv", "pip", "install", "--quiet", TEST_NEW_REQ],
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
@ -101,15 +103,33 @@ def test_install(mock_sys, mock_popen, mock_env_copy, mock_venv) -> None:
assert mock_popen.return_value.communicate.call_count == 1
def test_install_upgrade(mock_sys, mock_popen, mock_env_copy, mock_venv) -> None:
@pytest.mark.usefixtures("mock_sys", "mock_venv")
def test_install_with_timeout(mock_popen: MagicMock, mock_env_copy: MagicMock) -> None:
"""Test an install attempt on a package that doesn't exist with a timeout set."""
env = mock_env_copy()
assert package.install_package(TEST_NEW_REQ, False, timeout=10)
assert mock_popen.call_count == 2
env["HTTP_TIMEOUT"] = "10"
assert mock_popen.mock_calls[0] == call(
["uv", "pip", "install", "--quiet", TEST_NEW_REQ],
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
env=env,
close_fds=False,
)
assert mock_popen.return_value.communicate.call_count == 1
@pytest.mark.usefixtures("mock_sys", "mock_venv")
def test_install_upgrade(mock_popen, mock_env_copy) -> None:
"""Test an upgrade attempt on a package."""
env = mock_env_copy()
assert package.install_package(TEST_NEW_REQ)
assert mock_popen.call_count == 2
assert mock_popen.mock_calls[0] == call(
[
mock_sys.executable,
"-m",
"uv",
"pip",
"install",
"--quiet",
@ -133,8 +153,7 @@ def test_install_target(mock_sys, mock_popen, mock_env_copy, mock_venv) -> None:
mock_venv.return_value = False
mock_sys.platform = "linux"
args = [
mock_sys.executable,
"-m",
"uv",
"pip",
"install",
"--quiet",
@ -150,16 +169,16 @@ def test_install_target(mock_sys, mock_popen, mock_env_copy, mock_venv) -> None:
assert mock_popen.return_value.communicate.call_count == 1
def test_install_target_venv(mock_sys, mock_popen, mock_env_copy, mock_venv) -> None:
@pytest.mark.usefixtures("mock_sys", "mock_popen", "mock_env_copy", "mock_venv")
def test_install_target_venv() -> None:
"""Test an install with a target in a virtual environment."""
target = "target_folder"
with pytest.raises(AssertionError):
package.install_package(TEST_NEW_REQ, False, target=target)
def test_install_error(
caplog: pytest.LogCaptureFixture, mock_sys, mock_popen, mock_venv
) -> None:
@pytest.mark.usefixtures("mock_sys", "mock_venv")
def test_install_error(caplog: pytest.LogCaptureFixture, mock_popen) -> None:
"""Test an install that errors out."""
caplog.set_level(logging.WARNING)
mock_popen.return_value.returncode = 1
@ -169,7 +188,8 @@ def test_install_error(
assert record.levelname == "ERROR"
def test_install_constraint(mock_sys, mock_popen, mock_env_copy, mock_venv) -> None:
@pytest.mark.usefixtures("mock_sys", "mock_venv")
def test_install_constraint(mock_popen, mock_env_copy) -> None:
"""Test install with constraint file on not installed package."""
env = mock_env_copy()
constraints = "constraints_file.txt"
@ -177,8 +197,7 @@ def test_install_constraint(mock_sys, mock_popen, mock_env_copy, mock_venv) -> N
assert mock_popen.call_count == 2
assert mock_popen.mock_calls[0] == call(
[
mock_sys.executable,
"-m",
"uv",
"pip",
"install",
"--quiet",