Skip to main content
Home Assistant maintains high code quality standards through automated linting, formatting, and type checking.

Code Quality Tools

Ruff

Ruff is a fast Python linter and formatter that enforces code style and catches common issues.

Running Ruff

# Check for issues
ruff check .

# Check and automatically fix issues
ruff check --fix .

# Format code
ruff format .

# Check only changed files
prek run ruff-check

Configuration

Ruff is configured in pyproject.toml:
[tool.ruff]
required-version = ">=0.15.1"

[tool.ruff.lint]
select = [
  "E",      # pycodestyle errors
  "F",      # pyflakes/autoflake
  "W",      # pycodestyle warnings
  "I",      # isort (import sorting)
  "UP",     # pyupgrade (modern Python syntax)
  "ASYNC",  # flake8-async
  "B",      # flake8-bugbear
  "SIM",    # flake8-simplify
  "PL",     # pylint
  "RUF",    # ruff-specific rules
  # ... and many more
]

Pylint

Pylint provides deeper code analysis with custom Home Assistant plugins.

Running Pylint

# Lint a specific file
pylint homeassistant/components/light/init.py

# Lint changed files
script/lint

# Lint via pre-commit
prek run pylint

Custom Plugins

Home Assistant includes custom pylint plugins in pylint/plugins/:
  • hass_async_load_fixtures - Validates fixture loading patterns
  • hass_decorator - Checks decorator usage
  • hass_enforce_class_module - Ensures classes are in correct modules
  • hass_enforce_sorted_platforms - Enforces platform sorting
  • hass_enforce_type_hints - Requires type hints
  • hass_imports - Validates import patterns
  • hass_logger - Checks logger usage

Configuration

Pylint is configured in pyproject.toml:
[tool.pylint.MAIN]
py-version = "3.14"
jobs = 2
load-plugins = [
  "pylint.extensions.code_style",
  "pylint.extensions.typing",
  "hass_async_load_fixtures",
  "hass_decorator",
  # ... custom plugins
]

Mypy

Mypy performs static type checking to catch type-related bugs.

Running Mypy

# Check all files
mypy homeassistant/

# Check specific file
mypy homeassistant/components/light/__init__.py

# Via pre-commit
prek run mypy

Type Hints

All new code must include type hints:
from homeassistant.core import HomeAssistant
from homeassistant.config_entries import ConfigEntry

async def async_setup_entry(
    hass: HomeAssistant,
    entry: ConfigEntry,
) -> bool:
    """Set up from a config entry."""
    return True

Strict Typing

Certain modules enforce strict typing. These are tracked in the .strict-typing file.

Codespell

Catches spelling mistakes in code, comments, and documentation.
# Check spelling
codespell

# Via pre-commit
prek run codespell
Configuration in .pre-commit-config.yaml:
- id: codespell
  args:
    - --ignore-words-list=aiport,astroid,checkin,currenty,hass,iif,incomfort,lookin,nam,NotIn
    - --skip="./.*,*.csv,*.json,*.ambr"
    - --quiet-level=2

Hassfest

Validates integration metadata, manifests, and configurations.
# Run hassfest checks
python3 -m script.hassfest

# Via pre-commit
prek run hassfest
Hashfest validates:
  • Integration manifests (manifest.json)
  • Service definitions (services.yaml)
  • String translations (strings.json)
  • Icons (icons.json)
  • Requirements and dependencies

Pre-commit Hooks

Home Assistant uses prek (a pre-commit hook manager) to run quality checks automatically.

Install Hooks

prek install

Hook Configuration

Hooks are defined in .pre-commit-config.yaml:
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format

  - repo: https://github.com/codespell-project/codespell
    hooks:
      - id: codespell

  - repo: local
    hooks:
      - id: mypy
        entry: script/run-in-env.sh mypy
      - id: pylint
        entry: script/run-in-env.sh pylint --ignore-missing-annotations=y
      - id: hassfest
        entry: script/run-in-env.sh python3 -m script.hassfest

