Compare commits

...

1 Commits

Author SHA1 Message Date
Erik
65f073ca15 Remove support for installing Python dependencies in the config dir 2026-04-14 09:05:43 +02:00
7 changed files with 19 additions and 82 deletions

View File

@@ -11,10 +11,21 @@ import threading
from .backup_restore import restore_backup
from .const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
from .util.package import is_docker_env, is_virtual_env
FAULT_LOG_FILENAME = "home-assistant.log.fault"
def validate_environment() -> None:
"""Validate that Home Assistant is started from a container or a venv."""
if not is_virtual_env() and not is_docker_env():
print(
"Home Assistant must be run in a Python virtual environment or a container.",
file=sys.stderr,
)
sys.exit(1)
def validate_os() -> None:
"""Validate that Home Assistant is running in a supported operating system."""
if not sys.platform.startswith(("darwin", "linux")):
@@ -40,8 +51,6 @@ def ensure_config_path(config_dir: str) -> None:
"""Validate the configuration directory."""
from . import config as config_util # noqa: PLC0415
lib_dir = os.path.join(config_dir, "deps")
# Test if configuration directory exists
if not os.path.isdir(config_dir):
if config_dir != config_util.get_default_config_dir():
@@ -65,17 +74,6 @@ def ensure_config_path(config_dir: str) -> None:
)
sys.exit(1)
# Test if library directory exists
if not os.path.isdir(lib_dir):
try:
os.mkdir(lib_dir)
except OSError as ex:
print(
f"Fatal Error: Unable to create library directory {lib_dir}: {ex}",
file=sys.stderr,
)
sys.exit(1)
def get_arguments() -> argparse.Namespace:
"""Get parsed passed in arguments."""
@@ -168,6 +166,7 @@ def check_threads() -> None:
def main() -> int:
"""Start Home Assistant."""
validate_python()
validate_environment()
args = get_arguments()

View File

