# How the Home Assistant Requirements System Manages Python Dependencies for Integrations

> Discover how Home Assistant manages Python dependencies for integrations using its requirements system. Learn about automatic installation, caching, and dependency chains via manifest.json and the core requirements module.

- Repository: [Home Assistant/core](https://github.com/home-assistant/core)
- Tags: internals
- Published: 2026-02-28

---

**The Home Assistant requirements system uses a singleton `RequirementsManager` to automatically install Python packages declared in integration [`manifest.json`](https://github.com/home-assistant/core/blob/main/manifest.json) files before instantiation, handling caching, retries, and dependency chains through the core [`homeassistant/requirements.py`](https://github.com/home-assistant/core/blob/main/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`](https://github.com/home-assistant/core/blob/main/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`](https://github.com/home-assistant/core/blob/main/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:

```python

# 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`](https://github.com/home-assistant/core/blob/main/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`](https://github.com/home-assistant/core/blob/main/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`](https://github.com/home-assistant/core/blob/main/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`](https://github.com/home-assistant/core/blob/main/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:

- **[`requirements_all.txt`](https://github.com/home-assistant/core/blob/main/requirements_all.txt)**: Every runtime dependency across all integrations
- **[`requirements_test_all.txt`](https://github.com/home-assistant/core/blob/main/requirements_test_all.txt)**: Test-only dependencies for the full suite

These files, along with **[`package_constraints.txt`](https://github.com/home-assistant/core/blob/main/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):

```python
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`](https://github.com/home-assistant/core/blob/main/homeassistant/requirements.py).

### Loading an Integration with Full Dependencies

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

```python
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):

```python
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`](https://github.com/home-assistant/core/blob/main/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`](https://github.com/home-assistant/core/blob/main/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`](https://github.com/home-assistant/core/blob/main/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`](https://github.com/home-assistant/core/blob/main/script/hassfest/requirements.py)** through the `validate_requirements` function (line 367). This CI script checks every integration's [`manifest.json`](https://github.com/home-assistant/core/blob/main/manifest.json) for proper version specifier formatting and range correctness before code is merged into the home-assistant/core repository.