Running Hooks Manually

# Run all hooks on all files
prek run --all-files

# Run specific hook
prek run ruff-check --all-files

# Run hooks on specific files
prek run --files path/to/file.py

Code Style Guidelines

Imports

Imports should be organized and sorted:
"""Module docstring."""
# Standard library imports
import asyncio
import logging
from typing import Any

# Third-party imports
import voluptuous as vol

# Home Assistant imports
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
Ruff’s isort integration handles this automatically:
[tool.ruff.lint.isort]
force-sort-within-sections = true
known-first-party = ["homeassistant"]
combine-as-imports = true

Docstrings

Use Google-style docstrings:
def my_function(param1: str, param2: int) -> bool:
    """Brief description of function.

    Longer description if needed.

    Args:
        param1: Description of param1.
        param2: Description of param2.

    Returns:
        Description of return value.

    Raises:
        ValueError: When parameter is invalid.
    """
    return True
Configuration:
[tool.ruff.lint.pydocstyle]
convention = "google"

Line Length

Maximum line length is not strictly enforced (E501 is disabled), but keep lines reasonable for readability. Ruff’s formatter will handle most cases.

Naming Conventions

  • Variables and functions: snake_case
  • Classes: PascalCase
  • Constants: UPPER_CASE
  • Private methods: _leading_underscore

Async Code

Prefix async functions with async_:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up from config entry."""
    await async_initialize()
    return True

Common Issues and Fixes

Import Sorting

If imports are out of order:
ruff check --select I --fix path/to/file.py

Type Hint Issues

Add missing type hints:
# Before
def my_function(hass, config):
    pass

# After
def my_function(hass: HomeAssistant, config: dict[str, Any]) -> None:
    pass

Pylint: Too Many Arguments

Refactor functions with too many parameters:
# Before: Too many arguments
def setup(hass, config, arg1, arg2, arg3, arg4, arg5, arg6):
    pass

# After: Use a config object or dataclass
from dataclasses import dataclass

@dataclass
class SetupConfig:
    arg1: str
    arg2: int
    # ...

def setup(hass: HomeAssistant, config: SetupConfig) -> None:
    pass

Complexity Issues

If code is too complex (McCabe complexity > 25), refactor:
# Break complex functions into smaller ones
def complex_function():
    result = _step_one()
    result = _step_two(result)
    return _step_three(result)

def _step_one():
    pass

def _step_two(data):
    pass

def _step_three(data):
    pass

Integration Quality Scale

Home Assistant uses a quality scale for integrations:
  • Platinum - Highest quality, exemplary code
  • Gold - High quality, good example to follow
  • Silver - Good quality
  • Bronze - Basic quality requirements met
Quality level is indicated in manifest.json:
{
  "domain": "example",
  "quality_scale": "gold"
}
When looking for code examples, refer to Platinum or Gold-level integrations.

Automated Checks

All checks run automatically in CI/CD:

On Pre-commit

  • Ruff formatting and linting
  • Codespell
  • Mypy type checking (for changed files)
  • Pylint (for changed files)
  • Hassfest validation

On Pull Request

  • All pre-commit checks
  • Full test suite
  • Coverage report
  • Additional validation checks

Best Practices

Write Clean Code

  • Keep it simple - Avoid unnecessary complexity
  • Be consistent - Follow existing patterns
  • Use type hints - Makes code self-documenting
  • Add docstrings - Explain what and why, not how
  • Handle errors - Don’t let exceptions crash Home Assistant

Performance

  • Avoid blocking I/O - Use async for I/O operations
  • Cache when appropriate - Don’t repeat expensive operations
  • Clean up resources - Implement proper shutdown handlers

Security

  • Validate input - Never trust user input
  • Use constants - Avoid hardcoded secrets
  • Handle credentials safely - Use config entries for sensitive data

Resources

Build docs developers (and LLMs) love