Skip to content

Commit

Permalink
Fix docker hassfest (home-assistant#132823)
Browse files Browse the repository at this point in the history
  • Loading branch information
edenhaus authored Dec 11, 2024
1 parent 5e17721 commit af83807
Show file tree
Hide file tree
Showing 20 changed files with 85 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ jobs:
tags: ${{ env.HASSFEST_IMAGE_TAG }}

- name: Run hassfest against core
run: docker run --rm -v ${{ github.workspace }}/homeassistant:/github/workspace/homeassistant ${{ env.HASSFEST_IMAGE_TAG }} --core-integrations-path=/github/workspace/homeassistant/components
run: docker run --rm -v ${{ github.workspace }}:/github/workspace ${{ env.HASSFEST_IMAGE_TAG }} --core-path=/github/workspace

- name: Push Docker image
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
Expand Down
1 change: 0 additions & 1 deletion script/gen_requirements_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,6 @@ def _get_hassfest_config() -> Config:
specific_integrations=None,
action="validate",
requirements=True,
core_integrations_path=Path("homeassistant/components"),
)


Expand Down
14 changes: 8 additions & 6 deletions script/hassfest/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ def get_config() -> Config:
help="Comma-separate list of plugins to run. Valid plugin names: %(default)s",
)
parser.add_argument(
"--core-integrations-path",
"--core-path",
type=Path,
default=Path("homeassistant/components"),
help="Path to core integrations",
default=Path(),
help="Path to core",
)
parsed = parser.parse_args()

Expand All @@ -125,16 +125,18 @@ def get_config() -> Config:
"Generate is not allowed when limiting to specific integrations"
)

if not parsed.integration_path and not Path("requirements_all.txt").is_file():
if (
not parsed.integration_path
and not (parsed.core_path / "requirements_all.txt").is_file()
):
raise RuntimeError("Run from Home Assistant root")

return Config(
root=Path().absolute(),
root=parsed.core_path.absolute(),
specific_integrations=parsed.integration_path,
action=parsed.action,
requirements=parsed.requirements,
plugins=set(parsed.plugins),
core_integrations_path=parsed.core_integrations_path,
)


Expand Down
6 changes: 3 additions & 3 deletions script/hassfest/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,12 @@ def _generate_files(config: Config) -> list[File]:
+ 10
) * 1000

package_versions = _get_package_versions(Path("requirements.txt"), {"uv"})
package_versions = _get_package_versions(config.root / "requirements.txt", {"uv"})
package_versions |= _get_package_versions(
Path("requirements_test.txt"), {"pipdeptree", "tqdm"}
config.root / "requirements_test.txt", {"pipdeptree", "tqdm"}
)
package_versions |= _get_package_versions(
Path("requirements_test_pre_commit.txt"), {"ruff"}
config.root / "requirements_test_pre_commit.txt", {"ruff"}
)

return [
Expand Down
26 changes: 19 additions & 7 deletions script/hassfest/docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,28 @@

integrations=""
integration_path=""
core_path_provided=false

# Enable recursive globbing using find
for manifest in $(find . -name "manifest.json"); do
manifest_path=$(realpath "${manifest}")
integrations="$integrations --integration-path ${manifest_path%/*}"
for arg in "$@"; do
case "$arg" in
--core-path=*)
core_path_provided=true
break
;;
esac
done

if [ -z "$integrations" ]; then
echo "Error: No integrations found!"
exit 1
if [ "$core_path_provided" = false ]; then
# Enable recursive globbing using find
for manifest in $(find . -name "manifest.json"); do
manifest_path=$(realpath "${manifest}")
integrations="$integrations --integration-path ${manifest_path%/*}"
done

if [ -z "$integrations" ]; then
echo "Error: No integrations found!"
exit 1
fi
fi

cd /usr/src/homeassistant || exit 1
Expand Down
6 changes: 5 additions & 1 deletion script/hassfest/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ class Config:
root: pathlib.Path
action: Literal["validate", "generate"]
requirements: bool
core_integrations_path: pathlib.Path
core_integrations_path: pathlib.Path = field(init=False)
errors: list[Error] = field(default_factory=list)
cache: dict[str, Any] = field(default_factory=dict)
plugins: set[str] = field(default_factory=set)

def __post_init__(self) -> None:
"""Post init."""
self.core_integrations_path = self.root / "homeassistant/components"

def add_error(self, *args: Any, **kwargs: Any) -> None:
"""Add an error."""
self.errors.append(Error(*args, **kwargs))
Expand Down
2 changes: 1 addition & 1 deletion script/hassfest/quality_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -1358,7 +1358,7 @@ def validate_iqs_file(config: Config, integration: Integration) -> None:

for rule_name in rules_done:
if (validator := VALIDATORS.get(rule_name)) and (
errors := validator.validate(integration, rules_done=rules_done)
errors := validator.validate(config, integration, rules_done=rules_done)
):
for error in errors:
integration.add_error("quality_scale", f"[{rule_name}] {error}")
Expand Down
4 changes: 2 additions & 2 deletions script/hassfest/quality_scale_validation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

from typing import Protocol

from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration


class RuleValidationProtocol(Protocol):
"""Protocol for rule validation."""

def validate(
self, integration: Integration, *, rules_done: set[str]
self, config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate a quality scale rule.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import ast

from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration


def _has_unload_entry_function(module: ast.Module) -> bool:
Expand All @@ -17,7 +17,9 @@ def _has_unload_entry_function(module: ast.Module) -> bool:
)


