mirror of
https://github.com/home-assistant/core
synced 2024-07-21 02:25:24 +00:00
Allow only specific packages to be skipped during startup dependency installation (#82758)
This commit is contained in:
parent
fcf60a3b53
commit
8c8994352d
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue