Allow only specific packages to be skipped during startup dependency installation (#82758)

This commit is contained in:
puddly 2022-11-30 02:38:52 -05:00 committed by GitHub
parent fcf60a3b53
commit 8c8994352d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 78 additions and 2 deletions

View file

@ -89,11 +89,21 @@ def get_arguments() -> argparse.Namespace:
parser.add_argument(
"--open-ui", action="store_true", help="Open the webinterface in a browser"
)
parser.add_argument(
skip_pip_group = parser.add_mutually_exclusive_group()
skip_pip_group.add_argument(
"--skip-pip",
action="store_true",
help="Skips pip install of required packages on startup",
)
skip_pip_group.add_argument(
"--skip-pip-packages",
metavar="package_names",
type=lambda arg: arg.split(","),
default=[],
help="Skip pip install of specific packages on startup",
)
parser.add_argument(
"-v", "--verbose", action="store_true", help="Enable verbose logging to file."
)
@ -180,6 +190,7 @@ def main() -> int:
log_file=args.log_file,
log_no_color=args.log_no_color,
skip_pip=args.skip_pip,
skip_pip_packages=args.skip_pip_packages,
safe_mode=args.safe_mode,
debug=args.debug,
open_ui=args.open_ui,

View file

@ -118,7 +118,8 @@ async def async_setup_hass(
)
hass.config.skip_pip = runtime_config.skip_pip
if runtime_config.skip_pip:
hass.config.skip_pip_packages = runtime_config.skip_pip_packages
if runtime_config.skip_pip or runtime_config.skip_pip_packages:
_LOGGER.warning(
"Skipping pip installation of required modules. This may cause issues"
)
@ -176,6 +177,7 @@ async def async_setup_hass(
if old_logging:
hass.data[DATA_LOGGING] = old_logging
hass.config.skip_pip = old_config.skip_pip
hass.config.skip_pip_packages = old_config.skip_pip_packages
hass.config.internal_url = old_config.internal_url
hass.config.external_url = old_config.external_url
hass.config.config_dir = old_config.config_dir

View file

@ -1816,6 +1816,9 @@ class Config:
# If True, pip install is skipped for requirements on startup
self.skip_pip: bool = False
# List of packages to skip when installing requirements on startup
self.skip_pip_packages: list[str] = []
# List of loaded components
self.components: set[str] = set()

View file

@ -7,6 +7,8 @@ import logging
import os
from typing import Any, cast
import pkg_resources
from .core import HomeAssistant, callback
from .exceptions import HomeAssistantError
from .helpers.typing import UNDEFINED, UndefinedType
@ -225,6 +227,19 @@ class RequirementsManager:
This method is a coroutine. It will raise RequirementsNotFound
if an requirement can't be satisfied.
"""
if self.hass.config.skip_pip_packages:
skipped_requirements = [
req
for req in requirements
if pkg_resources.Requirement.parse(req).project_name
in self.hass.config.skip_pip_packages
]
for req in skipped_requirements:
_LOGGER.warning("Skipping requirement %s. This may cause issues", req)
requirements = [r for r in requirements if r not in skipped_requirements]
if not (missing := self._find_missing_requirements(requirements)):
return
self._raise_for_failed_requirements(name, missing)

View file

@ -36,6 +36,7 @@ class RuntimeConfig:
config_dir: str
skip_pip: bool = False
skip_pip_packages: list[str] = dataclasses.field(default_factory=list)
safe_mode: bool = False
verbose: bool = False

View file

@ -289,6 +289,7 @@ async def async_test_home_assistant(event_loop, load_registries=True):
hass.config.units = METRIC_SYSTEM
hass.config.media_dirs = {"local": get_test_config_dir("media")}
hass.config.skip_pip = True
hass.config.skip_pip_packages = []
hass.config_entries = config_entries.ConfigEntries(
hass,

View file

@ -939,6 +939,7 @@ async def test_config_defaults():
assert config.external_url is None
assert config.config_source is ha.ConfigSource.DEFAULT
assert config.skip_pip is False
assert config.skip_pip_packages == []
assert config.components == set()
assert config.api is None
assert config.config_dir is None

View file

@ -61,3 +61,27 @@ def test_validate_python(mock_exit):
assert mock_exit.called is False
mock_exit.reset_mock()
@patch("sys.exit")
def test_skip_pip_mutually_exclusive(mock_exit):
"""Test --skip-pip and --skip-pip-package are mutually exclusive."""
def parse_args(*args):
with patch("sys.argv", ["python"] + list(args)):
return main.get_arguments()
args = parse_args("--skip-pip")
assert args.skip_pip is True
args = parse_args("--skip-pip-packages", "foo")
assert args.skip_pip is False
assert args.skip_pip_packages == ["foo"]
args = parse_args("--skip-pip-packages", "foo-asd,bar-xyz")
assert args.skip_pip is False
assert args.skip_pip_packages == ["foo-asd", "bar-xyz"]
assert mock_exit.called is False
args = parse_args("--skip-pip", "--skip-pip-packages", "foo")
assert mock_exit.called is True

View file

@ -1,4 +1,5 @@
"""Test requirements module."""
import logging
import os
from unittest.mock import call, patch
@ -93,6 +94,23 @@ async def test_install_missing_package(hass):
assert len(mock_inst.mock_calls) == 3
async def test_install_skipped_package(hass, caplog):
"""Test an install attempt on a dependency that should be skipped."""
with patch(
"homeassistant.util.package.install_package", return_value=True
) as mock_inst:
hass.config.skip_pip_packages = ["hello"]
with caplog.at_level(logging.WARNING):
await async_process_requirements(
hass, "test_component", ["hello==1.0.0", "not_skipped==1.2.3"]
)
assert "Skipping requirement hello==1.0.0" in caplog.text
assert len(mock_inst.mock_calls) == 1
assert mock_inst.mock_calls[0].args[0] == "not_skipped==1.2.3"
async def test_get_integration_with_requirements(hass):
"""Check getting an integration with loaded requirements."""
hass.config.skip_pip = False