def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration has a config flow."""

init_file = integration.path / "__init__.py"
Expand Down
6 changes: 4 additions & 2 deletions script/hassfest/quality_scale_validation/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
https://developers.home-assistant.io/docs/core/integration-quality-scale/rules/config-flow/
"""

from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration


def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration implements config flow."""

if not integration.config_flow:
Expand Down
6 changes: 4 additions & 2 deletions script/hassfest/quality_scale_validation/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import ast

from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration

DIAGNOSTICS_FUNCTIONS = {
"async_get_config_entry_diagnostics",
Expand All @@ -22,7 +22,9 @@ def _has_diagnostics_function(module: ast.Module) -> bool:
)


def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration implements diagnostics."""

diagnostics_file = integration.path / "diagnostics.py"
Expand Down
6 changes: 4 additions & 2 deletions script/hassfest/quality_scale_validation/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import ast

from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration

MANIFEST_KEYS = [
"bluetooth",
Expand Down Expand Up @@ -38,7 +38,9 @@ def _has_discovery_function(module: ast.Module) -> bool:
)


def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration implements diagnostics."""

config_flow_file = integration.path / "config_flow.py"
Expand Down
6 changes: 4 additions & 2 deletions script/hassfest/quality_scale_validation/parallel_updates.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from homeassistant.const import Platform
from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration


def _has_parallel_updates_defined(module: ast.Module) -> bool:
Expand All @@ -18,7 +18,9 @@ def _has_parallel_updates_defined(module: ast.Module) -> bool:
)


def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration sets PARALLEL_UPDATES constant."""

errors = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import ast

from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration


def _has_step_reauth_function(module: ast.Module) -> bool:
Expand All @@ -17,7 +17,9 @@ def _has_step_reauth_function(module: ast.Module) -> bool:
)


def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration has a reauthentication flow."""

config_flow_file = integration.path / "config_flow.py"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import ast

from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration


def _has_step_reconfigure_function(module: ast.Module) -> bool:
Expand All @@ -17,7 +17,9 @@ def _has_step_reconfigure_function(module: ast.Module) -> bool:
)


def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration has a reconfiguration flow."""

config_flow_file = integration.path / "config_flow.py"
Expand Down
6 changes: 4 additions & 2 deletions script/hassfest/quality_scale_validation/runtime_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from homeassistant.const import Platform
from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration

_ANNOTATION_MATCH = re.compile(r"^[A-Za-z]+ConfigEntry$")
_FUNCTIONS: dict[str, dict[str, int]] = {
Expand Down Expand Up @@ -102,7 +102,9 @@ def _check_typed_config_entry(integration: Integration) -> list[str]:
return errors


def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate correct use of ConfigEntry.runtime_data."""
init_file = integration.path / "__init__.py"
init = ast_parse_module(init_file)
Expand Down
13 changes: 8 additions & 5 deletions script/hassfest/quality_scale_validation/strict_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,30 @@
from pathlib import Path
import re

from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration

_STRICT_TYPING_FILE = Path(".strict-typing")
_COMPONENT_REGEX = r"homeassistant.components.([^.]+).*"


@lru_cache
def _strict_typing_components() -> set[str]:
def _strict_typing_components(strict_typing_file: Path) -> set[str]:
return set(
{
match.group(1)
for line in _STRICT_TYPING_FILE.read_text(encoding="utf-8").splitlines()
for line in strict_typing_file.read_text(encoding="utf-8").splitlines()
if (match := re.match(_COMPONENT_REGEX, line)) is not None
}
)


def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration has strict typing enabled."""
strict_typing_file = config.root / _STRICT_TYPING_FILE

if integration.domain not in _strict_typing_components():
if integration.domain not in _strict_typing_components(strict_typing_file):
return [
"Integration does not have strict typing enabled "
"(is missing from .strict-typing)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import ast

from script.hassfest import ast_parse_module
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration


def _has_method_call(module: ast.Module, name: str) -> bool:
Expand All @@ -30,7 +30,9 @@ def _has_abort_unique_id_configured(module: ast.Module) -> bool:
)


def validate(integration: Integration, *, rules_done: set[str]) -> list[str] | None:
def validate(
config: Config, integration: Integration, *, rules_done: set[str]
) -> list[str] | None:
"""Validate that the integration prevents duplicate devices."""

if integration.manifest.get("single_config_entry"):
Expand Down
3 changes: 1 addition & 2 deletions tests/hassfest/test_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@
def integration():
"""Fixture for hassfest integration model."""
return Integration(
path=Path("homeassistant/components/test"),
path=Path("homeassistant/components/test").absolute(),
_config=Config(
root=Path(".").absolute(),
specific_integrations=None,
action="validate",
requirements=True,
core_integrations_path=Path("homeassistant/components"),
),
_manifest={
"domain": "test",
Expand Down
3 changes: 1 addition & 2 deletions tests/hassfest/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@
def integration():
"""Fixture for hassfest integration model."""
integration = Integration(
"",
Path(),
_config=Config(
root=Path(".").absolute(),
specific_integrations=None,
action="validate",
requirements=True,
core_integrations_path=Path("homeassistant/components"),
),
)
integration._manifest = {
Expand Down

0 comments on commit af83807

Please sign in to comment.