@@ -108,7 +108,7 @@ from .setup import (
from .util.async_ import create_eager_task
from .util.hass_dict import HassKey
from .util.logging import async_activate_log_queue_handler
from .util.package import async_get_user_site, is_docker_env, is_virtual_env
from .util.package import is_docker_env
from .util.system_info import is_official_image
with contextlib.suppress(ImportError):
@@ -353,9 +353,6 @@ async def async_setup_hass(
err,
)
else:
if not is_virtual_env():
await async_mount_local_lib_path(runtime_config.config_dir)
if hass.config.safe_mode:
_LOGGER.info("Starting in safe mode")
@@ -704,17 +701,6 @@ class _RotatingFileHandlerWithoutShouldRollOver(RotatingFileHandler):
return False
async def async_mount_local_lib_path(config_dir: str) -> str:
"""Add local library to Python Path.
This function is a coroutine.
"""
deps_dir = os.path.join(config_dir, "deps")
if (lib_dir := await async_get_user_site(deps_dir)) not in sys.path:
sys.path.insert(0, lib_dir)
return deps_dir
def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
"""Get domains of components to set up."""
# The common config section [homeassistant] could be filtered here,

View File

@@ -13,7 +13,6 @@ import logging
import operator
import os
from pathlib import Path
import shutil
from types import ModuleType
from typing import TYPE_CHECKING, Any, Literal, overload
@@ -32,7 +31,6 @@ from .helpers.typing import ConfigType
from .loader import ComponentProtocol, Integration, IntegrationNotFound
from .requirements import RequirementsNotFound, async_get_integration_with_requirements
from .util.async_ import create_eager_task
from .util.package import is_docker_env
from .util.yaml import SECRET_YAML, Secrets, YamlTypeError, load_yaml_dict
from .util.yaml.objects import NodeStrClass
@@ -308,12 +306,6 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None:
version_obj = AwesomeVersion(conf_version)
if version_obj < AwesomeVersion("0.50"):
# 0.50 introduced persistent deps dir.
lib_path = hass.config.path("deps")
if os.path.isdir(lib_path):
shutil.rmtree(lib_path)
if version_obj < AwesomeVersion("0.92"):
# 0.92 moved google/tts.py to google_translate/tts.py
config_path = hass.config.path(YAML_CONFIG_FILE)
@@ -330,13 +322,6 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None:
except OSError:
_LOGGER.exception("Migrating to google_translate tts failed")
if version_obj < AwesomeVersion("0.94") and is_docker_env():
# In 0.94 we no longer install packages inside the deps folder when
# running inside a Docker container.
lib_path = hass.config.path("deps")
if os.path.isdir(lib_path):
shutil.rmtree(lib_path)
with open(version_path, "w", encoding="utf8") as outp:
outp.write(__version__)

View File

@@ -94,16 +94,12 @@ def async_clear_install_history(hass: HomeAssistant) -> None:
_async_get_manager(hass).install_failure_history.clear()
def pip_kwargs(config_dir: str | None) -> dict[str, Any]:
def pip_kwargs() -> dict[str, Any]:
"""Return keyword arguments for PIP install."""
is_docker = pkg_util.is_docker_env()
kwargs = {
return {
"constraints": os.path.join(os.path.dirname(__file__), CONSTRAINT_FILE),
"timeout": PIP_TIMEOUT,
}
if not (config_dir is None or pkg_util.is_virtual_env()) and not is_docker:
kwargs["target"] = os.path.join(config_dir, "deps")
return kwargs
def _install_with_retry(requirement: str, kwargs: dict[str, Any]) -> bool:
@@ -336,7 +332,7 @@ class RequirementsManager:
requirements: list[str],
) -> None:
"""Install a requirement and save failures."""
kwargs = pip_kwargs(self.hass.config.config_dir)
kwargs = pip_kwargs()
installed, failures = await self.hass.async_add_executor_job(
_install_requirements_if_missing, requirements, kwargs
)

View File

@@ -11,10 +11,9 @@ import os
import sys
from homeassistant import runner
from homeassistant.bootstrap import async_mount_local_lib_path
from homeassistant.config import get_default_config_dir
from homeassistant.requirements import pip_kwargs
from homeassistant.util.package import install_package, is_installed, is_virtual_env
from homeassistant.util.package import install_package, is_installed
# mypy: allow-untyped-defs, disallow-any-generics, no-warn-return-any
@@ -44,12 +43,7 @@ def run(args: list[str]) -> int:
script = importlib.import_module(f"homeassistant.scripts.{args[0]}")
config_dir = extract_config_dir()
if not is_virtual_env():
asyncio.run(async_mount_local_lib_path(config_dir))
_pip_kwargs = pip_kwargs(config_dir)
_pip_kwargs = pip_kwargs()
logging.basicConfig(stream=sys.stdout, level=logging.INFO)

View File

@@ -149,7 +149,7 @@ def run(script_args: list) -> int:
yaml_files = [
f
for f in glob(os.path.join(config_dir, "**/*.yaml"), recursive=True)
if not f.startswith(deps)
if not f.startswith(deps) # Avoid scanning legacy deps folder
]
for yfn in sorted(yaml_files):

View File

@@ -52,29 +52,6 @@ async def test_requirement_installed_in_venv(hass: HomeAssistant) -> None:
)
async def test_requirement_installed_in_deps(hass: HomeAssistant) -> None:
"""Test requirement installed in deps directory."""
with (
patch("os.path.dirname", return_value="ha_package_path"),
patch("homeassistant.util.package.is_virtual_env", return_value=False),
patch("homeassistant.util.package.is_docker_env", return_value=False),
patch(
"homeassistant.util.package.install_package", return_value=True
) as mock_install,
patch.dict(os.environ, env_without_wheel_links(), clear=True),
):
hass.config.skip_pip = False
mock_integration(hass, MockModule("comp", requirements=["package==0.0.1"]))
assert await setup.async_setup_component(hass, "comp", {})
assert "comp" in hass.config.components
assert mock_install.call_args == call(
"package==0.0.1",
target=hass.config.path("deps"),
constraints=os.path.join("ha_package_path", CONSTRAINT_FILE),
timeout=60,
)
async def test_install_existing_package(hass: HomeAssistant) -> None:
"""Test an install attempt on an existing package."""
with patch(