How the Home Assistant Requirements System Manages Python Dependencies for Integrations

The Home Assistant requirements system uses a singleton RequirementsManager to automatically install Python packages declared in integration manifest.json files before instantiation, handling caching, retries, and dependency chains through the core homeassistant/requirements.py module.

When Home Assistant loads an integration—whether built-in or custom—it must ensure all declared Python dependencies are present in the environment. The system parses the "requirements" field from each integration's manifest.json and orchestrates installation through a thread-safe manager that prevents conflicts and tracks failures across the application lifecycle.

Core Architecture of the Requirements Manager

The requirements system centers on the RequirementsManager singleton class defined in homeassistant/requirements.py. Accessed via _async_get_manager(hass) (decorated with @singleton), this manager maintains state across the entire Home Assistant instance to avoid redundant installations.

Key Responsibilities

The manager handles several critical functions through specific internal methods:

  • Dependency Collection: Receives requirement strings (e.g., ["paho-mqtt>=1.6", "pyserial-asyncio==0.6"]) from integration manifests
  • Missing Package Detection: Uses _find_missing_requirements() to check against the in-memory self.is_installed_cache (lines 10-12)
  • Thread-Safe Installation: Acquires self.pip_lock before invoking _async_process_requirements() to ensure only one pip process runs at a time (lines 5-8)
  • Retry Logic: _install_with_retry() attempts installation up to MAX_INSTALL_FAILURES (3) times before recording permanent failures
  • Failure Tracking: Stores failed attempts in self.install_failure_history to raise RequirementsNotFound immediately on subsequent calls, preventing endless loops
  • Deprecation & Skip Logic: Evaluates DEPRECATED_PACKAGES and self.hass.config.skip_pip_packages before installing, warning on deprecated packages and omitting skipped ones (lines 59-99)

Public API Interface

The module exposes three primary coroutine helpers that forward calls to the singleton manager:


# Process requirements for a specific integration

await async_process_requirements(hass, name, requirements, is_built_in=True)

# Load integration with full dependency resolution

await async_get_integration_with_requirements(hass, domain)

# Refresh cached versions for specific packages

await async_load_installed_versions(hass, requirements_set)

These functions are implemented at lines 64-74, 51-62, and 77-82 of homeassistant/requirements.py, respectively.

The Installation Flow from Manifest to Environment

The requirements system operates during the integration loading phase, intercepting declarations before the integration class is instantiated.

1. Integration Discovery and Parsing

When loader.async_get_integration() creates an Integration object, it parses the manifest.json to extract integration.requirements and integration.dependencies. The requirements manager receives these strings without version resolution—raw specifiers like "requests>=2.28.0".

2. Recursive Dependency Processing

The method _async_process_integration() (lines 86-94) orchestrates the full chain:

  1. Calls async_process_requirements() for the integration's own packages
  2. Iterates over declared dependencies, recursively invoking async_get_integration_with_requirements() for each
  3. Respects the DISCOVERY_INTEGRATIONS map to handle special cases (e.g., dhcp discovery pulling in the dhcp integration)

3. Installation Pipeline

Inside async_process_requirements() (lines 51-58), the manager executes a strict sequence:

  • Parses and filters requirements against deprecated and skip lists
  • Checks _find_missing_requirements() against the cache
  • Acquires self.pip_lock and re-checks for race conditions
  • Builds pip arguments via pip_kwargs() (lines 97-104), referencing package_constraints.txt for transitive dependency pins
  • Delegates to _install_requirements_if_missing() (lines 17-28), which uses pkg_util.is_installed() and pkg_util.install_package() wrappers

4. Result Handling and Caching

Successful installations add package names to self.is_installed_cache (line 44). Failures populate self.install_failure_history (line 45), with any remaining errors bubbling up as RequirementsNotFound (line 46).

Handling Complex Dependency Chains

The system resolves deep dependency graphs through async_get_integration_with_requirements(). This coroutine not only installs the target integration's requirements but also traverses its entire dependency tree.

When an integration depends on others (e.g., a platform integration requiring mqtt), the manager:

  1. Loads each dependency integration via the loader
  2. Recursively processes their requirements through the same pipeline
  3. Respects the skip_pip configuration flag (line 67), which bypasses all automatic installation—critical for containerized deployments with pre-baked images

This recursive approach ensures that by the time an integration's async_setup() runs, every package in its transitive closure is importable.

Validation, Constraints, and Edge Cases

The requirements system includes multiple safeguards beyond runtime installation.

CI-Time Manifest Validation

A separate linting script at script/hassfest/requirements.py validates the format of every integration's requirements list. The validate_requirements function (line 367) checks version-range correctness and syntax during continuous integration, catching malformed manifests before code merges.

Deprecation and Security

Before any installation, the manager checks against DEPRECATED_PACKAGES. If a requirement matches a deprecated package, the system logs a warning but may still install it for backward compatibility. Conversely, packages listed in self.hass.config.skip_pip_packages are silently omitted from the install list.

Consolidated Dependency Lists

Home Assistant maintains two canonical files for dependency tracking:

These files, along with package_constraints.txt, ensure deterministic resolution when pip_kwargs() constructs installation commands.

Practical Code Examples

Installing Requirements for a Single Integration

To manually ensure an integration's packages are present (useful for custom components):

await async_process_requirements(
    hass,
    "mqtt",
    ["paho-mqtt>=1.6"],
    is_built_in=True,
)

This corresponds to the wrapper implementation at lines 64-74 of homeassistant/requirements.py.

Loading an Integration with Full Dependencies

To safely load an integration and guarantee all recursive dependencies are installed:

integration = await async_get_integration_with_requirements(hass, "zwave_js")

# integration.requirements are now satisfied, platforms are safe to import

This uses the public helper at lines 51-62.

Refreshing the Installation Cache

After external package modifications (rare in production, useful in testing):

await async_load_installed_versions(
    hass,
    {"paho-mqtt", "aiohttp"}
)

This updates self.is_installed_cache via the helper at lines 77-82.

Summary

  • The RequirementsManager singleton in homeassistant/requirements.py orchestrates all Python dependency installation for Home Assistant integrations.
  • It maintains self.is_installed_cache to avoid redundant work and self.pip_lock to prevent concurrent pip processes.
  • The system supports 3 retry attempts per package, tracks permanent failures to avoid loops, and respects skip_pip configuration for containerized environments.
  • async_get_integration_with_requirements() recursively resolves entire dependency graphs before integration instantiation.
  • script/hassfest/requirements.py validates manifest syntax during CI to catch errors before runtime.

Frequently Asked Questions

How does Home Assistant prevent pip from running multiple times simultaneously?

The RequirementsManager maintains an asyncio.Lock called self.pip_lock (line 5). Before any installation, _async_process_requirements() acquires this lock and performs a second missing-requirements check to handle race conditions where another task may have installed the package during the wait.

What happens if a required Python package fails to install?

The system attempts installation up to MAX_INSTALL_FAILURES (3) times through _install_with_retry(). After exhausting retries, the package is added to self.install_failure_history. Subsequent attempts to load the integration will immediately raise RequirementsNotFound without retrying, preventing endless startup loops and log spam.

Can I disable automatic pip installation in Home Assistant?

Yes. Set skip_pip: true in your configuration.yaml or use the skip_pip_packages list to omit specific packages. This is commonly used in Docker containers where all dependencies are pre-installed in the image, avoiding runtime network calls and ensuring reproducible deployments.

Where does Home Assistant validate the syntax of integration requirements?

Validation occurs in script/hassfest/requirements.py through the validate_requirements function (line 367). This CI script checks every integration's manifest.json for proper version specifier formatting and range correctness before code is merged into the home-assistant/core repository.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →